@elliemae/ssf-guest 2.23.6 → 2.25.0

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 (58) hide show
  1. package/dist/cjs/callchain-grandchild.html +60 -0
  2. package/dist/cjs/e2e-guest.html +323 -0
  3. package/dist/cjs/guest.js +36 -14
  4. package/dist/cjs/tests/utils.js +6 -2
  5. package/dist/esm/callchain-grandchild.html +60 -0
  6. package/dist/esm/e2e-guest.html +323 -0
  7. package/dist/esm/guest.js +38 -15
  8. package/dist/esm/tests/utils.js +6 -2
  9. package/dist/public/callchain-grandchild.html +1 -0
  10. package/dist/public/callchain-grandchild.js +4 -0
  11. package/dist/public/callchain-grandchild.js.br +0 -0
  12. package/dist/public/callchain-grandchild.js.gz +0 -0
  13. package/dist/public/callchain-grandchild.js.map +1 -0
  14. package/dist/public/creditService.html +1 -1
  15. package/dist/public/e2e-guest.html +1 -0
  16. package/dist/public/e2e-guest.js +3 -0
  17. package/dist/public/e2e-guest.js.br +0 -0
  18. package/dist/public/e2e-guest.js.gz +0 -0
  19. package/dist/public/e2e-guest.js.map +1 -0
  20. package/dist/public/index.html +1 -1
  21. package/dist/public/js/emuiSsfGuest.42c1a5949e7eb13de9e0.js +3 -0
  22. package/dist/public/js/emuiSsfGuest.42c1a5949e7eb13de9e0.js.br +0 -0
  23. package/dist/public/js/emuiSsfGuest.42c1a5949e7eb13de9e0.js.gz +0 -0
  24. package/dist/public/js/emuiSsfGuest.42c1a5949e7eb13de9e0.js.map +1 -0
  25. package/dist/public/loanValidation.html +1 -1
  26. package/dist/public/pricingService.html +1 -1
  27. package/dist/public/titleService.html +1 -1
  28. package/dist/public/util.js +1 -1
  29. package/dist/public/util.js.br +0 -0
  30. package/dist/public/util.js.gz +0 -0
  31. package/dist/public/util.js.map +1 -1
  32. package/dist/public/v2-guest.html +1 -1
  33. package/dist/types/lib/guest.d.ts +16 -0
  34. package/dist/types/lib/tests/utils.d.ts +4 -2
  35. package/dist/types/tsconfig.tsbuildinfo +1 -1
  36. package/dist/umd/callchain-grandchild.html +1 -0
  37. package/dist/umd/callchain-grandchild.js +4 -0
  38. package/dist/umd/callchain-grandchild.js.br +0 -0
  39. package/dist/umd/callchain-grandchild.js.gz +0 -0
  40. package/dist/umd/callchain-grandchild.js.map +1 -0
  41. package/dist/umd/e2e-guest.html +1 -0
  42. package/dist/umd/e2e-guest.js +3 -0
  43. package/dist/umd/e2e-guest.js.br +0 -0
  44. package/dist/umd/e2e-guest.js.gz +0 -0
  45. package/dist/umd/e2e-guest.js.map +1 -0
  46. package/dist/umd/index.js +1 -1
  47. package/dist/umd/index.js.br +0 -0
  48. package/dist/umd/index.js.gz +0 -0
  49. package/dist/umd/index.js.map +1 -1
  50. package/dist/umd/util.js +1 -1
  51. package/dist/umd/util.js.br +0 -0
  52. package/dist/umd/util.js.gz +0 -0
  53. package/dist/umd/util.js.map +1 -1
  54. package/package.json +4 -4
  55. package/dist/public/js/emuiSsfGuest.650afabac7fe99fb3a5b.js +0 -3
  56. package/dist/public/js/emuiSsfGuest.650afabac7fe99fb3a5b.js.br +0 -0
  57. package/dist/public/js/emuiSsfGuest.650afabac7fe99fb3a5b.js.gz +0 -0
  58. package/dist/public/js/emuiSsfGuest.650afabac7fe99fb3a5b.js.map +0 -1
@@ -0,0 +1,323 @@
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>SSF E2E Guest</title>
7
+ <script src="https://cdn.tailwindcss.com?plugins=forms"></script>
8
+ <script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script>
9
+ </head>
10
+ <body class="bg-emerald-50 p-2 text-xs">
11
+ <!-- Header with connection status -->
12
+ <div class="flex items-center justify-between mb-1">
13
+ <h3 class="font-semibold text-emerald-800">E2E Guest</h3>
14
+ <span
15
+ data-testid="guest-connection-status"
16
+ id="connStatus"
17
+ class="px-2 py-0.5 rounded text-white bg-gray-400"
18
+ >disconnected</span
19
+ >
20
+ </div>
21
+
22
+ <!-- Guest info -->
23
+ <div
24
+ data-testid="guest-info"
25
+ id="guestInfo"
26
+ class="bg-emerald-100 rounded p-1 mb-1 text-xs text-emerald-700"
27
+ >
28
+ Params: <span id="searchParams">—</span>
29
+ </div>
30
+
31
+ <!-- Quick guide -->
32
+ <details
33
+ class="bg-emerald-100 rounded p-1 mb-1 text-[10px] text-emerald-700"
34
+ >
35
+ <summary class="font-semibold cursor-pointer">
36
+ Quick Guide (click to expand)
37
+ </summary>
38
+ <ul class="mt-1 list-disc list-inside space-y-0.5">
39
+ <li>
40
+ <strong>Objects tab:</strong> Use <code>getObject()</code> to fetch a
41
+ scripting object, then <code>Invoke</code> to call a method. Result +
42
+ errors appear below.
43
+ </li>
44
+ <li>
45
+ <strong>Events tab:</strong> Subscribe to events (with or without
46
+ criteria). When the host dispatches, received events appear below. Use
47
+ the Feedback dropdown to control what the callback returns.
48
+ </li>
49
+ <li>
50
+ <strong>Lifecycle tab:</strong> Change log level or
51
+ disconnect/reconnect. Status badge at top right should reflect the
52
+ state.
53
+ </li>
54
+ <li>
55
+ <strong>Verify:</strong> Connection badge =
56
+ <span class="text-green-700 font-bold">connected</span>. After
57
+ getObject: methods are listed. After invoke: result appears. After
58
+ subscribe+dispatch: events appear.
59
+ </li>
60
+ </ul>
61
+ </details>
62
+
63
+ <!-- Tabs -->
64
+ <div class="flex gap-1 mb-2 border-b border-emerald-300">
65
+ <button
66
+ data-testid="tab-objects"
67
+ class="tab-btn px-2 py-1 rounded-t font-medium bg-emerald-200 text-emerald-800"
68
+ data-tab="objects"
69
+ >
70
+ Objects
71
+ </button>
72
+ <button
73
+ data-testid="tab-events"
74
+ class="tab-btn px-2 py-1 rounded-t text-emerald-600"
75
+ data-tab="events"
76
+ >
77
+ Events
78
+ </button>
79
+ <button
80
+ data-testid="tab-lifecycle"
81
+ class="tab-btn px-2 py-1 rounded-t text-emerald-600"
82
+ data-tab="lifecycle"
83
+ >
84
+ Lifecycle
85
+ </button>
86
+ </div>
87
+
88
+ <!-- Tab: Objects -->
89
+ <div id="panel-objects" class="tab-panel">
90
+ <div class="space-y-1">
91
+ <button
92
+ data-testid="btn-list-objects"
93
+ id="btnListObjects"
94
+ class="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-700"
95
+ >
96
+ listObjects()
97
+ </button>
98
+ <div
99
+ data-testid="objects-list"
100
+ id="objectsList"
101
+ class="bg-white rounded p-1 min-h-[20px]"
102
+ >
103
+
104
+ </div>
105
+
106
+ <div class="flex gap-1 items-center">
107
+ <input
108
+ data-testid="input-object-id"
109
+ id="inputObjectId"
110
+ type="text"
111
+ value="Loan"
112
+ class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1"
113
+ placeholder="Object ID"
114
+ />
115
+ <button
116
+ data-testid="btn-get-object"
117
+ id="btnGetObject"
118
+ class="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-700"
119
+ >
120
+ getObject()
121
+ </button>
122
+ </div>
123
+ <div
124
+ data-testid="current-object"
125
+ id="currentObject"
126
+ class="bg-white rounded p-1 min-h-[20px]"
127
+ >
128
+
129
+ </div>
130
+
131
+ <div class="flex gap-1 items-center">
132
+ <input
133
+ data-testid="input-method"
134
+ id="inputMethod"
135
+ type="text"
136
+ value="getLoanDetails"
137
+ class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1"
138
+ placeholder="Method name"
139
+ />
140
+ <input
141
+ data-testid="input-args"
142
+ id="inputArgs"
143
+ type="text"
144
+ value=""
145
+ class="rounded border-emerald-300 px-2 py-0.5 text-xs w-24"
146
+ placeholder="Args (JSON)"
147
+ />
148
+ <button
149
+ data-testid="btn-invoke"
150
+ id="btnInvoke"
151
+ class="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-700"
152
+ >
153
+ Invoke
154
+ </button>
155
+ </div>
156
+ <div class="flex gap-1">
157
+ <button
158
+ data-testid="btn-invoke-error"
159
+ id="btnInvokeError"
160
+ class="rounded bg-red-500 px-2 py-1 text-white hover:bg-red-600"
161
+ >
162
+ TC-SO-08: Call throwError()
163
+ </button>
164
+ <button
165
+ data-testid="btn-invoke-async"
166
+ id="btnInvokeAsync"
167
+ class="rounded bg-blue-500 px-2 py-1 text-white hover:bg-blue-600"
168
+ >
169
+ TC-SO-07: Call asyncMethod(200)
170
+ </button>
171
+ </div>
172
+ <div
173
+ data-testid="invoke-result"
174
+ id="invokeResult"
175
+ class="bg-white rounded p-1 min-h-[30px] whitespace-pre-wrap"
176
+ >
177
+
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ <!-- Tab: Events -->
183
+ <div id="panel-events" class="tab-panel hidden">
184
+ <div class="space-y-1">
185
+ <div class="flex gap-1 items-center">
186
+ <input
187
+ data-testid="input-event-id"
188
+ id="inputEventId"
189
+ type="text"
190
+ value="Loan.onPreSave"
191
+ class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1"
192
+ placeholder="Event ID"
193
+ />
194
+ <button
195
+ data-testid="btn-subscribe"
196
+ id="btnSubscribe"
197
+ class="rounded bg-purple-600 px-2 py-1 text-white hover:bg-purple-700"
198
+ >
199
+ Subscribe
200
+ </button>
201
+ </div>
202
+
203
+ <div class="flex gap-1 items-center">
204
+ <input
205
+ data-testid="input-criteria"
206
+ id="inputCriteria"
207
+ type="text"
208
+ value=""
209
+ class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1"
210
+ placeholder='Criteria JSON e.g. {"amount":{"gt":100000}}'
211
+ />
212
+ <button
213
+ data-testid="btn-subscribe-criteria"
214
+ id="btnSubscribeCriteria"
215
+ class="rounded bg-purple-600 px-2 py-1 text-white hover:bg-purple-700"
216
+ >
217
+ Subscribe+Criteria
218
+ </button>
219
+ </div>
220
+
221
+ <div class="flex gap-1 items-center">
222
+ <select
223
+ data-testid="select-unsub-token"
224
+ id="selectUnsubToken"
225
+ class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1"
226
+ >
227
+ <option value="">— select token —</option>
228
+ </select>
229
+ <button
230
+ data-testid="btn-unsubscribe"
231
+ id="btnUnsubscribe"
232
+ class="rounded bg-yellow-600 px-2 py-1 text-white hover:bg-yellow-700"
233
+ >
234
+ Unsubscribe
235
+ </button>
236
+ </div>
237
+
238
+ <h4 class="font-medium text-emerald-700 mt-1">Active Subscriptions</h4>
239
+ <div
240
+ data-testid="subscriptions-list"
241
+ id="subscriptionsList"
242
+ class="bg-white rounded p-1 min-h-[20px]"
243
+ >
244
+
245
+ </div>
246
+
247
+ <h4 class="font-medium text-emerald-700 mt-1">Received Events</h4>
248
+ <div
249
+ data-testid="events-received"
250
+ id="eventsReceived"
251
+ class="bg-white rounded p-1 min-h-[40px] max-h-[120px] overflow-y-auto"
252
+ >
253
+
254
+ </div>
255
+
256
+ <h4 class="font-medium text-emerald-700 mt-1">Feedback Return Value</h4>
257
+ <div class="flex gap-1 items-center">
258
+ <select
259
+ data-testid="select-feedback"
260
+ id="selectFeedback"
261
+ class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1"
262
+ >
263
+ <option value="true">return true (approve)</option>
264
+ <option value="false">return false (reject)</option>
265
+ <option value="object">return {status: "ok"}</option>
266
+ </select>
267
+ </div>
268
+ </div>
269
+ </div>
270
+
271
+ <!-- Tab: Lifecycle -->
272
+ <div id="panel-lifecycle" class="tab-panel hidden">
273
+ <div class="space-y-1">
274
+ <button
275
+ data-testid="btn-set-loglevel-0"
276
+ id="btnLogLevel0"
277
+ class="rounded bg-gray-600 px-2 py-1 text-white hover:bg-gray-700"
278
+ >
279
+ setLogLevel(0) — Silent
280
+ </button>
281
+ <button
282
+ data-testid="btn-set-loglevel-10"
283
+ id="btnLogLevel10"
284
+ class="rounded bg-gray-600 px-2 py-1 text-white hover:bg-gray-700"
285
+ >
286
+ setLogLevel(10) — Debug
287
+ </button>
288
+ <button
289
+ data-testid="btn-disconnect"
290
+ id="btnDisconnect"
291
+ class="rounded bg-red-600 px-2 py-1 text-white hover:bg-red-700"
292
+ >
293
+ close() — Disconnect
294
+ </button>
295
+ <p class="text-[10px] text-gray-400">
296
+ Expected: status badge → "disconnected", further calls fail
297
+ </p>
298
+ <button
299
+ data-testid="btn-reconnect"
300
+ id="btnReconnect"
301
+ class="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-700"
302
+ >
303
+ connect() — Reconnect
304
+ </button>
305
+ <p class="text-[10px] text-gray-400">
306
+ Expected: status badge → "connected", calls work again
307
+ </p>
308
+ </div>
309
+ </div>
310
+
311
+ <!-- Log -->
312
+ <div class="mt-2">
313
+ <h4 class="font-medium text-emerald-700">Guest Log</h4>
314
+ <div
315
+ data-testid="guest-log"
316
+ id="guestLog"
317
+ class="bg-white rounded p-1 text-xs min-h-[40px] max-h-[100px] overflow-y-auto font-mono"
318
+ ></div>
319
+ </div>
320
+
321
+ <script src="./e2e-guest.js" type="module"></script>
322
+ </body>
323
+ </html>
package/dist/esm/guest.js CHANGED
@@ -6,7 +6,8 @@ import {
6
6
  MessageType,
7
7
  getEventId,
8
8
  ProxyEvent,
9
- ScriptingObjectProxy
9
+ ScriptingObjectProxy,
10
+ AuditThrottler
10
11
  } from "@elliemae/microfe-common";
11
12
  import {
12
13
  logger as puiLogger,
@@ -29,6 +30,7 @@ var ResponseType = /* @__PURE__ */ ((ResponseType2) => {
29
30
  return ResponseType2;
30
31
  })(ResponseType || {});
31
32
  const KEEP_ALIVE_INTERVAL = 12e4;
33
+ const KEEP_ALIVE_OBJECT_TIMEOUT = 2e3;
32
34
  const capabilities = {
33
35
  eventFeedback: true
34
36
  };
@@ -130,6 +132,7 @@ class SSFGuest {
130
132
  * throttled keep alive function
131
133
  */
132
134
  #throttledKeepAlive = null;
135
+ #auditThrottler;
133
136
  /**
134
137
  * Create new guest
135
138
  * @param {GuestOption} options - options for the guest
@@ -155,6 +158,11 @@ class SSFGuest {
155
158
  team,
156
159
  appName
157
160
  });
161
+ this.#auditThrottler = new AuditThrottler({
162
+ logger: this.#logger,
163
+ enabled: options?.auditOperations,
164
+ throttleMs: options?.auditThrottleMs
165
+ });
158
166
  webvitals(this.#logger);
159
167
  logUnhandledErrors(this.#logger);
160
168
  this.#correlationId = uuidv4();
@@ -187,12 +195,17 @@ class SSFGuest {
187
195
  if (soJSON.functions) {
188
196
  soJSON.functions.forEach((functionName) => {
189
197
  Object.defineProperty(ctrl, functionName, {
190
- value: async (...args) => this.#invoke({
191
- objectId: ctrl.id,
192
- functionName,
193
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
194
- functionParams: [...args]
195
- }),
198
+ value: async (...args) => {
199
+ const fnCallContext = ctrl[functionName]?.callContext;
200
+ const callerChain = fnCallContext?.callChain;
201
+ return this.#invoke({
202
+ objectId: ctrl.id,
203
+ functionName,
204
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
205
+ functionParams: [...args],
206
+ callerChain
207
+ });
208
+ },
196
209
  enumerable: true
197
210
  });
198
211
  });
@@ -220,12 +233,14 @@ class SSFGuest {
220
233
  * @param {string} param0.objectId - The object ID to invoke
221
234
  * @param {string} param0.functionName - The function name to call
222
235
  * @param {Array<any>} param0.functionParams - The parameters for the function
236
+ * @param {Array<{id: string}>} [param0.callerChain] - Chain of original callers when forwarding through intermediaries
223
237
  * @returns {Promise<any>} The result of the function call
224
238
  */
225
239
  #invoke = async ({
226
240
  objectId,
227
241
  functionName,
228
- functionParams
242
+ functionParams,
243
+ callerChain
229
244
  }) => {
230
245
  this.#logger.debug(
231
246
  `Invoking scripting object method ${objectId}.${functionName}()...`
@@ -252,11 +267,12 @@ class SSFGuest {
252
267
  messageBody: {
253
268
  objectId,
254
269
  functionName,
255
- functionParams
270
+ functionParams,
271
+ ...callerChain?.length ? { callerChain } : {}
256
272
  }
257
273
  });
258
274
  const retVal = this.#handleResponse(response);
259
- this.#logger.audit({
275
+ this.#auditThrottler.log(`invoke:${objectId}.${functionName}`, {
260
276
  message: "Guest proxy invoked Scripting Object method",
261
277
  scriptingObject: objectId,
262
278
  scriptingMethod: functionName,
@@ -444,14 +460,14 @@ class SSFGuest {
444
460
  requestId,
445
461
  response: values
446
462
  });
447
- this.#logger.audit({
463
+ this.#auditThrottler.log(`event:${eventId}`, {
448
464
  message: "Guest proxy processed event from host and responded",
449
465
  scriptingEventId: eventId,
450
466
  scriptingObject: object.id,
451
467
  ...this.#getGuestInfo()
452
468
  });
453
469
  } else {
454
- this.#logger.audit({
470
+ this.#auditThrottler.log(`event:${eventId}`, {
455
471
  message: "Guest proxy processed event from host",
456
472
  scriptingEventId: eventId,
457
473
  scriptingObject: object.id,
@@ -515,7 +531,14 @@ class SSFGuest {
515
531
  #startKeepSessionAlive = async () => {
516
532
  if (this.#keepAlive) {
517
533
  try {
518
- const appObj = await this.getObject("application");
534
+ const appObj = await Promise.race([
535
+ this.getObject(
536
+ "application"
537
+ ),
538
+ new Promise((resolve) => {
539
+ setTimeout(() => resolve(null), KEEP_ALIVE_OBJECT_TIMEOUT);
540
+ })
541
+ ]);
519
542
  if (appObj) {
520
543
  this.#throttledKeepAlive = throttle(
521
544
  async () => {
@@ -721,7 +744,7 @@ class SSFGuest {
721
744
  }
722
745
  });
723
746
  const obj = this.#fromJSON(response.object);
724
- this.#logger.audit({
747
+ this.#auditThrottler.log(`getObject:${objectId}`, {
725
748
  message: "Received scripting object from host",
726
749
  scriptingObject: objectId,
727
750
  ...this.#getGuestInfo()
@@ -744,7 +767,7 @@ class SSFGuest {
744
767
  messageType: MessageType.ListObjects,
745
768
  messageBody: {}
746
769
  });
747
- this.#logger.audit({
770
+ this.#auditThrottler.log("listObjects", {
748
771
  message: "Received names of all scripting objects exposed by host",
749
772
  objects,
750
773
  ...this.#getGuestInfo()
@@ -11,7 +11,9 @@ const createGuest = ({
11
11
  keepAlive,
12
12
  usesDevConnectAPI,
13
13
  keepAliveInterval,
14
- console
14
+ console,
15
+ auditOperations,
16
+ auditThrottleMs
15
17
  }) => new SSFGuest({
16
18
  logger: {
17
19
  console: console ?? true,
@@ -21,7 +23,9 @@ const createGuest = ({
21
23
  },
22
24
  keepAlive,
23
25
  usesDevConnectAPI,
24
- keepAliveInterval
26
+ keepAliveInterval,
27
+ auditOperations,
28
+ auditThrottleMs
25
29
  });
26
30
  const postMessage = ({
27
31
  srcWindow,
@@ -0,0 +1 @@
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Grandchild Guest (C)</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiSsfGuest.42c1a5949e7eb13de9e0.js"></script></head><body class="bg-green-50 p-2"><h3 class="text-sm font-semibold text-green-800 mb-2">Grandchild Guest (C)</h3><div class="flex flex-wrap gap-2 mb-2"><button data-testid="btn-get-caller-info" id="btnGetCallerInfo" disabled="disabled" class="rounded bg-green-600 px-3 py-1 text-xs text-white hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed">Call getCallerInfo()</button> <button data-testid="btn-ping" id="btnPing" disabled="disabled" class="rounded bg-green-600 px-3 py-1 text-xs text-white hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed">Call ping("hello from C")</button> <button data-testid="btn-get-loan" id="btnGetLoan" disabled="disabled" class="rounded bg-green-600 px-3 py-1 text-xs text-white hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed">Call getLoanDetails()</button></div><div data-testid="grandchild-status" id="status" class="text-xs bg-green-100 rounded p-2 mb-2 min-h-[20px]">Connecting...</div><div data-testid="grandchild-result" id="result" class="text-xs bg-white rounded p-2 min-h-[40px]"><pre id="resultPre" class="whitespace-pre-wrap">No results yet</pre></div><script src="./callchain-grandchild.js" type="module"></script></body></html>
@@ -0,0 +1,4 @@
1
+ const statusEl=document.getElementById("status"),resultEl=document.getElementById("resultPre"),log=e=>{const t=new Date().toLocaleTimeString();statusEl.innerHTML+=`<div>[${t}] ${e}</div>`},showResult=(e,t)=>{resultEl.textContent=`${e}:
2
+ ${JSON.stringify(t,null,2)}`};window.addEventListener("DOMContentLoaded",async()=>{try{const e=new ice.guest.SSFGuest({logger:{index:"grandchild-c",team:"ui platform",appName:"grandchild-c"},keepAlive:!1});await e.connect(),log("Connected to Intermediate Host (B)");const t=await e.getObject("TestService");log("Got TestService proxy \u2014 buttons enabled");const l=n=>{document.getElementById(n).disabled=!1};l("btnGetCallerInfo"),l("btnPing"),l("btnGetLoan"),document.getElementById("btnGetCallerInfo").addEventListener("click",async()=>{log("Calling getCallerInfo()...");const n=await t.getCallerInfo();log("getCallerInfo() returned"),showResult("getCallerInfo()",n)}),document.getElementById("btnPing").addEventListener("click",async()=>{log('Calling ping("hello from C")...');const n=await t.ping("hello from C");log("ping() returned"),showResult('ping("hello from C")',n)}),document.getElementById("btnGetLoan").addEventListener("click",async()=>{log("Calling getLoanDetails()...");const n=await t.getLoanDetails();log("getLoanDetails() returned"),showResult("getLoanDetails()",n)})}catch(e){log(`ERROR: ${e.message}`)}});
3
+
4
+ //# sourceMappingURL=callchain-grandchild.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["webpack://ice.guest/callchain-grandchild.js"],"sourcesContent":["const statusEl = document.getElementById('status');\nconst resultEl = document.getElementById('resultPre');\n\nconst log = (msg) => {\n const ts = new Date().toLocaleTimeString();\n statusEl.innerHTML += `<div>[${ts}] ${msg}</div>`;\n};\n\nconst showResult = (label, data) => {\n resultEl.textContent = `${label}:\\n${JSON.stringify(data, null, 2)}`;\n};\n\nwindow.addEventListener('DOMContentLoaded', async () => {\n try {\n const guest = new ice.guest.SSFGuest({\n logger: {\n index: 'grandchild-c',\n team: 'ui platform',\n appName: 'grandchild-c',\n },\n keepAlive: false,\n });\n await guest.connect();\n log('Connected to Intermediate Host (B)');\n\n const testService = await guest.getObject('TestService');\n log('Got TestService proxy — buttons enabled');\n\n const enable = (id) => {\n document.getElementById(id).disabled = false;\n };\n enable('btnGetCallerInfo');\n enable('btnPing');\n enable('btnGetLoan');\n\n document\n .getElementById('btnGetCallerInfo')\n .addEventListener('click', async () => {\n log('Calling getCallerInfo()...');\n const result = await testService.getCallerInfo();\n log('getCallerInfo() returned');\n showResult('getCallerInfo()', result);\n });\n\n document.getElementById('btnPing').addEventListener('click', async () => {\n log('Calling ping(\"hello from C\")...');\n const result = await testService.ping('hello from C');\n log('ping() returned');\n showResult('ping(\"hello from C\")', result);\n });\n\n document\n .getElementById('btnGetLoan')\n .addEventListener('click', async () => {\n log('Calling getLoanDetails()...');\n const result = await testService.getLoanDetails();\n log('getLoanDetails() returned');\n showResult('getLoanDetails()', result);\n });\n } catch (err) {\n log(`ERROR: ${err.message}`);\n }\n});\n"],"mappings":"AAAA,MAAM,SAAW,SAAS,eAAe,QAAQ,EAC3C,SAAW,SAAS,eAAe,WAAW,EAE9C,IAAOA,GAAQ,CACnB,MAAMC,EAAK,IAAI,KAAK,EAAE,mBAAmB,EACzC,SAAS,WAAa,SAASA,CAAE,KAAKD,CAAG,QAC3C,EAEM,WAAa,CAACE,EAAOC,IAAS,CAClC,SAAS,YAAc,GAAGD,CAAK;AAAA,EAAM,KAAK,UAAUC,EAAM,KAAM,CAAC,CAAC,EACpE,EAEA,OAAO,iBAAiB,mBAAoB,SAAY,CACtD,GAAI,CACF,MAAMC,EAAQ,IAAI,IAAI,MAAM,SAAS,CACnC,OAAQ,CACN,MAAO,eACP,KAAM,cACN,QAAS,cACX,EACA,UAAW,EACb,CAAC,EACD,MAAMA,EAAM,QAAQ,EACpB,IAAI,oCAAoC,EAExC,MAAMC,EAAc,MAAMD,EAAM,UAAU,aAAa,EACvD,IAAI,8CAAyC,EAE7C,MAAME,EAAUC,GAAO,CACrB,SAAS,eAAeA,CAAE,EAAE,SAAW,EACzC,EACAD,EAAO,kBAAkB,EACzBA,EAAO,SAAS,EAChBA,EAAO,YAAY,EAEnB,SACG,eAAe,kBAAkB,EACjC,iBAAiB,QAAS,SAAY,CACrC,IAAI,4BAA4B,EAChC,MAAME,EAAS,MAAMH,EAAY,cAAc,EAC/C,IAAI,0BAA0B,EAC9B,WAAW,kBAAmBG,CAAM,CACtC,CAAC,EAEH,SAAS,eAAe,SAAS,EAAE,iBAAiB,QAAS,SAAY,CACvE,IAAI,iCAAiC,EACrC,MAAMA,EAAS,MAAMH,EAAY,KAAK,cAAc,EACpD,IAAI,iBAAiB,EACrB,WAAW,uBAAwBG,CAAM,CAC3C,CAAC,EAED,SACG,eAAe,YAAY,EAC3B,iBAAiB,QAAS,SAAY,CACrC,IAAI,6BAA6B,EACjC,MAAMA,EAAS,MAAMH,EAAY,eAAe,EAChD,IAAI,2BAA2B,EAC/B,WAAW,mBAAoBG,CAAM,CACvC,CAAC,CACL,OAASC,EAAK,CACZ,IAAI,UAAUA,EAAI,OAAO,EAAE,CAC7B,CACF,CAAC","names":["msg","ts","label","data","guest","testService","enable","id","result","err"],"sourceRoot":"","file":"callchain-grandchild.js"}
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Credit Service</title><style>body{margin:0}</style><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script>window.addEventListener("DOMContentLoaded",async()=>{window.__ICE__={diagnosticsUrl:"https://int.api.ellielabs.com/diagnostics/v2/logging"};const e=new URL(window.location),i=e?.searchParams?.get?.("src");window.__ICE__.ssfGuest=new ice.guest.SSFGuest({logger:{index:"creditServiceGuest",team:"ui platform",appName:"credit-service"}}),await window.__ICE__.ssfGuest.addScript(i,document.body)})</script><script defer="defer" src="js/emuiSsfGuest.650afabac7fe99fb3a5b.js"></script></head><body></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Credit Service</title><style>body{margin:0}</style><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script>window.addEventListener("DOMContentLoaded",async()=>{window.__ICE__={diagnosticsUrl:"https://int.api.ellielabs.com/diagnostics/v2/logging"};const e=new URL(window.location),i=e?.searchParams?.get?.("src");window.__ICE__.ssfGuest=new ice.guest.SSFGuest({logger:{index:"creditServiceGuest",team:"ui platform",appName:"credit-service"}}),await window.__ICE__.ssfGuest.addScript(i,document.body)})</script><script defer="defer" src="js/emuiSsfGuest.42c1a5949e7eb13de9e0.js"></script></head><body></body></html>
@@ -0,0 +1 @@
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>SSF E2E Guest</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiSsfGuest.42c1a5949e7eb13de9e0.js"></script></head><body class="bg-emerald-50 p-2 text-xs"><div class="flex items-center justify-between mb-1"><h3 class="font-semibold text-emerald-800">E2E Guest</h3><span data-testid="guest-connection-status" id="connStatus" class="px-2 py-0.5 rounded text-white bg-gray-400">disconnected</span></div><div data-testid="guest-info" id="guestInfo" class="bg-emerald-100 rounded p-1 mb-1 text-xs text-emerald-700">Params: <span id="searchParams">—</span></div><details class="bg-emerald-100 rounded p-1 mb-1 text-[10px] text-emerald-700"><summary class="font-semibold cursor-pointer">Quick Guide (click to expand)</summary><ul class="mt-1 list-disc list-inside space-y-0.5"><li><strong>Objects tab:</strong> Use <code>getObject()</code> to fetch a scripting object, then <code>Invoke</code> to call a method. Result + errors appear below.</li><li><strong>Events tab:</strong> Subscribe to events (with or without criteria). When the host dispatches, received events appear below. Use the Feedback dropdown to control what the callback returns.</li><li><strong>Lifecycle tab:</strong> Change log level or disconnect/reconnect. Status badge at top right should reflect the state.</li><li><strong>Verify:</strong> Connection badge = <span class="text-green-700 font-bold">connected</span>. After getObject: methods are listed. After invoke: result appears. After subscribe+dispatch: events appear.</li></ul></details><div class="flex gap-1 mb-2 border-b border-emerald-300"><button data-testid="tab-objects" class="tab-btn px-2 py-1 rounded-t font-medium bg-emerald-200 text-emerald-800" data-tab="objects">Objects</button> <button data-testid="tab-events" class="tab-btn px-2 py-1 rounded-t text-emerald-600" data-tab="events">Events</button> <button data-testid="tab-lifecycle" class="tab-btn px-2 py-1 rounded-t text-emerald-600" data-tab="lifecycle">Lifecycle</button></div><div id="panel-objects" class="tab-panel"><div class="space-y-1"><button data-testid="btn-list-objects" id="btnListObjects" class="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-700">listObjects()</button><div data-testid="objects-list" id="objectsList" class="bg-white rounded p-1 min-h-[20px]">—</div><div class="flex gap-1 items-center"><input data-testid="input-object-id" id="inputObjectId" value="Loan" class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1" placeholder="Object ID"/> <button data-testid="btn-get-object" id="btnGetObject" class="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-700">getObject()</button></div><div data-testid="current-object" id="currentObject" class="bg-white rounded p-1 min-h-[20px]">—</div><div class="flex gap-1 items-center"><input data-testid="input-method" id="inputMethod" value="getLoanDetails" class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1" placeholder="Method name"/> <input data-testid="input-args" id="inputArgs" class="rounded border-emerald-300 px-2 py-0.5 text-xs w-24" placeholder="Args (JSON)"/> <button data-testid="btn-invoke" id="btnInvoke" class="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-700">Invoke</button></div><div class="flex gap-1"><button data-testid="btn-invoke-error" id="btnInvokeError" class="rounded bg-red-500 px-2 py-1 text-white hover:bg-red-600">TC-SO-08: Call throwError()</button> <button data-testid="btn-invoke-async" id="btnInvokeAsync" class="rounded bg-blue-500 px-2 py-1 text-white hover:bg-blue-600">TC-SO-07: Call asyncMethod(200)</button></div><div data-testid="invoke-result" id="invokeResult" class="bg-white rounded p-1 min-h-[30px] whitespace-pre-wrap">—</div></div></div><div id="panel-events" class="tab-panel hidden"><div class="space-y-1"><div class="flex gap-1 items-center"><input data-testid="input-event-id" id="inputEventId" value="Loan.onPreSave" class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1" placeholder="Event ID"/> <button data-testid="btn-subscribe" id="btnSubscribe" class="rounded bg-purple-600 px-2 py-1 text-white hover:bg-purple-700">Subscribe</button></div><div class="flex gap-1 items-center"><input data-testid="input-criteria" id="inputCriteria" class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1" placeholder='Criteria JSON e.g. {"amount":{"gt":100000}}'/> <button data-testid="btn-subscribe-criteria" id="btnSubscribeCriteria" class="rounded bg-purple-600 px-2 py-1 text-white hover:bg-purple-700">Subscribe+Criteria</button></div><div class="flex gap-1 items-center"><select data-testid="select-unsub-token" id="selectUnsubToken" class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1"><option value="">— select token —</option></select> <button data-testid="btn-unsubscribe" id="btnUnsubscribe" class="rounded bg-yellow-600 px-2 py-1 text-white hover:bg-yellow-700">Unsubscribe</button></div><h4 class="font-medium text-emerald-700 mt-1">Active Subscriptions</h4><div data-testid="subscriptions-list" id="subscriptionsList" class="bg-white rounded p-1 min-h-[20px]">—</div><h4 class="font-medium text-emerald-700 mt-1">Received Events</h4><div data-testid="events-received" id="eventsReceived" class="bg-white rounded p-1 min-h-[40px] max-h-[120px] overflow-y-auto">—</div><h4 class="font-medium text-emerald-700 mt-1">Feedback Return Value</h4><div class="flex gap-1 items-center"><select data-testid="select-feedback" id="selectFeedback" class="rounded border-emerald-300 px-2 py-0.5 text-xs flex-1"><option value="true">return true (approve)</option><option value="false">return false (reject)</option><option value="object">return {status: "ok"}</option></select></div></div></div><div id="panel-lifecycle" class="tab-panel hidden"><div class="space-y-1"><button data-testid="btn-set-loglevel-0" id="btnLogLevel0" class="rounded bg-gray-600 px-2 py-1 text-white hover:bg-gray-700">setLogLevel(0) — Silent</button> <button data-testid="btn-set-loglevel-10" id="btnLogLevel10" class="rounded bg-gray-600 px-2 py-1 text-white hover:bg-gray-700">setLogLevel(10) — Debug</button> <button data-testid="btn-disconnect" id="btnDisconnect" class="rounded bg-red-600 px-2 py-1 text-white hover:bg-red-700">close() — Disconnect</button><p class="text-[10px] text-gray-400">Expected: status badge → "disconnected", further calls fail</p><button data-testid="btn-reconnect" id="btnReconnect" class="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-700">connect() — Reconnect</button><p class="text-[10px] text-gray-400">Expected: status badge → "connected", calls work again</p></div></div><div class="mt-2"><h4 class="font-medium text-emerald-700">Guest Log</h4><div data-testid="guest-log" id="guestLog" class="bg-white rounded p-1 text-xs min-h-[40px] max-h-[100px] overflow-y-auto font-mono"></div></div><script src="./e2e-guest.js" type="module"></script></body></html>
@@ -0,0 +1,3 @@
1
+ import{getGuest as I}from"./util.js";let o=null,s=null;const a=new Map;let d=0;const l=document.getElementById("guestLog"),n=(e,t="info")=>{const c=new Date().toLocaleTimeString(),r=t==="error"?"text-red-600":t==="success"?"text-green-600":"text-gray-600";l.innerHTML+=`<div class="${r}">[${c}] ${e}</div>`,l.scrollTop=l.scrollHeight},u=e=>{const t=document.getElementById("connStatus");t.textContent=e?"connected":"disconnected",t.className=`px-2 py-0.5 rounded text-white ${e?"bg-green-500":"bg-gray-400"}`};document.querySelectorAll(".tab-btn").forEach(e=>{e.addEventListener("click",()=>{document.querySelectorAll(".tab-btn").forEach(t=>t.classList.replace("bg-emerald-200","bg-transparent")),e.classList.replace("bg-transparent","bg-emerald-200"),document.querySelectorAll(".tab-panel").forEach(t=>t.classList.add("hidden")),document.getElementById(`panel-${e.dataset.tab}`).classList.remove("hidden")})});const f=new URLSearchParams(window.location.search),g=Object.fromEntries(f.entries());document.getElementById("searchParams").textContent=Object.keys(g).length?JSON.stringify(g):"(none)";const b=async()=>{try{o=I("e2e-guest"),await o.connect(),u(!0),n("Connected to host","success")}catch(e){u(!1),n(`Connect failed: ${e.message}`,"error")}},y=()=>{const e=document.getElementById("selectFeedback").value;return e==="true"?!0:e==="false"?!1:{status:"ok"}},m=()=>{const e=document.getElementById("subscriptionsList");if(!a.size){e.textContent="(none)";return}e.innerHTML=Array.from(a.entries()).map(([c,r])=>`<div>${r.eventId} \u2192 ${c.slice(0,8)}... ${r.criteria?`[criteria: ${JSON.stringify(r.criteria)}]`:""}</div>`).join("");const t=document.getElementById("selectUnsubToken");t.innerHTML='<option value="">\u2014 select token \u2014</option>',a.forEach((c,r)=>{const i=document.createElement("option");i.value=`${c.eventId}|${r}`,i.textContent=`${c.eventId} (${r.slice(0,8)}...)`,t.appendChild(i)})},E=(e,t)=>{d++;const c=document.getElementById("eventsReceived");d===1&&(c.innerHTML="");const r=new Date().toLocaleTimeString();c.innerHTML+=`<div class="border-b border-gray-100 py-0.5">[${r}] #${d} <strong>${e}</strong>: ${JSON.stringify(t)}</div>`,c.scrollTop=c.scrollHeight};window.addEventListener("DOMContentLoaded",async()=>{await b(),document.getElementById("btnListObjects").addEventListener("click",async()=>{try{const e=await o.listObjects();document.getElementById("objectsList").textContent=e.join(", ")||"(empty)",n(`listObjects(): [${e.join(", ")}]`,"success")}catch(e){n(`listObjects failed: ${e.message}`,"error")}}),document.getElementById("btnGetObject").addEventListener("click",async()=>{const e=document.getElementById("inputObjectId").value.trim();if(e)try{s=await o.getObject(e);const t=Object.keys(s).filter(c=>typeof s[c]=="function"||s[c]&&s[c].id);document.getElementById("currentObject").textContent=`"${e}" \u2014 keys: ${t.join(", ")}`,n(`getObject("${e}") success \u2014 ${t.length} members`,"success")}catch(t){document.getElementById("currentObject").textContent=`Error: ${t.message}`,n(`getObject("${e}") failed: ${t.message}`,"error")}}),document.getElementById("btnInvoke").addEventListener("click",async()=>{if(!s){n("No object loaded \u2014 call getObject first","error");return}const e=document.getElementById("inputMethod").value.trim(),t=document.getElementById("inputArgs").value.trim();let c=[];if(t)try{const r=JSON.parse(t);c=Array.isArray(r)?r:[r]}catch{c=[t]}try{const r=s[e];if(typeof r!="function"){n(`"${e}" is not a function on the proxy`,"error");return}n(`Invoking ${e}(${c.map(v=>JSON.stringify(v)).join(", ")})...`);const i=await r(...c);document.getElementById("invokeResult").textContent=JSON.stringify(i,null,2),n(`${e}() returned`,"success")}catch(r){document.getElementById("invokeResult").textContent=`Error: ${r.message||r}`,n(`${e}() threw: ${r.message||r}`,"error")}}),document.getElementById("btnInvokeError").addEventListener("click",async()=>{if(!s){n('No object \u2014 call getObject("Loan") first',"error");return}try{n("Invoking throwError()..."),await s.throwError(),n("throwError() did not throw (unexpected)","warn")}catch(e){document.getElementById("invokeResult").textContent=`Caught error: ${e.message||e}`,n(`throwError() caught: ${e.message||e}`,"success")}}),document.getElementById("btnInvokeAsync").addEventListener("click",async()=>{if(!s){n('No object \u2014 call getObject("Loan") first',"error");return}try{n("Invoking asyncMethod(200)...");const e=await s.asyncMethod(200);document.getElementById("invokeResult").textContent=JSON.stringify(e,null,2),n("asyncMethod(200) resolved","success")}catch(e){document.getElementById("invokeResult").textContent=`Error: ${e.message||e}`,n(`asyncMethod failed: ${e.message}`,"error")}}),document.getElementById("btnSubscribe").addEventListener("click",()=>{const e=document.getElementById("inputEventId").value.trim();if(e)try{const t=o.subscribe({eventId:e,callback:({eventParams:c,eventOptions:r})=>(E(e,c),y())});a.set(t,{eventId:e}),m(),n(`Subscribed to "${e}" \u2192 ${t.slice(0,8)}...`,"success")}catch(t){n(`Subscribe failed: ${t.message}`,"error")}}),document.getElementById("btnSubscribeCriteria").addEventListener("click",()=>{const e=document.getElementById("inputEventId").value.trim(),t=document.getElementById("inputCriteria").value.trim();if(!e)return;let c;try{c=t?JSON.parse(t):void 0}catch{n("Invalid criteria JSON","error");return}try{const r=o.subscribe({eventId:e,criteria:c,callback:({eventParams:i})=>(E(`${e} [criteria]`,i),y())});a.set(r,{eventId:e,criteria:c}),m(),n(`Subscribed to "${e}" with criteria \u2192 ${r.slice(0,8)}...`,"success")}catch(r){n(`Subscribe+criteria failed: ${r.message}`,"error")}}),document.getElementById("btnUnsubscribe").addEventListener("click",()=>{const e=document.getElementById("selectUnsubToken").value;if(!e){n("Select a subscription to unsubscribe","warn");return}const[t,c]=e.split("|");try{o.unsubscribe({eventId:t,token:c}),a.delete(c),m(),n(`Unsubscribed from "${t}"`,"success")}catch(r){n(`Unsubscribe failed: ${r.message}`,"error")}}),document.getElementById("btnLogLevel0").addEventListener("click",()=>{o.setLogLevel(0),n("setLogLevel(0)","success")}),document.getElementById("btnLogLevel10").addEventListener("click",()=>{o.setLogLevel(10),n("setLogLevel(10)","success")}),document.getElementById("btnDisconnect").addEventListener("click",async()=>{n("Calling close()..."),await o.close(),u(!1),n("Disconnected","success")}),document.getElementById("btnReconnect").addEventListener("click",async()=>{n("Reconnecting..."),await b()})});
2
+
3
+ //# sourceMappingURL=e2e-guest.js.map
Binary file
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["webpack://ice.guest/e2e-guest.js"],"sourcesContent":["import { getGuest } from './util.js';\n\nlet guest = null;\nlet currentProxy = null;\nconst subscriptions = new Map();\nlet eventCounter = 0;\n\n// --- Logging ---\nconst logEl = document.getElementById('guestLog');\nconst log = (msg, type = 'info') => {\n const ts = new Date().toLocaleTimeString();\n const color =\n type === 'error'\n ? 'text-red-600'\n : type === 'success'\n ? 'text-green-600'\n : 'text-gray-600';\n logEl.innerHTML += `<div class=\"${color}\">[${ts}] ${msg}</div>`;\n logEl.scrollTop = logEl.scrollHeight;\n};\n\nconst setStatus = (connected) => {\n const el = document.getElementById('connStatus');\n el.textContent = connected ? 'connected' : 'disconnected';\n el.className = `px-2 py-0.5 rounded text-white ${\n connected ? 'bg-green-500' : 'bg-gray-400'\n }`;\n};\n\n// --- Tab switching ---\ndocument.querySelectorAll('.tab-btn').forEach((btn) => {\n btn.addEventListener('click', () => {\n document\n .querySelectorAll('.tab-btn')\n .forEach((b) => b.classList.replace('bg-emerald-200', 'bg-transparent'));\n btn.classList.replace('bg-transparent', 'bg-emerald-200');\n document\n .querySelectorAll('.tab-panel')\n .forEach((p) => p.classList.add('hidden'));\n document\n .getElementById(`panel-${btn.dataset.tab}`)\n .classList.remove('hidden');\n });\n});\n\n// --- Display search params ---\nconst params = new URLSearchParams(window.location.search);\nconst paramsObj = Object.fromEntries(params.entries());\ndocument.getElementById('searchParams').textContent = Object.keys(paramsObj)\n .length\n ? JSON.stringify(paramsObj)\n : '(none)';\n\n// --- Connect ---\nconst doConnect = async () => {\n try {\n guest = getGuest('e2e-guest');\n await guest.connect();\n setStatus(true);\n log('Connected to host', 'success');\n } catch (err) {\n setStatus(false);\n log(`Connect failed: ${err.message}`, 'error');\n }\n};\n\n// --- Feedback value helper ---\nconst getFeedbackValue = () => {\n const v = document.getElementById('selectFeedback').value;\n if (v === 'true') return true;\n if (v === 'false') return false;\n return { status: 'ok' };\n};\n\n// --- Refresh subscriptions UI ---\nconst refreshSubscriptions = () => {\n const el = document.getElementById('subscriptionsList');\n if (!subscriptions.size) {\n el.textContent = '(none)';\n return;\n }\n el.innerHTML = Array.from(subscriptions.entries())\n .map(\n ([token, info]) =>\n `<div>${info.eventId} → ${token.slice(0, 8)}... ${\n info.criteria ? `[criteria: ${JSON.stringify(info.criteria)}]` : ''\n }</div>`,\n )\n .join('');\n\n const select = document.getElementById('selectUnsubToken');\n select.innerHTML = '<option value=\"\">— select token —</option>';\n subscriptions.forEach((info, token) => {\n const opt = document.createElement('option');\n opt.value = `${info.eventId}|${token}`;\n opt.textContent = `${info.eventId} (${token.slice(0, 8)}...)`;\n select.appendChild(opt);\n });\n};\n\n// --- Append event to received list ---\nconst appendEvent = (eventId, data) => {\n eventCounter++;\n const el = document.getElementById('eventsReceived');\n if (eventCounter === 1) el.innerHTML = '';\n const ts = new Date().toLocaleTimeString();\n el.innerHTML += `<div class=\"border-b border-gray-100 py-0.5\">[${ts}] #${eventCounter} <strong>${eventId}</strong>: ${JSON.stringify(\n data,\n )}</div>`;\n el.scrollTop = el.scrollHeight;\n};\n\n// --- Init ---\nwindow.addEventListener('DOMContentLoaded', async () => {\n await doConnect();\n\n // --- Objects Tab ---\n\n document\n .getElementById('btnListObjects')\n .addEventListener('click', async () => {\n try {\n const objects = await guest.listObjects();\n document.getElementById('objectsList').textContent =\n objects.join(', ') || '(empty)';\n log(`listObjects(): [${objects.join(', ')}]`, 'success');\n } catch (err) {\n log(`listObjects failed: ${err.message}`, 'error');\n }\n });\n\n document\n .getElementById('btnGetObject')\n .addEventListener('click', async () => {\n const objectId = document.getElementById('inputObjectId').value.trim();\n if (!objectId) return;\n try {\n currentProxy = await guest.getObject(objectId);\n const methods = Object.keys(currentProxy).filter(\n (k) =>\n typeof currentProxy[k] === 'function' ||\n (currentProxy[k] && currentProxy[k].id),\n );\n document.getElementById(\n 'currentObject',\n ).textContent = `\"${objectId}\" — keys: ${methods.join(', ')}`;\n log(\n `getObject(\"${objectId}\") success — ${methods.length} members`,\n 'success',\n );\n } catch (err) {\n document.getElementById(\n 'currentObject',\n ).textContent = `Error: ${err.message}`;\n log(`getObject(\"${objectId}\") failed: ${err.message}`, 'error');\n }\n });\n\n document.getElementById('btnInvoke').addEventListener('click', async () => {\n if (!currentProxy) {\n log('No object loaded — call getObject first', 'error');\n return;\n }\n const method = document.getElementById('inputMethod').value.trim();\n const argsRaw = document.getElementById('inputArgs').value.trim();\n let args = [];\n if (argsRaw) {\n try {\n const parsed = JSON.parse(argsRaw);\n args = Array.isArray(parsed) ? parsed : [parsed];\n } catch {\n args = [argsRaw];\n }\n }\n try {\n const fn = currentProxy[method];\n if (typeof fn !== 'function') {\n log(`\"${method}\" is not a function on the proxy`, 'error');\n return;\n }\n log(\n `Invoking ${method}(${args\n .map((a) => JSON.stringify(a))\n .join(', ')})...`,\n );\n const result = await fn(...args);\n document.getElementById('invokeResult').textContent = JSON.stringify(\n result,\n null,\n 2,\n );\n log(`${method}() returned`, 'success');\n } catch (err) {\n document.getElementById('invokeResult').textContent = `Error: ${\n err.message || err\n }`;\n log(`${method}() threw: ${err.message || err}`, 'error');\n }\n });\n\n document\n .getElementById('btnInvokeError')\n .addEventListener('click', async () => {\n if (!currentProxy) {\n log('No object — call getObject(\"Loan\") first', 'error');\n return;\n }\n try {\n log('Invoking throwError()...');\n await currentProxy.throwError();\n log('throwError() did not throw (unexpected)', 'warn');\n } catch (err) {\n document.getElementById('invokeResult').textContent = `Caught error: ${\n err.message || err\n }`;\n log(`throwError() caught: ${err.message || err}`, 'success');\n }\n });\n\n document\n .getElementById('btnInvokeAsync')\n .addEventListener('click', async () => {\n if (!currentProxy) {\n log('No object — call getObject(\"Loan\") first', 'error');\n return;\n }\n try {\n log('Invoking asyncMethod(200)...');\n const result = await currentProxy.asyncMethod(200);\n document.getElementById('invokeResult').textContent = JSON.stringify(\n result,\n null,\n 2,\n );\n log('asyncMethod(200) resolved', 'success');\n } catch (err) {\n document.getElementById('invokeResult').textContent = `Error: ${\n err.message || err\n }`;\n log(`asyncMethod failed: ${err.message}`, 'error');\n }\n });\n\n // --- Events Tab ---\n\n document.getElementById('btnSubscribe').addEventListener('click', () => {\n const eventId = document.getElementById('inputEventId').value.trim();\n if (!eventId) return;\n try {\n const token = guest.subscribe({\n eventId,\n callback: ({ eventParams, eventOptions }) => {\n appendEvent(eventId, eventParams);\n return getFeedbackValue();\n },\n });\n subscriptions.set(token, { eventId });\n refreshSubscriptions();\n log(`Subscribed to \"${eventId}\" → ${token.slice(0, 8)}...`, 'success');\n } catch (err) {\n log(`Subscribe failed: ${err.message}`, 'error');\n }\n });\n\n document\n .getElementById('btnSubscribeCriteria')\n .addEventListener('click', () => {\n const eventId = document.getElementById('inputEventId').value.trim();\n const criteriaRaw = document.getElementById('inputCriteria').value.trim();\n if (!eventId) return;\n let criteria;\n try {\n criteria = criteriaRaw ? JSON.parse(criteriaRaw) : undefined;\n } catch {\n log('Invalid criteria JSON', 'error');\n return;\n }\n try {\n const token = guest.subscribe({\n eventId,\n criteria,\n callback: ({ eventParams }) => {\n appendEvent(`${eventId} [criteria]`, eventParams);\n return getFeedbackValue();\n },\n });\n subscriptions.set(token, { eventId, criteria });\n refreshSubscriptions();\n log(\n `Subscribed to \"${eventId}\" with criteria → ${token.slice(0, 8)}...`,\n 'success',\n );\n } catch (err) {\n log(`Subscribe+criteria failed: ${err.message}`, 'error');\n }\n });\n\n document.getElementById('btnUnsubscribe').addEventListener('click', () => {\n const val = document.getElementById('selectUnsubToken').value;\n if (!val) {\n log('Select a subscription to unsubscribe', 'warn');\n return;\n }\n const [eventId, token] = val.split('|');\n try {\n guest.unsubscribe({ eventId, token });\n subscriptions.delete(token);\n refreshSubscriptions();\n log(`Unsubscribed from \"${eventId}\"`, 'success');\n } catch (err) {\n log(`Unsubscribe failed: ${err.message}`, 'error');\n }\n });\n\n // --- Lifecycle Tab ---\n\n document.getElementById('btnLogLevel0').addEventListener('click', () => {\n guest.setLogLevel(0);\n log('setLogLevel(0)', 'success');\n });\n\n document.getElementById('btnLogLevel10').addEventListener('click', () => {\n guest.setLogLevel(10);\n log('setLogLevel(10)', 'success');\n });\n\n document\n .getElementById('btnDisconnect')\n .addEventListener('click', async () => {\n log('Calling close()...');\n await guest.close();\n setStatus(false);\n log('Disconnected', 'success');\n });\n\n document\n .getElementById('btnReconnect')\n .addEventListener('click', async () => {\n log('Reconnecting...');\n await doConnect();\n });\n});\n"],"mappings":"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","names":["getGuest","guest","currentProxy","subscriptions","eventCounter","logEl","log","msg","type","ts","color","setStatus","connected","el","btn","b","p","params","paramsObj","doConnect","err","getFeedbackValue","v","refreshSubscriptions","token","info","select","opt","appendEvent","eventId","data","objects","objectId","methods","k","method","argsRaw","args","parsed","fn","a","result","eventParams","eventOptions","criteriaRaw","criteria","val"],"sourceRoot":"","file":"e2e-guest.js"}
@@ -1 +1 @@
1
- <!doctype html><html lang="en" style="height:100%"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Plugin</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script defer="defer" src="js/emuiSsfGuest.650afabac7fe99fb3a5b.js"></script></head><body class="px-2 h-full"><main class="h-full"><h1 class="text-md font-bold">Credit Score Service</h1><div class="h-full mt-2"><output id="msg" class="mt-2 p-2"></output></div></main></body></html>
1
+ <!doctype html><html lang="en" style="height:100%"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Plugin</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script defer="defer" src="js/emuiSsfGuest.42c1a5949e7eb13de9e0.js"></script></head><body class="px-2 h-full"><main class="h-full"><h1 class="text-md font-bold">Credit Score Service</h1><div class="h-full mt-2"><output id="msg" class="mt-2 p-2"></output></div></main></body></html>