@elliemae/ssf-host 2.24.0 → 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 (40) hide show
  1. package/dist/cjs/guest.js +15 -16
  2. package/dist/cjs/host.js +37 -55
  3. package/dist/cjs/performanceTracker.js +111 -0
  4. package/dist/esm/guest.js +15 -16
  5. package/dist/esm/host.js +37 -55
  6. package/dist/esm/performanceTracker.js +91 -0
  7. package/dist/public/callchain-host.html +1 -1
  8. package/dist/public/callchain-intermediate.html +1 -1
  9. package/dist/public/e2e-host.html +1 -1
  10. package/dist/public/e2e-index.html +1 -1
  11. package/dist/public/index.html +1 -1
  12. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js +3 -0
  13. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.br +0 -0
  14. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.gz +0 -0
  15. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.map +1 -0
  16. package/dist/public/popup-focus-host.html +1 -1
  17. package/dist/public/utils.js +1 -1
  18. package/dist/public/utils.js.br +0 -0
  19. package/dist/public/utils.js.gz +0 -0
  20. package/dist/public/utils.js.map +1 -1
  21. package/dist/public/v1-guest-v2-host.html +1 -1
  22. package/dist/public/v2-host-v1-guest.html +1 -1
  23. package/dist/types/lib/guest.d.ts +4 -3
  24. package/dist/types/lib/host.d.ts +0 -1
  25. package/dist/types/lib/ihost.d.ts +18 -1
  26. package/dist/types/lib/performanceTracker.d.ts +46 -0
  27. package/dist/types/tsconfig.tsbuildinfo +1 -1
  28. package/dist/umd/index.js +1 -1
  29. package/dist/umd/index.js.br +0 -0
  30. package/dist/umd/index.js.gz +0 -0
  31. package/dist/umd/index.js.map +1 -1
  32. package/dist/umd/utils.js +1 -1
  33. package/dist/umd/utils.js.br +0 -0
  34. package/dist/umd/utils.js.gz +0 -0
  35. package/dist/umd/utils.js.map +1 -1
  36. package/package.json +4 -4
  37. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js +0 -3
  38. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.br +0 -0
  39. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.gz +0 -0
  40. package/dist/public/js/emuiSsfHost.071827d0d7e775690fbb.js.map +0 -1
@@ -0,0 +1,91 @@
1
+ class PerformanceTracker {
2
+ #analyticsObj;
3
+ #enabled;
4
+ #samplingRatio;
5
+ #samplingConfigured = /* @__PURE__ */ new Set();
6
+ #dedupWindowMs;
7
+ #lastRecorded = /* @__PURE__ */ new Map();
8
+ #onError;
9
+ static DEFAULT_SAMPLING_RATIO = 0.05;
10
+ static DEFAULT_DEDUP_WINDOW_MS = 1e4;
11
+ static #LOW_FREQUENCY = /* @__PURE__ */ new Set(["SSF.Guest.Load"]);
12
+ constructor(options) {
13
+ this.#analyticsObj = options.analyticsObj;
14
+ this.#enabled = options.enabled ?? false;
15
+ this.#samplingRatio = options.samplingRatio ?? PerformanceTracker.DEFAULT_SAMPLING_RATIO;
16
+ this.#dedupWindowMs = options.dedupWindowMs ?? PerformanceTracker.DEFAULT_DEDUP_WINDOW_MS;
17
+ this.#onError = options.onError;
18
+ }
19
+ /**
20
+ * Whether performance tracking is active. Callers can use this to
21
+ * skip expensive work (e.g. building timing name strings) when
22
+ * tracking is disabled.
23
+ * @returns {boolean} true when performance tracking is enabled
24
+ */
25
+ get enabled() {
26
+ return this.#enabled;
27
+ }
28
+ /**
29
+ * Begin a timing measurement. Returns the timing name as a token when
30
+ * timing was started, or `undefined` when timing is disabled, deduped,
31
+ * or skipped. Pass the returned token to {@link end}.
32
+ * Never throws — failures are reported via the `onError` callback.
33
+ * @param {string} name - timing event name
34
+ * @param {TimingOptions} options - context passed to the analytics object
35
+ * @returns {string | undefined} the timing token, or undefined if skipped
36
+ */
37
+ start(name, options) {
38
+ if (!this.#enabled) return void 0;
39
+ try {
40
+ const lowFreq = PerformanceTracker.#LOW_FREQUENCY.has(name);
41
+ if (!lowFreq && !this.#shouldTrack(name)) return void 0;
42
+ if (!lowFreq && !this.#samplingConfigured.has(name)) {
43
+ this.#analyticsObj.setTimingEventSamplingRatio({
44
+ [name]: this.#samplingRatio
45
+ });
46
+ this.#samplingConfigured.add(name);
47
+ }
48
+ this.#analyticsObj.startTiming(name, options).catch((e) => {
49
+ this.#reportError(`startTiming failed: ${e.message}`);
50
+ });
51
+ return name;
52
+ } catch (e) {
53
+ this.#reportError(`startTiming failed: ${e.message}`);
54
+ return void 0;
55
+ }
56
+ }
57
+ /**
58
+ * End a timing measurement previously started via {@link start}.
59
+ * No-op when token is `undefined` (timing was not started).
60
+ * Never throws — failures are reported via the `onError` callback.
61
+ * @param {string | undefined} token - token returned by {@link start}
62
+ * @param {TimingOptions} options - context passed to the analytics object
63
+ */
64
+ end(token, options) {
65
+ if (!token) return;
66
+ try {
67
+ this.#analyticsObj.endTiming(token, options).catch((e) => {
68
+ this.#reportError(`endTiming failed: ${e.message}`);
69
+ });
70
+ } catch (e) {
71
+ this.#reportError(`endTiming failed: ${e.message}`);
72
+ }
73
+ }
74
+ #reportError(message) {
75
+ try {
76
+ this.#onError?.(message);
77
+ } catch {
78
+ }
79
+ }
80
+ #shouldTrack(name) {
81
+ if (this.#dedupWindowMs <= 0) return true;
82
+ const now = performance.now();
83
+ const last = this.#lastRecorded.get(name);
84
+ if (last !== void 0 && now - last < this.#dedupWindowMs) return false;
85
+ this.#lastRecorded.set(name, now);
86
+ return true;
87
+ }
88
+ }
89
+ export {
90
+ PerformanceTracker
91
+ };
@@ -1,4 +1,4 @@
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">
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.5bb7139d7e86c74f0b6d.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
2
  Root Host (A) [localhost:4000]
3
3
  ├─ Exposes: <strong>TestService</strong> scripting object
4
4
  ├─ Loads guest <strong>"intermediateHost"</strong>
@@ -1 +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>
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.5bb7139d7e86c74f0b6d.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>
@@ -1,4 +1,4 @@
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]">
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.5bb7139d7e86c74f0b6d.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
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
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
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">
@@ -1 +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 Test Suite</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script defer="defer" src="js/emuiSsfHost.071827d0d7e775690fbb.js"></script></head><body class="bg-gray-50"><header class="bg-gray-900 text-white px-6 py-4"><h1 class="text-xl font-bold">SSF End-to-End Test Suite</h1><p class="text-sm text-gray-400 mt-1">Comprehensive blackbox tests for SSF Host &amp; Guest. Each page includes test steps, expected values, and a verification checklist.</p></header><main class="mx-auto max-w-5xl px-6 py-6"><section class="mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4"><h2 class="text-sm font-semibold text-yellow-800 mb-2">Setup Instructions</h2><ol class="text-xs text-yellow-700 list-decimal ml-4 space-y-1"><li>Install dependencies: <code class="bg-yellow-100 px-1 rounded">pnpm install</code></li><li>Start SSF Host dev server: <code class="bg-yellow-100 px-1 rounded">pnpm nx run ssf-host:start-server</code> (port 4000)</li><li>Start SSF Guest dev server: <code class="bg-yellow-100 px-1 rounded">pnpm nx run ssf-guest:start</code> (port 4001)</li><li>Open <code class="bg-yellow-100 px-1 rounded">http://localhost:4000/e2e-index.html</code> in the browser</li><li>Each test page has <code class="bg-yellow-100 px-1 rounded">data-testid</code> attributes for Playwright/Cypress selectors</li></ol></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">1. Core Host-Guest Communication</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./e2e-host.html" data-testid="link-e2e-host" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Comprehensive E2E Host</h3><p class="text-xs text-gray-500 mt-1">All host-guest scenarios: guest loading, scripting objects, events, lifecycle, metadata, sandbox, guest-scoped objects.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Guests load in iframes/popups. Scripting objects can be added/removed/invoked. Events reach subscribed guests. Unload/close cleans up properly. Sandbox attrs applied.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-LOAD-01..05 &bull; TC-SO-01..08 &bull; TC-EVT-01..04 &bull; TC-LIFE-01..03 &bull; TC-META-01 &bull; TC-SB-01..03</div></a><a href="./index.html" data-testid="link-main-demo" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Main Demo App</h3><p class="text-xs text-gray-500 mt-1">Original Loan Application demo with embedded and popup guests, events, scripting objects.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Loan form data flows to guests. Events (onLoanAmountChanged, etc.) update guest UIs. Popup guests open and interact with the Loan object.</p></a></div></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">2. Call Chain &amp; Metadata Propagation</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./callchain-host.html" data-testid="link-callchain" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Nested CallChain Test (A → B → C)</h3><p class="text-xs text-gray-500 mt-1">Root Host (A) → Intermediate Guest+Host (B) → Grandchild Guest (C). Verifies callContext.callChain and metadata propagation.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Click buttons in Grandchild C. Host A should show <code>callContext.guest</code> = B and <code>callChain</code> containing C and B with their metadata. Return values should flow back to C.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-CC-01..07</div></a></div></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">3. Popup Guest Behavior</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./popup-focus-host.html" data-testid="link-popup-focus" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Popup Focus &amp; Lifecycle</h3><p class="text-xs text-gray-500 mt-1">Open, close, and refocus popup guests. Tests trusted vs untrusted domain behavior.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Trusted popups (localhost): re-open brings existing popup to front. Untrusted popups: opener is nulled, focus falls back to WindowProxy. Closed popup detection updates guest status.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-POP-01..11</div></a></div></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">4. V1 / V2 Interoperability</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./v2-host-v1-guest.html" data-testid="link-v2-host-v1-guest" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">V2 Host → V1 Guest</h3><p class="text-xs text-gray-500 mt-1">V2 host loads V1 guest. Loan object, save flow, event feedback.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> V1 guest connects, gets objects, subscribes to events, and returns feedback correctly.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-INTEROP-01..04</div></a><a href="./v2-host-v1-guest.html?nestV1GuestV2Host=true" data-testid="link-v2-v1-v2-chain" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">V2 Host → V1 Guest → V2 Host → V2 Guest</h3><p class="text-xs text-gray-500 mt-1">Full mixed-version chain. V1 guest acts as intermediate host for a V2 grandchild.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Scripting objects are cloned across V1/V2 boundary. Events flow through all layers.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-INTEROP-05..07</div></a></div><div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3"><a href="./v1-host.html" data-testid="link-v1-host" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">V1 Host → V1 Guest → V2 Host → V2 Guest</h3><p class="text-xs text-gray-500 mt-1">Starting from V1 host. Exercises the full V1-to-V2 upgrade path.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> V1 host initializes. V1 guest connects. V2 host/guest chain works through V1 intermediate.</p></a></div></section></main></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>SSF E2E Test Suite</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script defer="defer" src="js/emuiSsfHost.5bb7139d7e86c74f0b6d.js"></script></head><body class="bg-gray-50"><header class="bg-gray-900 text-white px-6 py-4"><h1 class="text-xl font-bold">SSF End-to-End Test Suite</h1><p class="text-sm text-gray-400 mt-1">Comprehensive blackbox tests for SSF Host &amp; Guest. Each page includes test steps, expected values, and a verification checklist.</p></header><main class="mx-auto max-w-5xl px-6 py-6"><section class="mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4"><h2 class="text-sm font-semibold text-yellow-800 mb-2">Setup Instructions</h2><ol class="text-xs text-yellow-700 list-decimal ml-4 space-y-1"><li>Install dependencies: <code class="bg-yellow-100 px-1 rounded">pnpm install</code></li><li>Start SSF Host dev server: <code class="bg-yellow-100 px-1 rounded">pnpm nx run ssf-host:start-server</code> (port 4000)</li><li>Start SSF Guest dev server: <code class="bg-yellow-100 px-1 rounded">pnpm nx run ssf-guest:start</code> (port 4001)</li><li>Open <code class="bg-yellow-100 px-1 rounded">http://localhost:4000/e2e-index.html</code> in the browser</li><li>Each test page has <code class="bg-yellow-100 px-1 rounded">data-testid</code> attributes for Playwright/Cypress selectors</li></ol></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">1. Core Host-Guest Communication</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./e2e-host.html" data-testid="link-e2e-host" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Comprehensive E2E Host</h3><p class="text-xs text-gray-500 mt-1">All host-guest scenarios: guest loading, scripting objects, events, lifecycle, metadata, sandbox, guest-scoped objects.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Guests load in iframes/popups. Scripting objects can be added/removed/invoked. Events reach subscribed guests. Unload/close cleans up properly. Sandbox attrs applied.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-LOAD-01..05 &bull; TC-SO-01..08 &bull; TC-EVT-01..04 &bull; TC-LIFE-01..03 &bull; TC-META-01 &bull; TC-SB-01..03</div></a><a href="./index.html" data-testid="link-main-demo" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Main Demo App</h3><p class="text-xs text-gray-500 mt-1">Original Loan Application demo with embedded and popup guests, events, scripting objects.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Loan form data flows to guests. Events (onLoanAmountChanged, etc.) update guest UIs. Popup guests open and interact with the Loan object.</p></a></div></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">2. Call Chain &amp; Metadata Propagation</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./callchain-host.html" data-testid="link-callchain" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Nested CallChain Test (A → B → C)</h3><p class="text-xs text-gray-500 mt-1">Root Host (A) → Intermediate Guest+Host (B) → Grandchild Guest (C). Verifies callContext.callChain and metadata propagation.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Click buttons in Grandchild C. Host A should show <code>callContext.guest</code> = B and <code>callChain</code> containing C and B with their metadata. Return values should flow back to C.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-CC-01..07</div></a></div></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">3. Popup Guest Behavior</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./popup-focus-host.html" data-testid="link-popup-focus" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Popup Focus &amp; Lifecycle</h3><p class="text-xs text-gray-500 mt-1">Open, close, and refocus popup guests. Tests trusted vs untrusted domain behavior.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Trusted popups (localhost): re-open brings existing popup to front. Untrusted popups: opener is nulled, focus falls back to WindowProxy. Closed popup detection updates guest status.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-POP-01..11</div></a></div></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">4. V1 / V2 Interoperability</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./v2-host-v1-guest.html" data-testid="link-v2-host-v1-guest" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">V2 Host → V1 Guest</h3><p class="text-xs text-gray-500 mt-1">V2 host loads V1 guest. Loan object, save flow, event feedback.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> V1 guest connects, gets objects, subscribes to events, and returns feedback correctly.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-INTEROP-01..04</div></a><a href="./v2-host-v1-guest.html?nestV1GuestV2Host=true" data-testid="link-v2-v1-v2-chain" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">V2 Host → V1 Guest → V2 Host → V2 Guest</h3><p class="text-xs text-gray-500 mt-1">Full mixed-version chain. V1 guest acts as intermediate host for a V2 grandchild.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Scripting objects are cloned across V1/V2 boundary. Events flow through all layers.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-INTEROP-05..07</div></a></div><div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3"><a href="./v1-host.html" data-testid="link-v1-host" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">V1 Host → V1 Guest → V2 Host → V2 Guest</h3><p class="text-xs text-gray-500 mt-1">Starting from V1 host. Exercises the full V1-to-V2 upgrade path.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> V1 host initializes. V1 guest connects. V2 host/guest chain works through V1 intermediate.</p></a></div></section></main></body></html>
@@ -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>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><header class="bg-indigo-300 h-10 flex place-items-center justify-between"><div class="px-2">ICE Mortgage Product</div><nav class="flex gap-3 px-2 text-xs"><a href="./e2e-index.html" data-testid="nav-e2e-suite" class="text-indigo-800 hover:text-indigo-950 underline font-bold">E2E Test Suite</a> <a href="./e2e-host.html" data-testid="nav-e2e-host" class="text-indigo-800 hover:text-indigo-950 underline">Comprehensive E2E</a> <a href="./callchain-host.html" data-testid="nav-callchain-test" class="text-indigo-800 hover:text-indigo-950 underline">CallChain</a> <a href="./popup-focus-host.html" data-testid="nav-popup-focus-test" class="text-indigo-800 hover:text-indigo-950 underline">Popup Focus</a> <a href="./v2-host-v1-guest.html" data-testid="nav-v1-interop" class="text-indigo-800 hover:text-indigo-950 underline">V1 Interop</a></nav></header><main class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8"><div class="min-w-0 flex-1 mt-4"><h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">Loan Application</h1></div><div id="successFeedback" class="hidden rounded-md bg-green-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"/></svg></div><div class="ml-3"><p class="text-sm font-medium text-green-800">Loan Saved Successfully</p></div></div></div><div id="errorFeedback" class="hidden rounded-md bg-red-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd"/></svg></div><div class="ml-3"><h3 class="text-sm font-medium text-red-800">Credit Score is not meeting the requirement</h3></div></div></div><div class="mt-2 sm:grid sm:grid-cols-2 sm:gap-2"><form class="px-2 py-2 space-y-8 divide-y divide-gray-200 bg-gray-50"><div class="space-y-8 divide-y divide-gray-200 sm:space-y-5"><div class="space-y-6 sm:space-y-5"><div><h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="firstName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">First name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="firstName" id="firstName" autocomplete="given-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="John" placeholder="John"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="lastName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Last name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="lastName" id="lastName" autocomplete="family-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="Doe" placeholder="Doe"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="ssn" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">SSN</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="ssn" id="ssn" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="123456789" placeholder="123456789"/></div></div></div><div><h3 class="text-lg font-medium leading-6 text-gray-900">Loan Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="amount" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Amount</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="amount" id="amount" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="500000" placeholder="500000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="Term" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Term (years)</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="term" id="term" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="30" placeholder="30"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="downPayment" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Down Payment</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="downPayment" id="downPayment" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="50000" placeholder="50000"/></div></div><div><h3 class="text-lg font-medium leading-6 text-gray-900">Order Services</h3></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><div class="mt-1 sm:mt-0"><button id="title" type="button" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-offset-2"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M7.502 6h7.128A3.375 3.375 0 0118 9.375v9.375a3 3 0 003-3V6.108c0-1.505-1.125-2.811-2.664-2.94a48.972 48.972 0 00-.673-.05A3 3 0 0015 1.5h-1.5a3 3 0 00-2.663 1.618c-.225.015-.45.032-.673.05C8.662 3.295 7.554 4.542 7.502 6zM13.5 3A1.5 1.5 0 0012 4.5h4.5A1.5 1.5 0 0015 3h-1.5z" clip-rule="evenodd"/><path fill-rule="evenodd" d="M3 9.375C3 8.339 3.84 7.5 4.875 7.5h9.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 013 20.625V9.375zM6 12a.75.75 0 01.75-.75h.008a.75.75 0 01.75.75v.008a.75.75 0 01-.75.75H6.75a.75.75 0 01-.75-.75V12zm2.25 0a.75.75 0 01.75-.75h3.75a.75.75 0 010 1.5H9a.75.75 0 01-.75-.75zM6 15a.75.75 0 01.75-.75h.008a.75.75 0 01.75.75v.008a.75.75 0 01-.75.75H6.75a.75.75 0 01-.75-.75V15zm2.25 0a.75.75 0 01.75-.75h3.75a.75.75 0 010 1.5H9a.75.75 0 01-.75-.75zM6 18a.75.75 0 01.75-.75h.008a.75.75 0 01.75.75v.008a.75.75 0 01-.75.75H6.75a.75.75 0 01-.75-.75V18zm2.25 0a.75.75 0 01.75-.75h3.75a.75.75 0 010 1.5H9a.75.75 0 01-.75-.75z" clip-rule="evenodd"/></svg> Title</button></div><div class="mt-1 sm:mt-0"><button id="credit" type="button" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-offset-2"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M2.25 13.5a8.25 8.25 0 018.25-8.25.75.75 0 01.75.75v6.75H18a.75.75 0 01.75.75 8.25 8.25 0 01-16.5 0z" clip-rule="evenodd"/><path fill-rule="evenodd" d="M12.75 3a.75.75 0 01.75-.75 8.25 8.25 0 018.25 8.25.75.75 0 01-.75.75h-7.5a.75.75 0 01-.75-.75V3z" clip-rule="evenodd"/></svg> Credit Score</button></div></div></div></div></div><div class="flex flex-col"><button id="saveLoan" type="button" class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button></div></form><div id="aside-container" class="flex flex-col gap-4 items-start mt-4 border-2 p-2 rounded-lg border-dashed border-cyan-300 sm:mt-0"></div></div><div id="bottom-container" class="flex flex-col gap-4 items-start mt-4 p-2 sm:mt-0"></div></main><script src="./init.js" type="module"></script></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>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.5bb7139d7e86c74f0b6d.js"></script></head><body><header class="bg-indigo-300 h-10 flex place-items-center justify-between"><div class="px-2">ICE Mortgage Product</div><nav class="flex gap-3 px-2 text-xs"><a href="./e2e-index.html" data-testid="nav-e2e-suite" class="text-indigo-800 hover:text-indigo-950 underline font-bold">E2E Test Suite</a> <a href="./e2e-host.html" data-testid="nav-e2e-host" class="text-indigo-800 hover:text-indigo-950 underline">Comprehensive E2E</a> <a href="./callchain-host.html" data-testid="nav-callchain-test" class="text-indigo-800 hover:text-indigo-950 underline">CallChain</a> <a href="./popup-focus-host.html" data-testid="nav-popup-focus-test" class="text-indigo-800 hover:text-indigo-950 underline">Popup Focus</a> <a href="./v2-host-v1-guest.html" data-testid="nav-v1-interop" class="text-indigo-800 hover:text-indigo-950 underline">V1 Interop</a></nav></header><main class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8"><div class="min-w-0 flex-1 mt-4"><h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">Loan Application</h1></div><div id="successFeedback" class="hidden rounded-md bg-green-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"/></svg></div><div class="ml-3"><p class="text-sm font-medium text-green-800">Loan Saved Successfully</p></div></div></div><div id="errorFeedback" class="hidden rounded-md bg-red-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd"/></svg></div><div class="ml-3"><h3 class="text-sm font-medium text-red-800">Credit Score is not meeting the requirement</h3></div></div></div><div class="mt-2 sm:grid sm:grid-cols-2 sm:gap-2"><form class="px-2 py-2 space-y-8 divide-y divide-gray-200 bg-gray-50"><div class="space-y-8 divide-y divide-gray-200 sm:space-y-5"><div class="space-y-6 sm:space-y-5"><div><h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="firstName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">First name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="firstName" id="firstName" autocomplete="given-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="John" placeholder="John"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="lastName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Last name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="lastName" id="lastName" autocomplete="family-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="Doe" placeholder="Doe"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="ssn" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">SSN</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="ssn" id="ssn" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="123456789" placeholder="123456789"/></div></div></div><div><h3 class="text-lg font-medium leading-6 text-gray-900">Loan Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="amount" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Amount</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="amount" id="amount" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="500000" placeholder="500000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="Term" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Term (years)</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="term" id="term" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="30" placeholder="30"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="downPayment" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Down Payment</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="downPayment" id="downPayment" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="50000" placeholder="50000"/></div></div><div><h3 class="text-lg font-medium leading-6 text-gray-900">Order Services</h3></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><div class="mt-1 sm:mt-0"><button id="title" type="button" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-offset-2"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M7.502 6h7.128A3.375 3.375 0 0118 9.375v9.375a3 3 0 003-3V6.108c0-1.505-1.125-2.811-2.664-2.94a48.972 48.972 0 00-.673-.05A3 3 0 0015 1.5h-1.5a3 3 0 00-2.663 1.618c-.225.015-.45.032-.673.05C8.662 3.295 7.554 4.542 7.502 6zM13.5 3A1.5 1.5 0 0012 4.5h4.5A1.5 1.5 0 0015 3h-1.5z" clip-rule="evenodd"/><path fill-rule="evenodd" d="M3 9.375C3 8.339 3.84 7.5 4.875 7.5h9.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 013 20.625V9.375zM6 12a.75.75 0 01.75-.75h.008a.75.75 0 01.75.75v.008a.75.75 0 01-.75.75H6.75a.75.75 0 01-.75-.75V12zm2.25 0a.75.75 0 01.75-.75h3.75a.75.75 0 010 1.5H9a.75.75 0 01-.75-.75zM6 15a.75.75 0 01.75-.75h.008a.75.75 0 01.75.75v.008a.75.75 0 01-.75.75H6.75a.75.75 0 01-.75-.75V15zm2.25 0a.75.75 0 01.75-.75h3.75a.75.75 0 010 1.5H9a.75.75 0 01-.75-.75zM6 18a.75.75 0 01.75-.75h.008a.75.75 0 01.75.75v.008a.75.75 0 01-.75.75H6.75a.75.75 0 01-.75-.75V18zm2.25 0a.75.75 0 01.75-.75h3.75a.75.75 0 010 1.5H9a.75.75 0 01-.75-.75z" clip-rule="evenodd"/></svg> Title</button></div><div class="mt-1 sm:mt-0"><button id="credit" type="button" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-offset-2"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M2.25 13.5a8.25 8.25 0 018.25-8.25.75.75 0 01.75.75v6.75H18a.75.75 0 01.75.75 8.25 8.25 0 01-16.5 0z" clip-rule="evenodd"/><path fill-rule="evenodd" d="M12.75 3a.75.75 0 01.75-.75 8.25 8.25 0 018.25 8.25.75.75 0 01-.75.75h-7.5a.75.75 0 01-.75-.75V3z" clip-rule="evenodd"/></svg> Credit Score</button></div></div></div></div></div><div class="flex flex-col"><button id="saveLoan" type="button" class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button></div></form><div id="aside-container" class="flex flex-col gap-4 items-start mt-4 border-2 p-2 rounded-lg border-dashed border-cyan-300 sm:mt-0"></div></div><div id="bottom-container" class="flex flex-col gap-4 items-start mt-4 p-2 sm:mt-0"></div></main><script src="./init.js" type="module"></script></body></html>
@@ -0,0 +1,3 @@
1
+ (function(I,O){typeof exports=="object"&&typeof module=="object"?module.exports=O():typeof define=="function"&&define.amd?define([],O):typeof exports=="object"?exports.ice=O():(I.ice=I.ice||{},I.ice.host=O())})(globalThis,()=>(()=>{"use strict";var j={};j.d=(n,e)=>{for(var t in e)j.o(e,t)&&!j.o(n,t)&&Object.defineProperty(n,t,{enumerable:!0,get:e[t]})},j.o=(n,e)=>Object.prototype.hasOwnProperty.call(n,e),j.r=n=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})};var I={};j.r(I),j.d(I,{Event:()=>O,Guest:()=>M,IFrameSandboxValues:()=>S,OpenMode:()=>w,SANDBOX_DEFAULT:()=>F,SSFHost:()=>re,ScriptingObject:()=>U});class O{name;objectId;id;constructor(e){const{name:t,objectId:i}=e;if(!t)throw new Error("Event name is required");if(!i)throw new Error("Scripting object id is required");this.objectId=i,this.name=t,this.id=`${this.objectId}.${this.name}`.toLowerCase()}}class G{static[Symbol.hasInstance](e){return typeof e=="object"&&e!==null&&"getType"in e&&typeof e.getType=="function"&&e.getType()==="ProxyEvent"}#t;objectId;name;id;getType(){return"ProxyEvent"}constructor(e){const{name:t,objectId:i,eventSrc:s}=e;if(!t)throw new Error("Event name is required");if(!i)throw new Error("Scripting object id is required");if(!s)throw new Error("Event source is required");this.objectId=i,this.name=t,this.#t=s,this.id=`${this.objectId}.${this.name}`.toLowerCase()}subscribe=e=>this.#t.subscribe({eventId:this.id,callback:e});unsubscribe=e=>{this.#t.unsubscribe({eventId:this.id,token:e})}}const H=n=>n instanceof O,ce=(n,e)=>`${n.toLowerCase()}.${e.toLowerCase()}`,J="function",Y=(n,e)=>typeof n===J&&!!e&&!e.startsWith("_");class U{#t;#i="Object";constructor(e,t){this.#t=e,this.#i=t||this.#i}get id(){return this.#t}get objectType(){return this.#i}_toJSON=()=>{const e=[],t=[];return Object.keys(this).forEach(i=>{const s=this[i];H(s)?t.push(i):Y(s,i)&&e.push(i)}),{objectId:this.#t,objectType:this.#i,functions:e,events:t}};_dispose=()=>{};dispose=()=>{}}var g=(n=>(n.GuestClose="guest:close",n.GuestEventSubscribe="guest:eventSubscribe",n.GuestEventUnsubscribe="guest:eventUnsubscribe",n.GuestFocus="guest:focus",n.GuestReady="guest:ready",n.GuestReadyComplete="guest:readyComplete",n.GuestResize="guest:resize",n.HandShake="handshake",n.HandShakeAck="handshake:ack",n.HostClose="host:close",n.HostConfig="host:config",n.ListObjects="list:objects",n.ObjectEvent="object:event",n.ObjectGet="object:get",n.ObjectInvoke="object:invoke",n))(g||{}),w=(n=>(n.Popup="popup",n.Embed="embed",n))(w||{}),S=(n=>(n.AllowDownloadsWithoutUserActivation="allow-downloads-without-user-activation",n.AllowDownloads="allow-downloads",n.AllowForms="allow-forms",n.AllowModals="allow-modals",n.AllowOrientationLock="allow-orientation-lock",n.AllowPointerLock="allow-pointer-lock",n.AllowPopups="allow-popups",n.AllowPopupsToEscapeSandbox="allow-popups-to-escape-sandbox",n.AllowPresentation="allow-presentation",n.AllowSameOrigin="allow-same-origin",n.AllowScripts="allow-scripts",n.AllowStorageAccessByUserActivation="allow-storage-access-by-user-activation",n.AllowTopNavigation="allow-top-navigation",n.AllowTopNavigationByUserActivation="allow-top-navigation-by-user-activation",n))(S||{});const X=n=>{if(n==="about:blank")return"*";try{const{origin:e}=new URL(n);return e==="null"||!e?n:e}catch{const{origin:t}=new URL(n,document.baseURI);return t}},Q=n=>n.flat(1/0).filter(e=>e!==void 0);function R(n){return typeof n=="function"}const K=[".ice.com",".elliemae.com",".ellielabs.com"],Z=["localhost","127.0.0.1"],_=n=>{try{const{hostname:e}=new URL(n,document.baseURI);return Z.includes(e)||K.some(t=>e===t.slice(1)||e.endsWith(t))}catch{return!1}};class M{id;title;url;searchParams;domElement;window;openMode;origin;initialized=!1;ready=!1;capabilities;#t;#i;constructor(e){const{guestId:t,domElement:i=null,title:s,url:o,window:r,searchParams:c={},openMode:l=w.Embed,remoting:a,perfTracker:d}=e;this.id=t,this.title=s,this.url=o,this.origin=X(o),this.searchParams=c,this.domElement=i,this.window=r,this.openMode=l,this.capabilities={},this.#i=d,this.#t=a}dispose=()=>{if(this.openMode===w.Popup&&!this.window.closed)try{this.window.document,this.window.close()}catch{this.#t.send({targetWin:this.window,targetOrigin:this.origin,messageType:g.HostClose,messageBody:{}})}else this.domElement?.remove?.();this.#t.removeSender({origin:this.origin,window:this.window})};getInfo=()=>({guestId:this.id,guestTitle:this.title,guestUrl:this.url});handShake=()=>new Promise(e=>{let t=0;const i=5;let s;const o=()=>{clearInterval(s),this.#t.unlisten({messageType:g.HandShakeAck,callback:o}),e(!0)};s=setInterval(()=>{t>=i?(clearInterval(s),this.#t.unlisten({messageType:g.HandShakeAck,callback:o}),e(!1)):(this.#t.send({targetWin:this.window,targetOrigin:this.origin,messageType:g.HandShake,messageBody:{}}),t+=1)},1e3),this.#t.listen({messageType:g.HandShakeAck,callback:o})});init=()=>{this.#t.addSender({origin:this.origin,window:this.window}),this.openMode===w.Popup&&this.handShake().catch(()=>{})};dispatchEvent=(e,t)=>{let i;if(this.#i.enabled){const s=`ScriptingObject.Event.${e.object.objectId}.${e.eventName}`;i=this.#i.start(s,{appId:this.id,appUrl:this.url})}return this.#t.invoke({targetWin:this.window,targetOrigin:this.origin,messageType:g.ObjectEvent,messageBody:e,responseTimeoutMs:t}).finally(()=>{i&&this.#i.end(i,{appId:this.id,appUrl:this.url})})};send=e=>{this.#t.send({targetWin:this.window,targetOrigin:this.origin,...e})}}const L={randomUUID:typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};let P;const V=new Uint8Array(16);function ee(){if(!P&&(P=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!P))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return P(V)}var te;const p=[];for(let n=0;n<256;++n)p.push((n+256).toString(16).slice(1));function q(n,e=0){return p[n[e+0]]+p[n[e+1]]+p[n[e+2]]+p[n[e+3]]+"-"+p[n[e+4]]+p[n[e+5]]+"-"+p[n[e+6]]+p[n[e+7]]+"-"+p[n[e+8]]+p[n[e+9]]+"-"+p[n[e+10]]+p[n[e+11]]+p[n[e+12]]+p[n[e+13]]+p[n[e+14]]+p[n[e+15]]}function de(n,e=0){const t=q(n,e);if(!te(t))throw TypeError("Stringified UUID is invalid");return t}const le=null;function ie(n,e,t){if(L.randomUUID&&!e&&!n)return L.randomUUID();n=n||{};const i=n.random||(n.rng||ee)();if(i[6]=i[6]&15|64,i[8]=i[8]&63|128,e){t=t||0;for(let s=0;s<16;++s)e[t+s]=i[s];return e}return q(i)}const C=ie,D="elli:remoting",x="elli:remoting:response",W="elli:remoting:exception",k=({messageType:n,messageBody:e,requestId:t,onewayMsg:i=!1})=>({requestId:t??(i?null:C()),source:D,type:n,body:e}),ue=n=>{const{targetWin:e,targetOrigin:t,messageType:i,messageBody:s}=n,o=k({messageType:i,messageBody:s});e.postMessage(o,t)};class se{#t;#i;#e=new Map;#c=new Map;#s=null;#o=null;#r=new Map;constructor(e,t){if(!e)throw new Error("logger is required");if(!t)throw new Error("correlationId is required");this.#t=t,this.#i=e}#u=()=>{this.#s=null;const e=Date.now(),t=[];let i=null;if(this.#c.forEach((s,o)=>{const{requestId:r,cancelTime:c}=s;c&&c<=e?(this.#i.debug(`Detected response timeout for requestId: ${r}...`),t.push(o),s.resolve(void 0)):c&&(i=i===null?c:Math.min(i,c))}),t.forEach(s=>{this.#c.delete(s)}),i!==null){const s=Math.max(i-Date.now(),0);this.#n(s)}};#n=e=>{this.#s===null&&(this.#s=window.setTimeout(this.#u,e))};#h=()=>{this.#s!==null&&(window.clearTimeout(this.#s),this.#s=null)};#d=e=>{const t=this.#c.get(e);return this.#i.debug(`serving requestId: ${e}`),this.#c.delete(e),t};#p=e=>{const{requestId:t}=e;this.#i.debug(`Response received for invocation requestId: ${t}`);const i=this.#d(t);return i?(i.resolve(e.body),!0):(this.#i.debug(`Received response to stale/invalid request with requestId: ${t}`),!1)};#f=e=>{this.#i.debug(`Exception received for invocation (requestId = ${e.requestId})`);const t=this.#d(e.requestId);return t?(t.reject(new Error(e.body)),!0):(this.#i.warn(`Received exception for stale/invalid request (requestId = ${e.requestId})`),!1)};#g=({sourceWin:e,sourceOrigin:t,message:i})=>{this.#i.debug(`Received message of type "${i.type}"`);const s=this.#e.get(i.type);return s?(s.forEach(o=>{this.#i.debug(`Invoking message handler ${o.name}`),o({sourceWin:e,sourceOrigin:t,requestId:i.requestId,type:i.type,body:i.body})}),!0):!1};#a=e=>{if(this.#r.size===0||!e.source)return!1;const t=this.#r.get(e.source);return!t||e?.data?.source!==D?!1:(this.#i.debug(`Remoting: Received message of type "${e.data.type}"`),e.data.type===x?this.#p(e.data):e.data.type===W?this.#f(e.data):this.#g({sourceWin:e.source,sourceOrigin:t,message:e.data}),!0)};addSender=e=>{const{origin:t,window:i}=e;if(!t)throw new Error("origin is required");if(!i)throw new Error("window is required");this.#r.set(i,t)};initialize=e=>{this.#o&&this.#o.removeEventListener("message",this.#a),e.addEventListener("message",this.#a),this.#o=e,this.#i.debug(`initialized remoting id: ${this.#t}`)};close=()=>{this.#o&&(this.#o.removeEventListener("message",this.#a),this.#o=null),this.#h(),this.#i.debug(`closed remoting id: ${this.#t}`)};invoke=e=>{const{targetWin:t,targetOrigin:i,messageType:s,messageBody:o,responseTimeoutMs:r}=e;return new Promise((c,l)=>{const a=k({messageType:s,messageBody:o});this.#c.set(a.requestId,{requestId:a.requestId,resolve:c,reject:l,cancelTime:r?Date.now()+r:null}),t.postMessage(a,i);const{requestId:d}=a;this.#i.debug(`Posted invocation message of type ${s} requestId: ${d||""}`),r&&(this.#i.debug(`scheduling timeout check for requestId: ${d||""} in ${r} ms`),this.#n(r))})};listen=e=>{const{messageType:t,callback:i}=e,s=this.#e.get(t)||[];s.push(i),this.#e.set(t,s)};unlisten=e=>{const{messageType:t,callback:i}=e,s=this.#e.get(t);if(!s)return;const o=s.indexOf(i);o!==-1&&s.splice(o,1)};send=e=>{const{targetWin:t,targetOrigin:i,messageType:s,messageBody:o}=e,r=k({messageType:s,messageBody:o,onewayMsg:!0});t.postMessage(r,i),this.#i.debug(`Posted one-way message of type "${s}"`)};removeSender=e=>{const{window:t}=e;t&&this.#r.delete(t)};respond=e=>{const{targetWin:t,targetOrigin:i,requestId:s,response:o}=e,r=k({messageType:x,messageBody:o,requestId:s});t.postMessage(r,i),this.#i.debug(`Response sent to caller for invocation requestId: ${s}`)};raiseException=e=>{const{targetWin:t,targetOrigin:i,requestId:s,ex:o}=e,r=o instanceof Error?o.message:o,c=k({messageType:W,messageBody:r,requestId:s});t.postMessage(c,i),this.#i.debug(`Exception sent to caller for invocation. requestId: ${s}`)}}const N="module";var ne=(n=>(n.USER="USER",n.PARTNER="PARTNER",n))(ne||{});class oe{#t=e=>e.trim().toLowerCase();#i=new Map;#e=new Map;#c=e=>{const{so:t,guestId:i}=e,s=this.#t(t.id),o=this.#e.get(i);if(!o)this.#e.set(i,new Map([[s,e]]));else{if(o.has(s))throw new Error(`Scripting Object ${t.id} already exists for guest ${i}`);o.set(s,e)}};#s=({so:e})=>{if(e._dispose&&typeof e._dispose=="function")try{e._dispose()}catch{}};#o=({objectId:e,guestId:t})=>{if(e===N&&!t)for(const[,s]of this.#e){const o=s.get(e);if(o)return o}const i=t?this.#e.get(t):null;return i?i.get(e)??null:null};#r=({objectId:e,guestId:t}={})=>{if(t){if(!e){const s=this.#e.get(t);s&&s.forEach(this.#s),this.#e.delete(t);return}const i=this.#e.get(t);if(i){const s=i.get(e);s&&this.#s(s),i.delete(e)}}else e&&this.#e.forEach(i=>{const s=i.get(e);s&&this.#s(s),i.delete(e)})};addScriptingObject=(e,t)=>{const{guestId:i}=t||{};if(!e?.id||!e?._toJSON)throw new Error("Object is not derived from ScriptingObject");const s=this.#t(e.id);if(s===N&&!i)throw new Error("Guest id is required to add Module scripting object");if(i){this.#c({so:e,...t,guestId:i});return}if(this.#i.has(s))throw new Error(`Scripting Object ${e.id} already exists`);this.#i.set(s,{so:e,...t})};getObject=(e,t)=>{const i=this.#t(e);let s=this.#o({objectId:i,guestId:t?.id});s=s??this.#i.get(i)??null;const{so:o}=s||{};return o||null};has=(e,t)=>this.getObject(e,t)!==null;listScriptingObjects=e=>{const t=new Set(this.#i.keys()),i=this.#e.get(e);if(i)for(const s of i.keys())t.add(s);return Array.from(t)};removeScriptingObject=(e,t)=>{const i=this.#t(e);if(t)this.#r({objectId:i,guestId:t});else{this.#r({objectId:i});const s=this.#i.get(i);s&&this.#s(s),this.#i.delete(i)}};removeAllScriptingObjects=e=>{e?this.#r({guestId:e}):(this.#i.forEach(this.#s),this.#i.clear())}}class he{__TYPE__="Proxy";id;objectType;constructor(e,t){this.id=e,this.objectType=t}}const z=n=>n?.constructor?.name==="Proxy"||n?.constructor?.name==="ScriptingObjectProxy"||n?.__TYPE__==="Proxy";class ${#t;#i;#e;#c=new Set;#s;#o=new Map;#r;static DEFAULT_SAMPLING_RATIO=.05;static DEFAULT_DEDUP_WINDOW_MS=1e4;static#u=new Set(["SSF.Guest.Load"]);constructor(e){this.#t=e.analyticsObj,this.#i=e.enabled??!1,this.#e=e.samplingRatio??$.DEFAULT_SAMPLING_RATIO,this.#s=e.dedupWindowMs??$.DEFAULT_DEDUP_WINDOW_MS,this.#r=e.onError}get enabled(){return this.#i}start(e,t){if(this.#i)try{const i=$.#u.has(e);return!i&&!this.#h(e)?void 0:(!i&&!this.#c.has(e)&&(this.#t.setTimingEventSamplingRatio({[e]:this.#e}),this.#c.add(e)),this.#t.startTiming(e,t).catch(s=>{this.#n(`startTiming failed: ${s.message}`)}),e)}catch(i){this.#n(`startTiming failed: ${i.message}`);return}}end(e,t){if(e)try{this.#t.endTiming(e,t).catch(i=>{this.#n(`endTiming failed: ${i.message}`)})}catch(i){this.#n(`endTiming failed: ${i.message}`)}}#n(e){try{this.#r?.(e)}catch{}}#h(e){if(this.#s<=0)return!0;const t=performance.now(),i=this.#o.get(e);return i!==void 0&&t-i<this.#s?!1:(this.#o.set(e,t),!0)}}const F=[S.AllowScripts,S.AllowPopups,S.AllowModals,S.AllowForms,S.AllowDownloads,S.AllowSameOrigin];class re{hostId;#t;#i;#e;#c;#s=new Map;#o=new Map;#r=new Map;#u=new Map;#n;#h=null;#d=null;#p=null;#f=null;#g=new Map;#a;#m;constructor(e,t){if(this.hostId=e,!t?.logger)throw new Error("Logger is required");if(!t?.analyticsObj)throw new Error("Analytics object is required");this.#e=t.logger,this.#c=t.analyticsObj;const i={analyticsObj:t.analyticsObj,enabled:t?.measurePerformance,samplingRatio:t?.performanceSamplingRatio,onError:s=>this.#e.debug(s)};if(this.#a=new $({...i,dedupWindowMs:t?.performanceDedupWindowMs}),this.#m=new $({...i,dedupWindowMs:0}),this.#i=C(),this.#t=new se(this.#e,this.#i),t?.readyStateCallback&&typeof t?.readyStateCallback!="function")throw new Error("readyStateCallback must be a function");this.#h=t?.readyStateCallback||null,this.#p=t?.onGuestEventSubscribe||null,this.#f=t?.onGuestEventUnsubscribe||null,this.#n=new oe,this.#t.initialize(window),this.#M(),window.addEventListener("beforeunload",this.#b),this.#e.debug(`host is initialized. hostId: ${this.hostId}, correlationId: ${this.#i}`)}#N=(e,t)=>{const i={event:e,...t};this.#c.sendBAEvent(i).catch(s=>{this.#e.debug(`Analytics sendBAEvent failed: ${s.message}`)})};#b=()=>{Array.from(this.#s.values()).filter(t=>t.openMode===w.Popup).map(t=>t.id).forEach(t=>this.unloadGuest(t))};#l=e=>this.#o.get(e);#O=e=>this.#r.get(e)??null;#S=e=>typeof e?._toJSON=="function";#v=(e,t)=>this.#S(e)?(this.#n.has(e?.id,t)||this.#n.addScriptingObject(e,t?{guestId:t?.id}:{}),{type:"object",object:e._toJSON()}):{type:"value",value:e};#z=e=>typeof e=="string"?e:e instanceof Error?e.message:"An unexpected error occurred in the host application";#E=e=>{e.ready&&this.#t.send({targetWin:e.window,targetOrigin:e.origin,messageType:g.HostConfig,messageBody:{logLevel:this.#e.getLogLevel(),...e.getInfo()}})};#I=({guest:e,obj:t,functionName:i,functionParams:s,callerChain:o})=>{const r=t[i];return R(r)?(this.#e.debug(`Invoking host implementation of ${t.id}.${String(i)}()`),new Promise(c=>{Object.defineProperty(r,"callContext",{value:{guest:e,...o?.length?{callChain:o}:{}},configurable:!0,enumerable:!0,writable:!0}),c(r(...s))})):(this.#e.warn(`Attempt to call invalid function on object type ${t.objectType}: ${String(i)}`),Promise.reject(new Error(`Method '${i}' not found in Scripting Object '${t.id}'`)))};#y=({sourceWin:e,sourceOrigin:t,requestId:i})=>{const s=this.#l(e);if(!s){this.#e.warn(`Received ready event for unknown guest. requestId: ${i}`);return}if(!s.initialized){this.#e.warn("Guest must be initialized before it is marked as ready"),this.#t.raiseException({targetWin:e,targetOrigin:t,requestId:i,ex:"Guest must be initialized before it is marked as ready"});return}if(!s.ready){s.ready=!0;const o=s.getInfo();this.#a.end("SSF.Guest.Load",{appId:o.guestId,appUrl:o.guestUrl}),this.#E(s),this.#h?.(s),this.#e.audit({message:"Guest is ready",...o})}};#$=({sourceWin:e,sourceOrigin:t,requestId:i,body:s})=>{const o=this.#l(e);if(!o){this.#e.warn(`Received ready event for unknown guest. requestid = ${i}`);return}o.initialized||(o.initialized=!0,o.capabilities=s||{},this.#e.audit({message:"Guest is initialized",...o.getInfo()})),(!s||!s.onReady)&&this.#y({sourceWin:e,sourceOrigin:t,requestId:i,type:"",body:null})};#k=async({sourceWin:e})=>{if(e?.window){const t=this.#w(e);t&&!await t.handShake()&&this.unloadGuest(e)}};#T=({sourceWin:e,sourceOrigin:t,requestId:i})=>{this.#e.debug(`Processing listObjects request. requestId = ${i}`);const s=this.#l(e);if(!s)return this.#e.warn("Rejected object request from unknown guest window"),!1;const o=this.#n.listScriptingObjects(s.id);return this.#t.respond({targetWin:e,targetOrigin:t,requestId:i,response:o}),this.#e.debug({message:"name of scripting objects returned",requestId:i,objects:o,...s.getInfo()}),!0};#P=({sourceWin:e,sourceOrigin:t,requestId:i,body:s})=>{const{objectId:o}=s;this.#e.debug(`Processing getObject request for object ${o}. requestId = ${i}`);const r=this.#l(e);if(!r)return this.#e.warn("Rejected object request from unknown guest window"),!1;const c=this.getScriptingObject(o,{guest:r});return c?(this.#t.respond({targetWin:e,targetOrigin:t,requestId:i,response:this.#v(c,r)}),this.#e.debug({message:"Scripting Object returned",requestId:i,scriptingObject:o,...r.getInfo()}),!0):(this.#e.warn(`unknown or unauthorized object ${o} from guest ${r.id}`),this.#t.raiseException({targetWin:e,targetOrigin:t,requestId:i,ex:`The requested object (${o}) is not available`}),!1)};#A=({sourceWin:e,requestId:t,body:i})=>{const{eventId:s,criteria:o,token:r}=i;this.#e.debug(`Processing guest event subscribe request for event ${s}. requestId = ${t}`);const c=this.#l(e);if(!c){this.#e.warn("Rejected event subscribe request from unknown guest window");return}setTimeout(()=>{try{this.#p?.({guestId:c.id,eventId:s,criteria:o,token:r})}catch(l){this.#e.warn(`Error in onGuestEventSubscribe callback for event ${s}: ${l.message}`)}},0)};#G=({sourceWin:e,requestId:t,body:i})=>{const{eventId:s,token:o}=i;this.#e.debug(`Processing guest event unsubscribe request for event ${s}. requestId = ${t}`);const r=this.#l(e);if(!r){this.#e.warn("Rejected event unsubscribe request from unknown guest window");return}setTimeout(()=>{try{this.#f?.({guestId:r.id,eventId:s,token:o})}catch(c){this.#e.warn(`Error in onGuestEventUnsubscribe callback for event ${s}: ${c.message}`)}},0)};#U=({sourceWin:e,requestId:t,body:i})=>{const s=this.#l(e);if(!s){this.#e.warn(`Received resize event from unknown guest. requestid = ${t}`);return}s.domElement&&(s.domElement.style.height=`${i.height}px`),this.#e.debug(`Guest ${s.id} resized to ${i.width}x${i.height}`)};#R=({sourceWin:e,sourceOrigin:t,requestId:i,body:s})=>{const{objectId:o,callerChain:r}=s,c=this.#l(e);if(!c)return this.#e.warn("Rejected method invocation request from unknown guest window"),!1;this.#e.debug(`Function ${o}.${String(s.functionName)}() called from guest "${c.id}" (requestId = ${i})`);const l=this.getScriptingObject(o,{guest:c});if(!l)return this.#e.warn(`Invocation of unknown or unauthorized object ${o} from guest ${c.id}`),this.#t.raiseException({targetWin:e,targetOrigin:t,requestId:i,ex:`The requested object (${o}) is not available`}),!1;const a=c.getInfo();let d;if(this.#a.enabled){const u=`ScriptingObject.API.${o}.${s.functionName}`;d=this.#a.start(u,{appId:a.guestId,appUrl:a.guestUrl})}return this.#I({guest:c,obj:l,functionName:s.functionName,functionParams:s.functionParams,callerChain:r}).then(u=>{this.#t.respond({targetWin:e,targetOrigin:t,requestId:i,response:this.#v(u,c)}),this.#e.debug({message:"Value returned for Scripting Object method call",requestId:i,scriptingObject:o,scriptingMethod:s.functionName,...a})}).catch(u=>{this.#t.raiseException({targetWin:e,targetOrigin:c.origin,requestId:i,ex:u}),this.#e.error({message:"Exception thrown for Scripting Object method call",requestId:i,scriptingObject:o,scriptingMethod:s.functionName,...a})}).finally(()=>{d&&this.#a.end(d,{appId:a.guestId,appUrl:a.guestUrl})}),!0};#_=()=>{this.#t.listen({messageType:g.GuestResize,callback:this.#U})};#M=()=>{this.#t.listen({messageType:g.GuestReady,callback:this.#$}),this.#t.listen({messageType:g.GuestReadyComplete,callback:this.#y}),this.#t.listen({messageType:g.GuestClose,callback:this.#k}),this.#t.listen({messageType:g.ListObjects,callback:this.#T}),this.#t.listen({messageType:g.ObjectGet,callback:this.#P}),this.#t.listen({messageType:g.ObjectInvoke,callback:this.#R}),this.#t.listen({messageType:g.GuestEventSubscribe,callback:this.#A}),this.#t.listen({messageType:g.GuestEventUnsubscribe,callback:this.#G})};#L=e=>e instanceof G||typeof e?.subscribe=="function";#j=e=>{const t=new M({...e,remoting:this.#t,perfTracker:this.#m});return t.init(),this.#s.set(e.guestId,t),this.#o.set(t.window,t),this.#r.set(t.url,t),t};#w=e=>{if(typeof e=="string")return this.#s.get(e);const t=this.#o.get(e);return t||Array.from(this.#s.values()).find(i=>i.domElement===e)};#q=()=>{this.#d||(this.#d=setInterval(()=>{const e=[];this.#s.forEach(t=>{t.openMode===w.Popup&&t.window.closed&&e.push(t)}),e.forEach(t=>{const{id:i}=t;this.unloadGuest(i),this.#u.get(i)?.forEach(o=>{Promise.resolve(o({id:i})).catch(()=>{})})})},1e3))};#C=()=>{if(!this.#d)return;Array.from(this.#s.values()).some(t=>t.openMode===w.Popup)||(clearInterval(this.#d),this.#d=null)};#D=e=>{const{url:t,title:i,popupWindowFeatures:s={},searchParams:o,guestId:r,onLoad:c,onError:l}=e,{width:a=800,height:d=600,top:u=100,left:b=100}=s;let m=this.#O(t);if(m)m.window.closed||(_(t)?window.open("",i):m.window.focus());else{const E=Object.entries({width:a,height:d,top:u,left:b}).filter(([,h])=>h).map(([h,f])=>`${h}=${f}`).join(","),v=window.open(t,i,`popup, ${E}`);if(v)setTimeout(()=>{try{c?.(r)}catch(h){this.#e.debug(`Error occurred in onLoad for guest with id '${r}': ${h.message}`)}},0);else throw setTimeout(()=>{try{l?.(r)}catch(h){this.#e.debug(`Error occurred in onError for guest with id '${r}': ${h.message}`)}},0),new Error("Failed to open guest application in popup window");_(t)||(v.opener=null),m=this.#j({guestId:r,window:v,title:i,url:t,searchParams:o,openMode:w.Popup}),this.#q()}return m};#x=e=>{const{url:t,title:i,targetElement:s,searchParams:o,guestId:r,onLoad:c,onError:l,options:a={}}=e,d=s.ownerDocument??document,{fitToContent:u=!1,disableSandbox:b=!1,sandboxValues:m=[],customSandboxValues:E=[],style:v="",permissionPolicy:h=""}=a;if(!i)throw new Error("title is required");u&&this.#_();const f=d.createElement("iframe");f.setAttribute("id",r);const y=()=>{setTimeout(()=>{try{c?.(r)}catch(A){this.#e.debug(`Error occurred in onLoad for guest with id '${r}': ${A.message}`)}},0),this.#e.debug(`frame loaded for guest with id '${r}'`),f.removeEventListener("load",y)},T=()=>{setTimeout(()=>{try{l?.(r)}catch(A){this.#e.debug(`Error occurred in onError for guest with id '${r}': ${A.message}`)}},0),this.#e.error(`frame load failed for guest with id '${r}'`),f.removeEventListener("error",T)};f.addEventListener("load",y),f.addEventListener("error",T),f.setAttribute("style",`min-width: 100%; height: 100%; border: 0px; ${v}`),b||f.setAttribute("sandbox",E.length>0?E.join(" "):[...F,...m].join(" ")),f.setAttribute("title",i),f.setAttribute("src",t),h&&f.setAttribute("allow",h),s.appendChild(f);const B=d.getElementById(r);return this.#j({guestId:r,domElement:B,window:B.contentWindow,title:i,url:t,searchParams:o,openMode:w.Embed})};#W=(e,t)=>{let i="";return Object.keys(t).forEach(s=>{i+=`${(i.length?"&":"")+encodeURIComponent(s)}=${encodeURIComponent(t[s])}`}),e+(i?(e.indexOf("?")>=0?"&":"?")+i:"")};addScriptingObject=(e,t)=>{if(z(e)){const i=this.cloneScriptingObject(e);this.#n.addScriptingObject(i,t)}else this.#n.addScriptingObject(e,t)};cloneScriptingObject=e=>{if(!e)throw new Error("proxy is required");const t=new U(e.id,e.objectType);let i=[];return Object.keys(e).forEach(s=>{const o=e[s];if(this.#L(o)){const r=new O({name:o.name||s,objectId:t.id});if(Object.defineProperty(t,s,{value:r,enumerable:!0}),o instanceof G){const c=({eventParams:a,eventOptions:d})=>this.dispatchEvent({event:r,eventParams:a,eventOptions:d}),l=o.subscribe(c);i.push(()=>{o.unsubscribe(l)})}else{const c=o.subscribe?.((l,a,d)=>this.dispatchEvent({event:r,eventParams:a,eventOptions:d}));i.push(()=>{o.unsubscribe?.(c)})}}else if(R(o)&&(Object.defineProperty(t,s,{value:async(...r)=>{const c=t[s]?.callContext;if(c?.guest){const a=c.callChain??[],d=c.guest.id,u=this.#g.get(d),b={id:d};u&&(b.metadata=u),Object.defineProperty(o,"callContext",{value:{callChain:[...a,b]},configurable:!0,enumerable:!0,writable:!0})}const l=await o(...r);return z(l)?this.cloneScriptingObject(l):l},enumerable:!0}),s==="dispose")){const r=t.dispose;Object.defineProperty(t,s,{value:()=>(t._dispose(),r.apply(t)),enumerable:!0})}}),t._dispose=()=>{i.forEach(s=>{s?.()}),i=[]},t};close=()=>{this.#d&&(clearInterval(this.#d),this.#d=null),this.#b(),this.#g.clear(),this.#t.close(),window.removeEventListener("beforeunload",this.#b),this.#e.debug(`host is closed. hostId: ${this.hostId}, correlationId: ${this.#i}`)};dispatchEvent=async e=>{const{event:{id:t,name:i},eventParams:s,eventOptions:o={}}=e,{eventHandler:r=null,timeout:c=null,window:l=null,guestId:a}=o,d=t.split(".")[0],u=a||l,b=u?this.#w(u):null,m=b?this.getScriptingObject(d,{guest:b}):this.getScriptingObject(d);if(!m)return this.#e.warn(`Attempt to dispatch event ${i} on unknown object ${d}`),Promise.resolve([]);const E={object:m._toJSON(),eventName:i,eventParams:s,eventHandler:r,eventOptions:{allowsFeedback:!1}};c&&!Number.isNaN(c)&&(E.eventOptions={allowsFeedback:!0,timeout:Number(c)});const v=[];let h;const f=y=>{const T=y.getInfo();c&&y?.capabilities?.eventFeedback?(v.push(y.dispatchEvent(E,c)),!h&&this.#a.enabled&&(h=this.#a.start(`ScriptingObject.Event.${m.id}.${i}`,{appId:this.hostId,appUrl:window.location.href})),this.#e.debug({message:"Event dispatched and awaiting feedback",scriptingEventId:t,...T})):(y.send({messageType:g.ObjectEvent,messageBody:E}),this.#e.debug({message:"Event dispatched",scriptingEventId:t,...T}))};return b?f(b):this.#s.forEach(f),Promise.all(v).then(y=>(this.#e.debug({message:"Event feedback received",scriptingEventId:t}),Q(y))).catch(y=>{throw this.#e.error({message:"Error processing event",eventId:t,exception:y}),y}).finally(()=>{h&&this.#a.end(h,{appId:this.hostId,appUrl:window.location.href})})};getGuests=()=>Array.from(this.#s.values());getScriptingObject=(e,t)=>this.#n.getObject(e,t?.guest);loadGuest=e=>{const{id:t,url:i,targetElement:s,title:o,searchParams:r={},onLoad:c,onError:l,options:a={},metadata:d}=e;if(!t)throw new Error("id for guest application is required");let u=t;if(this.#s.has(u)){let h=1;for(;this.#s.has(`${t}-${h}`);)h+=1;u=`${t}-${h}`}const{openMode:b=w.Embed,popupWindowFeatures:m={}}=a,E=this.#W(i,r);let v=null;if(this.#a.start("SSF.Guest.Load",{appId:u,appUrl:E}),b===w.Popup)v=this.#D({guestId:u,url:E,title:o,searchParams:r,popupWindowFeatures:m,onLoad:c,onError:l});else if(b===w.Embed)v=this.#x({guestId:u,url:E,title:o,targetElement:s,searchParams:r,onLoad:c,onError:l,options:a});else throw new Error(`Invalid openMode: ${b}`);return d&&this.#g.set(u,d),this.#e.audit({message:"Guest loaded",...v.getInfo()}),v};loadGuests=e=>{const{id:t,url:i,targetElement:s,title:o,searchParamsList:r=[],options:c={}}=e;r.forEach((l,a)=>{this.loadGuest({id:`${t}-${a}`,url:i,title:o,targetElement:s,searchParams:l,options:c})},this)};removeAllScriptingObjects=e=>{this.#n.removeAllScriptingObjects(e)};removeScriptingObject=(e,t)=>{this.#n.removeScriptingObject(e,t)};setLogLevel=e=>{this.#e.setLogLevel(e),this.#s.forEach(this.#E),this.#e.debug("Dispatched config events to all guests")};unloadGuest=e=>{const t=this.#w(e);t&&(t.dispose(),this.#o.delete(t.window),this.#r.delete(t.url),this.#g.delete(t.id),this.#s.delete(t.id),this.#e.audit({message:"Guest is removed from host",...t.getInfo()}),this.#C())};onGuestClose=e=>{const{id:t,guestCloseCallback:i}=e,s=this.#u.get(t)||[];s.push(i),this.#u.set(t,s)}}return I})());
2
+
3
+ //# sourceMappingURL=emuiSsfHost.5bb7139d7e86c74f0b6d.js.map