@o3r/third-party 13.0.0-next.0 → 13.0.0-next.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"o3r-third-party.mjs","sources":["../../src/bridge/ab-testing/ab-testing-bridge.ts","../../src/bridge/iframe/helpers.ts","../../src/bridge/iframe/bridge.ts","../../src/o3r-third-party.ts"],"sourcesContent":["import type {\n Logger,\n} from '@o3r/core';\nimport {\n BehaviorSubject,\n distinctUntilChanged,\n Observable,\n} from 'rxjs';\n\n/**\n * Shared interface with the A/B testing provider\n */\nexport interface AbTestBridgeInterface<T> {\n /**\n * Start an AB testing experiment\n */\n start(experiments: T | T[]): void;\n /**\n * Stop an AB testing experiment\n */\n stop(experiments?: T | T[]): void;\n}\n\n/**\n * Configure the A/B testing script interfaces with the application\n */\nexport interface AbTestBridgeConfig {\n /**\n * Reference to communicate with the bridge from the window\n * @default 'abTestBridge'\n */\n bridgeName: string;\n /**\n * Debug logger\n * @default console\n */\n logger: Logger;\n /**\n * Event a third party can subscribe to before starting the communication with the bridge\n * @default 'ab-test-ready'\n */\n readyEventName: string;\n}\n\n/**\n * Default options that will represent the interface\n */\nconst defaultOptions = {\n bridgeName: 'abTestBridge',\n readyEventName: 'ab-test-ready',\n logger: console\n} as const satisfies AbTestBridgeConfig;\n\n/**\n * Bridge between the application and a third party A/B testing provider.\n * Exposes a start and stop methods to allow the external script to set the list of experiments to run over the\n * application.\n *\n * Share the resulting list of experiments with the rest of the application via an observable.\n */\nexport class AbTestBridge<T> implements AbTestBridgeInterface<T> {\n /**\n * Behaviour subject to control the experiments via the exposed interface\n */\n private readonly experimentSubject$: BehaviorSubject<T[]> = new BehaviorSubject<T[]>([]);\n /**\n * Options to configure the communication between the AB Testing bridge and third parties\n */\n private readonly options: AbTestBridgeConfig;\n /**\n * Observable with the list of AB testing experiments currently applied\n */\n public experiments$: Observable<T[]>;\n\n /**\n * AbTestBridge constructor\n * @param isExperimentEqual check two different experiments match to identify an experiment to start or to stop\n * @param options configure the communication with the A/B testing third party provider\n */\n constructor(private readonly isExperimentEqual: (value1?: T, value2?: T) => boolean, options?: Partial<AbTestBridgeConfig>) {\n this.experiments$ = this.experimentSubject$.pipe(\n distinctUntilChanged((experimentsA: T[], experimentsB: T[]) =>\n experimentsB.length === experimentsA.length && experimentsA.every((eA) => experimentsB.find((eB) => isExperimentEqual(eA, eB))\n ))\n );\n this.options = {\n ...defaultOptions,\n ...options\n };\n if ((window as any)[this.options.bridgeName]) {\n this.log(`An instance of ${this.options.bridgeName} already exists. This AbTestBridge instance will be ignored`);\n } else {\n (window as any)[this.options.bridgeName] = { start: this.start.bind(this), stop: this.stop.bind(this) };\n }\n document.dispatchEvent(new CustomEvent(this.options.readyEventName));\n }\n\n /**\n * Use configured logger to log AB testing related information\n * @param args\n */\n private log(...args: any[]) {\n (this.options.logger.debug || this.options.logger.log)('A/B Test', ...args);\n }\n\n /**\n * @inheritDoc\n */\n public start(experiments: T | T[]) {\n this.log('Start experiment', experiments);\n const currentProfile = this.experimentSubject$.getValue();\n this.experimentSubject$.next([\n ...currentProfile,\n ...(Array.isArray(experiments)\n ? experiments\n : [experiments]).filter((exp) => !currentProfile.some((expB: T) => this.isExperimentEqual(exp, expB))\n )\n ]);\n }\n\n /**\n * @inheritDoc\n */\n public stop(experiments?: T | T[]) {\n this.log('Stop experiment', experiments);\n const currentExperiments = this.experimentSubject$.getValue();\n if (experiments) {\n // Stop the mentioned experiment\n this.experimentSubject$.next(currentExperiments.filter((expB: T) => !(Array.isArray(experiments) ? experiments : [experiments]).some((expA) => this.isExperimentEqual(expB, expA)))\n );\n } else {\n // Stop all the experiment\n this.experimentSubject$.next([]);\n }\n }\n}\n","import {\n IFrameBridgeOptions,\n InternalIframeMessage,\n} from './contracts';\n\n/**\n * Default options for an IFrameBridge\n */\nexport const IFRAME_BRIDGE_DEFAULT_OPTIONS: Readonly<IFrameBridgeOptions> = {\n handshakeTries: 10,\n handshakeTimeout: 200,\n messageWithResponseTimeout: 1000\n} as const;\n\n/**\n * Verifies if a message respects the format expected by an IFrameBridge\n * @param message\n */\nexport function isSupportedMessage(message: any): message is InternalIframeMessage {\n return typeof message === 'object'\n && !!message.action\n && !!message.version\n && !!message.channelId;\n}\n\n/**\n * Generates the html content of an iframe\n * @param scriptUrl script to be executed inside the iframe\n * @param additionalHeader custom html headers stringified\n */\nexport function generateIFrameContent(scriptUrl: string, additionalHeader = '') {\n return `<html>\n <head>\n <script>\n class Bridge {\n handshakeDone = false;\n\n queuedMessages = [];\n\n channelId;\n\n messagesBuffer = [];\n\n listener;\n\n constructor() {\n if (window.parent) {\n window.addEventListener('message', (event) => {\n const message = event.data;\n if (this.isValidMessage(message)) {\n if (message.action === 'HANDSHAKE_PARENT') {\n this.channelId = message.channelId;\n this.sendMessage({action: 'HANDSHAKE_CHILD', version: '1.0', id: message.id});\n this.handshakeDone = true;\n this.queuedMessages.forEach((queuedMessage) => this.sendMessage(queuedMessage));\n this.queuedMessages = [];\n } else if (this.channelId && this.channelId === message.channelId) {\n // actual message\n if (this.listener) {\n this.listener(message);\n } else {\n this.messagesBuffer.push(message);\n }\n }\n }\n });\n } else {\n throw new Error('Error in child frame bridge: can\\\\'t access parent window.');\n }\n }\n\n register(handlerFunction, replayMissedMessages) {\n if (!this.listener) {\n this.listener = handlerFunction;\n if (replayMissedMessages) {\n this.messagesBuffer.forEach((message) => handlerFunction(message));\n }\n this.messagesBuffer = [];\n }\n }\n\n isValidMessage(message) {\n return !!message.action && !!message.version && !!message.channelId;\n }\n\n sendMessage(message) {\n if(this.handshakeDone) {\n window.parent.postMessage({...message, channelId: this.channelId}, '*');\n } else {\n this.queuedMessages.push(message);\n }\n }\n\n uuid() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n }\n\n const BRIDGE = new Bridge();\n </script>\n <script src='${scriptUrl}'></script>\n ${additionalHeader}\n </head>\n <body></body>\n</html>`;\n}\n","import {\n firstValueFrom,\n fromEvent,\n Observable,\n} from 'rxjs';\nimport {\n filter,\n map,\n share,\n timeout,\n} from 'rxjs/operators';\nimport {\n v4,\n} from 'uuid';\nimport {\n IFrameBridgeOptions,\n IframeMessage,\n InternalIframeMessage,\n} from './contracts';\nimport {\n IFRAME_BRIDGE_DEFAULT_OPTIONS,\n isSupportedMessage,\n} from './helpers';\n\n/**\n * Bridge that exposes an easy abstraction layer to communicate between a Host and an IFrame using the\n * postMessage API.\n */\nexport class IframeBridge {\n /** ID used to ensure that the Bridge only processes messages meant for this instance, since postMessage is global to the window. */\n private readonly channelId: string;\n\n /** Observable that emits all the messages received from the IFrame. */\n private readonly internalMessages$: Observable<InternalIframeMessage>;\n\n /** Promise that will resolve once the handshake has been completed, undefined if it's already done. */\n private readonly handshakePromise?: Promise<void>;\n\n /** Options to configure the behaviour of the Bridge. */\n private readonly options: IFrameBridgeOptions;\n\n /**\n * Observable that emits all the messages received from the IFrame and that are\n * not a direct response to a request.\n */\n public readonly messages$: Observable<InternalIframeMessage>;\n\n constructor(parent: Window, private readonly child: HTMLIFrameElement, options: Partial<IFrameBridgeOptions> = {}) {\n this.options = { ...IFRAME_BRIDGE_DEFAULT_OPTIONS, ...options };\n this.channelId = v4();\n this.internalMessages$ = fromEvent(parent, 'message').pipe(\n filter((event): event is MessageEvent<InternalIframeMessage> => {\n const messageEvent = event as MessageEvent<InternalIframeMessage>;\n return isSupportedMessage(messageEvent.data) && messageEvent.data.channelId === this.channelId;\n }),\n map((event) => event.data),\n share()\n );\n this.messages$ = this.internalMessages$.pipe(\n // Here we remove all the messages having an \"ID\" because they are bound to their corresponding request and\n // are returned directly by the function sendMessageAndWaitForResponse\n filter((message) => !message.id)\n );\n\n this.handshakePromise = this.handshake();\n }\n\n private async handshake() {\n for (let i = 0; i < this.options.handshakeTries; i++) {\n try {\n await this._sendMessageAndWaitForResponse({\n action: 'HANDSHAKE_PARENT',\n version: '1.0'\n }, this.options.handshakeTimeout);\n return;\n } catch {}\n }\n throw new Error('Handshake failed.');\n }\n\n private _sendMessage(message: IframeMessage, messageId?: string) {\n if (this.child.contentWindow) {\n this.child.contentWindow.postMessage({ ...message, channelId: this.channelId, id: messageId }, '*');\n }\n }\n\n private _sendMessageAndWaitForResponse(message: IframeMessage, timeoutMilliseconds: number = this.options.messageWithResponseTimeout) {\n const id = v4();\n const promise = firstValueFrom(\n this.internalMessages$.pipe(\n filter((response) => response.id === id),\n timeout(timeoutMilliseconds)\n )\n );\n void this._sendMessage(message, id);\n return promise;\n }\n\n /**\n * Method to send a message to the script run in the iframe\n * @param message message object\n * @param messageId message identifier\n */\n public async sendMessage(message: IframeMessage, messageId?: string) {\n await this.handshakePromise;\n this._sendMessage(message, messageId);\n }\n\n /**\n * Method to send a message to the script run in the iframe and wait for an answer\n * @param message\n * @param timeoutMilliseconds\n */\n public async sendMessageAndWaitForResponse(message: IframeMessage, timeoutMilliseconds: number = this.options.messageWithResponseTimeout) {\n await this.handshakePromise;\n return this._sendMessageAndWaitForResponse(message, timeoutMilliseconds);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"o3r-third-party.mjs","sources":["../../src/bridge/ab-testing/ab-testing-bridge.ts","../../src/bridge/iframe/helpers.ts","../../src/bridge/iframe/bridge.ts","../../src/o3r-third-party.ts"],"sourcesContent":["import type {\n Logger,\n} from '@o3r/core';\nimport {\n BehaviorSubject,\n distinctUntilChanged,\n Observable,\n} from 'rxjs';\n\n/**\n * Shared interface with the A/B testing provider\n */\nexport interface AbTestBridgeInterface<T> {\n /**\n * Start an AB testing experiment\n */\n start(experiments: T | T[]): void;\n /**\n * Stop an AB testing experiment\n */\n stop(experiments?: T | T[]): void;\n}\n\n/**\n * Configure the A/B testing script interfaces with the application\n */\nexport interface AbTestBridgeConfig {\n /**\n * Reference to communicate with the bridge from the window\n * @default 'abTestBridge'\n */\n bridgeName: string;\n /**\n * Debug logger\n * @default console\n */\n logger: Logger;\n /**\n * Event a third party can subscribe to before starting the communication with the bridge\n * @default 'ab-test-ready'\n */\n readyEventName: string;\n}\n\n/**\n * Default options that will represent the interface\n */\nconst defaultOptions = {\n bridgeName: 'abTestBridge',\n readyEventName: 'ab-test-ready',\n logger: console\n} as const satisfies AbTestBridgeConfig;\n\n/**\n * Bridge between the application and a third party A/B testing provider.\n * Exposes a start and stop methods to allow the external script to set the list of experiments to run over the\n * application.\n *\n * Share the resulting list of experiments with the rest of the application via an observable.\n */\nexport class AbTestBridge<T> implements AbTestBridgeInterface<T> {\n /**\n * Behaviour subject to control the experiments via the exposed interface\n */\n private readonly experimentSubject$: BehaviorSubject<T[]> = new BehaviorSubject<T[]>([]);\n /**\n * Options to configure the communication between the AB Testing bridge and third parties\n */\n private readonly options: AbTestBridgeConfig;\n /**\n * Observable with the list of AB testing experiments currently applied\n */\n public experiments$: Observable<T[]>;\n\n /**\n * AbTestBridge constructor\n * @param isExperimentEqual check two different experiments match to identify an experiment to start or to stop\n * @param options configure the communication with the A/B testing third party provider\n */\n constructor(private readonly isExperimentEqual: (value1?: T, value2?: T) => boolean, options?: Partial<AbTestBridgeConfig>) {\n this.experiments$ = this.experimentSubject$.pipe(\n distinctUntilChanged((experimentsA: T[], experimentsB: T[]) =>\n experimentsB.length === experimentsA.length && experimentsA.every((eA) => experimentsB.find((eB) => isExperimentEqual(eA, eB))\n ))\n );\n this.options = {\n ...defaultOptions,\n ...options\n };\n if ((window as any)[this.options.bridgeName]) {\n this.log(`An instance of ${this.options.bridgeName} already exists. This AbTestBridge instance will be ignored`);\n } else {\n (window as any)[this.options.bridgeName] = { start: this.start.bind(this), stop: this.stop.bind(this) };\n }\n document.dispatchEvent(new CustomEvent(this.options.readyEventName));\n }\n\n /**\n * Use configured logger to log AB testing related information\n * @param args\n */\n private log(...args: any[]) {\n (this.options.logger.debug || this.options.logger.log)('A/B Test', ...args);\n }\n\n /**\n * @inheritDoc\n */\n public start(experiments: T | T[]) {\n this.log('Start experiment', experiments);\n const currentProfile = this.experimentSubject$.getValue();\n this.experimentSubject$.next([\n ...currentProfile,\n ...(Array.isArray(experiments)\n ? experiments\n : [experiments]).filter((exp) => !currentProfile.some((expB: T) => this.isExperimentEqual(exp, expB))\n )\n ]);\n }\n\n /**\n * @inheritDoc\n */\n public stop(experiments?: T | T[]) {\n this.log('Stop experiment', experiments);\n const currentExperiments = this.experimentSubject$.getValue();\n if (experiments) {\n // Stop the mentioned experiment\n this.experimentSubject$.next(currentExperiments.filter((expB: T) => !(Array.isArray(experiments) ? experiments : [experiments]).some((expA) => this.isExperimentEqual(expB, expA)))\n );\n } else {\n // Stop all the experiment\n this.experimentSubject$.next([]);\n }\n }\n}\n","import {\n IFrameBridgeOptions,\n InternalIframeMessage,\n} from './contracts';\n\n/**\n * Default options for an IFrameBridge\n */\nexport const IFRAME_BRIDGE_DEFAULT_OPTIONS: Readonly<IFrameBridgeOptions> = {\n handshakeTries: 10,\n handshakeTimeout: 200,\n messageWithResponseTimeout: 1000\n} as const;\n\n/**\n * Verifies if a message respects the format expected by an IFrameBridge\n * @param message\n */\nexport function isSupportedMessage(message: any): message is InternalIframeMessage {\n return typeof message === 'object'\n && !!message.action\n && !!message.version\n && !!message.channelId;\n}\n\n/**\n * Generates the html content of an iframe\n * @param scriptUrl script to be executed inside the iframe\n * @param additionalHeader custom html headers stringified\n */\nexport function generateIFrameContent(scriptUrl: string, additionalHeader = '') {\n return `<html>\n <head>\n <script>\n class Bridge {\n handshakeDone = false;\n\n queuedMessages = [];\n\n channelId;\n\n messagesBuffer = [];\n\n listener;\n\n constructor() {\n if (window.parent) {\n window.addEventListener('message', (event) => {\n const message = event.data;\n if (this.isValidMessage(message)) {\n if (message.action === 'HANDSHAKE_PARENT') {\n this.channelId = message.channelId;\n this.sendMessage({action: 'HANDSHAKE_CHILD', version: '1.0', id: message.id});\n this.handshakeDone = true;\n this.queuedMessages.forEach((queuedMessage) => this.sendMessage(queuedMessage));\n this.queuedMessages = [];\n } else if (this.channelId && this.channelId === message.channelId) {\n // actual message\n if (this.listener) {\n this.listener(message);\n } else {\n this.messagesBuffer.push(message);\n }\n }\n }\n });\n } else {\n throw new Error('Error in child frame bridge: can\\\\'t access parent window.');\n }\n }\n\n register(handlerFunction, replayMissedMessages) {\n if (!this.listener) {\n this.listener = handlerFunction;\n if (replayMissedMessages) {\n this.messagesBuffer.forEach((message) => handlerFunction(message));\n }\n this.messagesBuffer = [];\n }\n }\n\n isValidMessage(message) {\n return !!message.action && !!message.version && !!message.channelId;\n }\n\n sendMessage(message) {\n if(this.handshakeDone) {\n window.parent.postMessage({...message, channelId: this.channelId}, '*');\n } else {\n this.queuedMessages.push(message);\n }\n }\n\n uuid() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n }\n\n const BRIDGE = new Bridge();\n </script>\n <script src='${scriptUrl}'></script>\n ${additionalHeader}\n </head>\n <body></body>\n</html>`;\n}\n","import {\n firstValueFrom,\n fromEvent,\n Observable,\n} from 'rxjs';\nimport {\n filter,\n map,\n share,\n timeout,\n} from 'rxjs/operators';\nimport {\n v4,\n} from 'uuid';\nimport {\n IFrameBridgeOptions,\n IframeMessage,\n InternalIframeMessage,\n} from './contracts';\nimport {\n IFRAME_BRIDGE_DEFAULT_OPTIONS,\n isSupportedMessage,\n} from './helpers';\n\n/**\n * Bridge that exposes an easy abstraction layer to communicate between a Host and an IFrame using the\n * postMessage API.\n */\nexport class IframeBridge {\n /** ID used to ensure that the Bridge only processes messages meant for this instance, since postMessage is global to the window. */\n private readonly channelId: string;\n\n /** Observable that emits all the messages received from the IFrame. */\n private readonly internalMessages$: Observable<InternalIframeMessage>;\n\n /** Promise that will resolve once the handshake has been completed, undefined if it's already done. */\n private readonly handshakePromise?: Promise<void>;\n\n /** Options to configure the behaviour of the Bridge. */\n private readonly options: IFrameBridgeOptions;\n\n /**\n * Observable that emits all the messages received from the IFrame and that are\n * not a direct response to a request.\n */\n public readonly messages$: Observable<InternalIframeMessage>;\n\n constructor(parent: Window, private readonly child: HTMLIFrameElement, options: Partial<IFrameBridgeOptions> = {}) {\n this.options = { ...IFRAME_BRIDGE_DEFAULT_OPTIONS, ...options };\n this.channelId = v4();\n this.internalMessages$ = fromEvent(parent, 'message').pipe(\n filter((event): event is MessageEvent<InternalIframeMessage> => {\n const messageEvent = event as MessageEvent<InternalIframeMessage>;\n return isSupportedMessage(messageEvent.data) && messageEvent.data.channelId === this.channelId;\n }),\n map((event) => event.data),\n share()\n );\n this.messages$ = this.internalMessages$.pipe(\n // Here we remove all the messages having an \"ID\" because they are bound to their corresponding request and\n // are returned directly by the function sendMessageAndWaitForResponse\n filter((message) => !message.id)\n );\n\n this.handshakePromise = this.handshake();\n }\n\n private async handshake() {\n for (let i = 0; i < this.options.handshakeTries; i++) {\n try {\n await this._sendMessageAndWaitForResponse({\n action: 'HANDSHAKE_PARENT',\n version: '1.0'\n }, this.options.handshakeTimeout);\n return;\n } catch {}\n }\n throw new Error('Handshake failed.');\n }\n\n private _sendMessage(message: IframeMessage, messageId?: string) {\n if (this.child.contentWindow) {\n this.child.contentWindow.postMessage({ ...message, channelId: this.channelId, id: messageId }, '*');\n }\n }\n\n private _sendMessageAndWaitForResponse(message: IframeMessage, timeoutMilliseconds: number = this.options.messageWithResponseTimeout) {\n const id = v4();\n const promise = firstValueFrom(\n this.internalMessages$.pipe(\n filter((response) => response.id === id),\n timeout(timeoutMilliseconds)\n )\n );\n void this._sendMessage(message, id);\n return promise;\n }\n\n /**\n * Method to send a message to the script run in the iframe\n * @param message message object\n * @param messageId message identifier\n */\n public async sendMessage(message: IframeMessage, messageId?: string) {\n await this.handshakePromise;\n this._sendMessage(message, messageId);\n }\n\n /**\n * Method to send a message to the script run in the iframe and wait for an answer\n * @param message\n * @param timeoutMilliseconds\n */\n public async sendMessageAndWaitForResponse(message: IframeMessage, timeoutMilliseconds: number = this.options.messageWithResponseTimeout) {\n await this.handshakePromise;\n return this._sendMessageAndWaitForResponse(message, timeoutMilliseconds);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -1,2 +1,182 @@
1
- export * from './bridge/index';
2
- //# sourceMappingURL=index.d.ts.map
1
+ import { Logger } from '@o3r/core';
2
+ import { Observable } from 'rxjs';
3
+
4
+ /**
5
+ * Shared interface with the A/B testing provider
6
+ */
7
+ interface AbTestBridgeInterface<T> {
8
+ /**
9
+ * Start an AB testing experiment
10
+ */
11
+ start(experiments: T | T[]): void;
12
+ /**
13
+ * Stop an AB testing experiment
14
+ */
15
+ stop(experiments?: T | T[]): void;
16
+ }
17
+ /**
18
+ * Configure the A/B testing script interfaces with the application
19
+ */
20
+ interface AbTestBridgeConfig {
21
+ /**
22
+ * Reference to communicate with the bridge from the window
23
+ * @default 'abTestBridge'
24
+ */
25
+ bridgeName: string;
26
+ /**
27
+ * Debug logger
28
+ * @default console
29
+ */
30
+ logger: Logger;
31
+ /**
32
+ * Event a third party can subscribe to before starting the communication with the bridge
33
+ * @default 'ab-test-ready'
34
+ */
35
+ readyEventName: string;
36
+ }
37
+ /**
38
+ * Bridge between the application and a third party A/B testing provider.
39
+ * Exposes a start and stop methods to allow the external script to set the list of experiments to run over the
40
+ * application.
41
+ *
42
+ * Share the resulting list of experiments with the rest of the application via an observable.
43
+ */
44
+ declare class AbTestBridge<T> implements AbTestBridgeInterface<T> {
45
+ private readonly isExperimentEqual;
46
+ /**
47
+ * Behaviour subject to control the experiments via the exposed interface
48
+ */
49
+ private readonly experimentSubject$;
50
+ /**
51
+ * Options to configure the communication between the AB Testing bridge and third parties
52
+ */
53
+ private readonly options;
54
+ /**
55
+ * Observable with the list of AB testing experiments currently applied
56
+ */
57
+ experiments$: Observable<T[]>;
58
+ /**
59
+ * AbTestBridge constructor
60
+ * @param isExperimentEqual check two different experiments match to identify an experiment to start or to stop
61
+ * @param options configure the communication with the A/B testing third party provider
62
+ */
63
+ constructor(isExperimentEqual: (value1?: T, value2?: T) => boolean, options?: Partial<AbTestBridgeConfig>);
64
+ /**
65
+ * Use configured logger to log AB testing related information
66
+ * @param args
67
+ */
68
+ private log;
69
+ /**
70
+ * @inheritDoc
71
+ */
72
+ start(experiments: T | T[]): void;
73
+ /**
74
+ * @inheritDoc
75
+ */
76
+ stop(experiments?: T | T[]): void;
77
+ }
78
+
79
+ /**
80
+ * Represents messages exchanged between host and iFrame.
81
+ */
82
+ interface IframeMessage {
83
+ /**
84
+ * String used to identify the type of action to perform.
85
+ */
86
+ action: string;
87
+ /**
88
+ * The version of the action, to allow for backward and forward compatibility.
89
+ */
90
+ version: string;
91
+ /**
92
+ * Payload of the message.
93
+ */
94
+ data?: unknown;
95
+ }
96
+ interface InternalIframeMessage extends IframeMessage {
97
+ /**
98
+ * The ID associated to the channel Host <-> iFrame
99
+ */
100
+ channelId: string;
101
+ /**
102
+ * ID used to handle messages that expect a response from the other party.
103
+ * No used for unidirectional messages.
104
+ */
105
+ id?: string;
106
+ }
107
+ /**
108
+ * Options that can be passed to configure an IFrameBridge
109
+ */
110
+ interface IFrameBridgeOptions {
111
+ /**
112
+ * Number of times the Host will try to handshake with the iFrame before failing.
113
+ */
114
+ handshakeTries: number;
115
+ /**
116
+ * For a given handshake try, how long will the host wait for an answer before considering
117
+ * that the try failed.
118
+ */
119
+ handshakeTimeout: number;
120
+ /**
121
+ * When sending a message that expects a response, how long should the Bridge wait before
122
+ * considering there will be answer.
123
+ */
124
+ messageWithResponseTimeout: number;
125
+ }
126
+
127
+ /**
128
+ * Bridge that exposes an easy abstraction layer to communicate between a Host and an IFrame using the
129
+ * postMessage API.
130
+ */
131
+ declare class IframeBridge {
132
+ private readonly child;
133
+ /** ID used to ensure that the Bridge only processes messages meant for this instance, since postMessage is global to the window. */
134
+ private readonly channelId;
135
+ /** Observable that emits all the messages received from the IFrame. */
136
+ private readonly internalMessages$;
137
+ /** Promise that will resolve once the handshake has been completed, undefined if it's already done. */
138
+ private readonly handshakePromise?;
139
+ /** Options to configure the behaviour of the Bridge. */
140
+ private readonly options;
141
+ /**
142
+ * Observable that emits all the messages received from the IFrame and that are
143
+ * not a direct response to a request.
144
+ */
145
+ readonly messages$: Observable<InternalIframeMessage>;
146
+ constructor(parent: Window, child: HTMLIFrameElement, options?: Partial<IFrameBridgeOptions>);
147
+ private handshake;
148
+ private _sendMessage;
149
+ private _sendMessageAndWaitForResponse;
150
+ /**
151
+ * Method to send a message to the script run in the iframe
152
+ * @param message message object
153
+ * @param messageId message identifier
154
+ */
155
+ sendMessage(message: IframeMessage, messageId?: string): Promise<void>;
156
+ /**
157
+ * Method to send a message to the script run in the iframe and wait for an answer
158
+ * @param message
159
+ * @param timeoutMilliseconds
160
+ */
161
+ sendMessageAndWaitForResponse(message: IframeMessage, timeoutMilliseconds?: number): Promise<InternalIframeMessage>;
162
+ }
163
+
164
+ /**
165
+ * Default options for an IFrameBridge
166
+ */
167
+ declare const IFRAME_BRIDGE_DEFAULT_OPTIONS: Readonly<IFrameBridgeOptions>;
168
+ /**
169
+ * Verifies if a message respects the format expected by an IFrameBridge
170
+ * @param message
171
+ */
172
+ declare function isSupportedMessage(message: any): message is InternalIframeMessage;
173
+ /**
174
+ * Generates the html content of an iframe
175
+ * @param scriptUrl script to be executed inside the iframe
176
+ * @param additionalHeader custom html headers stringified
177
+ */
178
+ declare function generateIFrameContent(scriptUrl: string, additionalHeader?: string): string;
179
+
180
+ export { AbTestBridge, IFRAME_BRIDGE_DEFAULT_OPTIONS, IframeBridge, generateIFrameContent, isSupportedMessage };
181
+ export type { AbTestBridgeConfig, AbTestBridgeInterface, IFrameBridgeOptions, IframeMessage, InternalIframeMessage };
182
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sources":["../src/bridge/ab-testing/ab-testing-bridge.ts","../src/bridge/iframe/contracts.ts","../src/bridge/iframe/bridge.ts","../src/bridge/iframe/helpers.ts"],"sourcesContent":[null,null,null,null],"names":[],"mappings":";;;AASA;;AAEG;AACG;AACJ;;AAEG;;AAEH;;AAEG;;AAEJ;AAED;;AAEG;;AAED;;;AAGG;;AAEH;;;AAGG;;AAEH;;;AAGG;;AAEJ;AAWD;;;;;;AAMG;AACH;;AACE;;AAEG;AACH;AACA;;AAEG;AACH;AACA;;AAEG;AACI;AAEP;;;;AAIG;;AAmBH;;;AAGG;AACH;AAIA;;AAEG;AACI;AAYP;;AAEG;AACI;AAYR;;ACvID;;AAEG;;AAED;;AAEG;;AAGH;;AAEG;;AAGH;;AAEG;;AAEJ;AAEK;AACJ;;AAEG;;AAGH;;;AAGG;;AAEJ;AAED;;AAEG;;AAED;;AAEG;;AAGH;;;AAGG;;AAGH;;;AAGG;;AAEJ;;AC7BD;;;AAGG;AACH;;;AAEE;;AAGA;;AAGA;;AAGA;AAEA;;;AAGG;AACH;AAEY;;AAiCZ;AAMA;AAYA;;;;AAIG;;AAMH;;;;AAIG;AACU;AAId;;AChHD;;AAEG;AACH;AAMA;;;AAGG;AACH;AAOA;;;;AAIG;AACH;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o3r/third-party",
3
- "version": "13.0.0-next.0",
3
+ "version": "13.0.0-next.10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -12,18 +12,18 @@
12
12
  "otter-module"
13
13
  ],
14
14
  "dependencies": {
15
- "@o3r/schematics": "^13.0.0-next.0",
15
+ "@o3r/schematics": "^13.0.0-next.10",
16
16
  "tslib": "^2.6.2",
17
17
  "uuid": "^11.0.5"
18
18
  },
19
19
  "peerDependencies": {
20
- "@angular-devkit/schematics": "^19.0.0",
21
- "@o3r/core": "^13.0.0-next.0",
22
- "@o3r/schematics": "^13.0.0-next.0",
23
- "@schematics/angular": "^19.0.0",
20
+ "@angular-devkit/schematics": "^20.0.0",
21
+ "@o3r/core": "^13.0.0-next.10",
22
+ "@o3r/schematics": "^13.0.0-next.10",
23
+ "@schematics/angular": "^20.0.0",
24
24
  "rxjs": "^7.8.1",
25
25
  "type-fest": "^4.30.1",
26
- "typescript": "^5.5.4"
26
+ "typescript": "^5.8.0"
27
27
  },
28
28
  "peerDependenciesMeta": {
29
29
  "@angular-devkit/schematics": {
@@ -46,7 +46,7 @@
46
46
  }
47
47
  },
48
48
  "engines": {
49
- "node": "^20.11.1 || >=22.0.0"
49
+ "node": "^20.19.0 || ^22.12.0 || ^24.0.0"
50
50
  },
51
51
  "schematics": "./collection.json",
52
52
  "module": "fesm2022/o3r-third-party.mjs",
@@ -1,77 +0,0 @@
1
- import type { Logger } from '@o3r/core';
2
- import { Observable } from 'rxjs';
3
- /**
4
- * Shared interface with the A/B testing provider
5
- */
6
- export interface AbTestBridgeInterface<T> {
7
- /**
8
- * Start an AB testing experiment
9
- */
10
- start(experiments: T | T[]): void;
11
- /**
12
- * Stop an AB testing experiment
13
- */
14
- stop(experiments?: T | T[]): void;
15
- }
16
- /**
17
- * Configure the A/B testing script interfaces with the application
18
- */
19
- export interface AbTestBridgeConfig {
20
- /**
21
- * Reference to communicate with the bridge from the window
22
- * @default 'abTestBridge'
23
- */
24
- bridgeName: string;
25
- /**
26
- * Debug logger
27
- * @default console
28
- */
29
- logger: Logger;
30
- /**
31
- * Event a third party can subscribe to before starting the communication with the bridge
32
- * @default 'ab-test-ready'
33
- */
34
- readyEventName: string;
35
- }
36
- /**
37
- * Bridge between the application and a third party A/B testing provider.
38
- * Exposes a start and stop methods to allow the external script to set the list of experiments to run over the
39
- * application.
40
- *
41
- * Share the resulting list of experiments with the rest of the application via an observable.
42
- */
43
- export declare class AbTestBridge<T> implements AbTestBridgeInterface<T> {
44
- private readonly isExperimentEqual;
45
- /**
46
- * Behaviour subject to control the experiments via the exposed interface
47
- */
48
- private readonly experimentSubject$;
49
- /**
50
- * Options to configure the communication between the AB Testing bridge and third parties
51
- */
52
- private readonly options;
53
- /**
54
- * Observable with the list of AB testing experiments currently applied
55
- */
56
- experiments$: Observable<T[]>;
57
- /**
58
- * AbTestBridge constructor
59
- * @param isExperimentEqual check two different experiments match to identify an experiment to start or to stop
60
- * @param options configure the communication with the A/B testing third party provider
61
- */
62
- constructor(isExperimentEqual: (value1?: T, value2?: T) => boolean, options?: Partial<AbTestBridgeConfig>);
63
- /**
64
- * Use configured logger to log AB testing related information
65
- * @param args
66
- */
67
- private log;
68
- /**
69
- * @inheritDoc
70
- */
71
- start(experiments: T | T[]): void;
72
- /**
73
- * @inheritDoc
74
- */
75
- stop(experiments?: T | T[]): void;
76
- }
77
- //# sourceMappingURL=ab-testing-bridge.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ab-testing-bridge.d.ts","sourceRoot":"","sources":["../../../src/bridge/ab-testing/ab-testing-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACP,MAAM,WAAW,CAAC;AACnB,OAAO,EAGL,UAAU,EACX,MAAM,MAAM,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC;;OAEG;IACH,KAAK,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC;IAClC;;OAEG;IACH,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;CACxB;AAWD;;;;;;GAMG;AACH,qBAAa,YAAY,CAAC,CAAC,CAAE,YAAW,qBAAqB,CAAC,CAAC,CAAC;IAmBlD,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAlB9C;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAsD;IACzF;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C;;OAEG;IACI,YAAY,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC;IAErC;;;;OAIG;gBAC0B,iBAAiB,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC;IAkB1H;;;OAGG;IACH,OAAO,CAAC,GAAG;IAIX;;OAEG;IACI,KAAK,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,EAAE;IAYjC;;OAEG;IACI,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE;CAYlC"}
@@ -1,2 +0,0 @@
1
- export * from './ab-testing-bridge';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bridge/ab-testing/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC"}
@@ -1,39 +0,0 @@
1
- import { Observable } from 'rxjs';
2
- import { IFrameBridgeOptions, IframeMessage, InternalIframeMessage } from './contracts';
3
- /**
4
- * Bridge that exposes an easy abstraction layer to communicate between a Host and an IFrame using the
5
- * postMessage API.
6
- */
7
- export declare class IframeBridge {
8
- private readonly child;
9
- /** ID used to ensure that the Bridge only processes messages meant for this instance, since postMessage is global to the window. */
10
- private readonly channelId;
11
- /** Observable that emits all the messages received from the IFrame. */
12
- private readonly internalMessages$;
13
- /** Promise that will resolve once the handshake has been completed, undefined if it's already done. */
14
- private readonly handshakePromise?;
15
- /** Options to configure the behaviour of the Bridge. */
16
- private readonly options;
17
- /**
18
- * Observable that emits all the messages received from the IFrame and that are
19
- * not a direct response to a request.
20
- */
21
- readonly messages$: Observable<InternalIframeMessage>;
22
- constructor(parent: Window, child: HTMLIFrameElement, options?: Partial<IFrameBridgeOptions>);
23
- private handshake;
24
- private _sendMessage;
25
- private _sendMessageAndWaitForResponse;
26
- /**
27
- * Method to send a message to the script run in the iframe
28
- * @param message message object
29
- * @param messageId message identifier
30
- */
31
- sendMessage(message: IframeMessage, messageId?: string): Promise<void>;
32
- /**
33
- * Method to send a message to the script run in the iframe and wait for an answer
34
- * @param message
35
- * @param timeoutMilliseconds
36
- */
37
- sendMessageAndWaitForResponse(message: IframeMessage, timeoutMilliseconds?: number): Promise<InternalIframeMessage>;
38
- }
39
- //# sourceMappingURL=bridge.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../src/bridge/iframe/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EACX,MAAM,MAAM,CAAC;AAUd,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAMrB;;;GAGG;AACH,qBAAa,YAAY;IAmBK,OAAO,CAAC,QAAQ,CAAC,KAAK;IAlBlD,oIAAoI;IACpI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,uEAAuE;IACvE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoC;IAEtE,uGAAuG;IACvG,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAgB;IAElD,wDAAwD;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAE9C;;;OAGG;IACH,SAAgB,SAAS,EAAE,UAAU,CAAC,qBAAqB,CAAC,CAAC;gBAEjD,MAAM,EAAE,MAAM,EAAmB,KAAK,EAAE,iBAAiB,EAAE,OAAO,GAAE,OAAO,CAAC,mBAAmB,CAAM;YAoBnG,SAAS;IAavB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,8BAA8B;IAYtC;;;;OAIG;IACU,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,MAAM;IAKnE;;;;OAIG;IACU,6BAA6B,CAAC,OAAO,EAAE,aAAa,EAAE,mBAAmB,GAAE,MAAgD;CAIzI"}
@@ -1,48 +0,0 @@
1
- /**
2
- * Represents messages exchanged between host and iFrame.
3
- */
4
- export interface IframeMessage {
5
- /**
6
- * String used to identify the type of action to perform.
7
- */
8
- action: string;
9
- /**
10
- * The version of the action, to allow for backward and forward compatibility.
11
- */
12
- version: string;
13
- /**
14
- * Payload of the message.
15
- */
16
- data?: unknown;
17
- }
18
- export interface InternalIframeMessage extends IframeMessage {
19
- /**
20
- * The ID associated to the channel Host <-> iFrame
21
- */
22
- channelId: string;
23
- /**
24
- * ID used to handle messages that expect a response from the other party.
25
- * No used for unidirectional messages.
26
- */
27
- id?: string;
28
- }
29
- /**
30
- * Options that can be passed to configure an IFrameBridge
31
- */
32
- export interface IFrameBridgeOptions {
33
- /**
34
- * Number of times the Host will try to handshake with the iFrame before failing.
35
- */
36
- handshakeTries: number;
37
- /**
38
- * For a given handshake try, how long will the host wait for an answer before considering
39
- * that the try failed.
40
- */
41
- handshakeTimeout: number;
42
- /**
43
- * When sending a message that expects a response, how long should the Bridge wait before
44
- * considering there will be answer.
45
- */
46
- messageWithResponseTimeout: number;
47
- }
48
- //# sourceMappingURL=contracts.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../../src/bridge/iframe/contracts.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,0BAA0B,EAAE,MAAM,CAAC;CACpC"}
@@ -1,17 +0,0 @@
1
- import { IFrameBridgeOptions, InternalIframeMessage } from './contracts';
2
- /**
3
- * Default options for an IFrameBridge
4
- */
5
- export declare const IFRAME_BRIDGE_DEFAULT_OPTIONS: Readonly<IFrameBridgeOptions>;
6
- /**
7
- * Verifies if a message respects the format expected by an IFrameBridge
8
- * @param message
9
- */
10
- export declare function isSupportedMessage(message: any): message is InternalIframeMessage;
11
- /**
12
- * Generates the html content of an iframe
13
- * @param scriptUrl script to be executed inside the iframe
14
- * @param additionalHeader custom html headers stringified
15
- */
16
- export declare function generateIFrameContent(scriptUrl: string, additionalHeader?: string): string;
17
- //# sourceMappingURL=helpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/bridge/iframe/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,eAAO,MAAM,6BAA6B,EAAE,QAAQ,CAAC,mBAAmB,CAI9D,CAAC;AAEX;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,IAAI,qBAAqB,CAKjF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,SAAK,UA8E7E"}
@@ -1,4 +0,0 @@
1
- export * from './bridge';
2
- export * from './contracts';
3
- export * from './helpers';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bridge/iframe/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
package/bridge/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './ab-testing/index';
2
- export * from './iframe/index';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"o3r-third-party.d.ts","sourceRoot":"","sources":["../src/o3r-third-party.ts"],"names":[],"mappings":"AAAA;;GAEG;;AAEH,cAAc,SAAS,CAAC"}