serverless-spy 0.0.35 → 0.0.37

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 (116) hide show
  1. package/.jsii +236 -9
  2. package/API.md +161 -0
  3. package/cli/cli.ts +145 -75
  4. package/cli/icons/Arch_AWS-Lambda_16.svg +18 -0
  5. package/cli/icons/Arch_Amazon-DynamoDB_16.svg +18 -0
  6. package/cli/icons/Arch_Amazon-EventBridge_16.svg +18 -0
  7. package/cli/icons/Arch_Amazon-Simple-Notification-Service_16.svg +18 -0
  8. package/cli/icons/Arch_Amazon-Simple-Queue-Service_16.svg +18 -0
  9. package/cli/icons/Arch_Amazon-Simple-Storage-Service_16.svg +18 -0
  10. package/cli/index.html +84 -25
  11. package/cli/node_modules/commander/LICENSE +22 -0
  12. package/cli/node_modules/commander/Readme.md +1114 -0
  13. package/cli/node_modules/commander/esm.mjs +16 -0
  14. package/cli/node_modules/commander/index.js +27 -0
  15. package/cli/node_modules/commander/lib/argument.js +147 -0
  16. package/cli/node_modules/commander/lib/command.js +2161 -0
  17. package/cli/node_modules/commander/lib/error.js +45 -0
  18. package/cli/node_modules/commander/lib/help.js +406 -0
  19. package/cli/node_modules/commander/lib/option.js +324 -0
  20. package/cli/node_modules/commander/lib/suggestSimilar.js +100 -0
  21. package/cli/node_modules/commander/package-support.json +16 -0
  22. package/cli/node_modules/commander/package.json +80 -0
  23. package/cli/node_modules/commander/typings/index.d.ts +879 -0
  24. package/cli/package.json +23 -0
  25. package/cli/sampleData.ts +518 -0
  26. package/cli/style.css +66 -42
  27. package/cli/webServerlessSpy.ts +461 -0
  28. package/common/SpyEventSender.ts +291 -0
  29. package/common/getWebSocketUrl.ts +21 -4
  30. package/common/spyEvents/EventBridgeBaseSpyEvent.ts +13 -0
  31. package/common/spyEvents/EventBridgeRuleSpyEvent.ts +2 -7
  32. package/common/spyEvents/EventBridgeSpyEvent.ts +2 -7
  33. package/common/spyEvents/FunctionBaseSpyEvent.ts +7 -0
  34. package/common/spyEvents/FunctionConsole.ts +5 -0
  35. package/common/spyEvents/FunctionConsoleSpyEvent.ts +5 -8
  36. package/common/spyEvents/FunctionResponseSpyEvent.ts +2 -5
  37. package/common/spyEvents/SnsSpyEventBase.ts +11 -0
  38. package/common/spyEvents/SnsSubscriptionSpyEvent.ts +3 -9
  39. package/common/spyEvents/SnsTopicSpyEvent.ts +3 -9
  40. package/dist/releasetag.txt +1 -1
  41. package/extension/interceptor.ts +107 -27
  42. package/functions/sendMessage.ts +4 -2
  43. package/functions/sqsSubscriptionAndDropAllMessages.ts +3 -0
  44. package/lib/cli/cli.js +124 -65
  45. package/lib/cli/cli.mjs +125 -66
  46. package/lib/cli/sampleData.d.ts +892 -0
  47. package/lib/cli/sampleData.js +481 -0
  48. package/lib/cli/sampleData.mjs +478 -0
  49. package/lib/cli/webServerlessSpy.js +5516 -0
  50. package/lib/cli/webServerlessSpy.js.map +7 -0
  51. package/lib/common/SpyEventSender.d.ts +17 -0
  52. package/lib/common/SpyEventSender.js +227 -0
  53. package/lib/common/SpyEventSender.mjs +223 -0
  54. package/lib/common/getWebSocketUrl.d.ts +1 -1
  55. package/lib/common/getWebSocketUrl.js +19 -7
  56. package/lib/common/getWebSocketUrl.mjs +17 -5
  57. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.d.ts +9 -0
  58. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.js +3 -0
  59. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.mjs +2 -0
  60. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.d.ts +2 -7
  61. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.js +1 -1
  62. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.mjs +1 -1
  63. package/lib/common/spyEvents/EventBridgeSpyEvent.d.ts +2 -7
  64. package/lib/common/spyEvents/EventBridgeSpyEvent.js +1 -1
  65. package/lib/common/spyEvents/EventBridgeSpyEvent.mjs +1 -1
  66. package/lib/common/spyEvents/FunctionBaseSpyEvent.d.ts +6 -0
  67. package/lib/common/spyEvents/FunctionBaseSpyEvent.js +3 -0
  68. package/lib/common/spyEvents/FunctionBaseSpyEvent.mjs +2 -0
  69. package/lib/common/spyEvents/FunctionConsole.d.ts +5 -0
  70. package/lib/common/spyEvents/FunctionConsole.js +3 -0
  71. package/lib/common/spyEvents/FunctionConsole.mjs +2 -0
  72. package/lib/common/spyEvents/FunctionConsoleSpyEvent.d.ts +4 -8
  73. package/lib/common/spyEvents/FunctionConsoleSpyEvent.js +1 -1
  74. package/lib/common/spyEvents/FunctionConsoleSpyEvent.mjs +1 -1
  75. package/lib/common/spyEvents/FunctionResponseSpyEvent.d.ts +2 -5
  76. package/lib/common/spyEvents/FunctionResponseSpyEvent.js +1 -1
  77. package/lib/common/spyEvents/FunctionResponseSpyEvent.mjs +1 -1
  78. package/lib/common/spyEvents/SnsSpyEventBase.d.ts +10 -0
  79. package/lib/common/spyEvents/SnsSpyEventBase.js +3 -0
  80. package/lib/common/spyEvents/SnsSpyEventBase.mjs +2 -0
  81. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.d.ts +2 -9
  82. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.js +1 -1
  83. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.mjs +1 -1
  84. package/lib/common/spyEvents/SnsTopicSpyEvent.d.ts +2 -9
  85. package/lib/common/spyEvents/SnsTopicSpyEvent.js +1 -1
  86. package/lib/common/spyEvents/SnsTopicSpyEvent.mjs +1 -1
  87. package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js +10793 -23825
  88. package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js.map +4 -4
  89. package/lib/listener/ServerlessSpyListenerParams.d.ts +1 -0
  90. package/lib/listener/ServerlessSpyListenerParams.js +1 -1
  91. package/lib/listener/ServerlessSpyListenerParams.mjs +1 -1
  92. package/lib/listener/SpyHandlers.ts.d.ts +30 -2
  93. package/lib/listener/SpyHandlers.ts.js +1 -1
  94. package/lib/listener/SpyHandlers.ts.mjs +1 -1
  95. package/lib/listener/WsListener.d.ts +4 -21
  96. package/lib/listener/WsListener.js +21 -13
  97. package/lib/listener/WsListener.mjs +22 -14
  98. package/lib/src/ServerlessSpy.d.ts +44 -14
  99. package/lib/src/ServerlessSpy.js +228 -86
  100. package/lib/src/ServerlessSpy.mjs +227 -85
  101. package/lib/src/common/envVariableNames.d.ts +6 -2
  102. package/lib/src/common/envVariableNames.js +6 -2
  103. package/lib/src/common/envVariableNames.mjs +6 -2
  104. package/listener/ServerlessSpyListenerParams.ts +1 -0
  105. package/listener/SpyHandlers.ts.ts +70 -9
  106. package/listener/WsListener.ts +39 -24
  107. package/package.json +5 -3
  108. package/cli/serverlessSpy.js +0 -73
  109. package/cli/ws.ts +0 -79
  110. package/common/publishSpyEvent.ts +0 -269
  111. package/lib/cli/ws.d.ts +0 -1
  112. package/lib/cli/ws.js +0 -68
  113. package/lib/cli/ws.mjs +0 -66
  114. package/lib/common/publishSpyEvent.d.ts +0 -4
  115. package/lib/common/publishSpyEvent.js +0 -211
  116. package/lib/common/publishSpyEvent.mjs +0 -205
@@ -0,0 +1,461 @@
1
+ import { Modal, Tooltip } from 'bootstrap';
2
+ import formatHighlight from 'json-format-highlight';
3
+ import { EventBridgeBaseSpyEvent } from '../common/spyEvents/EventBridgeBaseSpyEvent';
4
+ import { FunctionBaseSpyEvent } from '../common/spyEvents/FunctionBaseSpyEvent';
5
+ import { SnsSpyEventBase } from '../common/spyEvents/SnsSpyEventBase';
6
+ import { SpyEvent } from '../common/spyEvents/SpyEvent';
7
+ import { SpyMessage } from '../common/spyEvents/SpyMessage';
8
+ //import { sampleData } from './sampleData'; // Leave this for testing
9
+
10
+ //needed because of strange bug in Bootstrap Tooltip
11
+ (window as any).process = { env: {} };
12
+
13
+ document.addEventListener('DOMContentLoaded', function () {
14
+ run();
15
+ });
16
+
17
+ function run() {
18
+ // ************* variables *************
19
+ const spyMessages: (SpyMessageExt | SpyMessageGroup)[] = [];
20
+ const snsEventsByMessageId: Record<string, SpyMessageExt<SnsSpyEventBase>[]> =
21
+ {};
22
+ const functionEventsByRequestId: Record<
23
+ string,
24
+ SpyMessageExt<FunctionBaseSpyEvent>[]
25
+ > = {};
26
+ const eventBridgeById: Record<
27
+ string,
28
+ SpyMessageExt<EventBridgeBaseSpyEvent>[]
29
+ > = {};
30
+ let uiNeedsRefresh = false;
31
+ let stackList: string[] | undefined;
32
+ const selectedStackLocalStorageKey = 'selectedStack';
33
+ let ws: WebSocket;
34
+
35
+ // ************* HTML elements *************
36
+ const tableBodyElement = document.getElementById('tableBody')!;
37
+ const modalTimeElement = document.getElementById('time')!;
38
+ const modalServiceKeyElement = document.getElementById('serviceKey')!;
39
+ const modalDataElement = document.getElementById('data')!;
40
+ const errorContentElement = document.getElementById('errorContent')!;
41
+ const stackListContainerElement =
42
+ document.getElementById('stackListContainer')!;
43
+ const stackListElement = document.getElementById(
44
+ 'stackList'
45
+ ) as HTMLSelectElement;
46
+ const serviceKeyFilterInputElement = document.getElementById(
47
+ 'serviceKeyFilter'
48
+ ) as HTMLInputElement;
49
+ const dataFilterInputElement = document.getElementById(
50
+ 'dataFilter'
51
+ ) as HTMLInputElement;
52
+ const detailsModal = new Modal('#detailsModal', {
53
+ backdrop: true,
54
+ keyboard: true,
55
+ });
56
+ const errorModal = new Modal('#errorModal', {
57
+ backdrop: true,
58
+ keyboard: true,
59
+ });
60
+
61
+ // ************* events *************
62
+ serviceKeyFilterInputElement.addEventListener('input', () => {
63
+ uiNeedsRefresh = true;
64
+ });
65
+ dataFilterInputElement.addEventListener('input', () => {
66
+ uiNeedsRefresh = true;
67
+ });
68
+ tableBodyElement.addEventListener('click', openDetails());
69
+ stackListElement.addEventListener('change', () => {
70
+ switchStack();
71
+ });
72
+
73
+ void (async () => {
74
+ await fillStacks();
75
+ await connectToWebSocket();
76
+ })();
77
+ window.requestAnimationFrame(render);
78
+ setupTooltip();
79
+
80
+ // Do not remove!
81
+ // sample data for testing purposes
82
+ // for (const sm of sampleData) {
83
+ // addSpyMessage(sm as any);
84
+ // }
85
+
86
+ function switchStack() {
87
+ spyMessages.length = 0;
88
+ uiNeedsRefresh = true;
89
+ ws.close();
90
+ }
91
+
92
+ async function fillStacks() {
93
+ const response = await fetch('/stackList');
94
+ try {
95
+ stackList = await response.json();
96
+ } catch {
97
+ stackList = [];
98
+ }
99
+
100
+ const selectedStack = localStorage.getItem(selectedStackLocalStorageKey);
101
+
102
+ stackListElement.innerHTML =
103
+ stackList
104
+ ?.map(
105
+ (s) =>
106
+ `<option value="${s}" ${
107
+ selectedStack === s ? 'selected' : ''
108
+ }>${s}</option>`
109
+ )
110
+ .join('') ?? '';
111
+
112
+ console.log(stackListContainerElement.style.display);
113
+ stackListContainerElement.style.display =
114
+ stackList && stackList.length > 1 ? '' : 'none';
115
+ }
116
+
117
+ async function connectToWebSocket() {
118
+ try {
119
+ const stack = stackListElement.value;
120
+ const response = await fetch(`/wsUrl/${stack}`);
121
+ if (response.ok) {
122
+ const url = await response.text();
123
+ ws = new WebSocket(url);
124
+
125
+ ws.addEventListener('open', () => {
126
+ console.log(
127
+ `Connected ${new Date().toISOString()} to stack ${stack}`
128
+ );
129
+ });
130
+ ws.addEventListener('message', receiveSpyMessage());
131
+ ws.addEventListener('close', () => {
132
+ console.log(`Disconnected ${new Date().toISOString()}, reconnecting`);
133
+ void connectToWebSocket();
134
+ });
135
+ } else {
136
+ showError(await response.text());
137
+ }
138
+ } catch (err) {
139
+ showError(err.message);
140
+ }
141
+ }
142
+
143
+ function showError(messsage: string) {
144
+ errorContentElement.innerHTML = messsage;
145
+ errorModal.show();
146
+ }
147
+
148
+ function render() {
149
+ if (uiNeedsRefresh) {
150
+ tableBodyElement.innerHTML = spyMessages
151
+ .map((sm) => renderSpyMessage(sm))
152
+ .join('');
153
+ uiNeedsRefresh = false;
154
+ }
155
+
156
+ window.requestAnimationFrame(render);
157
+ }
158
+
159
+ function receiveSpyMessage(): (
160
+ this: WebSocket,
161
+ ev: MessageEvent<any>
162
+ ) => any {
163
+ return ({ data }) => {
164
+ //console.log(data);
165
+ let parsed;
166
+ try {
167
+ parsed = JSON.parse(data);
168
+ } catch (err) {
169
+ console.error('Can not parse ' + data);
170
+ }
171
+
172
+ addSpyMessage(parsed);
173
+ };
174
+ }
175
+
176
+ function openDetails(): (this: HTMLElement, ev: MouseEvent) => any {
177
+ return (e) => {
178
+ const row = (e.target as HTMLElement).closest('tr');
179
+ const dataElements = row?.getElementsByClassName('data');
180
+ const timeElements = row?.getElementsByClassName('time');
181
+ const serviceKeyElements = row?.getElementsByClassName('serviceKey');
182
+
183
+ if (
184
+ !dataElements ||
185
+ dataElements.length === 0 ||
186
+ !timeElements ||
187
+ timeElements.length === 0 ||
188
+ !serviceKeyElements ||
189
+ serviceKeyElements.length === 0
190
+ ) {
191
+ return;
192
+ }
193
+
194
+ modalTimeElement.innerHTML = timeElements[0].textContent || '';
195
+ modalServiceKeyElement.innerHTML =
196
+ serviceKeyElements[0].textContent || '';
197
+ modalDataElement.innerHTML = formatDataJson(
198
+ dataElements[0].textContent || ''
199
+ );
200
+ detailsModal.show();
201
+ };
202
+ }
203
+
204
+ function formatDataJson(dataJson: string) {
205
+ return formatHighlight(JSON.stringify(JSON.parse(dataJson), null, 2), {
206
+ keyColor: 'black',
207
+ numberColor: 'blue',
208
+ stringColor: '#0B7500',
209
+ trueColor: '#00cc00',
210
+ falseColor: '#ff8080',
211
+ nullColor: 'cornflowerblue',
212
+ });
213
+ }
214
+
215
+ function addSpyMessage(spyMessage: SpyMessageExt) {
216
+ const spyMessageExt = spyMessage as SpyMessageExt;
217
+ spyMessageExt.dataJsonLowerCase = JSON.stringify(
218
+ spyMessageExt.data
219
+ ).toLocaleLowerCase();
220
+ spyMessageExt.serviceKeyLowerCase =
221
+ spyMessageExt.serviceKey.toLocaleLowerCase();
222
+
223
+ const service = getServiceNameFromServiceKey(spyMessageExt.serviceKey);
224
+ let spyMessageToAdd: SpyMessageExt | SpyMessageGroup | undefined =
225
+ spyMessageExt;
226
+
227
+ if (service === 'Function') {
228
+ const spyMessageFunction =
229
+ spyMessageExt as SpyMessageExt<FunctionBaseSpyEvent>;
230
+ const awsRequestId = spyMessageFunction.data.context.awsRequestId;
231
+ let functionEvents = functionEventsByRequestId[awsRequestId];
232
+ if (!functionEvents) {
233
+ functionEvents = [];
234
+ functionEventsByRequestId[awsRequestId] = functionEvents;
235
+ }
236
+
237
+ const step = getFunctionStepFromServiceKey(spyMessageExt.serviceKey);
238
+
239
+ if (step === 'Request') {
240
+ spyMessageToAdd = <SpyMessageGroup>{
241
+ timestamp: spyMessageExt.timestamp,
242
+ serviceKey: spyMessageExt.serviceKey,
243
+ messages: functionEvents,
244
+ };
245
+ functionEvents.unshift(spyMessageFunction);
246
+ } else {
247
+ addSpyMessageToArraySorted(spyMessageFunction, functionEvents);
248
+ spyMessageToAdd = undefined;
249
+ }
250
+ } else if (service === 'SnsSubscription' || service === 'SnsTopic') {
251
+ const spyMessageSns = spyMessageExt as SpyMessageExt<SnsSpyEventBase>;
252
+ const messageId = spyMessageSns.data.messageId;
253
+ let snsEvents = snsEventsByMessageId[messageId];
254
+ if (!snsEvents) {
255
+ snsEvents = [];
256
+ snsEventsByMessageId[messageId] = snsEvents;
257
+ }
258
+
259
+ if (service === 'SnsTopic') {
260
+ spyMessageToAdd = <SpyMessageGroup>{
261
+ timestamp: spyMessageExt.timestamp,
262
+ serviceKey: spyMessageExt.serviceKey,
263
+ messages: snsEvents,
264
+ };
265
+ snsEvents.unshift(spyMessageSns);
266
+ } else {
267
+ addSpyMessageToArraySorted(spyMessageSns, snsEvents);
268
+ spyMessageToAdd = undefined;
269
+ }
270
+ } else if (service === 'EventBridge' || service === 'EventBridgeRule') {
271
+ const spyMessageEventBridge =
272
+ spyMessageExt as SpyMessageExt<EventBridgeBaseSpyEvent>;
273
+ const eventBridgeId = spyMessageEventBridge.data.eventBridgeId;
274
+ let eventBridgeEvents = eventBridgeById[eventBridgeId];
275
+ if (!eventBridgeEvents) {
276
+ eventBridgeEvents = [];
277
+ eventBridgeById[eventBridgeId] = eventBridgeEvents;
278
+ }
279
+
280
+ if (service === 'EventBridge') {
281
+ spyMessageToAdd = <SpyMessageGroup>{
282
+ timestamp: spyMessageExt.timestamp,
283
+ serviceKey: spyMessageExt.serviceKey,
284
+ messages: eventBridgeEvents,
285
+ };
286
+ eventBridgeEvents.unshift(spyMessageEventBridge);
287
+ } else {
288
+ addSpyMessageToArraySorted(spyMessageEventBridge, eventBridgeEvents);
289
+ spyMessageToAdd = undefined;
290
+ }
291
+ }
292
+
293
+ if (spyMessageToAdd) {
294
+ //add in correct order
295
+ addSpyMessageToArraySorted(spyMessageToAdd, spyMessages);
296
+ uiNeedsRefresh = true;
297
+ }
298
+ }
299
+
300
+ function matchFilter(
301
+ spyMessage: SpyMessageExt,
302
+ filter: {
303
+ serviceKey: string;
304
+ data: string;
305
+ }
306
+ ) {
307
+ let testServiceKey = false;
308
+
309
+ try {
310
+ testServiceKey =
311
+ !filter.serviceKey ||
312
+ new RegExp(filter.serviceKey).test(spyMessage.serviceKeyLowerCase);
313
+ } catch {}
314
+
315
+ let testData = false;
316
+
317
+ try {
318
+ testData =
319
+ !filter.data ||
320
+ new RegExp(filter.data).test(spyMessage.dataJsonLowerCase);
321
+ } catch {}
322
+
323
+ return testServiceKey && testData;
324
+ }
325
+
326
+ function getFunctionStepFromServiceKey(serviceKey: string) {
327
+ const serviceKeyParts = serviceKey.split('#');
328
+ const step =
329
+ serviceKeyParts.length > 0
330
+ ? serviceKeyParts[serviceKeyParts.length - 1]
331
+ : undefined;
332
+ return step;
333
+ }
334
+
335
+ function renderSpyMessage(spyMessage: SpyMessageExt | SpyMessageGroup) {
336
+ const serviceKey = serviceKeyFilterInputElement.value?.toLocaleLowerCase();
337
+ const data = dataFilterInputElement.value?.toLocaleLowerCase();
338
+
339
+ let messages: SpyMessageExt[];
340
+ if ((spyMessage as SpyMessageGroup).messages) {
341
+ messages = (spyMessage as SpyMessageGroup).messages;
342
+ } else {
343
+ messages = [spyMessage as SpyMessageExt];
344
+ }
345
+
346
+ const html = messages
347
+ .filter((sm) => matchFilter(sm, { serviceKey, data }))
348
+ .map((sm, i) => {
349
+ const service = getServiceNameFromServiceKey(sm.serviceKey);
350
+ let icon: string | undefined;
351
+
352
+ let iconLinked = '<i class="icon-linked bi bi-arrow-return-right"></i>';
353
+
354
+ switch (service) {
355
+ case 'Function':
356
+ const step = getFunctionStepFromServiceKey(sm.serviceKey);
357
+
358
+ if (i === 0) {
359
+ icon = `<img class="aws-icon" src="icons/Arch_AWS-Lambda_16.svg" ></img>`;
360
+ } else {
361
+ icon = iconLinked;
362
+ }
363
+ break;
364
+ case 'S3':
365
+ icon =
366
+ '<img class="aws-icon" src="icons/Arch_Amazon-Simple-Storage-Service_16.svg"/>';
367
+ break;
368
+ case 'SnsTopic':
369
+ icon =
370
+ '<img class="aws-icon" src="icons/Arch_Amazon-Simple-Notification-Service_16.svg" />';
371
+ break;
372
+ case 'EventBridge':
373
+ icon =
374
+ '<img class="aws-icon" src="icons/Arch_Amazon-EventBridge_16.svg" />';
375
+ break;
376
+ case 'DynamoDB':
377
+ icon =
378
+ '<img class="aws-icon" src="icons/Arch_Amazon-DynamoDB_16.svg" />';
379
+ break;
380
+ case 'Sqs':
381
+ icon =
382
+ '<img class="aws-icon" src="icons/Arch_Amazon-Simple-Queue-Service_16.svg" />';
383
+ break;
384
+ case 'EventBridgeRule':
385
+ if (i === 0) {
386
+ icon =
387
+ '<img class="aws-icon" src="icons/Arch_Amazon-EventBridge_16.svg" />';
388
+ } else {
389
+ icon = iconLinked;
390
+ }
391
+ break;
392
+ case 'SnsSubscription':
393
+ if (i === 0) {
394
+ icon =
395
+ '<img class="aws-icon" src="icons/Arch_Amazon-Simple-Notification-Service_16.svg" />';
396
+ } else {
397
+ icon = iconLinked;
398
+ }
399
+ break;
400
+ default:
401
+ break;
402
+ }
403
+ return `<tr>
404
+ <td class="col-time"><span class="time">${new Date(
405
+ sm.timestamp
406
+ ).toISOString()}</span></td>
407
+ <td class="col-servicekey">${icon}<span class="serviceKey">${
408
+ sm.serviceKey
409
+ }</span></td>
410
+ <td class="col-data"><div class="data">${JSON.stringify(
411
+ sm.data
412
+ )}</div></td></tr>
413
+
414
+ `;
415
+ })
416
+ .join('');
417
+ return html;
418
+ }
419
+
420
+ function getServiceNameFromServiceKey(serviceKey: string) {
421
+ const serviceKeyParts = serviceKey.split('#');
422
+ const service = serviceKeyParts.length > 0 ? serviceKeyParts[0] : undefined;
423
+ return service;
424
+ }
425
+
426
+ function setupTooltip() {
427
+ var tooltipTriggerList = [].slice.call(
428
+ document.querySelectorAll('[data-bs-toggle="tooltip"]')
429
+ );
430
+
431
+ tooltipTriggerList.map(function (tooltipTriggerEl) {
432
+ return new Tooltip(tooltipTriggerEl);
433
+ });
434
+ }
435
+
436
+ function addSpyMessageToArraySorted(
437
+ spyMessageToAdd: SpyMessageExt | SpyMessageGroup,
438
+ spyMessages: (SpyMessageExt | SpyMessageGroup)[]
439
+ ) {
440
+ let index = 0;
441
+ for (let i = spyMessages.length - 1; i >= 0; i--) {
442
+ const currentSpyMessages = spyMessages[i];
443
+ index = i + 1;
444
+ if (i === 0 || currentSpyMessages.timestamp < spyMessageToAdd.timestamp) {
445
+ break;
446
+ }
447
+ }
448
+ spyMessages.splice(index, 0, spyMessageToAdd);
449
+ }
450
+ }
451
+
452
+ type SpyMessageExt<T extends SpyEvent = SpyEvent> = SpyMessage<T> & {
453
+ dataJsonLowerCase: string;
454
+ serviceKeyLowerCase: string;
455
+ };
456
+
457
+ type SpyMessageGroup<T extends SpyEvent = SpyEvent> = {
458
+ timestamp: string;
459
+ serviceKey: string;
460
+ messages: SpyMessageExt<T>[];
461
+ };