@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.
- package/dist/cjs/callchain-host.html +262 -0
- package/dist/cjs/callchain-intermediate.html +92 -0
- package/dist/cjs/e2e-host.html +603 -0
- package/dist/cjs/e2e-index.html +234 -0
- package/dist/cjs/guest.js +15 -16
- package/dist/cjs/host.js +91 -52
- package/dist/cjs/index.html +304 -151
- package/dist/cjs/performanceTracker.js +111 -0
- package/dist/cjs/popup-focus-host.html +318 -0
- package/dist/cjs/utils.js +14 -1
- package/dist/cjs/v2-host-v1-guest.html +3 -0
- package/dist/esm/callchain-host.html +262 -0
- package/dist/esm/callchain-intermediate.html +92 -0
- package/dist/esm/e2e-host.html +603 -0
- package/dist/esm/e2e-index.html +234 -0
- package/dist/esm/guest.js +15 -16
- package/dist/esm/host.js +92 -53
- package/dist/esm/index.html +304 -151
- package/dist/esm/performanceTracker.js +91 -0
- package/dist/esm/popup-focus-host.html +318 -0
- package/dist/esm/utils.js +14 -1
- package/dist/esm/v2-host-v1-guest.html +3 -0
- package/dist/public/callchain-host.html +34 -0
- package/dist/public/callchain-host.js +3 -0
- package/dist/public/callchain-host.js.br +0 -0
- package/dist/public/callchain-host.js.gz +0 -0
- package/dist/public/callchain-host.js.map +1 -0
- package/dist/public/callchain-intermediate.html +1 -0
- package/dist/public/e2e-host.html +5 -0
- package/dist/public/e2e-host.js +8 -0
- package/dist/public/e2e-host.js.br +0 -0
- package/dist/public/e2e-host.js.gz +0 -0
- package/dist/public/e2e-host.js.map +1 -0
- package/dist/public/e2e-index.html +1 -0
- package/dist/public/index.html +1 -1
- package/dist/public/init.js +1 -1
- package/dist/public/init.js.br +0 -0
- package/dist/public/init.js.gz +0 -0
- package/dist/public/init.js.map +1 -1
- package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js +3 -0
- package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.br +0 -0
- package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.gz +0 -0
- package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.map +1 -0
- package/dist/public/loan-object.js +1 -1
- package/dist/public/loan-object.js.br +0 -0
- package/dist/public/loan-object.js.gz +0 -0
- package/dist/public/loan-object.js.map +1 -1
- package/dist/public/popup-focus-host.html +1 -0
- package/dist/public/popup-focus-host.js +6 -0
- package/dist/public/popup-focus-host.js.br +0 -0
- package/dist/public/popup-focus-host.js.gz +0 -0
- package/dist/public/popup-focus-host.js.map +1 -0
- package/dist/public/utils.js +1 -1
- package/dist/public/utils.js.br +0 -0
- package/dist/public/utils.js.gz +0 -0
- package/dist/public/utils.js.map +1 -1
- package/dist/public/v1-guest-v2-host.html +1 -1
- package/dist/public/v2-host-v1-guest.html +1 -1
- package/dist/types/lib/guest.d.ts +4 -3
- package/dist/types/lib/ihost.d.ts +32 -0
- package/dist/types/lib/performanceTracker.d.ts +46 -0
- package/dist/types/lib/tests/timingDedup.test.d.ts +1 -0
- package/dist/types/lib/utils.d.ts +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dist/umd/callchain-host.html +34 -0
- package/dist/umd/callchain-host.js +3 -0
- package/dist/umd/callchain-host.js.br +0 -0
- package/dist/umd/callchain-host.js.gz +0 -0
- package/dist/umd/callchain-host.js.map +1 -0
- package/dist/umd/callchain-intermediate.html +1 -0
- package/dist/umd/e2e-host.html +5 -0
- package/dist/umd/e2e-host.js +8 -0
- package/dist/umd/e2e-host.js.br +0 -0
- package/dist/umd/e2e-host.js.gz +0 -0
- package/dist/umd/e2e-host.js.map +1 -0
- package/dist/umd/e2e-index.html +1 -0
- package/dist/umd/index.html +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.br +0 -0
- package/dist/umd/index.js.gz +0 -0
- package/dist/umd/index.js.map +1 -1
- package/dist/umd/init.js +1 -1
- package/dist/umd/init.js.br +0 -0
- package/dist/umd/init.js.gz +0 -0
- package/dist/umd/init.js.map +1 -1
- package/dist/umd/loan-object.js +1 -1
- package/dist/umd/loan-object.js.br +0 -0
- package/dist/umd/loan-object.js.gz +0 -0
- package/dist/umd/loan-object.js.map +1 -1
- package/dist/umd/popup-focus-host.html +1 -0
- package/dist/umd/popup-focus-host.js +6 -0
- package/dist/umd/popup-focus-host.js.br +0 -0
- package/dist/umd/popup-focus-host.js.gz +0 -0
- package/dist/umd/popup-focus-host.js.map +1 -0
- package/dist/umd/utils.js +1 -1
- package/dist/umd/utils.js.br +0 -0
- package/dist/umd/utils.js.gz +0 -0
- package/dist/umd/utils.js.map +1 -1
- package/dist/umd/v2-host-v1-guest.html +1 -1
- package/package.json +6 -6
- package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js +0 -3
- package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js.br +0 -0
- package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js.gz +0 -0
- 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 & 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 • TC-SO-01..08 • TC-EVT-01..04 •
|
|
85
|
+
TC-LIFE-01..03 • TC-META-01 • 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 & 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 & 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
|
-
*
|
|
54
|
+
* performance tracker for timing measurements
|
|
55
55
|
*/
|
|
56
|
-
#
|
|
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
|
-
|
|
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.#
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
{
|
|
186
|
+
if (timingToken) {
|
|
187
|
+
this.#perfTracker.end(timingToken, {
|
|
188
188
|
appId: this.id,
|
|
189
189
|
appUrl: this.url
|
|
190
|
-
}
|
|
191
|
-
|
|
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: {
|
|
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.#
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
|
|
559
|
-
|
|
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
|
-
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
994
|
-
this.#
|
|
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
|
-
|
|
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 (
|
|
1040
|
-
this.#
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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.#
|
|
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`,
|