@elliemae/ssf-host 2.23.6 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/cjs/callchain-host.html +262 -0
  2. package/dist/cjs/callchain-intermediate.html +92 -0
  3. package/dist/cjs/e2e-host.html +603 -0
  4. package/dist/cjs/e2e-index.html +234 -0
  5. package/dist/cjs/guest.js +15 -16
  6. package/dist/cjs/host.js +91 -52
  7. package/dist/cjs/index.html +304 -151
  8. package/dist/cjs/performanceTracker.js +111 -0
  9. package/dist/cjs/popup-focus-host.html +318 -0
  10. package/dist/cjs/utils.js +14 -1
  11. package/dist/cjs/v2-host-v1-guest.html +3 -0
  12. package/dist/esm/callchain-host.html +262 -0
  13. package/dist/esm/callchain-intermediate.html +92 -0
  14. package/dist/esm/e2e-host.html +603 -0
  15. package/dist/esm/e2e-index.html +234 -0
  16. package/dist/esm/guest.js +15 -16
  17. package/dist/esm/host.js +92 -53
  18. package/dist/esm/index.html +304 -151
  19. package/dist/esm/performanceTracker.js +91 -0
  20. package/dist/esm/popup-focus-host.html +318 -0
  21. package/dist/esm/utils.js +14 -1
  22. package/dist/esm/v2-host-v1-guest.html +3 -0
  23. package/dist/public/callchain-host.html +34 -0
  24. package/dist/public/callchain-host.js +3 -0
  25. package/dist/public/callchain-host.js.br +0 -0
  26. package/dist/public/callchain-host.js.gz +0 -0
  27. package/dist/public/callchain-host.js.map +1 -0
  28. package/dist/public/callchain-intermediate.html +1 -0
  29. package/dist/public/e2e-host.html +5 -0
  30. package/dist/public/e2e-host.js +8 -0
  31. package/dist/public/e2e-host.js.br +0 -0
  32. package/dist/public/e2e-host.js.gz +0 -0
  33. package/dist/public/e2e-host.js.map +1 -0
  34. package/dist/public/e2e-index.html +1 -0
  35. package/dist/public/index.html +1 -1
  36. package/dist/public/init.js +1 -1
  37. package/dist/public/init.js.br +0 -0
  38. package/dist/public/init.js.gz +0 -0
  39. package/dist/public/init.js.map +1 -1
  40. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js +3 -0
  41. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.br +0 -0
  42. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.gz +0 -0
  43. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.map +1 -0
  44. package/dist/public/loan-object.js +1 -1
  45. package/dist/public/loan-object.js.br +0 -0
  46. package/dist/public/loan-object.js.gz +0 -0
  47. package/dist/public/loan-object.js.map +1 -1
  48. package/dist/public/popup-focus-host.html +1 -0
  49. package/dist/public/popup-focus-host.js +6 -0
  50. package/dist/public/popup-focus-host.js.br +0 -0
  51. package/dist/public/popup-focus-host.js.gz +0 -0
  52. package/dist/public/popup-focus-host.js.map +1 -0
  53. package/dist/public/utils.js +1 -1
  54. package/dist/public/utils.js.br +0 -0
  55. package/dist/public/utils.js.gz +0 -0
  56. package/dist/public/utils.js.map +1 -1
  57. package/dist/public/v1-guest-v2-host.html +1 -1
  58. package/dist/public/v2-host-v1-guest.html +1 -1
  59. package/dist/types/lib/guest.d.ts +4 -3
  60. package/dist/types/lib/ihost.d.ts +32 -0
  61. package/dist/types/lib/performanceTracker.d.ts +46 -0
  62. package/dist/types/lib/tests/timingDedup.test.d.ts +1 -0
  63. package/dist/types/lib/utils.d.ts +1 -0
  64. package/dist/types/tsconfig.tsbuildinfo +1 -1
  65. package/dist/umd/callchain-host.html +34 -0
  66. package/dist/umd/callchain-host.js +3 -0
  67. package/dist/umd/callchain-host.js.br +0 -0
  68. package/dist/umd/callchain-host.js.gz +0 -0
  69. package/dist/umd/callchain-host.js.map +1 -0
  70. package/dist/umd/callchain-intermediate.html +1 -0
  71. package/dist/umd/e2e-host.html +5 -0
  72. package/dist/umd/e2e-host.js +8 -0
  73. package/dist/umd/e2e-host.js.br +0 -0
  74. package/dist/umd/e2e-host.js.gz +0 -0
  75. package/dist/umd/e2e-host.js.map +1 -0
  76. package/dist/umd/e2e-index.html +1 -0
  77. package/dist/umd/index.html +1 -1
  78. package/dist/umd/index.js +1 -1
  79. package/dist/umd/index.js.br +0 -0
  80. package/dist/umd/index.js.gz +0 -0
  81. package/dist/umd/index.js.map +1 -1
  82. package/dist/umd/init.js +1 -1
  83. package/dist/umd/init.js.br +0 -0
  84. package/dist/umd/init.js.gz +0 -0
  85. package/dist/umd/init.js.map +1 -1
  86. package/dist/umd/loan-object.js +1 -1
  87. package/dist/umd/loan-object.js.br +0 -0
  88. package/dist/umd/loan-object.js.gz +0 -0
  89. package/dist/umd/loan-object.js.map +1 -1
  90. package/dist/umd/popup-focus-host.html +1 -0
  91. package/dist/umd/popup-focus-host.js +6 -0
  92. package/dist/umd/popup-focus-host.js.br +0 -0
  93. package/dist/umd/popup-focus-host.js.gz +0 -0
  94. package/dist/umd/popup-focus-host.js.map +1 -0
  95. package/dist/umd/utils.js +1 -1
  96. package/dist/umd/utils.js.br +0 -0
  97. package/dist/umd/utils.js.gz +0 -0
  98. package/dist/umd/utils.js.map +1 -1
  99. package/dist/umd/v2-host-v1-guest.html +1 -1
  100. package/package.json +6 -6
  101. package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js +0 -3
  102. package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js.br +0 -0
  103. package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js.gz +0 -0
  104. package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js.map +0 -1
@@ -0,0 +1,234 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>SSF E2E Test Suite</title>
7
+ <script src="https://cdn.tailwindcss.com?plugins=forms"></script>
8
+ </head>
9
+ <body class="bg-gray-50">
10
+ <header class="bg-gray-900 text-white px-6 py-4">
11
+ <h1 class="text-xl font-bold">SSF End-to-End Test Suite</h1>
12
+ <p class="text-sm text-gray-400 mt-1">
13
+ Comprehensive blackbox tests for SSF Host &amp; Guest. Each page
14
+ includes test steps, expected values, and a verification checklist.
15
+ </p>
16
+ </header>
17
+
18
+ <main class="mx-auto max-w-5xl px-6 py-6">
19
+ <!-- Setup -->
20
+ <section
21
+ class="mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4"
22
+ >
23
+ <h2 class="text-sm font-semibold text-yellow-800 mb-2">
24
+ Setup Instructions
25
+ </h2>
26
+ <ol class="text-xs text-yellow-700 list-decimal ml-4 space-y-1">
27
+ <li>
28
+ Install dependencies:
29
+ <code class="bg-yellow-100 px-1 rounded">pnpm install</code>
30
+ </li>
31
+ <li>
32
+ Start SSF Host dev server:
33
+ <code class="bg-yellow-100 px-1 rounded"
34
+ >pnpm nx run ssf-host:start-server</code
35
+ >
36
+ (port 4000)
37
+ </li>
38
+ <li>
39
+ Start SSF Guest dev server:
40
+ <code class="bg-yellow-100 px-1 rounded"
41
+ >pnpm nx run ssf-guest:start</code
42
+ >
43
+ (port 4001)
44
+ </li>
45
+ <li>
46
+ Open
47
+ <code class="bg-yellow-100 px-1 rounded"
48
+ >http://localhost:4000/e2e-index.html</code
49
+ >
50
+ in the browser
51
+ </li>
52
+ <li>
53
+ Each test page has
54
+ <code class="bg-yellow-100 px-1 rounded">data-testid</code>
55
+ attributes for Playwright/Cypress selectors
56
+ </li>
57
+ </ol>
58
+ </section>
59
+
60
+ <!-- 1. Core Host-Guest Communication -->
61
+ <section class="mb-6">
62
+ <h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">
63
+ 1. Core Host-Guest Communication
64
+ </h2>
65
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
66
+ <a
67
+ href="./e2e-host.html"
68
+ data-testid="link-e2e-host"
69
+ class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"
70
+ >
71
+ <h3 class="font-medium text-indigo-700">Comprehensive E2E Host</h3>
72
+ <p class="text-xs text-gray-500 mt-1">
73
+ All host-guest scenarios: guest loading, scripting objects,
74
+ events, lifecycle, metadata, sandbox, guest-scoped objects.
75
+ </p>
76
+ <p class="text-xs text-gray-400 mt-2">
77
+ <strong>What to verify:</strong> Guests load in iframes/popups.
78
+ Scripting objects can be added/removed/invoked. Events reach
79
+ subscribed guests. Unload/close cleans up properly. Sandbox attrs
80
+ applied.
81
+ </p>
82
+ <div class="mt-2 text-xs text-gray-400">
83
+ <strong>Test Cases:</strong>
84
+ TC-LOAD-01..05 &bull; TC-SO-01..08 &bull; TC-EVT-01..04 &bull;
85
+ TC-LIFE-01..03 &bull; TC-META-01 &bull; TC-SB-01..03
86
+ </div>
87
+ </a>
88
+ <a
89
+ href="./index.html"
90
+ data-testid="link-main-demo"
91
+ class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"
92
+ >
93
+ <h3 class="font-medium text-indigo-700">Main Demo App</h3>
94
+ <p class="text-xs text-gray-500 mt-1">
95
+ Original Loan Application demo with embedded and popup guests,
96
+ events, scripting objects.
97
+ </p>
98
+ <p class="text-xs text-gray-400 mt-2">
99
+ <strong>What to verify:</strong> Loan form data flows to guests.
100
+ Events (onLoanAmountChanged, etc.) update guest UIs. Popup guests
101
+ open and interact with the Loan object.
102
+ </p>
103
+ </a>
104
+ </div>
105
+ </section>
106
+
107
+ <!-- 2. CallChain & Metadata -->
108
+ <section class="mb-6">
109
+ <h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">
110
+ 2. Call Chain &amp; Metadata Propagation
111
+ </h2>
112
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
113
+ <a
114
+ href="./callchain-host.html"
115
+ data-testid="link-callchain"
116
+ class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"
117
+ >
118
+ <h3 class="font-medium text-indigo-700">
119
+ Nested CallChain Test (A → B → C)
120
+ </h3>
121
+ <p class="text-xs text-gray-500 mt-1">
122
+ Root Host (A) → Intermediate Guest+Host (B) → Grandchild Guest
123
+ (C). Verifies callContext.callChain and metadata propagation.
124
+ </p>
125
+ <p class="text-xs text-gray-400 mt-2">
126
+ <strong>What to verify:</strong> Click buttons in Grandchild C.
127
+ Host A should show <code>callContext.guest</code> = B and
128
+ <code>callChain</code> containing C and B with their metadata.
129
+ Return values should flow back to C.
130
+ </p>
131
+ <div class="mt-2 text-xs text-gray-400">
132
+ <strong>Test Cases:</strong> TC-CC-01..07
133
+ </div>
134
+ </a>
135
+ </div>
136
+ </section>
137
+
138
+ <!-- 3. Popup Behavior -->
139
+ <section class="mb-6">
140
+ <h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">
141
+ 3. Popup Guest Behavior
142
+ </h2>
143
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
144
+ <a
145
+ href="./popup-focus-host.html"
146
+ data-testid="link-popup-focus"
147
+ class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"
148
+ >
149
+ <h3 class="font-medium text-indigo-700">
150
+ Popup Focus &amp; Lifecycle
151
+ </h3>
152
+ <p class="text-xs text-gray-500 mt-1">
153
+ Open, close, and refocus popup guests. Tests trusted vs untrusted
154
+ domain behavior.
155
+ </p>
156
+ <p class="text-xs text-gray-400 mt-2">
157
+ <strong>What to verify:</strong> Trusted popups (localhost):
158
+ re-open brings existing popup to front. Untrusted popups: opener
159
+ is nulled, focus falls back to WindowProxy. Closed popup detection
160
+ updates guest status.
161
+ </p>
162
+ <div class="mt-2 text-xs text-gray-400">
163
+ <strong>Test Cases:</strong> TC-POP-01..11
164
+ </div>
165
+ </a>
166
+ </div>
167
+ </section>
168
+
169
+ <!-- 4. V1/V2 Interoperability -->
170
+ <section class="mb-6">
171
+ <h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">
172
+ 4. V1 / V2 Interoperability
173
+ </h2>
174
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
175
+ <a
176
+ href="./v2-host-v1-guest.html"
177
+ data-testid="link-v2-host-v1-guest"
178
+ class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"
179
+ >
180
+ <h3 class="font-medium text-indigo-700">V2 Host → V1 Guest</h3>
181
+ <p class="text-xs text-gray-500 mt-1">
182
+ V2 host loads V1 guest. Loan object, save flow, event feedback.
183
+ </p>
184
+ <p class="text-xs text-gray-400 mt-2">
185
+ <strong>What to verify:</strong> V1 guest connects, gets objects,
186
+ subscribes to events, and returns feedback correctly.
187
+ </p>
188
+ <div class="mt-2 text-xs text-gray-400">
189
+ <strong>Test Cases:</strong> TC-INTEROP-01..04
190
+ </div>
191
+ </a>
192
+ <a
193
+ href="./v2-host-v1-guest.html?nestV1GuestV2Host=true"
194
+ data-testid="link-v2-v1-v2-chain"
195
+ class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"
196
+ >
197
+ <h3 class="font-medium text-indigo-700">
198
+ V2 Host → V1 Guest → V2 Host → V2 Guest
199
+ </h3>
200
+ <p class="text-xs text-gray-500 mt-1">
201
+ Full mixed-version chain. V1 guest acts as intermediate host for a
202
+ V2 grandchild.
203
+ </p>
204
+ <p class="text-xs text-gray-400 mt-2">
205
+ <strong>What to verify:</strong> Scripting objects are cloned
206
+ across V1/V2 boundary. Events flow through all layers.
207
+ </p>
208
+ <div class="mt-2 text-xs text-gray-400">
209
+ <strong>Test Cases:</strong> TC-INTEROP-05..07
210
+ </div>
211
+ </a>
212
+ </div>
213
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3">
214
+ <a
215
+ href="./v1-host.html"
216
+ data-testid="link-v1-host"
217
+ class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"
218
+ >
219
+ <h3 class="font-medium text-indigo-700">
220
+ V1 Host → V1 Guest → V2 Host → V2 Guest
221
+ </h3>
222
+ <p class="text-xs text-gray-500 mt-1">
223
+ Starting from V1 host. Exercises the full V1-to-V2 upgrade path.
224
+ </p>
225
+ <p class="text-xs text-gray-400 mt-2">
226
+ <strong>What to verify:</strong> V1 host initializes. V1 guest
227
+ connects. V2 host/guest chain works through V1 intermediate.
228
+ </p>
229
+ </a>
230
+ </div>
231
+ </section>
232
+ </main>
233
+ </body>
234
+ </html>
package/dist/esm/guest.js CHANGED
@@ -51,9 +51,9 @@ class Guest {
51
51
  */
52
52
  #remoting;
53
53
  /**
54
- * analytics object
54
+ * performance tracker for timing measurements
55
55
  */
56
- #analyticsObj;
56
+ #perfTracker;
57
57
  /**
58
58
  * Create object representing guest application
59
59
  * @param {GuestOption} option - options for creating a guest application
@@ -68,7 +68,7 @@ class Guest {
68
68
  searchParams = {},
69
69
  openMode = OpenMode.Embed,
70
70
  remoting,
71
- analyticsObj
71
+ perfTracker
72
72
  } = option;
73
73
  this.id = guestId;
74
74
  this.title = title;
@@ -79,7 +79,7 @@ class Guest {
79
79
  this.window = window;
80
80
  this.openMode = openMode;
81
81
  this.capabilities = {};
82
- this.#analyticsObj = analyticsObj;
82
+ this.#perfTracker = perfTracker;
83
83
  this.#remoting = remoting;
84
84
  }
85
85
  /**
@@ -165,16 +165,17 @@ class Guest {
165
165
  * invokes event callback on the guest application
166
166
  * @param {EventObject} event - event object
167
167
  * @param {number} timeout - timeout in milliseconds
168
+ * @returns {Promise} resolves when the guest handles the event
168
169
  */
169
170
  dispatchEvent = (event, timeout) => {
170
- this.#analyticsObj.startTiming(
171
- `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`,
172
- {
171
+ let timingToken;
172
+ if (this.#perfTracker.enabled) {
173
+ const name = `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`;
174
+ timingToken = this.#perfTracker.start(name, {
173
175
  appId: this.id,
174
176
  appUrl: this.url
175
- }
176
- ).catch(() => {
177
- });
177
+ });
178
+ }
178
179
  return this.#remoting.invoke({
179
180
  targetWin: this.window,
180
181
  targetOrigin: this.origin,
@@ -182,14 +183,12 @@ class Guest {
182
183
  messageBody: event,
183
184
  responseTimeoutMs: timeout
184
185
  }).finally(() => {
185
- this.#analyticsObj.endTiming(
186
- `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`,
187
- {
186
+ if (timingToken) {
187
+ this.#perfTracker.end(timingToken, {
188
188
  appId: this.id,
189
189
  appUrl: this.url
190
- }
191
- ).catch(() => {
192
- });
190
+ });
191
+ }
193
192
  });
194
193
  };
195
194
  /**
package/dist/esm/host.js CHANGED
@@ -13,7 +13,8 @@ import {
13
13
  OpenMode
14
14
  } from "./types.js";
15
15
  import { Guest } from "./guest.js";
16
- import { flatten, isFunction } from "./utils.js";
16
+ import { flatten, isFunction, isTrustedDomain } from "./utils.js";
17
+ import { PerformanceTracker } from "./performanceTracker.js";
17
18
  const SANDBOX_DEFAULT = [
18
19
  IFrameSandboxValues.AllowScripts,
19
20
  IFrameSandboxValues.AllowPopups,
@@ -80,6 +81,18 @@ class SSFHost {
80
81
  * callback for guest event unsubscription
81
82
  */
82
83
  #onGuestEventUnsubscribe = null;
84
+ /**
85
+ * host-supplied metadata keyed by guest id, forwarded in callChain
86
+ */
87
+ #guestMetadata = /* @__PURE__ */ new Map();
88
+ /**
89
+ * Performance tracker for host-side timing (with dedup).
90
+ */
91
+ #perfTracker;
92
+ /**
93
+ * Performance tracker shared by all guests (no dedup).
94
+ */
95
+ #guestPerfTracker;
83
96
  /**
84
97
  * Create a new host
85
98
  * @param hostId unique identifier for the host
@@ -91,6 +104,20 @@ class SSFHost {
91
104
  if (!option?.analyticsObj) throw new Error("Analytics object is required");
92
105
  this.#logger = option.logger;
93
106
  this.#analyticsObj = option.analyticsObj;
107
+ const perfOpts = {
108
+ analyticsObj: option.analyticsObj,
109
+ enabled: option?.measurePerformance,
110
+ samplingRatio: option?.performanceSamplingRatio,
111
+ onError: (msg) => this.#logger.debug(msg)
112
+ };
113
+ this.#perfTracker = new PerformanceTracker({
114
+ ...perfOpts,
115
+ dedupWindowMs: option?.performanceDedupWindowMs
116
+ });
117
+ this.#guestPerfTracker = new PerformanceTracker({
118
+ ...perfOpts,
119
+ dedupWindowMs: 0
120
+ });
94
121
  this.#correlationId = uuidv4();
95
122
  this.#remoting = new Remoting(this.#logger, this.#correlationId);
96
123
  if (option?.readyStateCallback && typeof option?.readyStateCallback !== "function")
@@ -114,18 +141,6 @@ class SSFHost {
114
141
  );
115
142
  });
116
143
  };
117
- #startTiming = (name, options) => {
118
- this.#analyticsObj.startTiming(name, options).catch((e) => {
119
- this.#logger.debug(
120
- `Analytics startTiming failed: ${e.message}`
121
- );
122
- });
123
- };
124
- #endTiming = (start, options) => {
125
- this.#analyticsObj.endTiming(start, options).catch((e) => {
126
- this.#logger.debug(`Analytics endTiming failed: ${e.message}`);
127
- });
128
- };
129
144
  #closeAllPopupGuests = () => {
130
145
  const popupIds = Array.from(this.#guests.values()).filter((guest) => guest.openMode === OpenMode.Popup).map((guest) => guest.id);
131
146
  popupIds.forEach((id) => this.unloadGuest(id));
@@ -191,7 +206,8 @@ class SSFHost {
191
206
  guest,
192
207
  obj,
193
208
  functionName,
194
- functionParams
209
+ functionParams,
210
+ callerChain
195
211
  }) => {
196
212
  const func = obj[functionName];
197
213
  if (!isFunction(func)) {
@@ -209,7 +225,10 @@ class SSFHost {
209
225
  );
210
226
  return new Promise((resolve) => {
211
227
  Object.defineProperty(func, "callContext", {
212
- value: { guest },
228
+ value: {
229
+ guest,
230
+ ...callerChain?.length ? { callChain: callerChain } : {}
231
+ },
213
232
  configurable: true,
214
233
  enumerable: true,
215
234
  writable: true
@@ -244,7 +263,7 @@ class SSFHost {
244
263
  if (!guest.ready) {
245
264
  guest.ready = true;
246
265
  const guestInfo = guest.getInfo();
247
- this.#endTiming("SSF.Guest.Load", {
266
+ this.#perfTracker.end("SSF.Guest.Load", {
248
267
  appId: guestInfo.guestId,
249
268
  appUrl: guestInfo.guestUrl
250
269
  });
@@ -492,7 +511,7 @@ class SSFHost {
492
511
  requestId,
493
512
  body
494
513
  }) => {
495
- const { objectId } = body;
514
+ const { objectId, callerChain } = body;
496
515
  const guest = this.#getGuestForWindow(sourceWin);
497
516
  if (!guest) {
498
517
  this.#logger.warn(
@@ -517,15 +536,20 @@ class SSFHost {
517
536
  return false;
518
537
  }
519
538
  const guestInfo = guest.getInfo();
520
- this.#startTiming(`ScriptingObject.API.${objectId}.${body.functionName}`, {
521
- appId: guestInfo.guestId,
522
- appUrl: guestInfo.guestUrl
523
- });
539
+ let timingToken;
540
+ if (this.#perfTracker.enabled) {
541
+ const name = `ScriptingObject.API.${objectId}.${body.functionName}`;
542
+ timingToken = this.#perfTracker.start(name, {
543
+ appId: guestInfo.guestId,
544
+ appUrl: guestInfo.guestUrl
545
+ });
546
+ }
524
547
  this.#invoke({
525
548
  guest,
526
549
  obj,
527
550
  functionName: body.functionName,
528
- functionParams: body.functionParams
551
+ functionParams: body.functionParams,
552
+ callerChain
529
553
  }).then((val) => {
530
554
  this.#remoting.respond({
531
555
  targetWin: sourceWin,
@@ -555,13 +579,12 @@ class SSFHost {
555
579
  ...guestInfo
556
580
  });
557
581
  }).finally(() => {
558
- this.#endTiming(
559
- `ScriptingObject.API.${objectId}.${body.functionName}`,
560
- {
582
+ if (timingToken) {
583
+ this.#perfTracker.end(timingToken, {
561
584
  appId: guestInfo.guestId,
562
585
  appUrl: guestInfo.guestUrl
563
- }
564
- );
586
+ });
587
+ }
565
588
  });
566
589
  return true;
567
590
  };
@@ -632,7 +655,7 @@ class SSFHost {
632
655
  const guest = new Guest({
633
656
  ...param,
634
657
  remoting: this.#remoting,
635
- analyticsObj: this.#analyticsObj
658
+ perfTracker: this.#guestPerfTracker
636
659
  });
637
660
  guest.init();
638
661
  this.#guests.set(param.guestId, guest);
@@ -704,10 +727,11 @@ class SSFHost {
704
727
  let guest = this.#getGuestForUrl(url);
705
728
  if (guest) {
706
729
  if (!guest.window.closed) {
707
- guest.send({
708
- messageType: MessageType.GuestFocus,
709
- messageBody: {}
710
- });
730
+ if (isTrustedDomain(url)) {
731
+ window.open("", title);
732
+ } else {
733
+ guest.window.focus();
734
+ }
711
735
  }
712
736
  } else {
713
737
  const windowFeatures = Object.entries({ width, height, top, left }).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join(",");
@@ -734,7 +758,9 @@ class SSFHost {
734
758
  }
735
759
  }, 0);
736
760
  }
737
- guestWindow.opener = null;
761
+ if (!isTrustedDomain(url)) {
762
+ guestWindow.opener = null;
763
+ }
738
764
  guest = this.#attachGuest({
739
765
  guestId,
740
766
  window: guestWindow,
@@ -901,6 +927,22 @@ class SSFHost {
901
927
  } else if (isFunction(propValue)) {
902
928
  Object.defineProperty(so, propName, {
903
929
  value: async (...args) => {
930
+ const callerCtx = so[propName]?.callContext;
931
+ if (callerCtx?.guest) {
932
+ const existingChain = callerCtx.callChain ?? [];
933
+ const guestId = callerCtx.guest.id;
934
+ const meta = this.#guestMetadata.get(guestId);
935
+ const callerEntry = { id: guestId };
936
+ if (meta) callerEntry.metadata = meta;
937
+ Object.defineProperty(propValue, "callContext", {
938
+ value: {
939
+ callChain: [...existingChain, callerEntry]
940
+ },
941
+ configurable: true,
942
+ enumerable: true,
943
+ writable: true
944
+ });
945
+ }
904
946
  const retVal = await propValue(...args);
905
947
  return isScriptingObjectProxy(retVal) ? this.cloneScriptingObject(retVal) : retVal;
906
948
  },
@@ -935,6 +977,7 @@ class SSFHost {
935
977
  this.#popupGuestMonitor = null;
936
978
  }
937
979
  this.#closeAllPopupGuests();
980
+ this.#guestMetadata.clear();
938
981
  this.#remoting.close();
939
982
  window.removeEventListener("beforeunload", this.#closeAllPopupGuests);
940
983
  this.#logger.debug(
@@ -985,20 +1028,16 @@ class SSFHost {
985
1028
  };
986
1029
  }
987
1030
  const guestPromises = [];
988
- let timingMetricStarted = false;
1031
+ let eventTimingToken;
989
1032
  const dispatchToGuest = (guest) => {
990
1033
  const guestInfo = guest.getInfo();
991
1034
  if (timeout && guest?.capabilities?.eventFeedback) {
992
1035
  guestPromises.push(guest.dispatchEvent(eventObj, timeout));
993
- if (!timingMetricStarted) {
994
- this.#startTiming(
1036
+ if (!eventTimingToken && this.#perfTracker.enabled) {
1037
+ eventTimingToken = this.#perfTracker.start(
995
1038
  `ScriptingObject.Event.${scriptingObject.id}.${name}`,
996
- {
997
- appId: this.hostId,
998
- appUrl: window.location.href
999
- }
1039
+ { appId: this.hostId, appUrl: window.location.href }
1000
1040
  );
1001
- timingMetricStarted = true;
1002
1041
  }
1003
1042
  this.#logger.debug({
1004
1043
  message: "Event dispatched and awaiting feedback",
@@ -1022,7 +1061,7 @@ class SSFHost {
1022
1061
  } else {
1023
1062
  this.#guests.forEach(dispatchToGuest);
1024
1063
  }
1025
- const retValue = await Promise.all(guestPromises).then((values) => {
1064
+ return Promise.all(guestPromises).then((values) => {
1026
1065
  this.#logger.debug({
1027
1066
  message: "Event feedback received",
1028
1067
  scriptingEventId: id
@@ -1036,16 +1075,13 @@ class SSFHost {
1036
1075
  });
1037
1076
  throw ex;
1038
1077
  }).finally(() => {
1039
- if (timingMetricStarted)
1040
- this.#endTiming(
1041
- `ScriptingObject.Event.${scriptingObject.id}.${name}`,
1042
- {
1043
- appId: this.hostId,
1044
- appUrl: window.location.href
1045
- }
1046
- );
1078
+ if (eventTimingToken) {
1079
+ this.#perfTracker.end(eventTimingToken, {
1080
+ appId: this.hostId,
1081
+ appUrl: window.location.href
1082
+ });
1083
+ }
1047
1084
  });
1048
- return retValue;
1049
1085
  };
1050
1086
  /**
1051
1087
  * get reference to all guest applications
@@ -1073,7 +1109,8 @@ class SSFHost {
1073
1109
  searchParams = {},
1074
1110
  onLoad,
1075
1111
  onError,
1076
- options = {}
1112
+ options = {},
1113
+ metadata
1077
1114
  } = param;
1078
1115
  if (!guestId) throw new Error("id for guest application is required");
1079
1116
  let instanceId = guestId;
@@ -1087,7 +1124,7 @@ class SSFHost {
1087
1124
  const { openMode = OpenMode.Embed, popupWindowFeatures = {} } = options;
1088
1125
  const srcUrl = this.#getGuestUrl(url, searchParams);
1089
1126
  let guest = null;
1090
- this.#startTiming("SSF.Guest.Load", {
1127
+ this.#perfTracker.start("SSF.Guest.Load", {
1091
1128
  appId: instanceId,
1092
1129
  appUrl: srcUrl
1093
1130
  });
@@ -1115,6 +1152,7 @@ class SSFHost {
1115
1152
  } else {
1116
1153
  throw new Error(`Invalid openMode: ${openMode}`);
1117
1154
  }
1155
+ if (metadata) this.#guestMetadata.set(instanceId, metadata);
1118
1156
  this.#logger.audit({
1119
1157
  message: "Guest loaded",
1120
1158
  ...guest.getInfo()
@@ -1180,6 +1218,7 @@ class SSFHost {
1180
1218
  guest.dispose();
1181
1219
  this.#guestsByWindow.delete(guest.window);
1182
1220
  this.#guestsByUrl.delete(guest.url);
1221
+ this.#guestMetadata.delete(guest.id);
1183
1222
  this.#guests.delete(guest.id);
1184
1223
  this.#logger.audit({
1185
1224
  message: `Guest is removed from host`,