tiny-essentials 1.25.2 → 1.25.3
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/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyIframeEvents.min.js +1 -1
- package/dist/v1/libs/TinyIframeEvents.cjs +266 -94
- package/dist/v1/libs/TinyIframeEvents.d.mts +119 -26
- package/dist/v1/libs/TinyIframeEvents.mjs +224 -86
- package/docs/v1/libs/TinyIframeEvents.md +61 -32
- package/package.json +1 -1
|
@@ -3,6 +3,52 @@ export default TinyIframeEvents;
|
|
|
3
3
|
* A function to handle incoming event payloads.
|
|
4
4
|
*/
|
|
5
5
|
export type handler = (payload: any, event: MessageEvent<any>) => any;
|
|
6
|
+
/**
|
|
7
|
+
* Configuration object for initializing TinyIframeEvents.
|
|
8
|
+
*/
|
|
9
|
+
export type TinyIframeEventsConfig = {
|
|
10
|
+
/**
|
|
11
|
+
* - The target iframe element to post messages to. Required if instantiated in the parent window.
|
|
12
|
+
*/
|
|
13
|
+
targetIframe?: HTMLIFrameElement | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* - The target origin to restrict messages to. Defaults to `window.location.origin`.
|
|
16
|
+
*/
|
|
17
|
+
targetOrigin?: string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* - Custom internal name used to validate standard routing messages.
|
|
20
|
+
*/
|
|
21
|
+
secretEventName?: string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* - Custom internal name used for the initial MessageChannel handshake.
|
|
24
|
+
*/
|
|
25
|
+
handshakeEventName?: string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* - Custom internal name used for the ready event trigger.
|
|
28
|
+
*/
|
|
29
|
+
readyEventName?: string | undefined;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Internal message structure for routed communication.
|
|
33
|
+
*/
|
|
34
|
+
export type IframeEventBase = {
|
|
35
|
+
/**
|
|
36
|
+
* - Dynamic key based on secretEventName to validate the message.
|
|
37
|
+
*/
|
|
38
|
+
secretIndicator?: boolean | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* - The name of the custom event route.
|
|
41
|
+
*/
|
|
42
|
+
eventName: string;
|
|
43
|
+
/**
|
|
44
|
+
* - The data being sent (can be any type).
|
|
45
|
+
*/
|
|
46
|
+
payload: any;
|
|
47
|
+
/**
|
|
48
|
+
* - Indicates the sender: 'iframe' or 'parent'.
|
|
49
|
+
*/
|
|
50
|
+
direction: "iframe" | "parent";
|
|
51
|
+
};
|
|
6
52
|
/**
|
|
7
53
|
* @callback handler
|
|
8
54
|
* A function to handle incoming event payloads.
|
|
@@ -10,32 +56,44 @@ export type handler = (payload: any, event: MessageEvent<any>) => any;
|
|
|
10
56
|
* @param {MessageEvent<any>} event - Metadata about the message.
|
|
11
57
|
*/
|
|
12
58
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
59
|
+
* @typedef {object} TinyIframeEventsConfig
|
|
60
|
+
* Configuration object for initializing TinyIframeEvents.
|
|
61
|
+
* @property {HTMLIFrameElement} [targetIframe] - The target iframe element to post messages to. Required if instantiated in the parent window.
|
|
62
|
+
* @property {string} [targetOrigin] - The target origin to restrict messages to. Defaults to `window.location.origin`.
|
|
63
|
+
* @property {string} [secretEventName] - Custom internal name used to validate standard routing messages.
|
|
64
|
+
* @property {string} [handshakeEventName] - Custom internal name used for the initial MessageChannel handshake.
|
|
65
|
+
* @property {string} [readyEventName] - Custom internal name used for the ready event trigger.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {object} IframeEventBase
|
|
69
|
+
* Internal message structure for routed communication.
|
|
70
|
+
* @property {boolean} [secretIndicator] - Dynamic key based on secretEventName to validate the message.
|
|
71
|
+
* @property {string} eventName - The name of the custom event route.
|
|
72
|
+
* @property {any} payload - The data being sent (can be any type).
|
|
73
|
+
* @property {'iframe' | 'parent'} direction - Indicates the sender: 'iframe' or 'parent'.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* A highly secure and flexible event routing system for structured communication
|
|
77
|
+
* between a parent window and its iframe using `MessageChannel`.
|
|
15
78
|
*
|
|
16
|
-
* This class abstracts the complexity of cross-origin
|
|
17
|
-
*
|
|
18
|
-
* - Send events with arbitrary payloads
|
|
19
|
-
* - Listen to specific event names
|
|
20
|
-
* - Filter events by origin and source
|
|
21
|
-
* - Work symmetrically from both sides with automatic direction handling
|
|
79
|
+
* This class abstracts the complexity of cross-origin communication by establishing
|
|
80
|
+
* a direct, un-interceptable port connection after a secure initial handshake.
|
|
22
81
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
82
|
+
* Features:
|
|
83
|
+
* - Secure direct pipeline via `MessageChannel`.
|
|
84
|
+
* - Customizable internal event names to avoid collisions.
|
|
85
|
+
* - Symmetrical usage for both parent and iframe.
|
|
86
|
+
* - Queue management for messages sent before the connection is established.
|
|
87
|
+
* - Auto-reconnects and resets ports if the target iframe is reloaded.
|
|
25
88
|
*/
|
|
26
89
|
declare class TinyIframeEvents {
|
|
27
90
|
/**
|
|
28
|
-
* Creates a new TinyIframeEvents instance to manage communication
|
|
29
|
-
* Automatically
|
|
91
|
+
* Creates a new TinyIframeEvents instance to manage secure communication.
|
|
92
|
+
* Automatically establishes a MessageChannel connection between contexts.
|
|
30
93
|
*
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {HTMLIFrameElement} [config.targetIframe] - The target window to post messages to. Defaults to `window.parent` (assumes this is inside an iframe).
|
|
33
|
-
* @param {string} [config.targetOrigin] - The target origin to restrict messages to. Defaults to `window.location.origin`.
|
|
94
|
+
* @param {TinyIframeEventsConfig} config - Configuration object.
|
|
34
95
|
*/
|
|
35
|
-
constructor({ targetIframe, targetOrigin }?:
|
|
36
|
-
targetIframe?: HTMLIFrameElement | undefined;
|
|
37
|
-
targetOrigin?: string | undefined;
|
|
38
|
-
});
|
|
96
|
+
constructor({ targetIframe, targetOrigin, secretEventName, handshakeEventName, readyEventName, }?: TinyIframeEventsConfig);
|
|
39
97
|
/**
|
|
40
98
|
* Enables or disables throwing an error when the maximum number of listeners is exceeded.
|
|
41
99
|
*
|
|
@@ -156,6 +214,20 @@ declare class TinyIframeEvents {
|
|
|
156
214
|
* @returns {number} The maximum number of listeners.
|
|
157
215
|
*/
|
|
158
216
|
getMaxListeners(): number;
|
|
217
|
+
get targetWindow(): Window;
|
|
218
|
+
get targetIframeElement(): HTMLIFrameElement | undefined;
|
|
219
|
+
get targetOrigin(): string;
|
|
220
|
+
get selfType(): "iframe" | "parent";
|
|
221
|
+
get ready(): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Executes the provided callback when the secure MessageChannel connection is fully established.
|
|
224
|
+
* If the connection is already ready, the callback is executed immediately.
|
|
225
|
+
*
|
|
226
|
+
* @param {function(): void} handler - The callback function to execute.
|
|
227
|
+
*/
|
|
228
|
+
onReady(handler: () => void): void;
|
|
229
|
+
_boundWindowMessage: (event: MessageEvent<any>) => void;
|
|
230
|
+
_boundPortMessage: (event: MessageEvent<any>) => void;
|
|
159
231
|
/**
|
|
160
232
|
* Sets the internal secret iframe event name.
|
|
161
233
|
* @param {string} name
|
|
@@ -163,28 +235,49 @@ declare class TinyIframeEvents {
|
|
|
163
235
|
*/
|
|
164
236
|
set secretEventName(name: string);
|
|
165
237
|
/**
|
|
166
|
-
* Gets the internal secret iframe event name.
|
|
238
|
+
* Gets the internal secret iframe event name used for validation.
|
|
167
239
|
* @returns {string}
|
|
168
240
|
*/
|
|
169
241
|
get secretEventName(): string;
|
|
170
|
-
_boundOnMessage: (event: MessageEvent<any>) => void;
|
|
171
|
-
_boundOnceMessage: () => void;
|
|
172
242
|
/**
|
|
173
|
-
*
|
|
243
|
+
* Sets the internal handshake event name.
|
|
244
|
+
* @param {string} name
|
|
245
|
+
* @throws {TypeError} If the value is not a string.
|
|
246
|
+
*/
|
|
247
|
+
set handshakeEventName(name: string);
|
|
248
|
+
/**
|
|
249
|
+
* Gets the internal handshake event name used for establishing the MessageChannel.
|
|
250
|
+
* @returns {string}
|
|
251
|
+
*/
|
|
252
|
+
get handshakeEventName(): string;
|
|
253
|
+
/**
|
|
254
|
+
* Sets the internal ready event name.
|
|
255
|
+
* @param {string} name
|
|
256
|
+
* @throws {TypeError} If the value is not a string.
|
|
257
|
+
*/
|
|
258
|
+
set readyEventName(name: string);
|
|
259
|
+
/**
|
|
260
|
+
* Gets the internal ready event name used when the connection is fully established.
|
|
261
|
+
* @returns {string}
|
|
262
|
+
*/
|
|
263
|
+
get readyEventName(): string;
|
|
264
|
+
/**
|
|
265
|
+
* Sends an event to the target window through the secure MessageChannel.
|
|
174
266
|
*
|
|
175
267
|
* @param {string} eventName - A unique name identifying the event.
|
|
176
268
|
* @param {*} payload - The data to send with the event. Can be any serializable value.
|
|
177
|
-
* @throws {
|
|
269
|
+
* @throws {TypeError} If `eventName` is not a string.
|
|
270
|
+
* @throws {Error} If instance has been destroyed.
|
|
178
271
|
*/
|
|
179
272
|
emit(eventName: string, payload: any): void;
|
|
180
273
|
/**
|
|
181
274
|
* Checks if the communication instance has been destroyed.
|
|
182
275
|
*
|
|
183
|
-
* @returns {boolean}
|
|
276
|
+
* @returns {boolean} True if destroyed, false otherwise.
|
|
184
277
|
*/
|
|
185
278
|
isDestroyed(): boolean;
|
|
186
279
|
/**
|
|
187
|
-
* Unsubscribes all registered event listeners
|
|
280
|
+
* Unsubscribes all registered event listeners, closes the message port, and cleans up references.
|
|
188
281
|
* Call this when the instance is no longer needed to prevent memory leaks.
|
|
189
282
|
*/
|
|
190
283
|
destroy(): void;
|
|
@@ -9,18 +9,35 @@ const instances = new WeakMap();
|
|
|
9
9
|
* @param {MessageEvent<any>} event - Metadata about the message.
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* @typedef {object} TinyIframeEventsConfig
|
|
13
|
+
* Configuration object for initializing TinyIframeEvents.
|
|
14
|
+
* @property {HTMLIFrameElement} [targetIframe] - The target iframe element to post messages to. Required if instantiated in the parent window.
|
|
15
|
+
* @property {string} [targetOrigin] - The target origin to restrict messages to. Defaults to `window.location.origin`.
|
|
16
|
+
* @property {string} [secretEventName] - Custom internal name used to validate standard routing messages.
|
|
17
|
+
* @property {string} [handshakeEventName] - Custom internal name used for the initial MessageChannel handshake.
|
|
18
|
+
* @property {string} [readyEventName] - Custom internal name used for the ready event trigger.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} IframeEventBase
|
|
22
|
+
* Internal message structure for routed communication.
|
|
23
|
+
* @property {boolean} [secretIndicator] - Dynamic key based on secretEventName to validate the message.
|
|
24
|
+
* @property {string} eventName - The name of the custom event route.
|
|
25
|
+
* @property {any} payload - The data being sent (can be any type).
|
|
26
|
+
* @property {'iframe' | 'parent'} direction - Indicates the sender: 'iframe' or 'parent'.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* A highly secure and flexible event routing system for structured communication
|
|
30
|
+
* between a parent window and its iframe using `MessageChannel`.
|
|
14
31
|
*
|
|
15
|
-
* This class abstracts the complexity of cross-origin
|
|
16
|
-
*
|
|
17
|
-
* - Send events with arbitrary payloads
|
|
18
|
-
* - Listen to specific event names
|
|
19
|
-
* - Filter events by origin and source
|
|
20
|
-
* - Work symmetrically from both sides with automatic direction handling
|
|
32
|
+
* This class abstracts the complexity of cross-origin communication by establishing
|
|
33
|
+
* a direct, un-interceptable port connection after a secure initial handshake.
|
|
21
34
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
35
|
+
* Features:
|
|
36
|
+
* - Secure direct pipeline via `MessageChannel`.
|
|
37
|
+
* - Customizable internal event names to avoid collisions.
|
|
38
|
+
* - Symmetrical usage for both parent and iframe.
|
|
39
|
+
* - Queue management for messages sent before the connection is established.
|
|
40
|
+
* - Auto-reconnects and resets ports if the target iframe is reloaded.
|
|
24
41
|
*/
|
|
25
42
|
class TinyIframeEvents {
|
|
26
43
|
#events = new TinyEvents();
|
|
@@ -188,29 +205,87 @@ class TinyIframeEvents {
|
|
|
188
205
|
///////////////////////////////////////////////////
|
|
189
206
|
/** @type {Window} */
|
|
190
207
|
#targetWindow;
|
|
208
|
+
get targetWindow() {
|
|
209
|
+
return this.#targetWindow;
|
|
210
|
+
}
|
|
211
|
+
/** @type {HTMLIFrameElement | undefined} */
|
|
212
|
+
#targetIframeElement;
|
|
213
|
+
get targetIframeElement() {
|
|
214
|
+
return this.#targetIframeElement;
|
|
215
|
+
}
|
|
191
216
|
/** @type {string} */
|
|
192
217
|
#targetOrigin;
|
|
193
|
-
|
|
218
|
+
get targetOrigin() {
|
|
219
|
+
return this.#targetOrigin;
|
|
220
|
+
}
|
|
221
|
+
/** @type {'iframe' | 'parent'} */
|
|
194
222
|
#selfType;
|
|
223
|
+
get selfType() {
|
|
224
|
+
return this.#selfType;
|
|
225
|
+
}
|
|
195
226
|
/** @type {boolean} */
|
|
196
227
|
#isDestroyed = false;
|
|
197
228
|
/** @type {boolean} */
|
|
198
229
|
#ready = false;
|
|
230
|
+
get ready() {
|
|
231
|
+
return this.#ready;
|
|
232
|
+
}
|
|
199
233
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* @
|
|
234
|
+
* Executes the provided callback when the secure MessageChannel connection is fully established.
|
|
235
|
+
* If the connection is already ready, the callback is executed immediately.
|
|
236
|
+
*
|
|
237
|
+
* @param {function(): void} handler - The callback function to execute.
|
|
204
238
|
*/
|
|
239
|
+
onReady(handler) {
|
|
240
|
+
if (this.#ready) {
|
|
241
|
+
handler();
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this.#events.once(this.#readyEventName, handler);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** @type {MessagePort | null} */
|
|
248
|
+
#port = null;
|
|
249
|
+
/** @type {IframeEventBase[]} */
|
|
250
|
+
#pendingQueue = [];
|
|
251
|
+
/** @type {string} */
|
|
252
|
+
#secretEventName;
|
|
253
|
+
/** @type {string} */
|
|
254
|
+
#handshakeEventName;
|
|
255
|
+
/** @type {string} */
|
|
256
|
+
#readyEventName;
|
|
257
|
+
/** @type {(() => void) | null} */
|
|
258
|
+
#boundSendPort = null;
|
|
205
259
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
260
|
+
* Creates a new TinyIframeEvents instance to manage secure communication.
|
|
261
|
+
* Automatically establishes a MessageChannel connection between contexts.
|
|
262
|
+
*
|
|
263
|
+
* @param {TinyIframeEventsConfig} config - Configuration object.
|
|
208
264
|
*/
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
265
|
+
constructor({ targetIframe, targetOrigin = window.location.origin, secretEventName = '__tinyIframeEvent__', handshakeEventName = '__tinyIframeHandshake__', readyEventName = '__tinyIframeReady__', } = {}) {
|
|
266
|
+
if (targetIframe !== undefined &&
|
|
267
|
+
(!(targetIframe instanceof HTMLIFrameElement) || !targetIframe.contentWindow)) {
|
|
268
|
+
throw new TypeError(`[TinyIframeEvents] Invalid "targetIframe": expected HTMLIFrameElement, received ${typeof targetIframe}`);
|
|
269
|
+
}
|
|
270
|
+
if (typeof targetOrigin !== 'string') {
|
|
271
|
+
throw new TypeError(`[TinyIframeEvents] Invalid "targetOrigin": expected string, received ${typeof targetOrigin}`);
|
|
272
|
+
}
|
|
273
|
+
this.#targetIframeElement = targetIframe;
|
|
274
|
+
this.#targetWindow = targetIframe?.contentWindow ?? window.parent;
|
|
275
|
+
this.#targetOrigin = targetOrigin;
|
|
276
|
+
this.#selfType = !targetIframe ? 'iframe' : 'parent';
|
|
277
|
+
this.#secretEventName = secretEventName;
|
|
278
|
+
this.#handshakeEventName = handshakeEventName;
|
|
279
|
+
this.#readyEventName = readyEventName;
|
|
280
|
+
if (instances.has(this.#targetWindow))
|
|
281
|
+
throw new Error('Duplicate window reference.');
|
|
282
|
+
this._boundWindowMessage = this.#onWindowMessage.bind(this);
|
|
283
|
+
this._boundPortMessage = this.#onPortMessage.bind(this);
|
|
284
|
+
this.#initializeConnection();
|
|
285
|
+
instances.set(this.#targetWindow, this);
|
|
286
|
+
}
|
|
212
287
|
/**
|
|
213
|
-
* Gets the internal secret iframe event name.
|
|
288
|
+
* Gets the internal secret iframe event name used for validation.
|
|
214
289
|
* @returns {string}
|
|
215
290
|
*/
|
|
216
291
|
get secretEventName() {
|
|
@@ -227,71 +302,140 @@ class TinyIframeEvents {
|
|
|
227
302
|
this.#secretEventName = name;
|
|
228
303
|
}
|
|
229
304
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this.#
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
305
|
+
* Gets the internal handshake event name used for establishing the MessageChannel.
|
|
306
|
+
* @returns {string}
|
|
307
|
+
*/
|
|
308
|
+
get handshakeEventName() {
|
|
309
|
+
return this.#handshakeEventName;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Sets the internal handshake event name.
|
|
313
|
+
* @param {string} name
|
|
314
|
+
* @throws {TypeError} If the value is not a string.
|
|
315
|
+
*/
|
|
316
|
+
set handshakeEventName(name) {
|
|
317
|
+
if (typeof name !== 'string')
|
|
318
|
+
throw new TypeError('TinyIframeEvents: handshakeEventName must be a string.');
|
|
319
|
+
this.#handshakeEventName = name;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Gets the internal ready event name used when the connection is fully established.
|
|
323
|
+
* @returns {string}
|
|
324
|
+
*/
|
|
325
|
+
get readyEventName() {
|
|
326
|
+
return this.#readyEventName;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Sets the internal ready event name.
|
|
330
|
+
* @param {string} name
|
|
331
|
+
* @throws {TypeError} If the value is not a string.
|
|
332
|
+
*/
|
|
333
|
+
set readyEventName(name) {
|
|
334
|
+
if (typeof name !== 'string')
|
|
335
|
+
throw new TypeError('TinyIframeEvents: readyEventName must be a string.');
|
|
336
|
+
this.#readyEventName = name;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Initializes the correct connection strategy based on the current context (parent or iframe).
|
|
340
|
+
*/
|
|
341
|
+
#initializeConnection() {
|
|
342
|
+
if (this.#selfType === 'parent') {
|
|
343
|
+
this.#boundSendPort = () => {
|
|
344
|
+
if (this.#isDestroyed)
|
|
345
|
+
return;
|
|
346
|
+
// Closes any existing port to prevent memory leaks during iframe reloads
|
|
347
|
+
if (this.#port) {
|
|
348
|
+
this.#port.close();
|
|
349
|
+
}
|
|
350
|
+
// Creates a fresh channel for every attempt to prevent clone errors
|
|
351
|
+
const channel = new MessageChannel();
|
|
352
|
+
this.#port = channel.port1;
|
|
353
|
+
this.#port.onmessage = this._boundPortMessage;
|
|
354
|
+
// Resets ready state to require a new ACK
|
|
355
|
+
this.#ready = false;
|
|
356
|
+
this.#targetWindow.postMessage({ type: this.#handshakeEventName }, this.#targetOrigin, [
|
|
357
|
+
channel.port2,
|
|
358
|
+
]);
|
|
359
|
+
};
|
|
360
|
+
if (this.#targetIframeElement) {
|
|
361
|
+
this.#targetIframeElement.addEventListener('load', this.#boundSendPort);
|
|
362
|
+
this.#boundSendPort();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
253
365
|
else {
|
|
254
|
-
|
|
255
|
-
this.#targetWindow.addEventListener('DOMContentLoaded', this._boundOnceMessage, false);
|
|
366
|
+
window.addEventListener('message', this._boundWindowMessage, false);
|
|
256
367
|
}
|
|
257
|
-
window.addEventListener('message', this._boundOnMessage, false);
|
|
258
|
-
instances.set(this.#targetWindow, this);
|
|
259
368
|
}
|
|
260
369
|
/**
|
|
261
|
-
*
|
|
370
|
+
* Internal handler for the initial window message event (used by iframe to receive the port).
|
|
371
|
+
*
|
|
372
|
+
* @param {MessageEvent<any>} event - The message event received via `postMessage`.
|
|
262
373
|
*/
|
|
263
|
-
#
|
|
264
|
-
|
|
374
|
+
#onWindowMessage(event) {
|
|
375
|
+
const data = event.data;
|
|
376
|
+
const ports = event.ports;
|
|
377
|
+
if (!isJsonObject(data) || data.type !== this.#handshakeEventName || ports.length === 0)
|
|
265
378
|
return;
|
|
266
|
-
this.#
|
|
267
|
-
this.#
|
|
379
|
+
this.#port = ports[0];
|
|
380
|
+
this.#port.onmessage = this._boundPortMessage;
|
|
381
|
+
window.removeEventListener('message', this._boundWindowMessage);
|
|
382
|
+
this.#port.postMessage({ type: `${this.#handshakeEventName}_ACK` });
|
|
383
|
+
this.#markReady();
|
|
268
384
|
}
|
|
269
385
|
/**
|
|
270
|
-
* Internal handler for
|
|
386
|
+
* Internal handler for messages received through the secure MessageChannel port.
|
|
271
387
|
*
|
|
272
|
-
* @param {MessageEvent<any>} event - The message event received via `
|
|
388
|
+
* @param {MessageEvent<any>} event - The message event received via `MessagePort`.
|
|
273
389
|
*/
|
|
274
|
-
#
|
|
275
|
-
|
|
276
|
-
|
|
390
|
+
#onPortMessage(event) {
|
|
391
|
+
/** @type {any} */
|
|
392
|
+
const data = event.data;
|
|
393
|
+
if (isJsonObject(data) && data.type === `${this.#handshakeEventName}_ACK`) {
|
|
394
|
+
this.#markReady();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
277
397
|
if (!isJsonObject(data) || !data[this.#secretEventName])
|
|
278
398
|
return;
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
399
|
+
const eventName = data.eventName;
|
|
400
|
+
const payload = data.payload;
|
|
401
|
+
const direction = data.direction;
|
|
402
|
+
if (typeof eventName !== 'string' ||
|
|
403
|
+
(this.#selfType === 'iframe' && direction !== 'iframe') ||
|
|
404
|
+
(this.#selfType === 'parent' && direction !== 'parent')) {
|
|
282
405
|
return;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
406
|
+
}
|
|
407
|
+
this.#events.emit(eventName, payload, event);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Marks the communication as ready, flushes any queued messages,
|
|
411
|
+
* and triggers the internal ready event for any waiting listeners.
|
|
412
|
+
*/
|
|
413
|
+
#markReady() {
|
|
414
|
+
if (this.#ready)
|
|
286
415
|
return;
|
|
287
|
-
this.#
|
|
416
|
+
this.#ready = true;
|
|
417
|
+
this.#flushQueue();
|
|
418
|
+
this.#events.emit(this.#readyEventName);
|
|
288
419
|
}
|
|
289
420
|
/**
|
|
290
|
-
* Sends
|
|
421
|
+
* Sends all pending messages queued before the secure port was established.
|
|
422
|
+
*/
|
|
423
|
+
#flushQueue() {
|
|
424
|
+
while (this.#pendingQueue.length > 0) {
|
|
425
|
+
/** @type {IframeEventBase | undefined} */
|
|
426
|
+
const data = this.#pendingQueue.shift();
|
|
427
|
+
if (data && this.#port) {
|
|
428
|
+
this.#port.postMessage(data);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Sends an event to the target window through the secure MessageChannel.
|
|
291
434
|
*
|
|
292
435
|
* @param {string} eventName - A unique name identifying the event.
|
|
293
436
|
* @param {*} payload - The data to send with the event. Can be any serializable value.
|
|
294
|
-
* @throws {
|
|
437
|
+
* @throws {TypeError} If `eventName` is not a string.
|
|
438
|
+
* @throws {Error} If instance has been destroyed.
|
|
295
439
|
*/
|
|
296
440
|
emit(eventName, payload) {
|
|
297
441
|
if (typeof eventName !== 'string')
|
|
@@ -305,41 +449,35 @@ class TinyIframeEvents {
|
|
|
305
449
|
payload,
|
|
306
450
|
direction: this.#selfType === 'parent' ? 'iframe' : 'parent',
|
|
307
451
|
};
|
|
308
|
-
if (!this.#ready) {
|
|
452
|
+
if (!this.#ready || !this.#port) {
|
|
309
453
|
this.#pendingQueue.push(message);
|
|
310
454
|
return;
|
|
311
455
|
}
|
|
312
|
-
this.#
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Sends all pending messages queued before handshake completion.
|
|
316
|
-
*
|
|
317
|
-
* @returns {void}
|
|
318
|
-
*/
|
|
319
|
-
#flushQueue() {
|
|
320
|
-
while (this.#pendingQueue.length) {
|
|
321
|
-
const data = this.#pendingQueue.shift();
|
|
322
|
-
if (data)
|
|
323
|
-
this.#targetWindow.postMessage(data, this.#targetOrigin);
|
|
324
|
-
}
|
|
456
|
+
this.#port.postMessage(message);
|
|
325
457
|
}
|
|
326
458
|
/**
|
|
327
459
|
* Checks if the communication instance has been destroyed.
|
|
328
460
|
*
|
|
329
|
-
* @returns {boolean}
|
|
461
|
+
* @returns {boolean} True if destroyed, false otherwise.
|
|
330
462
|
*/
|
|
331
463
|
isDestroyed() {
|
|
332
464
|
return this.#isDestroyed;
|
|
333
465
|
}
|
|
334
466
|
/**
|
|
335
|
-
* Unsubscribes all registered event listeners
|
|
467
|
+
* Unsubscribes all registered event listeners, closes the message port, and cleans up references.
|
|
336
468
|
* Call this when the instance is no longer needed to prevent memory leaks.
|
|
337
469
|
*/
|
|
338
470
|
destroy() {
|
|
339
471
|
this.#isDestroyed = true;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
this.#
|
|
472
|
+
this.#ready = false;
|
|
473
|
+
window.removeEventListener('message', this._boundWindowMessage);
|
|
474
|
+
if (this.#targetIframeElement && this.#boundSendPort) {
|
|
475
|
+
this.#targetIframeElement.removeEventListener('load', this.#boundSendPort);
|
|
476
|
+
}
|
|
477
|
+
if (this.#port) {
|
|
478
|
+
this.#port.close();
|
|
479
|
+
this.#port = null;
|
|
480
|
+
}
|
|
343
481
|
this.#events.offAllTypes();
|
|
344
482
|
this.#pendingQueue = [];
|
|
345
483
|
instances.delete(this.#targetWindow);
|