@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/cjs/guest.js CHANGED
@@ -74,9 +74,9 @@ class Guest {
74
74
  */
75
75
  #remoting;
76
76
  /**
77
- * analytics object
77
+ * performance tracker for timing measurements
78
78
  */
79
- #analyticsObj;
79
+ #perfTracker;
80
80
  /**
81
81
  * Create object representing guest application
82
82
  * @param {GuestOption} option - options for creating a guest application
@@ -91,7 +91,7 @@ class Guest {
91
91
  searchParams = {},
92
92
  openMode = import_types.OpenMode.Embed,
93
93
  remoting,
94
- analyticsObj
94
+ perfTracker
95
95
  } = option;
96
96
  this.id = guestId;
97
97
  this.title = title;
@@ -102,7 +102,7 @@ class Guest {
102
102
  this.window = window;
103
103
  this.openMode = openMode;
104
104
  this.capabilities = {};
105
- this.#analyticsObj = analyticsObj;
105
+ this.#perfTracker = perfTracker;
106
106
  this.#remoting = remoting;
107
107
  }
108
108
  /**
@@ -188,16 +188,17 @@ class Guest {
188
188
  * invokes event callback on the guest application
189
189
  * @param {EventObject} event - event object
190
190
  * @param {number} timeout - timeout in milliseconds
191
+ * @returns {Promise} resolves when the guest handles the event
191
192
  */
192
193
  dispatchEvent = (event, timeout) => {
193
- this.#analyticsObj.startTiming(
194
- `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`,
195
- {
194
+ let timingToken;
195
+ if (this.#perfTracker.enabled) {
196
+ const name = `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`;
197
+ timingToken = this.#perfTracker.start(name, {
196
198
  appId: this.id,
197
199
  appUrl: this.url
198
- }
199
- ).catch(() => {
200
- });
200
+ });
201
+ }
201
202
  return this.#remoting.invoke({
202
203
  targetWin: this.window,
203
204
  targetOrigin: this.origin,
@@ -205,14 +206,12 @@ class Guest {
205
206
  messageBody: event,
206
207
  responseTimeoutMs: timeout
207
208
  }).finally(() => {
208
- this.#analyticsObj.endTiming(
209
- `ScriptingObject.Event.${event.object.objectId}.${event.eventName}`,
210
- {
209
+ if (timingToken) {
210
+ this.#perfTracker.end(timingToken, {
211
211
  appId: this.id,
212
212
  appUrl: this.url
213
- }
214
- ).catch(() => {
215
- });
213
+ });
214
+ }
216
215
  });
217
216
  };
218
217
  /**
package/dist/cjs/host.js CHANGED
@@ -27,6 +27,7 @@ var import_microfe_common = require("@elliemae/microfe-common");
27
27
  var import_types = require("./types.js");
28
28
  var import_guest = require("./guest.js");
29
29
  var import_utils = require("./utils.js");
30
+ var import_performanceTracker = require("./performanceTracker.js");
30
31
  const SANDBOX_DEFAULT = [
31
32
  import_types.IFrameSandboxValues.AllowScripts,
32
33
  import_types.IFrameSandboxValues.AllowPopups,
@@ -93,6 +94,18 @@ class SSFHost {
93
94
  * callback for guest event unsubscription
94
95
  */
95
96
  #onGuestEventUnsubscribe = null;
97
+ /**
98
+ * host-supplied metadata keyed by guest id, forwarded in callChain
99
+ */
100
+ #guestMetadata = /* @__PURE__ */ new Map();
101
+ /**
102
+ * Performance tracker for host-side timing (with dedup).
103
+ */
104
+ #perfTracker;
105
+ /**
106
+ * Performance tracker shared by all guests (no dedup).
107
+ */
108
+ #guestPerfTracker;
96
109
  /**
97
110
  * Create a new host
98
111
  * @param hostId unique identifier for the host
@@ -104,6 +117,20 @@ class SSFHost {
104
117
  if (!option?.analyticsObj) throw new Error("Analytics object is required");
105
118
  this.#logger = option.logger;
106
119
  this.#analyticsObj = option.analyticsObj;
120
+ const perfOpts = {
121
+ analyticsObj: option.analyticsObj,
122
+ enabled: option?.measurePerformance,
123
+ samplingRatio: option?.performanceSamplingRatio,
124
+ onError: (msg) => this.#logger.debug(msg)
125
+ };
126
+ this.#perfTracker = new import_performanceTracker.PerformanceTracker({
127
+ ...perfOpts,
128
+ dedupWindowMs: option?.performanceDedupWindowMs
129
+ });
130
+ this.#guestPerfTracker = new import_performanceTracker.PerformanceTracker({
131
+ ...perfOpts,
132
+ dedupWindowMs: 0
133
+ });
107
134
  this.#correlationId = (0, import_uuid.v4)();
108
135
  this.#remoting = new import_microfe_common.Remoting(this.#logger, this.#correlationId);
109
136
  if (option?.readyStateCallback && typeof option?.readyStateCallback !== "function")
@@ -127,18 +154,6 @@ class SSFHost {
127
154
  );
128
155
  });
129
156
  };
130
- #startTiming = (name, options) => {
131
- this.#analyticsObj.startTiming(name, options).catch((e) => {
132
- this.#logger.debug(
133
- `Analytics startTiming failed: ${e.message}`
134
- );
135
- });
136
- };
137
- #endTiming = (start, options) => {
138
- this.#analyticsObj.endTiming(start, options).catch((e) => {
139
- this.#logger.debug(`Analytics endTiming failed: ${e.message}`);
140
- });
141
- };
142
157
  #closeAllPopupGuests = () => {
143
158
  const popupIds = Array.from(this.#guests.values()).filter((guest) => guest.openMode === import_types.OpenMode.Popup).map((guest) => guest.id);
144
159
  popupIds.forEach((id) => this.unloadGuest(id));
@@ -204,7 +219,8 @@ class SSFHost {
204
219
  guest,
205
220
  obj,
206
221
  functionName,
207
- functionParams
222
+ functionParams,
223
+ callerChain
208
224
  }) => {
209
225
  const func = obj[functionName];
210
226
  if (!(0, import_utils.isFunction)(func)) {
@@ -222,7 +238,10 @@ class SSFHost {
222
238
  );
223
239
  return new Promise((resolve) => {
224
240
  Object.defineProperty(func, "callContext", {
225
- value: { guest },
241
+ value: {
242
+ guest,
243
+ ...callerChain?.length ? { callChain: callerChain } : {}
244
+ },
226
245
  configurable: true,
227
246
  enumerable: true,
228
247
  writable: true
@@ -257,7 +276,7 @@ class SSFHost {
257
276
  if (!guest.ready) {
258
277
  guest.ready = true;
259
278
  const guestInfo = guest.getInfo();
260
- this.#endTiming("SSF.Guest.Load", {
279
+ this.#perfTracker.end("SSF.Guest.Load", {
261
280
  appId: guestInfo.guestId,
262
281
  appUrl: guestInfo.guestUrl
263
282
  });
@@ -505,7 +524,7 @@ class SSFHost {
505
524
  requestId,
506
525
  body
507
526
  }) => {
508
- const { objectId } = body;
527
+ const { objectId, callerChain } = body;
509
528
  const guest = this.#getGuestForWindow(sourceWin);
510
529
  if (!guest) {
511
530
  this.#logger.warn(
@@ -530,15 +549,20 @@ class SSFHost {
530
549
  return false;
531
550
  }
532
551
  const guestInfo = guest.getInfo();
533
- this.#startTiming(`ScriptingObject.API.${objectId}.${body.functionName}`, {
534
- appId: guestInfo.guestId,
535
- appUrl: guestInfo.guestUrl
536
- });
552
+ let timingToken;
553
+ if (this.#perfTracker.enabled) {
554
+ const name = `ScriptingObject.API.${objectId}.${body.functionName}`;
555
+ timingToken = this.#perfTracker.start(name, {
556
+ appId: guestInfo.guestId,
557
+ appUrl: guestInfo.guestUrl
558
+ });
559
+ }
537
560
  this.#invoke({
538
561
  guest,
539
562
  obj,
540
563
  functionName: body.functionName,
541
- functionParams: body.functionParams
564
+ functionParams: body.functionParams,
565
+ callerChain
542
566
  }).then((val) => {
543
567
  this.#remoting.respond({
544
568
  targetWin: sourceWin,
@@ -568,13 +592,12 @@ class SSFHost {
568
592
  ...guestInfo
569
593
  });
570
594
  }).finally(() => {
571
- this.#endTiming(
572
- `ScriptingObject.API.${objectId}.${body.functionName}`,
573
- {
595
+ if (timingToken) {
596
+ this.#perfTracker.end(timingToken, {
574
597
  appId: guestInfo.guestId,
575
598
  appUrl: guestInfo.guestUrl
576
- }
577
- );
599
+ });
600
+ }
578
601
  });
579
602
  return true;
580
603
  };
@@ -645,7 +668,7 @@ class SSFHost {
645
668
  const guest = new import_guest.Guest({
646
669
  ...param,
647
670
  remoting: this.#remoting,
648
- analyticsObj: this.#analyticsObj
671
+ perfTracker: this.#guestPerfTracker
649
672
  });
650
673
  guest.init();
651
674
  this.#guests.set(param.guestId, guest);
@@ -717,10 +740,11 @@ class SSFHost {
717
740
  let guest = this.#getGuestForUrl(url);
718
741
  if (guest) {
719
742
  if (!guest.window.closed) {
720
- guest.send({
721
- messageType: import_microfe_common.MessageType.GuestFocus,
722
- messageBody: {}
723
- });
743
+ if ((0, import_utils.isTrustedDomain)(url)) {
744
+ window.open("", title);
745
+ } else {
746
+ guest.window.focus();
747
+ }
724
748
  }
725
749
  } else {
726
750
  const windowFeatures = Object.entries({ width, height, top, left }).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join(",");
@@ -747,7 +771,9 @@ class SSFHost {
747
771
  }
748
772
  }, 0);
749
773
  }
750
- guestWindow.opener = null;
774
+ if (!(0, import_utils.isTrustedDomain)(url)) {
775
+ guestWindow.opener = null;
776
+ }
751
777
  guest = this.#attachGuest({
752
778
  guestId,
753
779
  window: guestWindow,
@@ -914,6 +940,22 @@ class SSFHost {
914
940
  } else if ((0, import_utils.isFunction)(propValue)) {
915
941
  Object.defineProperty(so, propName, {
916
942
  value: async (...args) => {
943
+ const callerCtx = so[propName]?.callContext;
944
+ if (callerCtx?.guest) {
945
+ const existingChain = callerCtx.callChain ?? [];
946
+ const guestId = callerCtx.guest.id;
947
+ const meta = this.#guestMetadata.get(guestId);
948
+ const callerEntry = { id: guestId };
949
+ if (meta) callerEntry.metadata = meta;
950
+ Object.defineProperty(propValue, "callContext", {
951
+ value: {
952
+ callChain: [...existingChain, callerEntry]
953
+ },
954
+ configurable: true,
955
+ enumerable: true,
956
+ writable: true
957
+ });
958
+ }
917
959
  const retVal = await propValue(...args);
918
960
  return (0, import_microfe_common.isScriptingObjectProxy)(retVal) ? this.cloneScriptingObject(retVal) : retVal;
919
961
  },
@@ -948,6 +990,7 @@ class SSFHost {
948
990
  this.#popupGuestMonitor = null;
949
991
  }
950
992
  this.#closeAllPopupGuests();
993
+ this.#guestMetadata.clear();
951
994
  this.#remoting.close();
952
995
  window.removeEventListener("beforeunload", this.#closeAllPopupGuests);
953
996
  this.#logger.debug(
@@ -998,20 +1041,16 @@ class SSFHost {
998
1041
  };
999
1042
  }
1000
1043
  const guestPromises = [];
1001
- let timingMetricStarted = false;
1044
+ let eventTimingToken;
1002
1045
  const dispatchToGuest = (guest) => {
1003
1046
  const guestInfo = guest.getInfo();
1004
1047
  if (timeout && guest?.capabilities?.eventFeedback) {
1005
1048
  guestPromises.push(guest.dispatchEvent(eventObj, timeout));
1006
- if (!timingMetricStarted) {
1007
- this.#startTiming(
1049
+ if (!eventTimingToken && this.#perfTracker.enabled) {
1050
+ eventTimingToken = this.#perfTracker.start(
1008
1051
  `ScriptingObject.Event.${scriptingObject.id}.${name}`,
1009
- {
1010
- appId: this.hostId,
1011
- appUrl: window.location.href
1012
- }
1052
+ { appId: this.hostId, appUrl: window.location.href }
1013
1053
  );
1014
- timingMetricStarted = true;
1015
1054
  }
1016
1055
  this.#logger.debug({
1017
1056
  message: "Event dispatched and awaiting feedback",
@@ -1035,7 +1074,7 @@ class SSFHost {
1035
1074
  } else {
1036
1075
  this.#guests.forEach(dispatchToGuest);
1037
1076
  }
1038
- const retValue = await Promise.all(guestPromises).then((values) => {
1077
+ return Promise.all(guestPromises).then((values) => {
1039
1078
  this.#logger.debug({
1040
1079
  message: "Event feedback received",
1041
1080
  scriptingEventId: id
@@ -1049,16 +1088,13 @@ class SSFHost {
1049
1088
  });
1050
1089
  throw ex;
1051
1090
  }).finally(() => {
1052
- if (timingMetricStarted)
1053
- this.#endTiming(
1054
- `ScriptingObject.Event.${scriptingObject.id}.${name}`,
1055
- {
1056
- appId: this.hostId,
1057
- appUrl: window.location.href
1058
- }
1059
- );
1091
+ if (eventTimingToken) {
1092
+ this.#perfTracker.end(eventTimingToken, {
1093
+ appId: this.hostId,
1094
+ appUrl: window.location.href
1095
+ });
1096
+ }
1060
1097
  });
1061
- return retValue;
1062
1098
  };
1063
1099
  /**
1064
1100
  * get reference to all guest applications
@@ -1086,7 +1122,8 @@ class SSFHost {
1086
1122
  searchParams = {},
1087
1123
  onLoad,
1088
1124
  onError,
1089
- options = {}
1125
+ options = {},
1126
+ metadata
1090
1127
  } = param;
1091
1128
  if (!guestId) throw new Error("id for guest application is required");
1092
1129
  let instanceId = guestId;
@@ -1100,7 +1137,7 @@ class SSFHost {
1100
1137
  const { openMode = import_types.OpenMode.Embed, popupWindowFeatures = {} } = options;
1101
1138
  const srcUrl = this.#getGuestUrl(url, searchParams);
1102
1139
  let guest = null;
1103
- this.#startTiming("SSF.Guest.Load", {
1140
+ this.#perfTracker.start("SSF.Guest.Load", {
1104
1141
  appId: instanceId,
1105
1142
  appUrl: srcUrl
1106
1143
  });
@@ -1128,6 +1165,7 @@ class SSFHost {
1128
1165
  } else {
1129
1166
  throw new Error(`Invalid openMode: ${openMode}`);
1130
1167
  }
1168
+ if (metadata) this.#guestMetadata.set(instanceId, metadata);
1131
1169
  this.#logger.audit({
1132
1170
  message: "Guest loaded",
1133
1171
  ...guest.getInfo()
@@ -1193,6 +1231,7 @@ class SSFHost {
1193
1231
  guest.dispose();
1194
1232
  this.#guestsByWindow.delete(guest.window);
1195
1233
  this.#guestsByUrl.delete(guest.url);
1234
+ this.#guestMetadata.delete(guest.id);
1196
1235
  this.#guests.delete(guest.id);
1197
1236
  this.#logger.audit({
1198
1237
  message: `Guest is removed from host`,