@gugananuvem/aws-local-simulator 1.0.35 → 1.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +662 -658
  2. package/package.json +10 -3
  3. package/src/config/default-config.js +71 -0
  4. package/src/config/env-loader.js +4 -2
  5. package/src/middleware/authorization.js +271 -0
  6. package/src/middleware/cloudtrail-logger.js +333 -0
  7. package/src/middleware/error-handler.js +138 -0
  8. package/src/middleware/index.js +79 -0
  9. package/src/middleware/policy-enforcement.js +407 -0
  10. package/src/server.js +74 -4
  11. package/src/services/acm/index.js +66 -0
  12. package/src/services/acm/models/certificate.js +202 -0
  13. package/src/services/acm/server.js +224 -0
  14. package/src/services/acm/simulator.js +293 -0
  15. package/src/services/alb/index.js +104 -0
  16. package/src/services/alb/models/loadBalancer.js +88 -0
  17. package/src/services/alb/models/targetGroup.js +128 -0
  18. package/src/services/alb/server.js +160 -0
  19. package/src/services/alb/simulator.js +262 -0
  20. package/src/services/apigateway/index.js +22 -0
  21. package/src/services/apigateway/server.js +247 -35
  22. package/src/services/apigateway/simulator.js +780 -26
  23. package/src/services/asg/index.js +100 -0
  24. package/src/services/asg/models/autoScalingGroup.js +110 -0
  25. package/src/services/asg/models/launchTemplate.js +41 -0
  26. package/src/services/asg/models/scalingPolicy.js +51 -0
  27. package/src/services/asg/server.js +236 -0
  28. package/src/services/asg/simulator.js +410 -0
  29. package/src/services/aurora/cluster-manager.js +731 -0
  30. package/src/services/aurora/data-api.js +514 -0
  31. package/src/services/aurora/db-engine.js +270 -0
  32. package/src/services/aurora/index.js +159 -0
  33. package/src/services/aurora/rds-api.js +189 -0
  34. package/src/services/aurora/server.js +213 -0
  35. package/src/services/aurora/sql-translator.js +334 -0
  36. package/src/services/cloudformation/index.js +2 -2
  37. package/src/services/cloudformation/server.js +1 -0
  38. package/src/services/cloudfront/cache-engine.js +225 -0
  39. package/src/services/cloudfront/geo-restrictions.js +377 -0
  40. package/src/services/cloudfront/index.js +220 -0
  41. package/src/services/cloudfront/models/behavior.js +158 -0
  42. package/src/services/cloudfront/models/distribution.js +284 -0
  43. package/src/services/cloudfront/models/index.js +99 -0
  44. package/src/services/cloudfront/models/invalidation.js +109 -0
  45. package/src/services/cloudfront/models/origin.js +155 -0
  46. package/src/services/cloudfront/origin-router.js +552 -0
  47. package/src/services/cloudfront/origin-validator.js +398 -0
  48. package/src/services/cloudfront/server.js +512 -0
  49. package/src/services/cloudfront/signed-url-generator.js +377 -0
  50. package/src/services/cloudfront/simulator.js +425 -0
  51. package/src/services/cloudfront/store.js +326 -0
  52. package/src/services/cloudshell/env-builder.js +27 -0
  53. package/src/services/cloudshell/index.js +48 -0
  54. package/src/services/cloudshell/routes.js +60 -0
  55. package/src/services/cloudshell/session-manager.js +226 -0
  56. package/src/services/cloudshell/static/.gitkeep +0 -0
  57. package/src/services/cloudshell/static/index.html +406 -0
  58. package/src/services/cloudshell/websocket-handler.js +107 -0
  59. package/src/services/cloudtrail/index.js +2 -3
  60. package/src/services/cloudtrail/server.js +68 -0
  61. package/src/services/cognito/server.js +4 -1
  62. package/src/services/dynamodb/server.js +4 -1
  63. package/src/services/dynamodb/simulator.js +2 -1
  64. package/src/services/ec2/index.js +101 -0
  65. package/src/services/ec2/instanceServer.js +311 -0
  66. package/src/services/ec2/models/ami.js +39 -0
  67. package/src/services/ec2/models/instance.js +98 -0
  68. package/src/services/ec2/models/networkInterface.js +74 -0
  69. package/src/services/ec2/models/securityGroup.js +71 -0
  70. package/src/services/ec2/server.js +350 -0
  71. package/src/services/ec2/simulator.js +436 -0
  72. package/src/services/eventbridge/server.js +36 -0
  73. package/src/services/eventbridge/simulator.js +230 -45
  74. package/src/services/iam/index.js +65 -0
  75. package/src/services/iam/middleware.js +99 -0
  76. package/src/services/iam/server.js +331 -0
  77. package/src/services/iam/simulator.js +1432 -0
  78. package/src/services/index.js +28 -0
  79. package/src/services/kinesis-analytics/index.js +105 -0
  80. package/src/services/kinesis-analytics/models/application.js +325 -0
  81. package/src/services/kinesis-analytics/models/index.js +12 -0
  82. package/src/services/kinesis-analytics/server.js +250 -0
  83. package/src/services/kinesis-analytics/simulator.js +407 -0
  84. package/src/services/kinesis-analytics/store.js +176 -0
  85. package/src/services/kinesis-data-streams/index.js +106 -0
  86. package/src/services/kinesis-data-streams/models/index.js +12 -0
  87. package/src/services/kinesis-data-streams/models/record.js +70 -0
  88. package/src/services/kinesis-data-streams/models/stream.js +280 -0
  89. package/src/services/kinesis-data-streams/server.js +255 -0
  90. package/src/services/kinesis-data-streams/simulator.js +514 -0
  91. package/src/services/kinesis-data-streams/store.js +163 -0
  92. package/src/services/kinesis-firehose/index.js +106 -0
  93. package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
  94. package/src/services/kinesis-firehose/models/index.js +10 -0
  95. package/src/services/kinesis-firehose/server.js +215 -0
  96. package/src/services/kinesis-firehose/simulator.js +388 -0
  97. package/src/services/kinesis-firehose/store.js +163 -0
  98. package/src/services/kms/server.js +4 -1
  99. package/src/services/lambda/index.js +26 -0
  100. package/src/services/lambda/server.js +12 -1
  101. package/src/services/lambda/simulator.js +4 -0
  102. package/src/services/opensearch/index.js +105 -0
  103. package/src/services/opensearch/models/document.js +115 -0
  104. package/src/services/opensearch/models/index.js +73 -0
  105. package/src/services/opensearch/server.js +310 -0
  106. package/src/services/opensearch/simulator.js +605 -0
  107. package/src/services/opensearch/store.js +162 -0
  108. package/src/services/organizations/control-tower-simulator.js +321 -0
  109. package/src/services/organizations/control-tower-store.js +182 -0
  110. package/src/services/organizations/iam-integration.js +413 -0
  111. package/src/services/organizations/index.js +172 -0
  112. package/src/services/organizations/models/index.js +13 -0
  113. package/src/services/organizations/models/invitation.js +48 -0
  114. package/src/services/organizations/models/organization.js +566 -0
  115. package/src/services/organizations/models/role.js +244 -0
  116. package/src/services/organizations/models/scp.js +131 -0
  117. package/src/services/organizations/server.js +736 -0
  118. package/src/services/organizations/simulator.js +593 -0
  119. package/src/services/organizations/store.js +235 -0
  120. package/src/services/organizations/vpc-integration.js +453 -0
  121. package/src/services/route53/index.js +67 -0
  122. package/src/services/route53/models/healthCheck.js +101 -0
  123. package/src/services/route53/models/hostedZone.js +37 -0
  124. package/src/services/route53/models/resourceRecord.js +106 -0
  125. package/src/services/route53/server.js +841 -0
  126. package/src/services/route53/simulator.js +350 -0
  127. package/src/services/s3/index.js +62 -0
  128. package/src/services/s3/presigned-url-generator.js +337 -0
  129. package/src/services/s3/server.js +231 -0
  130. package/src/services/s3/simulator.js +551 -4
  131. package/src/services/sns/index.js +31 -0
  132. package/src/services/sns/server.js +9 -0
  133. package/src/services/sqs/index.js +38 -0
  134. package/src/services/sqs/server.js +57 -4
  135. package/src/services/sqs/simulator.js +423 -33
  136. package/src/services/sts/simulator.js +209 -17
  137. package/src/services/vpc/README.md +459 -0
  138. package/src/services/vpc/ec2ResponseFormatter.js +431 -0
  139. package/src/services/vpc/index.js +90 -0
  140. package/src/services/vpc/models/internetGateway.js +71 -0
  141. package/src/services/vpc/models/routeTable.js +160 -0
  142. package/src/services/vpc/models/subnet.js +48 -0
  143. package/src/services/vpc/models/vpc.js +37 -0
  144. package/src/services/vpc/models/vpcEndpoint.js +100 -0
  145. package/src/services/vpc/models/vpcGateway.js +134 -0
  146. package/src/services/vpc/server.js +836 -0
  147. package/src/services/vpc/simulator.js +613 -0
  148. package/src/utils/policy-cache.js +215 -0
  149. package/aws-config +0 -154
@@ -0,0 +1,406 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CloudShell - AWS Local Simulator</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5/css/xterm.css">
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ background: #1e1e1e;
17
+ color: #d4d4d4;
18
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
19
+ height: 100vh;
20
+ display: flex;
21
+ flex-direction: column;
22
+ overflow: hidden;
23
+ }
24
+
25
+ #tab-bar {
26
+ display: flex;
27
+ align-items: center;
28
+ background: #252526;
29
+ border-bottom: 1px solid #3c3c3c;
30
+ height: 36px;
31
+ min-height: 36px;
32
+ overflow-x: auto;
33
+ scrollbar-width: thin;
34
+ }
35
+
36
+ #tab-bar::-webkit-scrollbar {
37
+ height: 4px;
38
+ }
39
+
40
+ #tab-bar::-webkit-scrollbar-thumb {
41
+ background: #555;
42
+ border-radius: 2px;
43
+ }
44
+
45
+ .tab {
46
+ display: flex;
47
+ align-items: center;
48
+ padding: 0 12px;
49
+ height: 100%;
50
+ cursor: pointer;
51
+ border-right: 1px solid #3c3c3c;
52
+ white-space: nowrap;
53
+ font-size: 13px;
54
+ color: #969696;
55
+ user-select: none;
56
+ transition: background 0.1s;
57
+ }
58
+
59
+ .tab:hover {
60
+ background: #2d2d2d;
61
+ }
62
+
63
+ .tab.active {
64
+ background: #1e1e1e;
65
+ color: #ffffff;
66
+ border-bottom: 2px solid #007acc;
67
+ }
68
+
69
+ .tab .tab-title {
70
+ margin-right: 8px;
71
+ }
72
+
73
+ .tab .tab-close {
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ width: 18px;
78
+ height: 18px;
79
+ border-radius: 3px;
80
+ font-size: 14px;
81
+ line-height: 1;
82
+ color: #969696;
83
+ }
84
+
85
+ .tab .tab-close:hover {
86
+ background: #4a4a4a;
87
+ color: #ffffff;
88
+ }
89
+
90
+ #new-tab-btn {
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ width: 36px;
95
+ height: 100%;
96
+ cursor: pointer;
97
+ color: #969696;
98
+ font-size: 18px;
99
+ border: none;
100
+ background: none;
101
+ transition: background 0.1s;
102
+ }
103
+
104
+ #new-tab-btn:hover {
105
+ background: #2d2d2d;
106
+ color: #ffffff;
107
+ }
108
+
109
+ #terminal-container {
110
+ flex: 1;
111
+ position: relative;
112
+ overflow: hidden;
113
+ }
114
+
115
+ .terminal-wrapper {
116
+ position: absolute;
117
+ top: 0;
118
+ left: 0;
119
+ right: 0;
120
+ bottom: 0;
121
+ display: none;
122
+ padding: 4px;
123
+ }
124
+
125
+ .terminal-wrapper.active {
126
+ display: block;
127
+ }
128
+
129
+ .terminal-wrapper .xterm {
130
+ height: 100%;
131
+ }
132
+ </style>
133
+ </head>
134
+ <body>
135
+ <div id="tab-bar">
136
+ <button id="new-tab-btn" title="New terminal session">+</button>
137
+ </div>
138
+ <div id="terminal-container"></div>
139
+
140
+ <script src="https://cdn.jsdelivr.net/npm/xterm@5/lib/xterm.js"></script>
141
+ <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0/lib/xterm-addon-fit.js"></script>
142
+ <script>
143
+ (function () {
144
+ const API_BASE = '/__admin/cloudshell/sessions';
145
+ const sessions = new Map(); // sessionId -> { term, fitAddon, ws, tabEl, wrapperEl }
146
+ let activeSessionId = null;
147
+ let tabCounter = 0;
148
+
149
+ const tabBar = document.getElementById('tab-bar');
150
+ const newTabBtn = document.getElementById('new-tab-btn');
151
+ const terminalContainer = document.getElementById('terminal-container');
152
+
153
+ // --- API Helpers ---
154
+
155
+ async function fetchSessions() {
156
+ const res = await fetch(API_BASE);
157
+ const data = await res.json();
158
+ return data.sessions || [];
159
+ }
160
+
161
+ async function createSessionAPI() {
162
+ const res = await fetch(API_BASE, { method: 'POST' });
163
+ return await res.json();
164
+ }
165
+
166
+ async function deleteSessionAPI(sessionId) {
167
+ await fetch(`${API_BASE}/${sessionId}`, { method: 'DELETE' });
168
+ }
169
+
170
+ // --- WebSocket ---
171
+
172
+ function connectWebSocket(sessionId) {
173
+ const wsUrl = `ws://${window.location.host}/ws/cloudshell/${sessionId}`;
174
+ const ws = new WebSocket(wsUrl);
175
+ ws.binaryType = 'arraybuffer';
176
+
177
+ const session = sessions.get(sessionId);
178
+ if (!session) return null;
179
+
180
+ ws.onopen = function () {
181
+ session.ws = ws;
182
+ };
183
+
184
+ ws.onmessage = function (event) {
185
+ const s = sessions.get(sessionId);
186
+ if (s && s.term) {
187
+ s.term.write(typeof event.data === 'string' ? event.data : new Uint8Array(event.data));
188
+ }
189
+ };
190
+
191
+ ws.onclose = function () {
192
+ const s = sessions.get(sessionId);
193
+ if (s) {
194
+ s.ws = null;
195
+ }
196
+ };
197
+
198
+ ws.onerror = function () {
199
+ // WebSocket errors are followed by onclose
200
+ };
201
+
202
+ return ws;
203
+ }
204
+
205
+ // --- Terminal & Session Management ---
206
+
207
+ function createTerminalSession(sessionId) {
208
+ tabCounter++;
209
+
210
+ // Create xterm instance
211
+ const term = new Terminal({
212
+ theme: {
213
+ background: '#1e1e1e',
214
+ foreground: '#d4d4d4',
215
+ cursor: '#d4d4d4',
216
+ selectionBackground: '#264f78',
217
+ },
218
+ fontFamily: "'Cascadia Code', 'Fira Code', 'Consolas', monospace",
219
+ fontSize: 14,
220
+ cursorBlink: true,
221
+ });
222
+
223
+ const fitAddon = new FitAddon.FitAddon();
224
+ term.loadAddon(fitAddon);
225
+
226
+ // Create terminal wrapper element
227
+ const wrapperEl = document.createElement('div');
228
+ wrapperEl.className = 'terminal-wrapper';
229
+ wrapperEl.id = `terminal-${sessionId}`;
230
+ terminalContainer.appendChild(wrapperEl);
231
+
232
+ // Open terminal in wrapper
233
+ term.open(wrapperEl);
234
+
235
+ // Create tab element
236
+ const tabEl = document.createElement('div');
237
+ tabEl.className = 'tab';
238
+ tabEl.dataset.sessionId = sessionId;
239
+ tabEl.innerHTML = `
240
+ <span class="tab-title">Terminal ${tabCounter}</span>
241
+ <span class="tab-close" title="Close session">&times;</span>
242
+ `;
243
+
244
+ // Insert tab before the new-tab button
245
+ tabBar.insertBefore(tabEl, newTabBtn);
246
+
247
+ // Tab click handler (switch tab)
248
+ tabEl.addEventListener('click', function (e) {
249
+ if (e.target.classList.contains('tab-close')) return;
250
+ switchTab(sessionId);
251
+ });
252
+
253
+ // Tab close handler
254
+ tabEl.querySelector('.tab-close').addEventListener('click', function (e) {
255
+ e.stopPropagation();
256
+ closeSession(sessionId);
257
+ });
258
+
259
+ // Store session data
260
+ const sessionData = { term, fitAddon, ws: null, tabEl, wrapperEl };
261
+ sessions.set(sessionId, sessionData);
262
+
263
+ // Connect WebSocket
264
+ const ws = connectWebSocket(sessionId);
265
+ sessionData.ws = ws;
266
+
267
+ // Terminal data handler -> send to WebSocket
268
+ term.onData(function (data) {
269
+ const s = sessions.get(sessionId);
270
+ if (s && s.ws && s.ws.readyState === WebSocket.OPEN) {
271
+ s.ws.send(data);
272
+ }
273
+ });
274
+
275
+ // Activate this tab
276
+ switchTab(sessionId);
277
+
278
+ // Fit terminal after it's visible
279
+ requestAnimationFrame(function () {
280
+ fitAddon.fit();
281
+ sendResize(sessionId);
282
+ });
283
+
284
+ return sessionData;
285
+ }
286
+
287
+ function switchTab(sessionId) {
288
+ // Deactivate all tabs and wrappers
289
+ for (const [id, session] of sessions) {
290
+ session.tabEl.classList.remove('active');
291
+ session.wrapperEl.classList.remove('active');
292
+ }
293
+
294
+ // Activate selected session
295
+ const session = sessions.get(sessionId);
296
+ if (session) {
297
+ session.tabEl.classList.add('active');
298
+ session.wrapperEl.classList.add('active');
299
+ activeSessionId = sessionId;
300
+
301
+ // Re-fit after becoming visible
302
+ requestAnimationFrame(function () {
303
+ session.fitAddon.fit();
304
+ session.term.focus();
305
+ });
306
+ }
307
+ }
308
+
309
+ async function closeSession(sessionId) {
310
+ const session = sessions.get(sessionId);
311
+ if (!session) return;
312
+
313
+ // Close WebSocket
314
+ if (session.ws) {
315
+ session.ws.close();
316
+ }
317
+
318
+ // Destroy terminal
319
+ session.term.dispose();
320
+
321
+ // Remove DOM elements
322
+ session.tabEl.remove();
323
+ session.wrapperEl.remove();
324
+
325
+ // Remove from map
326
+ sessions.delete(sessionId);
327
+
328
+ // Delete session on backend
329
+ await deleteSessionAPI(sessionId);
330
+
331
+ // If closed the active tab, switch to another
332
+ if (activeSessionId === sessionId) {
333
+ activeSessionId = null;
334
+ const remaining = Array.from(sessions.keys());
335
+ if (remaining.length > 0) {
336
+ switchTab(remaining[remaining.length - 1]);
337
+ }
338
+ }
339
+ }
340
+
341
+ function sendResize(sessionId) {
342
+ const session = sessions.get(sessionId);
343
+ if (!session || !session.ws || session.ws.readyState !== WebSocket.OPEN) return;
344
+
345
+ const cols = session.term.cols;
346
+ const rows = session.term.rows;
347
+
348
+ session.ws.send(JSON.stringify({
349
+ type: 'resize',
350
+ cols: cols,
351
+ rows: rows,
352
+ }));
353
+ }
354
+
355
+ // --- Window Resize Handler ---
356
+
357
+ let resizeTimeout = null;
358
+ window.addEventListener('resize', function () {
359
+ if (resizeTimeout) clearTimeout(resizeTimeout);
360
+ resizeTimeout = setTimeout(function () {
361
+ for (const [id, session] of sessions) {
362
+ session.fitAddon.fit();
363
+ }
364
+ if (activeSessionId) {
365
+ sendResize(activeSessionId);
366
+ }
367
+ }, 100);
368
+ });
369
+
370
+ // --- New Tab Button ---
371
+
372
+ newTabBtn.addEventListener('click', async function () {
373
+ try {
374
+ const data = await createSessionAPI();
375
+ createTerminalSession(data.id);
376
+ } catch (err) {
377
+ console.error('Failed to create session:', err);
378
+ }
379
+ });
380
+
381
+ // --- Page Load: Initialize ---
382
+
383
+ async function initialize() {
384
+ try {
385
+ const existingSessions = await fetchSessions();
386
+
387
+ if (existingSessions.length > 0) {
388
+ // Reconnect to existing sessions
389
+ for (const sess of existingSessions) {
390
+ createTerminalSession(sess.id);
391
+ }
392
+ } else {
393
+ // Create a first session automatically
394
+ const data = await createSessionAPI();
395
+ createTerminalSession(data.id);
396
+ }
397
+ } catch (err) {
398
+ console.error('Failed to initialize CloudShell:', err);
399
+ }
400
+ }
401
+
402
+ initialize();
403
+ })();
404
+ </script>
405
+ </body>
406
+ </html>
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const { WebSocketServer } = require('ws');
4
+ const url = require('url');
5
+
6
+ /**
7
+ * Extracts the session ID from a WebSocket upgrade request URL.
8
+ * Expected path format: /ws/cloudshell/:sessionId
9
+ *
10
+ * @param {string} requestUrl - the request URL path
11
+ * @returns {string|null} the session ID, or null if the path doesn't match
12
+ */
13
+ function extractSessionId(requestUrl) {
14
+ const pathname = url.parse(requestUrl).pathname;
15
+ const match = pathname && pathname.match(/^\/ws\/cloudshell\/([^/]+)$/);
16
+ return match ? match[1] : null;
17
+ }
18
+
19
+ /**
20
+ * Creates and configures the WebSocket server for CloudShell.
21
+ * Attaches to the existing HTTP server using noServer mode and handles
22
+ * upgrade requests manually for session validation.
23
+ *
24
+ * @param {import('http').Server} httpServer - the Management API HTTP server
25
+ * @param {import('./session-manager').SessionManager} sessionManager - session manager instance
26
+ * @returns {import('ws').WebSocketServer}
27
+ */
28
+ function createWebSocketServer(httpServer, sessionManager) {
29
+ const wss = new WebSocketServer({ noServer: true });
30
+
31
+ httpServer.on('upgrade', (request, socket, head) => {
32
+ const sessionId = extractSessionId(request.url);
33
+
34
+ // Only handle upgrade requests for our path; ignore all others
35
+ if (!sessionId) {
36
+ // Not a CloudShell WebSocket path — do NOT destroy the socket!
37
+ // Let other upgrade handlers (like Vite HMR) process it.
38
+ return;
39
+ }
40
+
41
+ console.log('CloudShell: WebSocket upgrade request for session:', sessionId);
42
+
43
+ // Validate that the session exists
44
+ const session = sessionManager.getSession(sessionId);
45
+ if (!session) {
46
+ console.log('CloudShell: Session not found, rejecting WebSocket');
47
+ // Reject connection: complete the WebSocket handshake then close with 4004
48
+ wss.handleUpgrade(request, socket, head, (ws) => {
49
+ ws.close(4004, 'Session not found');
50
+ });
51
+ return;
52
+ }
53
+
54
+ // Accept connection
55
+ console.log('CloudShell: Accepting WebSocket connection');
56
+ wss.handleUpgrade(request, socket, head, (ws) => {
57
+ wss.emit('connection', ws, request, sessionId);
58
+ });
59
+ });
60
+
61
+ wss.on('connection', (ws, request, sessionId) => {
62
+ const session = sessionManager.getSession(sessionId);
63
+ if (!session) {
64
+ ws.close(4004, 'Session not found');
65
+ return;
66
+ }
67
+
68
+ // Attach WebSocket to session (pipes PTY output to WS)
69
+ sessionManager.attachWebSocket(sessionId, ws);
70
+
71
+ // Handle incoming messages from the client
72
+ ws.on('message', (data) => {
73
+ const currentSession = sessionManager.getSession(sessionId);
74
+ if (!currentSession) return;
75
+
76
+ const message = data.toString();
77
+
78
+ // Try to parse as JSON resize event
79
+ try {
80
+ const parsed = JSON.parse(message);
81
+ if (parsed && parsed.type === 'resize') {
82
+ sessionManager.resizeSession(sessionId, parsed.cols, parsed.rows);
83
+ return;
84
+ }
85
+ } catch (e) {
86
+ // Not JSON; treat as raw terminal input
87
+ }
88
+
89
+ // Write raw input to PTY stdin
90
+ try {
91
+ currentSession.pty.write(message);
92
+ } catch (err) {
93
+ // PTY may have exited; ignore write errors
94
+ }
95
+ });
96
+
97
+ // On WebSocket close: detach from session (keep PTY alive)
98
+ ws.on('close', () => {
99
+ // The attachWebSocket already sets up close listener to null out ws,
100
+ // so no additional detach logic needed here. PTY stays alive.
101
+ });
102
+ });
103
+
104
+ return wss;
105
+ }
106
+
107
+ module.exports = { createWebSocketServer, extractSessionId };
@@ -42,10 +42,9 @@ class CloudTrailService {
42
42
 
43
43
  async start() {
44
44
  if (this.isRunning) return;
45
- const app = createCloudTrailServer(this.simulator, this.logger);
45
+ this._server = createCloudTrailServer(this.simulator, this.logger);
46
46
  return new Promise((resolve, reject) => {
47
- this._server = http.createServer(app);
48
- this._server.listen(this.port, () => {
47
+ this._server.listen(this.port, '0.0.0.0', () => {
49
48
  this.isRunning = true;
50
49
  this.logger.debug(`CloudTrail rodando na porta ${this.port}`);
51
50
  resolve();
@@ -129,6 +129,11 @@ function createCloudTrailServer(simulator, logger) {
129
129
  return handleAdmin(req, res, simulator, logger);
130
130
  }
131
131
 
132
+ // ── JSON routes for React frontend ──────────────────────────────────────
133
+ if (url.startsWith('/json/cloudtrail')) {
134
+ return handleJsonRoutes(req, res, simulator, logger);
135
+ }
136
+
132
137
  // ── Internal route (cross-service) ──────────────────────────────────────
133
138
  if (url === '/__internal/record-event' && method === 'POST') {
134
139
  const body = await parseBody(req);
@@ -176,6 +181,69 @@ function createCloudTrailServer(simulator, logger) {
176
181
  return server;
177
182
  }
178
183
 
184
+ // ─── JSON routes for React frontend ──────────────────────────────────────────
185
+
186
+ async function handleJsonRoutes(req, res, simulator, logger) {
187
+ const { method, url } = req;
188
+ const path = url.split('?')[0];
189
+
190
+ // GET /json/cloudtrail/trails
191
+ if (path === '/json/cloudtrail/trails' && method === 'GET') {
192
+ try {
193
+ const result = simulator.describeTrails({});
194
+ const trails = result.trailList || [];
195
+ sendJson(res, 200, trails);
196
+ } catch (err) {
197
+ logger.error('[CloudTrail] GET /json/cloudtrail/trails error:', err.message);
198
+ sendJson(res, 200, []);
199
+ }
200
+ return;
201
+ }
202
+
203
+ // POST /json/cloudtrail/trails
204
+ if (path === '/json/cloudtrail/trails' && method === 'POST') {
205
+ try {
206
+ const body = await parseBody(req);
207
+ const result = simulator.createTrail(body);
208
+ sendJson(res, 201, result);
209
+ } catch (err) {
210
+ logger.error('[CloudTrail] POST /json/cloudtrail/trails error:', err.message);
211
+ sendError(res, err);
212
+ }
213
+ return;
214
+ }
215
+
216
+ // DELETE /json/cloudtrail/trails/:trailName
217
+ if (path.startsWith('/json/cloudtrail/trails/') && method === 'DELETE') {
218
+ try {
219
+ const trailName = path.replace('/json/cloudtrail/trails/', '');
220
+ const result = simulator.deleteTrail({ Name: trailName });
221
+ sendJson(res, 200, result);
222
+ } catch (err) {
223
+ logger.error('[CloudTrail] DELETE /json/cloudtrail/trails error:', err.message);
224
+ sendError(res, err);
225
+ }
226
+ return;
227
+ }
228
+
229
+ // GET /json/cloudtrail/events
230
+ if (path === '/json/cloudtrail/events' && method === 'GET') {
231
+ try {
232
+ const urlObj = new URL(url, 'http://localhost');
233
+ const limit = parseInt(urlObj.searchParams.get('limit') || '50', 10);
234
+ const result = simulator.lookupEvents({ MaxResults: limit });
235
+ const events = result.Events || [];
236
+ sendJson(res, 200, events);
237
+ } catch (err) {
238
+ logger.error('[CloudTrail] GET /json/cloudtrail/events error:', err.message);
239
+ sendJson(res, 200, []);
240
+ }
241
+ return;
242
+ }
243
+
244
+ sendJson(res, 404, { message: `JSON route not found: ${path}` });
245
+ }
246
+
179
247
  // ─── Admin handlers ───────────────────────────────────────────────────────────
180
248
 
181
249
  async function handleAdmin(req, res, simulator, logger) {
@@ -1,3 +1,4 @@
1
+ const { setupServiceMiddleware } = require('../../middleware');
1
2
  /**
2
3
  * Cognito Server - Servidor HTTP para Cognito API
3
4
  */
@@ -7,7 +8,9 @@ const cors = require('cors');
7
8
  const logger = require('../../utils/logger');
8
9
 
9
10
  class CognitoServer {
10
- constructor(port, config) {
11
+ constructor(port, config) {
12
+ this.organizationsService = null;
13
+ this.stsService = null;
11
14
  this.port = port;
12
15
  this.config = config;
13
16
  this.app = express();
@@ -1,3 +1,4 @@
1
+ const { setupServiceMiddleware } = require('../../middleware');
1
2
  /**
2
3
  * DynamoDB Server - Servidor HTTP para DynamoDB
3
4
  */
@@ -8,7 +9,9 @@ const DynamoDBSimulator = require('./simulator');
8
9
  const logger = require('../../utils/logger');
9
10
 
10
11
  class DynamoDBServer {
11
- constructor(port, config) {
12
+ constructor(port, config) {
13
+ this.organizationsService = null;
14
+ this.stsService = null;
12
15
  this.port = port;
13
16
  this.config = config;
14
17
  this.app = express();
@@ -625,7 +625,8 @@ class DynamoDBSimulator {
625
625
  }
626
626
 
627
627
  // Tenta match de operador infix: attr OP :val
628
- const match = trimmedPart.match(/([^\s]+)\s*(=|>|<|>=|<=|BEGINS_WITH|BETWEEN)\s*([^\s]+)(?:\s+AND\s+([^\s]+))?/i);
628
+ const match = trimmedPart.match(/([^\s]+)\s*(>=|<=|=|>|<|BEGINS_WITH|BETWEEN)\s*([^\s]+)(?:\s+AND\s+([^\s]+))?/i);//trimmedPart.match(/^([#\w.]+)\s*(>=|<=|=|>|<)\s*(:\w+)\s*$/);
629
+
629
630
  if (match) {
630
631
  const attrPlaceholder = match[1];
631
632
  const operator = match[2].toUpperCase();