@elliemae/ssf-host 2.23.4 → 2.24.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 (91) hide show
  1. package/dist/cjs/callchain-host.html +262 -0
  2. package/dist/cjs/callchain-intermediate.html +92 -0
  3. package/dist/cjs/e2e-host.html +603 -0
  4. package/dist/cjs/e2e-index.html +234 -0
  5. package/dist/cjs/host.js +100 -43
  6. package/dist/cjs/index.html +304 -151
  7. package/dist/cjs/popup-focus-host.html +318 -0
  8. package/dist/cjs/utils.js +14 -1
  9. package/dist/cjs/v2-host-v1-guest.html +3 -0
  10. package/dist/esm/callchain-host.html +262 -0
  11. package/dist/esm/callchain-intermediate.html +92 -0
  12. package/dist/esm/e2e-host.html +603 -0
  13. package/dist/esm/e2e-index.html +234 -0
  14. package/dist/esm/host.js +101 -44
  15. package/dist/esm/index.html +304 -151
  16. package/dist/esm/popup-focus-host.html +318 -0
  17. package/dist/esm/utils.js +14 -1
  18. package/dist/esm/v2-host-v1-guest.html +3 -0
  19. package/dist/public/callchain-host.html +34 -0
  20. package/dist/public/callchain-host.js +3 -0
  21. package/dist/public/callchain-host.js.br +0 -0
  22. package/dist/public/callchain-host.js.gz +0 -0
  23. package/dist/public/callchain-host.js.map +1 -0
  24. package/dist/public/callchain-intermediate.html +1 -0
  25. package/dist/public/e2e-host.html +5 -0
  26. package/dist/public/e2e-host.js +8 -0
  27. package/dist/public/e2e-host.js.br +0 -0
  28. package/dist/public/e2e-host.js.gz +0 -0
  29. package/dist/public/e2e-host.js.map +1 -0
  30. package/dist/public/e2e-index.html +1 -0
  31. package/dist/public/index.html +1 -1
  32. package/dist/public/init.js +1 -1
  33. package/dist/public/init.js.br +0 -0
  34. package/dist/public/init.js.gz +0 -0
  35. package/dist/public/init.js.map +1 -1
  36. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js +3 -0
  37. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.br +0 -0
  38. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.gz +0 -0
  39. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.map +1 -0
  40. package/dist/public/loan-object.js +1 -1
  41. package/dist/public/loan-object.js.br +0 -0
  42. package/dist/public/loan-object.js.gz +0 -0
  43. package/dist/public/loan-object.js.map +1 -1
  44. package/dist/public/popup-focus-host.html +1 -0
  45. package/dist/public/popup-focus-host.js +6 -0
  46. package/dist/public/popup-focus-host.js.br +0 -0
  47. package/dist/public/popup-focus-host.js.gz +0 -0
  48. package/dist/public/popup-focus-host.js.map +1 -0
  49. package/dist/public/v1-guest-v2-host.html +1 -1
  50. package/dist/public/v2-host-v1-guest.html +1 -1
  51. package/dist/types/lib/host.d.ts +1 -0
  52. package/dist/types/lib/ihost.d.ts +15 -0
  53. package/dist/types/lib/tests/timingDedup.test.d.ts +1 -0
  54. package/dist/types/lib/utils.d.ts +1 -0
  55. package/dist/types/tsconfig.tsbuildinfo +1 -1
  56. package/dist/umd/callchain-host.html +34 -0
  57. package/dist/umd/callchain-host.js +3 -0
  58. package/dist/umd/callchain-host.js.br +0 -0
  59. package/dist/umd/callchain-host.js.gz +0 -0
  60. package/dist/umd/callchain-host.js.map +1 -0
  61. package/dist/umd/callchain-intermediate.html +1 -0
  62. package/dist/umd/e2e-host.html +5 -0
  63. package/dist/umd/e2e-host.js +8 -0
  64. package/dist/umd/e2e-host.js.br +0 -0
  65. package/dist/umd/e2e-host.js.gz +0 -0
  66. package/dist/umd/e2e-host.js.map +1 -0
  67. package/dist/umd/e2e-index.html +1 -0
  68. package/dist/umd/index.html +1 -1
  69. package/dist/umd/index.js +1 -1
  70. package/dist/umd/index.js.br +0 -0
  71. package/dist/umd/index.js.gz +0 -0
  72. package/dist/umd/index.js.map +1 -1
  73. package/dist/umd/init.js +1 -1
  74. package/dist/umd/init.js.br +0 -0
  75. package/dist/umd/init.js.gz +0 -0
  76. package/dist/umd/init.js.map +1 -1
  77. package/dist/umd/loan-object.js +1 -1
  78. package/dist/umd/loan-object.js.br +0 -0
  79. package/dist/umd/loan-object.js.gz +0 -0
  80. package/dist/umd/loan-object.js.map +1 -1
  81. package/dist/umd/popup-focus-host.html +1 -0
  82. package/dist/umd/popup-focus-host.js +6 -0
  83. package/dist/umd/popup-focus-host.js.br +0 -0
  84. package/dist/umd/popup-focus-host.js.gz +0 -0
  85. package/dist/umd/popup-focus-host.js.map +1 -0
  86. package/dist/umd/v2-host-v1-guest.html +1 -1
  87. package/package.json +5 -5
  88. package/dist/public/js/emuiSsfHost.a4526c5eda64df08190f.js +0 -3
  89. package/dist/public/js/emuiSsfHost.a4526c5eda64df08190f.js.br +0 -0
  90. package/dist/public/js/emuiSsfHost.a4526c5eda64df08190f.js.gz +0 -0
  91. package/dist/public/js/emuiSsfHost.a4526c5eda64df08190f.js.map +0 -1
@@ -0,0 +1,318 @@
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>Popup Focus E2E Test</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-gray-50">
11
+ <header
12
+ class="bg-purple-600 text-white px-4 py-3 flex items-center justify-between"
13
+ >
14
+ <h1 class="text-lg font-semibold">Popup Focus E2E Test</h1>
15
+ <a href="./index.html" class="text-purple-200 hover:text-white text-sm"
16
+ >&larr; Back to main</a
17
+ >
18
+ </header>
19
+
20
+ <main class="mx-auto max-w-5xl px-4 py-4">
21
+ <!-- Architecture + Behavior -->
22
+ <div class="grid grid-cols-2 gap-4 mb-4">
23
+ <div class="bg-purple-50 border border-purple-200 rounded-md p-3">
24
+ <h2 class="text-sm font-bold text-purple-900 mb-2">
25
+ Behavior Under Test
26
+ </h2>
27
+ <ul class="text-xs text-purple-800 space-y-1 list-disc list-inside">
28
+ <li>
29
+ <strong>Trusted domains</strong> (.ice.com, .elliemae.com,
30
+ .ellielabs.com, localhost): <code>window.opener</code> is kept
31
+ intact. Re-opening uses <code>window.open('', name)</code> to
32
+ bring the popup to front.
33
+ </li>
34
+ <li>
35
+ <strong>Untrusted domains</strong> (everything else):
36
+ <code>window.opener = null</code> is set for security. Re-opening
37
+ falls back to <code>guest.window.focus()</code>.
38
+ </li>
39
+ <li>
40
+ The host tracks popup windows and detects closure via a periodic
41
+ monitor.
42
+ </li>
43
+ </ul>
44
+ </div>
45
+
46
+ <div class="bg-green-50 border border-green-200 rounded-md p-3">
47
+ <h2 class="text-sm font-bold text-green-900 mb-2">Test Steps</h2>
48
+ <ol
49
+ class="text-xs text-green-800 space-y-1.5 list-decimal list-inside"
50
+ >
51
+ <li>
52
+ Wait for <code>"Popup Focus Host ready"</code> in the Event Log.
53
+ </li>
54
+ <li>
55
+ <strong>Trusted popup test:</strong> Click "Open Title Service" →
56
+ popup opens. Click "Re-open Title Service" → the
57
+ <em>existing</em> popup should come to the foreground (no new
58
+ window).
59
+ </li>
60
+ <li>
61
+ <strong>Second trusted popup:</strong> Click "Open Credit Service"
62
+ → second popup opens. Click "Re-open Credit" → same foreground
63
+ behavior.
64
+ </li>
65
+ <li>
66
+ <strong>Untrusted popup test:</strong> Click "Open Third-Party" →
67
+ popup opens to <code>example.com</code>. Click "Re-open
68
+ Third-Party" → focus may or may not work (opener was nulled).
69
+ Observe the behavior.
70
+ </li>
71
+ <li>
72
+ Verify the Guest Status panel shows the open/closed state of each
73
+ popup.
74
+ </li>
75
+ <li>
76
+ Manually close a popup window, then check Guest Status updates to
77
+ "closed".
78
+ </li>
79
+ </ol>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Expected Behavior -->
84
+ <div class="bg-amber-50 border border-amber-200 rounded-md p-3 mb-4">
85
+ <h2 class="text-sm font-bold text-amber-900 mb-2">Expected Behavior</h2>
86
+ <div class="grid grid-cols-3 gap-3 text-xs">
87
+ <div>
88
+ <h3 class="font-semibold text-amber-800 mb-1">
89
+ Title / Credit Service (Trusted)
90
+ </h3>
91
+ <ul class="text-amber-900 list-disc list-inside space-y-0.5">
92
+ <li>"Open" → new popup window appears</li>
93
+ <li>
94
+ "Re-open" → existing popup comes to front,
95
+ <strong>no new window</strong>
96
+ </li>
97
+ <li>
98
+ Guest Status shows
99
+ <span class="text-green-600 font-semibold">open</span>
100
+ </li>
101
+ <li>Embedded Pricing Service renders normally</li>
102
+ </ul>
103
+ </div>
104
+ <div>
105
+ <h3 class="font-semibold text-amber-800 mb-1">
106
+ Third-Party (Untrusted)
107
+ </h3>
108
+ <ul class="text-amber-900 list-disc list-inside space-y-0.5">
109
+ <li>"Open" → popup opens to <code>example.com</code></li>
110
+ <li>
111
+ "Re-open" → <code>focus()</code> is called on stored reference
112
+ (browser may or may not honor it)
113
+ </li>
114
+ <li><code>opener</code> is <code>null</code> inside the popup</li>
115
+ </ul>
116
+ </div>
117
+ <div>
118
+ <h3 class="font-semibold text-amber-800 mb-1">Popup Closure</h3>
119
+ <ul class="text-amber-900 list-disc list-inside space-y-0.5">
120
+ <li>Close a popup manually (X button)</li>
121
+ <li>
122
+ Guest Status updates to
123
+ <span class="text-red-500 font-semibold">closed</span> within 2
124
+ seconds
125
+ </li>
126
+ <li>Re-opening a closed popup should open a new window</li>
127
+ </ul>
128
+ </div>
129
+ </div>
130
+ </div>
131
+
132
+ <!-- Popup controls -->
133
+ <div class="grid grid-cols-2 gap-4 mb-4">
134
+ <div class="bg-white rounded-lg shadow p-4">
135
+ <h2 class="text-sm font-semibold text-gray-700 mb-3">
136
+ Popup Actions
137
+ </h2>
138
+ <div class="flex flex-col gap-2">
139
+ <p class="text-xs text-gray-400 font-medium">
140
+ Trusted (localhost — opener preserved)
141
+ </p>
142
+ <button
143
+ data-testid="btn-open-title-popup"
144
+ id="btnOpenTitle"
145
+ class="rounded bg-purple-600 px-4 py-2 text-sm text-white hover:bg-purple-700"
146
+ >
147
+ Open Title Service (Popup)
148
+ </button>
149
+ <button
150
+ data-testid="btn-reopen-title-popup"
151
+ id="btnReopenTitle"
152
+ class="rounded bg-purple-500 px-4 py-2 text-sm text-white hover:bg-purple-600"
153
+ >
154
+ Re-open Title Service (should focus existing)
155
+ </button>
156
+ <button
157
+ data-testid="btn-open-credit-popup"
158
+ id="btnOpenCredit"
159
+ class="rounded bg-indigo-600 px-4 py-2 text-sm text-white hover:bg-indigo-700"
160
+ >
161
+ Open Credit Service (Popup)
162
+ </button>
163
+ <button
164
+ data-testid="btn-reopen-credit-popup"
165
+ id="btnReopenCredit"
166
+ class="rounded bg-indigo-500 px-4 py-2 text-sm text-white hover:bg-indigo-600"
167
+ >
168
+ Re-open Credit Service (should focus existing)
169
+ </button>
170
+ <hr class="my-2 border-gray-300" />
171
+ <p class="text-xs text-gray-400 font-medium">
172
+ Untrusted (opener nulled)
173
+ </p>
174
+ <button
175
+ data-testid="btn-open-thirdparty-popup"
176
+ id="btnOpenThirdParty"
177
+ class="rounded bg-red-600 px-4 py-2 text-sm text-white hover:bg-red-700"
178
+ >
179
+ Open Third-Party (Popup) — untrusted
180
+ </button>
181
+ <button
182
+ data-testid="btn-reopen-thirdparty-popup"
183
+ id="btnReopenThirdParty"
184
+ class="rounded bg-red-500 px-4 py-2 text-sm text-white hover:bg-red-600"
185
+ >
186
+ Re-open Third-Party (focus test — opener nulled)
187
+ </button>
188
+ </div>
189
+ </div>
190
+
191
+ <div class="bg-white rounded-lg shadow p-4">
192
+ <h2 class="text-sm font-semibold text-gray-700 mb-3">Guest Status</h2>
193
+ <div
194
+ data-testid="popup-guest-list"
195
+ id="guestList"
196
+ class="text-xs space-y-1 min-h-[100px]"
197
+ >
198
+ <p class="text-gray-400">No popup guests loaded yet</p>
199
+ </div>
200
+ </div>
201
+ </div>
202
+
203
+ <!-- Event log -->
204
+ <div class="bg-white rounded-lg shadow p-4 mb-4">
205
+ <h2 class="text-sm font-semibold text-gray-700 mb-2">Event Log</h2>
206
+ <div
207
+ data-testid="popup-event-log"
208
+ id="eventLog"
209
+ class="text-xs bg-gray-100 rounded p-2 min-h-[100px] max-h-[250px] overflow-y-auto"
210
+ ></div>
211
+ </div>
212
+
213
+ <!-- Embedded guest area (for comparison) -->
214
+ <div class="mb-4">
215
+ <h2 class="text-sm font-semibold text-gray-700 mb-2">
216
+ Embedded Guest (for comparison)
217
+ </h2>
218
+ <div
219
+ data-testid="embedded-container"
220
+ id="embedded-container"
221
+ class="border-2 border-dashed border-purple-300 rounded-lg min-h-[150px]"
222
+ ></div>
223
+ </div>
224
+
225
+ <!-- Verification Checklist -->
226
+ <div class="bg-gray-100 border border-gray-300 rounded-md p-3">
227
+ <h2 class="text-sm font-bold text-gray-800 mb-2">
228
+ Verification Checklist
229
+ </h2>
230
+ <div class="grid grid-cols-2 gap-2 text-xs">
231
+ <label class="flex items-start gap-2">
232
+ <input type="checkbox" class="mt-0.5" />
233
+ <span
234
+ ><strong>TC-POP-01:</strong> Host initializes, embedded Pricing
235
+ Service guest loads without error</span
236
+ >
237
+ </label>
238
+ <label class="flex items-start gap-2">
239
+ <input type="checkbox" class="mt-0.5" />
240
+ <span
241
+ ><strong>TC-POP-02:</strong> "Open Title Service" opens a new
242
+ popup window</span
243
+ >
244
+ </label>
245
+ <label class="flex items-start gap-2">
246
+ <input type="checkbox" class="mt-0.5" />
247
+ <span
248
+ ><strong>TC-POP-03:</strong> "Re-open Title Service" brings the
249
+ existing popup to front —
250
+ <strong>no new window or about:blank tab</strong></span
251
+ >
252
+ </label>
253
+ <label class="flex items-start gap-2">
254
+ <input type="checkbox" class="mt-0.5" />
255
+ <span
256
+ ><strong>TC-POP-04:</strong> "Open Credit Service" opens a second
257
+ popup (both coexist)</span
258
+ >
259
+ </label>
260
+ <label class="flex items-start gap-2">
261
+ <input type="checkbox" class="mt-0.5" />
262
+ <span
263
+ ><strong>TC-POP-05:</strong> "Re-open Credit Service" brings
264
+ credit popup to front</span
265
+ >
266
+ </label>
267
+ <label class="flex items-start gap-2">
268
+ <input type="checkbox" class="mt-0.5" />
269
+ <span
270
+ ><strong>TC-POP-06:</strong> Guest Status shows each popup as
271
+ <span class="text-green-600 font-semibold">open</span></span
272
+ >
273
+ </label>
274
+ <label class="flex items-start gap-2">
275
+ <input type="checkbox" class="mt-0.5" />
276
+ <span
277
+ ><strong>TC-POP-07:</strong> Manually close a popup → Guest Status
278
+ updates to
279
+ <span class="text-red-500 font-semibold">closed</span> within
280
+ ~2s</span
281
+ >
282
+ </label>
283
+ <label class="flex items-start gap-2">
284
+ <input type="checkbox" class="mt-0.5" />
285
+ <span
286
+ ><strong>TC-POP-08:</strong> Re-opening a closed popup opens a new
287
+ window (not just focus)</span
288
+ >
289
+ </label>
290
+ <label class="flex items-start gap-2">
291
+ <input type="checkbox" class="mt-0.5" />
292
+ <span
293
+ ><strong>TC-POP-09:</strong> "Open Third-Party" opens
294
+ <code>example.com</code> popup</span
295
+ >
296
+ </label>
297
+ <label class="flex items-start gap-2">
298
+ <input type="checkbox" class="mt-0.5" />
299
+ <span
300
+ ><strong>TC-POP-10:</strong> "Re-open Third-Party" attempts
301
+ <code>focus()</code> (may open about:blank — expected for
302
+ untrusted, opener was nulled)</span
303
+ >
304
+ </label>
305
+ <label class="flex items-start gap-2">
306
+ <input type="checkbox" class="mt-0.5" />
307
+ <span
308
+ ><strong>TC-POP-11:</strong> No console errors during trusted
309
+ popup operations</span
310
+ >
311
+ </label>
312
+ </div>
313
+ </div>
314
+ </main>
315
+
316
+ <script src="./popup-focus-host.js" type="module"></script>
317
+ </body>
318
+ </html>
package/dist/esm/utils.js CHANGED
@@ -12,8 +12,21 @@ const flatten = (source) => source.flat(Infinity).filter((v) => v !== void 0);
12
12
  function isFunction(value) {
13
13
  return typeof value === "function";
14
14
  }
15
+ const TRUSTED_DOMAIN_SUFFIXES = [".ice.com", ".elliemae.com", ".ellielabs.com"];
16
+ const TRUSTED_HOSTNAMES = ["localhost", "127.0.0.1"];
17
+ const isTrustedDomain = (url) => {
18
+ try {
19
+ const { hostname } = new URL(url, document.baseURI);
20
+ return TRUSTED_HOSTNAMES.includes(hostname) || TRUSTED_DOMAIN_SUFFIXES.some(
21
+ (suffix) => hostname === suffix.slice(1) || hostname.endsWith(suffix)
22
+ );
23
+ } catch {
24
+ return false;
25
+ }
26
+ };
15
27
  export {
16
28
  flatten,
17
29
  getOrigin,
18
- isFunction
30
+ isFunction,
31
+ isTrustedDomain
19
32
  };
@@ -36,13 +36,16 @@
36
36
  </main>
37
37
  <script type="module">
38
38
  import { Loan } from './loan-object.js';
39
+ import { Application } from './application-object-v2.js';
39
40
  import { getGuestBaseUrl, getHost } from './utils.js';
40
41
  import { Analytics } from './analytics-object-v2.js';
41
42
 
42
43
  const analyticsObj = new Analytics();
43
44
  const hostV2 = getHost(analyticsObj);
44
45
  const loanObj = new Loan();
46
+ const appObj = new Application();
45
47
  hostV2.addScriptingObject(loanObj);
48
+ hostV2.addScriptingObject(appObj);
46
49
  const params = new URLSearchParams(document.location.search);
47
50
  const { id } = hostV2.loadGuest({
48
51
  id: 'guestV1',
@@ -0,0 +1,34 @@
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>CallChain E2E Test - Root Host</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/emuiSsfHost.071827d0d7e775690fbb.js"></script></head><body class="bg-gray-50"><header class="bg-indigo-600 text-white px-4 py-3 flex items-center justify-between"><h1 class="text-lg font-semibold">CallChain E2E Test — Root Host (A)</h1><a href="./index.html" class="text-indigo-200 hover:text-white text-sm">&larr; Back to main</a></header><main class="mx-auto max-w-7xl px-4 py-4"><div class="grid grid-cols-2 gap-4 mb-4"><div class="bg-blue-50 border border-blue-200 rounded-md p-3"><h2 class="text-sm font-bold text-blue-900 mb-2">Architecture</h2><pre class="text-xs text-blue-800 leading-relaxed">
2
+ Root Host (A) [localhost:4000]
3
+ ├─ Exposes: <strong>TestService</strong> scripting object
4
+ ├─ Loads guest <strong>"intermediateHost"</strong>
5
+ │ metadata: { vendor: "Acme Credit Corp", tier: "premium" }
6
+
7
+ └─ Intermediate (B) [localhost:4000, same-origin iframe]
8
+ ├─ Connects as V2 guest to A
9
+ ├─ Loads ice.guest from localhost:4001/index.js
10
+ ├─ Gets TestService proxy, clones it
11
+ ├─ Creates own SSFHost, loads guest <strong>"grandchildGuest"</strong>
12
+ │ metadata: { role: "validator", region: "US-East" }
13
+
14
+ └─ Grandchild (C) [localhost:4001, cross-origin iframe]
15
+ └─ Connects as V2 guest to B
16
+ └─ Gets TestService proxy → calls methods</pre></div><div class="bg-green-50 border border-green-200 rounded-md p-3"><h2 class="text-sm font-bold text-green-900 mb-2">Test Steps</h2><ol class="text-xs text-green-800 space-y-2 list-decimal list-inside"><li><strong>Wait for initialization</strong> — check the Invocation Log shows: <code>"Root Host (A) ready"</code>, Intermediate shows <code>"Intermediate (B) ready"</code>, Grandchild shows <code>"Got TestService proxy"</code>.</li><li><strong>Click any button</strong> inside the Grandchild (C) panel (e.g. "Call getCallerInfo()").</li><li><strong>Verify on the left</strong> — the "callContext.guest" and "callContext.callChain" panels update with the expected values shown below.</li><li><strong>Verify on grandchild</strong> — the return value appears in the grandchild's result area (proves round-trip works).</li></ol></div></div><div class="bg-amber-50 border border-amber-200 rounded-md p-3 mb-4"><h2 class="text-sm font-bold text-amber-900 mb-2">Expected Values (when grandchild C calls any method)</h2><div class="grid grid-cols-2 gap-4 text-xs"><div><h3 class="font-semibold text-amber-800 mb-1">callContext.guest (direct caller = B)</h3><pre data-testid="expected-guest" class="bg-white rounded p-2 text-amber-900">
17
+ {
18
+ "id": "intermediateHost",
19
+ "title": "Intermediate Guest+Host (B)",
20
+ "url": "...callchain-intermediate.html"
21
+ }</pre></div><div><h3 class="font-semibold text-amber-800 mb-1">callContext.callChain (originator chain)</h3><pre data-testid="expected-callchain" class="bg-white rounded p-2 text-amber-900">
22
+ [
23
+ {
24
+ "id": "grandchildGuest",
25
+ "metadata": { "role": "validator", "region": "US-East" }
26
+ },
27
+ {
28
+ "id": "intermediateHost",
29
+ "metadata": { "vendor": "Acme Credit Corp", "tier": "premium" }
30
+ }
31
+ ]</pre></div></div></div><div class="grid grid-cols-2 gap-4"><div><h2 class="text-md font-semibold mb-2">Actual Results (from Root Host A)</h2><div data-testid="direct-caller-panel" class="bg-white rounded-lg shadow p-3 mb-3"><h3 class="text-sm font-medium text-gray-500 mb-1">callContext.guest (direct caller)</h3><pre data-testid="direct-caller-result" id="directCallerResult" class="text-xs bg-gray-100 rounded p-2 min-h-[40px] whitespace-pre-wrap">
32
+ Waiting — click a button in Grandchild (C)...</pre></div><div data-testid="callchain-panel" class="bg-white rounded-lg shadow p-3 mb-3"><h3 class="text-sm font-medium text-gray-500 mb-1">callContext.callChain (original callers)</h3><pre data-testid="callchain-result" id="callChainResult" class="text-xs bg-gray-100 rounded p-2 min-h-[60px] whitespace-pre-wrap">
33
+ Waiting — click a button in Grandchild (C)...</pre></div><div data-testid="method-result-panel" class="bg-white rounded-lg shadow p-3 mb-3"><h3 class="text-sm font-medium text-gray-500 mb-1">Method return value (returned to grandchild)</h3><pre data-testid="method-return-result" id="methodReturnResult" class="text-xs bg-gray-100 rounded p-2 min-h-[40px] whitespace-pre-wrap">
34
+ Waiting — click a button in Grandchild (C)...</pre></div><div data-testid="invocation-log-panel" class="bg-white rounded-lg shadow p-3"><h3 class="text-sm font-medium text-gray-500 mb-1">Invocation Log</h3><div data-testid="invocation-log" id="invocationLog" class="text-xs bg-gray-100 rounded p-2 min-h-[80px] max-h-[200px] overflow-y-auto"></div></div></div><div><h2 class="text-md font-semibold mb-2">Intermediate Guest (B) → Grandchild Guest (C)</h2><div data-testid="intermediate-guest-container" id="intermediate-container" class="border-2 border-dashed border-indigo-300 rounded-lg h-[400px]"></div></div></div><div class="bg-gray-100 border border-gray-300 rounded-md p-3 mt-4"><h2 class="text-sm font-bold text-gray-800 mb-2">Verification Checklist</h2><div class="grid grid-cols-2 gap-2 text-xs"><label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CC-01:</strong> All three layers initialized without errors (Log shows "ready" messages, no ERROR lines)</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CC-02:</strong> <code>callContext.guest.id</code> = <code>"intermediateHost"</code> (B is the direct caller, not C)</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CC-03:</strong> <code>callChain[0].id</code> = <code>"grandchildGuest"</code> with metadata <code>{ role, region }</code></span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CC-04:</strong> <code>callChain[1].id</code> = <code>"intermediateHost"</code> with metadata <code>{ vendor, tier }</code></span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CC-05:</strong> Grandchild (C) receives the method return value (result appears in C's panel)</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CC-06:</strong> All three methods work: <code>getCallerInfo()</code>, <code>ping()</code>, <code>getLoanDetails()</code></span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CC-07:</strong> No cross-origin or console errors during invocations</span></label></div></div></main><script src="./callchain-host.js" type="module"></script></body></html>
@@ -0,0 +1,3 @@
1
+ import{Analytics as r}from"./analytics-object-v2.js";import{getGuestBaseUrl as s,getHost as a}from"./utils.js";const o=t=>{const e=document.getElementById("invocationLog"),n=new Date().toLocaleTimeString();e.innerHTML+=`<div>[${n}] ${t}</div>`,e.scrollTop=e.scrollHeight},c=t=>t?{id:t.id,title:t.title,url:t.url}:null,l=t=>{document.getElementById("directCallerResult").textContent=t?.guest?JSON.stringify(c(t.guest),null,2):"(none)",document.getElementById("callChainResult").textContent=t?.callChain?JSON.stringify(t.callChain,null,2):"(empty \u2014 direct call, no forwarding)"};class d extends ice.host.ScriptingObject{constructor(){super("TestService")}getCallerInfo=()=>{const e=this.getCallerInfo.callContext;o("getCallerInfo() invoked"),l(e);const n={status:"ok",timestamp:Date.now()};return document.getElementById("methodReturnResult").textContent=JSON.stringify(n,null,2),n};ping=e=>{const n=this.ping.callContext;o(`ping("${e}") invoked`),l(n);const i={pong:e};return document.getElementById("methodReturnResult").textContent=JSON.stringify(i,null,2),i};getLoanDetails=()=>{const e=this.getLoanDetails.callContext;o("getLoanDetails() invoked"),l(e);const n={id:"LOAN-001",borrower:"Jane Doe",amount:5e5};return document.getElementById("methodReturnResult").textContent=JSON.stringify(n,null,2),n}}const u=async()=>{o("Initializing Root Host (A)...");const t=new r,e=a(t);if(!e){o("ERROR: Failed to create SSFHost");return}const n=new d;e.addScriptingObject(n),o("TestService scripting object registered");const i=await s();o(`Loading intermediate guest from: ${window.location.origin}`),e.loadGuest({id:"intermediateHost",url:new URL("./callchain-intermediate.html",window.location.href).href,title:"Intermediate Guest+Host (B)",targetElement:document.getElementById("intermediate-container"),metadata:{vendor:"Acme Credit Corp",tier:"premium"},options:{allowSameOrigin:!0},onLoad:()=>o("Intermediate guest (B) loaded"),onError:()=>o("ERROR: Intermediate guest (B) failed to load")}),o("Root Host (A) ready \u2014 waiting for calls from grandchild (C)")};window.addEventListener("DOMContentLoaded",u);
2
+
3
+ //# sourceMappingURL=callchain-host.js.map
Binary file
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["webpack://ice.host/callchain-host.js"],"sourcesContent":["import { Analytics } from './analytics-object-v2.js';\nimport { getGuestBaseUrl, getHost } from './utils.js';\n\nconst log = (msg) => {\n const el = document.getElementById('invocationLog');\n const ts = new Date().toLocaleTimeString();\n el.innerHTML += `<div>[${ts}] ${msg}</div>`;\n el.scrollTop = el.scrollHeight;\n};\n\nconst safeGuest = (guest) =>\n guest ? { id: guest.id, title: guest.title, url: guest.url } : null;\n\nconst displayCallContext = (ctx) => {\n document.getElementById('directCallerResult').textContent = ctx?.guest\n ? JSON.stringify(safeGuest(ctx.guest), null, 2)\n : '(none)';\n document.getElementById('callChainResult').textContent = ctx?.callChain\n ? JSON.stringify(ctx.callChain, null, 2)\n : '(empty — direct call, no forwarding)';\n};\n\nclass TestService extends ice.host.ScriptingObject {\n constructor() {\n super('TestService');\n }\n\n getCallerInfo = () => {\n const ctx = this.getCallerInfo.callContext;\n log('getCallerInfo() invoked');\n displayCallContext(ctx);\n const result = { status: 'ok', timestamp: Date.now() };\n document.getElementById('methodReturnResult').textContent = JSON.stringify(\n result,\n null,\n 2,\n );\n return result;\n };\n\n ping = (message) => {\n const ctx = this.ping.callContext;\n log(`ping(\"${message}\") invoked`);\n displayCallContext(ctx);\n const result = { pong: message };\n document.getElementById('methodReturnResult').textContent = JSON.stringify(\n result,\n null,\n 2,\n );\n return result;\n };\n\n getLoanDetails = () => {\n const ctx = this.getLoanDetails.callContext;\n log('getLoanDetails() invoked');\n displayCallContext(ctx);\n const result = {\n id: 'LOAN-001',\n borrower: 'Jane Doe',\n amount: 500000,\n };\n document.getElementById('methodReturnResult').textContent = JSON.stringify(\n result,\n null,\n 2,\n );\n return result;\n };\n}\n\nconst init = async () => {\n log('Initializing Root Host (A)...');\n const analyticsObj = new Analytics();\n const host = getHost(analyticsObj);\n if (!host) {\n log('ERROR: Failed to create SSFHost');\n return;\n }\n\n const testService = new TestService();\n host.addScriptingObject(testService);\n log('TestService scripting object registered');\n\n const guestBaseUrl = await getGuestBaseUrl();\n log(`Loading intermediate guest from: ${window.location.origin}`);\n\n host.loadGuest({\n id: 'intermediateHost',\n url: new URL('./callchain-intermediate.html', window.location.href).href,\n title: 'Intermediate Guest+Host (B)',\n targetElement: document.getElementById('intermediate-container'),\n metadata: { vendor: 'Acme Credit Corp', tier: 'premium' },\n options: { allowSameOrigin: true },\n onLoad: () => log('Intermediate guest (B) loaded'),\n onError: () => log('ERROR: Intermediate guest (B) failed to load'),\n });\n\n log('Root Host (A) ready — waiting for calls from grandchild (C)');\n};\n\nwindow.addEventListener('DOMContentLoaded', init);\n"],"mappings":"AAAA,OAAS,aAAAA,MAAiB,2BAC1B,OAAS,mBAAAC,EAAiB,WAAAC,MAAe,aAEzC,MAAMC,EAAOC,GAAQ,CACnB,MAAMC,EAAK,SAAS,eAAe,eAAe,EAC5CC,EAAK,IAAI,KAAK,EAAE,mBAAmB,EACzCD,EAAG,WAAa,SAASC,CAAE,KAAKF,CAAG,SACnCC,EAAG,UAAYA,EAAG,YACpB,EAEME,EAAaC,GACjBA,EAAQ,CAAE,GAAIA,EAAM,GAAI,MAAOA,EAAM,MAAO,IAAKA,EAAM,GAAI,EAAI,KAE3DC,EAAsBC,GAAQ,CAClC,SAAS,eAAe,oBAAoB,EAAE,YAAcA,GAAK,MAC7D,KAAK,UAAUH,EAAUG,EAAI,KAAK,EAAG,KAAM,CAAC,EAC5C,SACJ,SAAS,eAAe,iBAAiB,EAAE,YAAcA,GAAK,UAC1D,KAAK,UAAUA,EAAI,UAAW,KAAM,CAAC,EACrC,2CACN,EAEA,MAAMC,UAAoB,IAAI,KAAK,eAAgB,CACjD,aAAc,CACZ,MAAM,aAAa,CACrB,CAEA,cAAgB,IAAM,CACpB,MAAMD,EAAM,KAAK,cAAc,YAC/BP,EAAI,yBAAyB,EAC7BM,EAAmBC,CAAG,EACtB,MAAME,EAAS,CAAE,OAAQ,KAAM,UAAW,KAAK,IAAI,CAAE,EACrD,gBAAS,eAAe,oBAAoB,EAAE,YAAc,KAAK,UAC/DA,EACA,KACA,CACF,EACOA,CACT,EAEA,KAAQC,GAAY,CAClB,MAAMH,EAAM,KAAK,KAAK,YACtBP,EAAI,SAASU,CAAO,YAAY,EAChCJ,EAAmBC,CAAG,EACtB,MAAME,EAAS,CAAE,KAAMC,CAAQ,EAC/B,gBAAS,eAAe,oBAAoB,EAAE,YAAc,KAAK,UAC/DD,EACA,KACA,CACF,EACOA,CACT,EAEA,eAAiB,IAAM,CACrB,MAAMF,EAAM,KAAK,eAAe,YAChCP,EAAI,0BAA0B,EAC9BM,EAAmBC,CAAG,EACtB,MAAME,EAAS,CACb,GAAI,WACJ,SAAU,WACV,OAAQ,GACV,EACA,gBAAS,eAAe,oBAAoB,EAAE,YAAc,KAAK,UAC/DA,EACA,KACA,CACF,EACOA,CACT,CACF,CAEA,MAAME,EAAO,SAAY,CACvBX,EAAI,+BAA+B,EACnC,MAAMY,EAAe,IAAIf,EACnBgB,EAAOd,EAAQa,CAAY,EACjC,GAAI,CAACC,EAAM,CACTb,EAAI,iCAAiC,EACrC,MACF,CAEA,MAAMc,EAAc,IAAIN,EACxBK,EAAK,mBAAmBC,CAAW,EACnCd,EAAI,yCAAyC,EAE7C,MAAMe,EAAe,MAAMjB,EAAgB,EAC3CE,EAAI,oCAAoC,OAAO,SAAS,MAAM,EAAE,EAEhEa,EAAK,UAAU,CACb,GAAI,mBACJ,IAAK,IAAI,IAAI,gCAAiC,OAAO,SAAS,IAAI,EAAE,KACpE,MAAO,8BACP,cAAe,SAAS,eAAe,wBAAwB,EAC/D,SAAU,CAAE,OAAQ,mBAAoB,KAAM,SAAU,EACxD,QAAS,CAAE,gBAAiB,EAAK,EACjC,OAAQ,IAAMb,EAAI,+BAA+B,EACjD,QAAS,IAAMA,EAAI,8CAA8C,CACnE,CAAC,EAEDA,EAAI,kEAA6D,CACnE,EAEA,OAAO,iBAAiB,mBAAoBW,CAAI","names":["Analytics","getGuestBaseUrl","getHost","log","msg","el","ts","safeGuest","guest","displayCallContext","ctx","TestService","result","message","init","analyticsObj","host","testService","guestBaseUrl"],"sourceRoot":"","file":"callchain-host.js"}
@@ -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>Intermediate Guest+Host (B)</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script src="http://localhost:4001/index.js"></script><script defer="defer" src="js/emuiSsfHost.071827d0d7e775690fbb.js"></script></head><body class="bg-orange-50 p-2"><h2 class="text-sm font-semibold text-orange-800 mb-1">Intermediate (B) — Guest + Host</h2><div data-testid="intermediate-status" id="status" class="text-xs bg-orange-100 rounded p-2 mb-2 min-h-[30px]">Initializing...</div><div data-testid="grandchild-container" id="grandchild-container" class="border border-dashed border-orange-400 rounded h-[250px]"></div><script type="module">import{Analytics}from"./analytics-object-v2.js";import{getGuestBaseUrl,getHost}from"./utils.js";const statusEl=document.getElementById("status"),log=t=>{const e=(new Date).toLocaleTimeString();statusEl.innerHTML+=`<div>[${e}] ${t}</div>`};(async()=>{try{log("Connecting to Root Host (A) as V2 guest...");const t=new ice.guest.SSFGuest({logger:{index:"intermediate-guest",team:"ui platform",appName:"intermediate-b"},keepAlive:!1});await t.connect(),log("Connected to Root Host (A)");const e=await t.getObject("TestService");log("Got TestService proxy from Root Host (A)");const o=new Analytics,a=getHost(o);if(!a)return void log("ERROR: Failed to create intermediate SSFHost");log("Created intermediate SSFHost (B)");const n=a.cloneScriptingObject(e);a.addScriptingObject(n),log("Cloned TestService for grandchild guests");const i=await getGuestBaseUrl();a.loadGuest({id:"grandchildGuest",url:new URL("./callchain-grandchild.html",i).href,title:"Grandchild Guest (C)",targetElement:document.getElementById("grandchild-container"),metadata:{role:"validator",region:"US-East"},onLoad:()=>log("Grandchild guest (C) loaded"),onError:()=>log("ERROR: Grandchild guest (C) failed to load")}),log("Intermediate (B) ready")}catch(t){log(`ERROR: ${t.message}`)}})()</script></body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>SSF E2E — Comprehensive Host Test</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/emuiSsfHost.071827d0d7e775690fbb.js"></script></head><body class="bg-gray-50 text-sm"><header class="bg-indigo-700 text-white px-4 py-3 flex items-center justify-between"><h1 class="text-lg font-semibold">Comprehensive E2E Host Test</h1><a href="./e2e-index.html" class="text-indigo-200 hover:text-white text-sm">&larr; Test Suite</a></header><main class="mx-auto max-w-7xl px-4 py-3"><div class="grid grid-cols-2 gap-3 mb-3"><div class="bg-blue-50 border border-blue-200 rounded-md p-3"><h2 class="text-sm font-bold text-blue-900 mb-1">How to Use This Page</h2><ol class="text-xs text-blue-800 space-y-1 list-decimal list-inside"><li>Start the host dev server (port 4000) and guest dev server (port 4001).</li><li>Buttons in <strong>Column 1</strong> (left) execute host-side actions.</li><li><strong>Column 2</strong> (center) shows embedded guest iframes — interact with them to invoke methods, subscribe to events, etc.</li><li><strong>Column 3</strong> (right) shows live results: Event Log, callContext, dispatch results, and the guest list.</li><li>Work through the buttons <strong>top to bottom</strong> for the recommended flow. Each button is labelled with a test case ID (TC-xxx).</li></ol></div><div class="bg-amber-50 border border-amber-200 rounded-md p-3"><h2 class="text-sm font-bold text-amber-900 mb-1">Recommended Flow</h2><ol class="text-xs text-amber-800 space-y-1 list-decimal list-inside"><li><strong>Load guests</strong>: TC-LOAD-01 (embed) → TC-LOAD-02 (popup) → TC-LOAD-04 (with params). Check Event Log for "loaded" messages.</li><li><strong>Scripting objects</strong>: TC-SO-01 (add Inventory). In the guest iframe, call <code>getObject("Inventory")</code> and invoke <code>getStock()</code>. Check return value and callContext.</li><li><strong>Events</strong>: In the guest, subscribe to <code>Loan.onPreSave</code>. Then click TC-EVT-01 on the host. Guest should receive the event.</li><li><strong>Lifecycle</strong>: TC-LIFE-01 (unload), TC-LIFE-03 (close host). Verify guests are removed and host is destroyed.</li></ol></div></div><div class="grid grid-cols-3 gap-3"><div class="space-y-3"><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Guest Loading</h2><div class="space-y-2"><button data-testid="btn-load-embed" id="btnLoadEmbed" class="w-full rounded bg-indigo-600 px-3 py-1.5 text-xs text-white hover:bg-indigo-700">TC-LOAD-01: Load Embedded Guest</button><p class="text-[10px] text-gray-400 -mt-1">Expected: guest iframe appears in center column, log shows "loaded"</p><button data-testid="btn-load-popup" id="btnLoadPopup" class="w-full rounded bg-indigo-600 px-3 py-1.5 text-xs text-white hover:bg-indigo-700">TC-LOAD-02: Load Popup Guest</button><p class="text-[10px] text-gray-400 -mt-1">Expected: new browser popup window opens</p><button data-testid="btn-load-multiple" id="btnLoadMultiple" class="w-full rounded bg-indigo-600 px-3 py-1.5 text-xs text-white hover:bg-indigo-700">TC-LOAD-03: loadGuests (3 instances)</button><p class="text-[10px] text-gray-400 -mt-1">Expected: 3 guest iframes load, "List all guests" shows 3+ entries</p><button data-testid="btn-load-with-params" id="btnLoadWithParams" class="w-full rounded bg-indigo-600 px-3 py-1.5 text-xs text-white hover:bg-indigo-700">TC-LOAD-04: Load with searchParams</button><p class="text-[10px] text-gray-400 -mt-1">Expected: guest shows URL params in its "Params:" line</p><button data-testid="btn-load-with-metadata" id="btnLoadWithMetadata" class="w-full rounded bg-indigo-600 px-3 py-1.5 text-xs text-white hover:bg-indigo-700">TC-META-01: Load with metadata</button><p class="text-[10px] text-gray-400 -mt-1">Expected: when guest invokes a method, callContext.callChain shows metadata</p><button data-testid="btn-load-bad-url" id="btnLoadBadUrl" class="w-full rounded bg-red-600 px-3 py-1.5 text-xs text-white hover:bg-red-700">TC-LOAD-05: Load invalid URL (onError)</button><p class="text-[10px] text-gray-400 -mt-1">Expected: log shows "ERROR" or onError callback fires</p></div></div><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Scripting Object Management</h2><div class="space-y-2"><button data-testid="btn-add-so" id="btnAddSO" class="w-full rounded bg-green-600 px-3 py-1.5 text-xs text-white hover:bg-green-700">TC-SO-01: Add "Inventory" Object</button><p class="text-[10px] text-gray-400 -mt-1">Expected: guest can now <code>getObject("Inventory")</code> and invoke its methods</p><button data-testid="btn-add-guest-so" id="btnAddGuestSO" class="w-full rounded bg-green-600 px-3 py-1.5 text-xs text-white hover:bg-green-700">TC-SO-04: Add Guest-Scoped Object</button><p class="text-[10px] text-gray-400 -mt-1">Expected: only the target guest can access this object, others get "not available"</p><button data-testid="btn-remove-so" id="btnRemoveSO" class="w-full rounded bg-yellow-600 px-3 py-1.5 text-xs text-white hover:bg-yellow-700">TC-SO-02: Remove "Inventory" Object</button><p class="text-[10px] text-gray-400 -mt-1">Expected: guest's <code>getObject("Inventory")</code> now fails</p><button data-testid="btn-remove-all-so" id="btnRemoveAllSO" class="w-full rounded bg-yellow-600 px-3 py-1.5 text-xs text-white hover:bg-yellow-700">TC-SO-02b: Remove All Objects</button><p class="text-[10px] text-gray-400 -mt-1">Expected: all getObject calls fail until objects are re-added</p></div></div><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Event Dispatching</h2><div class="space-y-2"><button data-testid="btn-dispatch-all" id="btnDispatchAll" class="w-full rounded bg-purple-600 px-3 py-1.5 text-xs text-white hover:bg-purple-700">TC-EVT-01: Dispatch onPreSave to all</button><p class="text-[10px] text-gray-400 -mt-1">Expected: all guests subscribed to onPreSave receive the event</p><button data-testid="btn-dispatch-targeted" id="btnDispatchTargeted" class="w-full rounded bg-purple-600 px-3 py-1.5 text-xs text-white hover:bg-purple-700">TC-EVT-02: Dispatch to specific guest</button><p class="text-[10px] text-gray-400 -mt-1">Expected: only the targeted guest receives the event</p><button data-testid="btn-dispatch-feedback" id="btnDispatchFeedback" class="w-full rounded bg-purple-600 px-3 py-1.5 text-xs text-white hover:bg-purple-700">TC-EVT-03: Dispatch with timeout (feedback)</button><p class="text-[10px] text-gray-400 -mt-1">Expected: Dispatch Results shows the guest's feedback return value</p><button data-testid="btn-dispatch-amount" id="btnDispatchAmount" class="w-full rounded bg-purple-600 px-3 py-1.5 text-xs text-white hover:bg-purple-700">TC-EVT-04: Dispatch onAmountChanged (criteria)</button><p class="text-[10px] text-gray-400 -mt-1">Expected: only guests subscribed with matching criteria receive it</p></div></div><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Guest Lifecycle</h2><div class="space-y-2"><button data-testid="btn-unload-guest" id="btnUnloadGuest" class="w-full rounded bg-orange-600 px-3 py-1.5 text-xs text-white hover:bg-orange-700">TC-LIFE-01: Unload first embedded guest</button><p class="text-[10px] text-gray-400 -mt-1">Expected: guest iframe removed, "List all guests" count decreases</p><button data-testid="btn-register-close" id="btnRegisterClose" class="w-full rounded bg-orange-600 px-3 py-1.5 text-xs text-white hover:bg-orange-700">TC-LIFE-02: Register onGuestClose</button><p class="text-[10px] text-gray-400 -mt-1">Expected: log shows callback registered; fires when guest calls close()</p><button data-testid="btn-list-guests" id="btnListGuests" class="w-full rounded bg-gray-600 px-3 py-1.5 text-xs text-white hover:bg-gray-700">List all guests</button><p class="text-[10px] text-gray-400 -mt-1">Expected: Guest List panel shows all currently loaded guests</p><button data-testid="btn-set-loglevel" id="btnSetLogLevel" class="w-full rounded bg-gray-600 px-3 py-1.5 text-xs text-white hover:bg-gray-700">Set Log Level (Debug)</button> <button data-testid="btn-close-host" id="btnCloseHost" class="w-full rounded bg-red-700 px-3 py-1.5 text-xs text-white hover:bg-red-800">TC-LIFE-03: Close host</button><p class="text-[10px] text-gray-400 -mt-1">Expected: all guests unloaded, host destroyed, further actions fail</p></div></div><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Sandbox Configuration</h2><div class="space-y-2"><button data-testid="btn-sandbox-default" id="btnSandboxDefault" class="w-full rounded bg-teal-600 px-3 py-1.5 text-xs text-white hover:bg-teal-700">TC-SB-01: Default sandbox</button><p class="text-[10px] text-gray-400 -mt-1">Expected: iframe has standard sandbox attributes</p><button data-testid="btn-sandbox-custom" id="btnSandboxCustom" class="w-full rounded bg-teal-600 px-3 py-1.5 text-xs text-white hover:bg-teal-700">TC-SB-02: Custom sandbox values</button><p class="text-[10px] text-gray-400 -mt-1">Expected: iframe sandbox attribute reflects custom permissions</p><button data-testid="btn-sandbox-disabled" id="btnSandboxDisabled" class="w-full rounded bg-teal-600 px-3 py-1.5 text-xs text-white hover:bg-teal-700">TC-SB-03: Sandbox disabled</button><p class="text-[10px] text-gray-400 -mt-1">Expected: iframe has no sandbox attribute (full permissions)</p></div></div></div><div class="space-y-3"><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Embedded Guests</h2><div data-testid="embed-container" id="embedContainer" class="border-2 border-dashed border-indigo-300 rounded min-h-[300px] space-y-2"></div></div><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Sandbox Test Container</h2><div data-testid="sandbox-container" id="sandboxContainer" class="border-2 border-dashed border-teal-300 rounded min-h-[150px] space-y-2"></div></div></div><div class="space-y-3"><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Event Log</h2><div data-testid="event-log" id="eventLog" class="text-xs bg-gray-100 rounded p-2 min-h-[200px] max-h-[350px] overflow-y-auto font-mono"></div><button data-testid="btn-clear-log" id="btnClearLog" class="mt-1 text-xs text-gray-400 hover:text-gray-600">Clear</button></div><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">callContext (last invocation)</h2><div data-testid="callcontext-panel"><h3 class="text-xs text-gray-500">callContext.guest</h3><pre data-testid="callcontext-guest" id="callContextGuest" class="text-xs bg-gray-100 rounded p-1 mb-1 whitespace-pre-wrap min-h-[30px]">
2
+ —</pre><h3 class="text-xs text-gray-500">callContext.callChain</h3><pre data-testid="callcontext-chain" id="callContextChain" class="text-xs bg-gray-100 rounded p-1 mb-1 whitespace-pre-wrap min-h-[30px]">
3
+ —</pre></div></div><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Dispatch Results</h2><pre data-testid="dispatch-result" id="dispatchResult" class="text-xs bg-gray-100 rounded p-2 min-h-[60px] whitespace-pre-wrap">
4
+ —</pre></div><div class="bg-white rounded-lg shadow p-3"><h2 class="font-semibold text-gray-700 mb-2 border-b pb-1">Guest List</h2><pre data-testid="guest-list" id="guestList" class="text-xs bg-gray-100 rounded p-2 min-h-[60px] whitespace-pre-wrap">
5
+ —</pre></div></div></div><div class="bg-gray-100 border border-gray-300 rounded-md p-3 mt-4"><h2 class="text-sm font-bold text-gray-800 mb-2">Verification Checklist</h2><div class="grid grid-cols-3 gap-2 text-xs"><div><p class="font-semibold text-gray-600 mb-1">Guest Loading</p><label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LOAD-01:</strong> Embedded guest loads in iframe</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LOAD-02:</strong> Popup guest opens in new window</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LOAD-03:</strong> loadGuests loads 3 guests at once</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LOAD-04:</strong> searchParams visible in guest</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LOAD-05:</strong> Bad URL triggers onError</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-META-01:</strong> Metadata appears in callChain</span></label></div><div><p class="font-semibold text-gray-600 mb-1">Scripting Objects & Events</p><label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-01:</strong> Guest can getObject after add</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-02:</strong> Guest can't getObject after remove</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-04:</strong> Scoped object only visible to one guest</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-EVT-01:</strong> All subscribed guests receive event</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-EVT-02:</strong> Only targeted guest receives event</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-EVT-03:</strong> Feedback return value in Dispatch Results</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-EVT-04:</strong> Criteria filtering works</span></label></div><div><p class="font-semibold text-gray-600 mb-1">Lifecycle & Sandbox</p><label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LIFE-01:</strong> Unload removes guest iframe</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LIFE-02:</strong> onGuestClose fires on guest disconnect</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LIFE-03:</strong> Close host destroys all guests</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SB-01:</strong> Default sandbox attrs present</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SB-02:</strong> Custom sandbox attrs applied</span></label> <label class="flex items-start gap-1"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SB-03:</strong> No sandbox attr when disabled</span></label></div></div></div></main><script src="./e2e-host.js" type="module"></script></body></html>
@@ -0,0 +1,8 @@
1
+ import{Analytics as E}from"./analytics-object-v2.js";import{getGuestBaseUrl as v,getHost as y}from"./utils.js";let n=null,l=null,a=0;const m=document.getElementById("eventLog"),e=(d,s="info")=>{const i=new Date().toLocaleTimeString(),c=s==="error"?"text-red-600":s==="success"?"text-green-600":s==="warn"?"text-yellow-600":"text-gray-700";m.innerHTML+=`<div class="${c}">[${i}] ${d}</div>`,m.scrollTop=m.scrollHeight};document.getElementById("btnClearLog").addEventListener("click",()=>{m.innerHTML=""});const g=d=>{document.getElementById("callContextGuest").textContent=d?.guest?JSON.stringify(d.guest,null,2):"(none \u2014 internal call)",document.getElementById("callContextChain").textContent=d?.callChain?JSON.stringify(d.callChain,null,2):"(empty \u2014 direct call)"};class L extends ice.host.ScriptingObject{constructor(){super("Loan"),this.loanId="E2E-001",this.creditScore=750,this.onPreSave=new ice.host.Event({name:"onPreSave",objectId:"Loan"}),this.onAmountChanged=new ice.host.Event({name:"onAmountChanged",objectId:"Loan"})}getLoanDetails=()=>{const s=this.getLoanDetails.callContext;return e("Loan.getLoanDetails() invoked"),g(s),{id:this.loanId,borrower:"Jane Doe",amount:5e5,term:30,creditScore:this.creditScore}};setCreditScore=s=>{const i=this.setCreditScore.callContext;return e(`Loan.setCreditScore(${s}) invoked`),g(i),this.creditScore=s,{updated:!0,score:s}};throwError=()=>{throw e("Loan.throwError() invoked \u2014 will throw"),new Error("Intentional error for TC-SO-08")};asyncMethod=async s=>{const i=this.asyncMethod.callContext;return e(`Loan.asyncMethod(${s}) invoked`),g(i),await new Promise(c=>setTimeout(c,s||100)),{resolved:!0,delay:s}}}class S extends ice.host.ScriptingObject{constructor(){super("Application")}keepSessionAlive=()=>Promise.resolve()}class p extends ice.host.ScriptingObject{constructor(){super("Inventory")}getItems=()=>(e("Inventory.getItems() invoked"),[{id:1,name:"Widget A"},{id:2,name:"Widget B"}])}const h=(d,s={})=>{const i=new URL("./e2e-guest.html",l).href,c=n.loadGuest({id:d,url:i,title:`Guest ${d}`,targetElement:document.getElementById("embedContainer"),options:{fitToContent:!0,...s.options},searchParams:s.searchParams||{},metadata:s.metadata,onLoad:t=>e(`onLoad("${t}")`,"success"),onError:t=>e(`onError("${t}")`,"error")});return e(`loadGuest("${d}") called`),c},u=()=>{const d=n?.getGuests()||[];document.getElementById("guestList").textContent=d.length?d.map(s=>`${s.id} [${s.window?.closed?"closed":"open"}] ready=${s.ready}`).join(`
2
+ `):"(no guests)"},f=async()=>{e("Initializing E2E Host...");const d=new E;if(n=y(d),!n){e("ERROR: Failed to create SSFHost","error");return}l=await v(),e(`Guest base URL: ${l}`);const s=new L,i=new S;n.addScriptingObject(s),n.addScriptingObject(i),e("Registered: Loan, Application"),document.getElementById("btnLoadEmbed").addEventListener("click",()=>{a++,h(`embed-${a}`)}),document.getElementById("btnLoadPopup").addEventListener("click",()=>{const t=new URL("./e2e-guest.html",l);t.searchParams.set("openMode","popup");try{n.loadGuest({id:"popup-guest",url:t.href,title:"Popup Guest",targetElement:document.getElementById("embedContainer"),options:{openMode:ice.host.OpenMode.Popup},onLoad:o=>e(`Popup onLoad("${o}")`,"success"),onError:o=>e(`Popup onError("${o}")`,"error")}),e('loadGuest("popup-guest") as popup')}catch(o){e(`Popup failed: ${o.message}`,"error")}u()}),document.getElementById("btnLoadMultiple").addEventListener("click",()=>{a++;const t=new URL("./e2e-guest.html",l).href;n.loadGuests({id:`multi-${a}`,url:t,title:`Multi Guest ${a}`,targetElement:document.getElementById("embedContainer"),options:{fitToContent:!0},searchParamsList:[{instance:"1",mode:"read"},{instance:"2",mode:"write"},{instance:"3",mode:"admin"}]}),e("loadGuests() with 3 instances"),u()}),document.getElementById("btnLoadWithParams").addEventListener("click",()=>{a++,h(`params-${a}`,{searchParams:{env:"qa",feature:"sandbox-test",debug:!0}})}),document.getElementById("btnLoadWithMetadata").addEventListener("click",()=>{a++,h(`meta-${a}`,{metadata:{vendor:"AcmeCorp",tier:"premium",region:"US-East"}})}),document.getElementById("btnLoadBadUrl").addEventListener("click",()=>{a++,n.loadGuest({id:`bad-${a}`,url:"http://localhost:9999/does-not-exist.html",title:`Bad Guest ${a}`,targetElement:document.getElementById("embedContainer"),onLoad:t=>e(`Unexpected onLoad("${t}") for bad URL`,"warn"),onError:t=>e(`Expected onError("${t}") for bad URL`,"success")}),e("loadGuest() with invalid URL")});let c=!1;document.getElementById("btnAddSO").addEventListener("click",()=>{if(c){e("Inventory already added","warn");return}n.addScriptingObject(new p),c=!0,e('Added "Inventory" scripting object',"success")}),document.getElementById("btnAddGuestSO").addEventListener("click",()=>{const t=n.getGuests();if(!t.length){e("No guests loaded \u2014 load a guest first","warn");return}const o=t[0].id;try{const r=new p;r.id="GuestInventory",n.addScriptingObject(r,{guestId:o}),e(`Added guest-scoped "GuestInventory" for ${o}`,"success")}catch(r){e(`addScriptingObject failed: ${r.message}`,"error")}}),document.getElementById("btnRemoveSO").addEventListener("click",()=>{try{n.removeScriptingObject("Inventory"),c=!1,e('Removed "Inventory" scripting object',"success")}catch(t){e(`removeScriptingObject failed: ${t.message}`,"error")}}),document.getElementById("btnRemoveAllSO").addEventListener("click",()=>{n.removeAllScriptingObjects(),c=!1,e("Removed all scripting objects","success")}),document.getElementById("btnDispatchAll").addEventListener("click",async()=>{e("Dispatching onPreSave to all guests...");try{const t=await n.dispatchEvent({event:s.onPreSave,eventParams:{creditScore:750,amount:5e5}});document.getElementById("dispatchResult").textContent=`onPreSave results:
3
+ ${JSON.stringify(t,null,2)}`,e(`onPreSave dispatched, ${t.length} responses`,"success")}catch(t){e(`Dispatch failed: ${t.message}`,"error")}}),document.getElementById("btnDispatchTargeted").addEventListener("click",async()=>{const t=n.getGuests();if(!t.length){e("No guests \u2014 load a guest first","warn");return}const o=t[0];e(`Dispatching onPreSave to guest "${o.id}" only...`);try{const r=await n.dispatchEvent({event:s.onPreSave,eventParams:{creditScore:800,amount:25e4},eventOptions:{guestId:o.id}});document.getElementById("dispatchResult").textContent=`Targeted results (${o.id}):
4
+ ${JSON.stringify(r,null,2)}`,e("Targeted dispatch complete","success")}catch(r){e(`Dispatch failed: ${r.message}`,"error")}}),document.getElementById("btnDispatchFeedback").addEventListener("click",async()=>{e("Dispatching onPreSave with 3s timeout (feedback)...");try{const t=await n.dispatchEvent({event:s.onPreSave,eventParams:{creditScore:600,amount:1e6},eventOptions:{timeout:3e3}});document.getElementById("dispatchResult").textContent=`Feedback results:
5
+ ${JSON.stringify(t,null,2)}`,e(`Feedback dispatch complete, ${t.length} responses`,"success")}catch(t){e(`Dispatch with feedback failed: ${t.message}`,"error")}}),document.getElementById("btnDispatchAmount").addEventListener("click",async()=>{e("Dispatching onAmountChanged (for criteria-based subscribers)...");try{const t=await n.dispatchEvent({event:s.onAmountChanged,eventParams:{amount:75e4}});document.getElementById("dispatchResult").textContent=`onAmountChanged results:
6
+ ${JSON.stringify(t,null,2)}`,e("onAmountChanged dispatched","success")}catch(t){e(`Dispatch failed: ${t.message}`,"error")}}),document.getElementById("btnUnloadGuest").addEventListener("click",()=>{const t=n.getGuests(),o=t.filter(b=>!b.window||b.window===window.frames[0]?.window);if(!t.length){e("No guests to unload","warn");return}const r=t[0];e(`Unloading guest "${r.id}"...`),n.unloadGuest(r.id),e(`Unloaded "${r.id}"`,"success"),u()}),document.getElementById("btnRegisterClose").addEventListener("click",()=>{const t=n.getGuests();if(!t.length){e("No guests \u2014 load a guest first","warn");return}t.forEach(o=>{n.onGuestClose({id:o.id,guestCloseCallback:({id:r})=>{e(`onGuestClose fired for "${r}"`,"success"),u()}})}),e(`Registered onGuestClose for ${t.length} guest(s)`,"success")}),document.getElementById("btnListGuests").addEventListener("click",()=>{u(),e(`Listed ${n.getGuests().length} guest(s)`)}),document.getElementById("btnSetLogLevel").addEventListener("click",()=>{n.setLogLevel(10),e("Set log level to 10 (debug)","success")}),document.getElementById("btnCloseHost").addEventListener("click",()=>{e("Closing host..."),n.close(),e("Host closed \u2014 all guests should be disconnected","success"),u()}),document.getElementById("btnSandboxDefault").addEventListener("click",()=>{a++;const t=new URL("./e2e-guest.html",l).href;n.loadGuest({id:`sb-default-${a}`,url:t,title:"Sandbox Default",targetElement:document.getElementById("sandboxContainer"),options:{fitToContent:!0},onLoad:()=>{e("SB-01: Default sandbox guest loaded","success");const o=document.querySelector("#sandboxContainer iframe:last-child");e(` sandbox="${o?.getAttribute("sandbox")||"(none)"}"`)}})}),document.getElementById("btnSandboxCustom").addEventListener("click",()=>{a++;const t=new URL("./e2e-guest.html",l).href;n.loadGuest({id:`sb-custom-${a}`,url:t,title:"Sandbox Custom",targetElement:document.getElementById("sandboxContainer"),options:{fitToContent:!0,customSandboxValues:[ice.host.IFrameSandboxValues.AllowScripts,ice.host.IFrameSandboxValues.AllowSameOrigin]},onLoad:()=>{e("SB-02: Custom sandbox guest loaded","success");const o=document.querySelector("#sandboxContainer iframe:last-child");e(` sandbox="${o?.getAttribute("sandbox")||"(none)"}"`)}})}),document.getElementById("btnSandboxDisabled").addEventListener("click",()=>{a++;const t=new URL("./e2e-guest.html",l).href;n.loadGuest({id:`sb-disabled-${a}`,url:t,title:"Sandbox Disabled",targetElement:document.getElementById("sandboxContainer"),options:{fitToContent:!0,disableSandbox:!0},onLoad:()=>{e("SB-03: Disabled sandbox guest loaded","success");const r=document.querySelector("#sandboxContainer iframe:last-child")?.getAttribute("sandbox");e(` sandbox=${r===null?"(not set \u2014 correct)":`"${r}"`}`)}})}),setInterval(u,3e3),e("E2E Host ready","success")};window.addEventListener("DOMContentLoaded",f);
7
+
8
+ //# sourceMappingURL=e2e-host.js.map
Binary file
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["webpack://ice.host/e2e-host.js"],"sourcesContent":["import { Analytics } from './analytics-object-v2.js';\nimport { getGuestBaseUrl, getHost } from './utils.js';\n\nlet host = null;\nlet guestBaseUrl = null;\nlet guestCounter = 0;\n\n// --- Logging ---\nconst logEl = document.getElementById('eventLog');\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 : type === 'warn'\n ? 'text-yellow-600'\n : 'text-gray-700';\n logEl.innerHTML += `<div class=\"${color}\">[${ts}] ${msg}</div>`;\n logEl.scrollTop = logEl.scrollHeight;\n};\n\ndocument.getElementById('btnClearLog').addEventListener('click', () => {\n logEl.innerHTML = '';\n});\n\nconst displayCallContext = (ctx) => {\n document.getElementById('callContextGuest').textContent = ctx?.guest\n ? JSON.stringify(ctx.guest, null, 2)\n : '(none — internal call)';\n document.getElementById('callContextChain').textContent = ctx?.callChain\n ? JSON.stringify(ctx.callChain, null, 2)\n : '(empty — direct call)';\n};\n\n// --- Scripting Objects ---\n\nclass Loan extends ice.host.ScriptingObject {\n constructor() {\n super('Loan');\n this.loanId = 'E2E-001';\n this.creditScore = 750;\n this.onPreSave = new ice.host.Event({\n name: 'onPreSave',\n objectId: 'Loan',\n });\n this.onAmountChanged = new ice.host.Event({\n name: 'onAmountChanged',\n objectId: 'Loan',\n });\n }\n\n getLoanDetails = () => {\n const ctx = this.getLoanDetails.callContext;\n log('Loan.getLoanDetails() invoked');\n displayCallContext(ctx);\n return {\n id: this.loanId,\n borrower: 'Jane Doe',\n amount: 500000,\n term: 30,\n creditScore: this.creditScore,\n };\n };\n\n setCreditScore = (score) => {\n const ctx = this.setCreditScore.callContext;\n log(`Loan.setCreditScore(${score}) invoked`);\n displayCallContext(ctx);\n this.creditScore = score;\n return { updated: true, score };\n };\n\n throwError = () => {\n log('Loan.throwError() invoked — will throw');\n throw new Error('Intentional error for TC-SO-08');\n };\n\n asyncMethod = async (delayMs) => {\n const ctx = this.asyncMethod.callContext;\n log(`Loan.asyncMethod(${delayMs}) invoked`);\n displayCallContext(ctx);\n await new Promise((resolve) => setTimeout(resolve, delayMs || 100));\n return { resolved: true, delay: delayMs };\n };\n}\n\nclass Application extends ice.host.ScriptingObject {\n constructor() {\n super('Application');\n }\n keepSessionAlive = () => Promise.resolve();\n}\n\nclass Inventory extends ice.host.ScriptingObject {\n constructor() {\n super('Inventory');\n }\n getItems = () => {\n log('Inventory.getItems() invoked');\n return [\n { id: 1, name: 'Widget A' },\n { id: 2, name: 'Widget B' },\n ];\n };\n}\n\n// --- Helper to load guest ---\nconst loadEmbedGuest = (id, opts = {}) => {\n const url = new URL('./e2e-guest.html', guestBaseUrl).href;\n const guest = host.loadGuest({\n id,\n url,\n title: `Guest ${id}`,\n targetElement: document.getElementById('embedContainer'),\n options: { fitToContent: true, ...opts.options },\n searchParams: opts.searchParams || {},\n metadata: opts.metadata,\n onLoad: (guestId) => log(`onLoad(\"${guestId}\")`, 'success'),\n onError: (guestId) => log(`onError(\"${guestId}\")`, 'error'),\n });\n log(`loadGuest(\"${id}\") called`);\n return guest;\n};\n\nconst refreshGuestList = () => {\n const guests = host?.getGuests() || [];\n document.getElementById('guestList').textContent = guests.length\n ? guests\n .map(\n (g) =>\n `${g.id} [${g.window?.closed ? 'closed' : 'open'}] ready=${\n g.ready\n }`,\n )\n .join('\\n')\n : '(no guests)';\n};\n\n// --- Init ---\nconst init = async () => {\n log('Initializing E2E Host...');\n\n const analyticsObj = new Analytics();\n host = getHost(analyticsObj);\n if (!host) {\n log('ERROR: Failed to create SSFHost', 'error');\n return;\n }\n\n guestBaseUrl = await getGuestBaseUrl();\n log(`Guest base URL: ${guestBaseUrl}`);\n\n // Register core scripting objects\n const loanObj = new Loan();\n const appObj = new Application();\n host.addScriptingObject(loanObj);\n host.addScriptingObject(appObj);\n log('Registered: Loan, Application');\n\n // --- Guest Loading ---\n\n document.getElementById('btnLoadEmbed').addEventListener('click', () => {\n guestCounter++;\n loadEmbedGuest(`embed-${guestCounter}`);\n });\n\n document.getElementById('btnLoadPopup').addEventListener('click', () => {\n const url = new URL('./e2e-guest.html', guestBaseUrl);\n url.searchParams.set('openMode', 'popup');\n try {\n host.loadGuest({\n id: 'popup-guest',\n url: url.href,\n title: 'Popup Guest',\n targetElement: document.getElementById('embedContainer'),\n options: { openMode: ice.host.OpenMode.Popup },\n onLoad: (id) => log(`Popup onLoad(\"${id}\")`, 'success'),\n onError: (id) => log(`Popup onError(\"${id}\")`, 'error'),\n });\n log('loadGuest(\"popup-guest\") as popup');\n } catch (e) {\n log(`Popup failed: ${e.message}`, 'error');\n }\n refreshGuestList();\n });\n\n document.getElementById('btnLoadMultiple').addEventListener('click', () => {\n guestCounter++;\n const url = new URL('./e2e-guest.html', guestBaseUrl).href;\n host.loadGuests({\n id: `multi-${guestCounter}`,\n url,\n title: `Multi Guest ${guestCounter}`,\n targetElement: document.getElementById('embedContainer'),\n options: { fitToContent: true },\n searchParamsList: [\n { instance: '1', mode: 'read' },\n { instance: '2', mode: 'write' },\n { instance: '3', mode: 'admin' },\n ],\n });\n log('loadGuests() with 3 instances');\n refreshGuestList();\n });\n\n document.getElementById('btnLoadWithParams').addEventListener('click', () => {\n guestCounter++;\n loadEmbedGuest(`params-${guestCounter}`, {\n searchParams: { env: 'qa', feature: 'sandbox-test', debug: true },\n });\n });\n\n document\n .getElementById('btnLoadWithMetadata')\n .addEventListener('click', () => {\n guestCounter++;\n loadEmbedGuest(`meta-${guestCounter}`, {\n metadata: { vendor: 'AcmeCorp', tier: 'premium', region: 'US-East' },\n });\n });\n\n document.getElementById('btnLoadBadUrl').addEventListener('click', () => {\n guestCounter++;\n host.loadGuest({\n id: `bad-${guestCounter}`,\n url: 'http://localhost:9999/does-not-exist.html',\n title: `Bad Guest ${guestCounter}`,\n targetElement: document.getElementById('embedContainer'),\n onLoad: (id) => log(`Unexpected onLoad(\"${id}\") for bad URL`, 'warn'),\n onError: (id) => log(`Expected onError(\"${id}\") for bad URL`, 'success'),\n });\n log('loadGuest() with invalid URL');\n });\n\n // --- Scripting Object Management ---\n\n let inventoryAdded = false;\n document.getElementById('btnAddSO').addEventListener('click', () => {\n if (inventoryAdded) {\n log('Inventory already added', 'warn');\n return;\n }\n host.addScriptingObject(new Inventory());\n inventoryAdded = true;\n log('Added \"Inventory\" scripting object', 'success');\n });\n\n document.getElementById('btnAddGuestSO').addEventListener('click', () => {\n const guests = host.getGuests();\n if (!guests.length) {\n log('No guests loaded — load a guest first', 'warn');\n return;\n }\n const guestId = guests[0].id;\n try {\n const scopedObj = new Inventory();\n scopedObj.id = 'GuestInventory';\n host.addScriptingObject(scopedObj, { guestId });\n log(`Added guest-scoped \"GuestInventory\" for ${guestId}`, 'success');\n } catch (e) {\n log(`addScriptingObject failed: ${e.message}`, 'error');\n }\n });\n\n document.getElementById('btnRemoveSO').addEventListener('click', () => {\n try {\n host.removeScriptingObject('Inventory');\n inventoryAdded = false;\n log('Removed \"Inventory\" scripting object', 'success');\n } catch (e) {\n log(`removeScriptingObject failed: ${e.message}`, 'error');\n }\n });\n\n document.getElementById('btnRemoveAllSO').addEventListener('click', () => {\n host.removeAllScriptingObjects();\n inventoryAdded = false;\n log('Removed all scripting objects', 'success');\n });\n\n // --- Event Dispatching ---\n\n document\n .getElementById('btnDispatchAll')\n .addEventListener('click', async () => {\n log('Dispatching onPreSave to all guests...');\n try {\n const results = await host.dispatchEvent({\n event: loanObj.onPreSave,\n eventParams: { creditScore: 750, amount: 500000 },\n });\n document.getElementById(\n 'dispatchResult',\n ).textContent = `onPreSave results:\\n${JSON.stringify(\n results,\n null,\n 2,\n )}`;\n log(`onPreSave dispatched, ${results.length} responses`, 'success');\n } catch (e) {\n log(`Dispatch failed: ${e.message}`, 'error');\n }\n });\n\n document\n .getElementById('btnDispatchTargeted')\n .addEventListener('click', async () => {\n const guests = host.getGuests();\n if (!guests.length) {\n log('No guests — load a guest first', 'warn');\n return;\n }\n const target = guests[0];\n log(`Dispatching onPreSave to guest \"${target.id}\" only...`);\n try {\n const results = await host.dispatchEvent({\n event: loanObj.onPreSave,\n eventParams: { creditScore: 800, amount: 250000 },\n eventOptions: { guestId: target.id },\n });\n document.getElementById(\n 'dispatchResult',\n ).textContent = `Targeted results (${target.id}):\\n${JSON.stringify(\n results,\n null,\n 2,\n )}`;\n log(`Targeted dispatch complete`, 'success');\n } catch (e) {\n log(`Dispatch failed: ${e.message}`, 'error');\n }\n });\n\n document\n .getElementById('btnDispatchFeedback')\n .addEventListener('click', async () => {\n log('Dispatching onPreSave with 3s timeout (feedback)...');\n try {\n const results = await host.dispatchEvent({\n event: loanObj.onPreSave,\n eventParams: { creditScore: 600, amount: 1000000 },\n eventOptions: { timeout: 3000 },\n });\n document.getElementById(\n 'dispatchResult',\n ).textContent = `Feedback results:\\n${JSON.stringify(\n results,\n null,\n 2,\n )}`;\n log(\n `Feedback dispatch complete, ${results.length} responses`,\n 'success',\n );\n } catch (e) {\n log(`Dispatch with feedback failed: ${e.message}`, 'error');\n }\n });\n\n document\n .getElementById('btnDispatchAmount')\n .addEventListener('click', async () => {\n log('Dispatching onAmountChanged (for criteria-based subscribers)...');\n try {\n const results = await host.dispatchEvent({\n event: loanObj.onAmountChanged,\n eventParams: { amount: 750000 },\n });\n document.getElementById(\n 'dispatchResult',\n ).textContent = `onAmountChanged results:\\n${JSON.stringify(\n results,\n null,\n 2,\n )}`;\n log('onAmountChanged dispatched', 'success');\n } catch (e) {\n log(`Dispatch failed: ${e.message}`, 'error');\n }\n });\n\n // --- Guest Lifecycle ---\n\n document.getElementById('btnUnloadGuest').addEventListener('click', () => {\n const guests = host.getGuests();\n const embedGuests = guests.filter(\n (g) => !g.window || g.window === window.frames[0]?.window,\n );\n if (!guests.length) {\n log('No guests to unload', 'warn');\n return;\n }\n const target = guests[0];\n log(`Unloading guest \"${target.id}\"...`);\n host.unloadGuest(target.id);\n log(`Unloaded \"${target.id}\"`, 'success');\n refreshGuestList();\n });\n\n document.getElementById('btnRegisterClose').addEventListener('click', () => {\n const guests = host.getGuests();\n if (!guests.length) {\n log('No guests — load a guest first', 'warn');\n return;\n }\n guests.forEach((g) => {\n host.onGuestClose({\n id: g.id,\n guestCloseCallback: ({ id }) => {\n log(`onGuestClose fired for \"${id}\"`, 'success');\n refreshGuestList();\n },\n });\n });\n log(`Registered onGuestClose for ${guests.length} guest(s)`, 'success');\n });\n\n document.getElementById('btnListGuests').addEventListener('click', () => {\n refreshGuestList();\n log(`Listed ${host.getGuests().length} guest(s)`);\n });\n\n document.getElementById('btnSetLogLevel').addEventListener('click', () => {\n host.setLogLevel(10);\n log('Set log level to 10 (debug)', 'success');\n });\n\n document.getElementById('btnCloseHost').addEventListener('click', () => {\n log('Closing host...');\n host.close();\n log('Host closed — all guests should be disconnected', 'success');\n refreshGuestList();\n });\n\n // --- Sandbox Configuration ---\n\n document.getElementById('btnSandboxDefault').addEventListener('click', () => {\n guestCounter++;\n const url = new URL('./e2e-guest.html', guestBaseUrl).href;\n host.loadGuest({\n id: `sb-default-${guestCounter}`,\n url,\n title: 'Sandbox Default',\n targetElement: document.getElementById('sandboxContainer'),\n options: { fitToContent: true },\n onLoad: () => {\n log('SB-01: Default sandbox guest loaded', 'success');\n const iframe = document.querySelector(\n '#sandboxContainer iframe:last-child',\n );\n log(` sandbox=\"${iframe?.getAttribute('sandbox') || '(none)'}\"`);\n },\n });\n });\n\n document.getElementById('btnSandboxCustom').addEventListener('click', () => {\n guestCounter++;\n const url = new URL('./e2e-guest.html', guestBaseUrl).href;\n host.loadGuest({\n id: `sb-custom-${guestCounter}`,\n url,\n title: 'Sandbox Custom',\n targetElement: document.getElementById('sandboxContainer'),\n options: {\n fitToContent: true,\n customSandboxValues: [\n ice.host.IFrameSandboxValues.AllowScripts,\n ice.host.IFrameSandboxValues.AllowSameOrigin,\n ],\n },\n onLoad: () => {\n log('SB-02: Custom sandbox guest loaded', 'success');\n const iframe = document.querySelector(\n '#sandboxContainer iframe:last-child',\n );\n log(` sandbox=\"${iframe?.getAttribute('sandbox') || '(none)'}\"`);\n },\n });\n });\n\n document\n .getElementById('btnSandboxDisabled')\n .addEventListener('click', () => {\n guestCounter++;\n const url = new URL('./e2e-guest.html', guestBaseUrl).href;\n host.loadGuest({\n id: `sb-disabled-${guestCounter}`,\n url,\n title: 'Sandbox Disabled',\n targetElement: document.getElementById('sandboxContainer'),\n options: { fitToContent: true, disableSandbox: true },\n onLoad: () => {\n log('SB-03: Disabled sandbox guest loaded', 'success');\n const iframe = document.querySelector(\n '#sandboxContainer iframe:last-child',\n );\n const sb = iframe?.getAttribute('sandbox');\n log(` sandbox=${sb === null ? '(not set — correct)' : `\"${sb}\"`}`);\n },\n });\n });\n\n // Periodic guest list refresh\n setInterval(refreshGuestList, 3000);\n\n log('E2E Host ready', 'success');\n};\n\nwindow.addEventListener('DOMContentLoaded', init);\n"],"mappings":"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","names":["Analytics","getGuestBaseUrl","getHost","host","guestBaseUrl","guestCounter","logEl","log","msg","type","ts","color","displayCallContext","ctx","Loan","score","delayMs","resolve","Application","Inventory","loadEmbedGuest","id","opts","url","guest","guestId","refreshGuestList","guests","g","init","analyticsObj","loanObj","appObj","e","inventoryAdded","scopedObj","results","target","embedGuests","iframe","sb"],"sourceRoot":"","file":"e2e-host.js"}