@schibsted/pulse-tracker-proxy 1.0.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.
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ALLOWED_METHODS = exports.MESSAGE_TYPE_RETURN_ERROR = exports.MESSAGE_TYPE_RETURN = exports.MESSAGE_TYPE_INVOKE = exports.MESSAGE_TYPE_ACK = exports.MESSAGE_TYPE_REQUEST = exports.MESSAGE_PREFIX = void 0;
4
+ exports.isPulseProxyMessage = isPulseProxyMessage;
5
+ // Sync the content between files below this line:
6
+ exports.MESSAGE_PREFIX = 'pulse-tracker-proxy';
7
+ exports.MESSAGE_TYPE_REQUEST = `${exports.MESSAGE_PREFIX}:request`;
8
+ exports.MESSAGE_TYPE_ACK = `${exports.MESSAGE_PREFIX}:ack`;
9
+ exports.MESSAGE_TYPE_INVOKE = `${exports.MESSAGE_PREFIX}:invoke`;
10
+ exports.MESSAGE_TYPE_RETURN = `${exports.MESSAGE_PREFIX}:return`;
11
+ exports.MESSAGE_TYPE_RETURN_ERROR = `${exports.MESSAGE_PREFIX}:return-error`;
12
+ exports.ALLOWED_METHODS = ['track'];
13
+ function isPulseProxyMessage(message) {
14
+ return (message !== null &&
15
+ typeof message === 'object' &&
16
+ 'type' in message &&
17
+ typeof message.type === 'string' &&
18
+ message.type.startsWith(exports.MESSAGE_PREFIX));
19
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPulseTrackerProxy = getPulseTrackerProxy;
4
+ const consts_1 = require("./consts");
5
+ const load_autotracker_1 = require("./load-autotracker");
6
+ const send_message_1 = require("./send-message");
7
+ const tracker_proxy_1 = require("./tracker-proxy");
8
+ /**
9
+ * Pulse Tracker Proxy System Architecture
10
+ * --------------------------------------
11
+ * The proxy system allows for cross-frame/cross-window tracking events without directly exposing
12
+ * the tracking API to child frames. It works in two modes:
13
+ *
14
+ * 1. Mobile App Webview Mode:
15
+ * - Detects if running in a mobile app webview by checking for `pulseEventHandler`
16
+ * - Loads the Pulse autotracker with the specified version and provider
17
+ * - Directly communicates with the native app's tracking system
18
+ *
19
+ * 2. Iframe/Cross-window Mode:
20
+ * - Establishes a connection with the parent window (or specified target)
21
+ * - Uses postMessage for communication
22
+ * - Handles timeouts, retries, and error cases for reliable messaging
23
+ * - Proxies tracking calls to the parent window which can then handle them
24
+ *
25
+ * The system adds a proxy version to all events to help with debugging and tracking.
26
+ */
27
+ /**
28
+ * Establishes a connection with the target frame and returns a pulse tracker proxy object.
29
+ * This function automatically detects whether it's running in a mobile app webview or
30
+ * needs to use iframe/cross-window communication.
31
+ *
32
+ * Usage:
33
+ * ```typescript
34
+ * const tracker = await getPulseTrackerProxy({
35
+ * provider: 'my-provider',
36
+ * debug: console.log
37
+ * });
38
+ *
39
+ * tracker.track('trackerEvent', { ... });
40
+ * ```
41
+ *
42
+ * @param options - Configuration options for the pulse tracker proxy
43
+ * @returns A promise that resolves to a proxy object implementing the Pulse tracker interface
44
+ * @throws Error if connection fails, timeout occurs, or required options are missing
45
+ */
46
+ function getPulseTrackerProxy({ provider, autotrackerVersion = '3', window = globalThis.window, target = window.parent, timeout = 5000, retries = 3, retryTimeout = 1000, debug = () => { }, } = {}) {
47
+ debug('[Pulse Tracker Proxy] Checking if `pulseEventHandler` is provided in the current window...');
48
+ if (window.pulseEventHandler) {
49
+ debug(`[Pulse Tracker Proxy] \`pulseEventHandler\` found in the current window. Assuming it is the app webview. Loading autotracker version ${autotrackerVersion}...`);
50
+ if (!provider) {
51
+ debug('[Pulse Tracker Proxy] `provider` is not set - cannot create pulse tracker.');
52
+ return Promise.reject('`provider` is not set - cannot create pulse tracker.');
53
+ }
54
+ return (0, load_autotracker_1.loadAutotracker)(window, provider, autotrackerVersion);
55
+ }
56
+ debug('[Pulse Tracker Proxy] `pulseEventHandler` not found in the current window - not a mobile app webview.');
57
+ debug('[Pulse Tracker Proxy] Trying to establish connection...', { target, timeout, retries });
58
+ if (!target) {
59
+ return Promise.reject(new Error('Target window not provided. Possibly window.parent is not available.'));
60
+ }
61
+ let ackTimeout;
62
+ let alreadyTimedOut = false;
63
+ const timeoutPromise = new Promise((_, reject) => {
64
+ ackTimeout = setTimeout(() => {
65
+ alreadyTimedOut = true;
66
+ debug('[Pulse Tracker Proxy] Ack timeout. Sorry, target seems to not support the proxy.');
67
+ reject(new Error(`Pulse tracker proxy not found within ${timeout} ms`));
68
+ }, timeout);
69
+ });
70
+ const connectionPromise = (async () => {
71
+ for (let i = 0; i < retries; i++) {
72
+ if (alreadyTimedOut) {
73
+ debug('[Pulse Tracker Proxy] Configured timeout has passed before the next attempt');
74
+ throw new Error('Configured timeout has passed before the next attempt.');
75
+ }
76
+ try {
77
+ debug(`[Pulse Tracker Proxy] Sending request to target window (attempt: ${i + 1})...`);
78
+ await (0, send_message_1.sendMessage)({ type: consts_1.MESSAGE_TYPE_REQUEST }, { target, timeout: retryTimeout });
79
+ }
80
+ catch (error) {
81
+ debug(`[Pulse Tracker Proxy] Ack Request failed: ${error}`);
82
+ continue;
83
+ }
84
+ clearTimeout(ackTimeout);
85
+ debug(`[Pulse Tracker Proxy] Ack Request successful (attempt: ${i + 1})`);
86
+ return new tracker_proxy_1.PulseTrackerProxyImpl(target, debug);
87
+ }
88
+ clearTimeout(ackTimeout);
89
+ debug('[Pulse Tracker Proxy] Retries exhausted. Target did not respond. Failing.');
90
+ throw new Error('Failed to establish connection with pulse tracker proxy');
91
+ })();
92
+ return Promise.race([timeoutPromise, connectionPromise]);
93
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./get-proxy"), exports);
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadAutotracker = loadAutotracker;
4
+ /**
5
+ * Loads the Pulse autotracker script onto the page and initializes it with the provided configuration.
6
+ *
7
+ * @param window - The browser window object to attach the script to
8
+ * @param provider - The provider ID to initialize the Pulse tracker with
9
+ * @param version - The version of the Pulse autotracker to load
10
+ * @returns A promise that resolves to the configured Tracker instance
11
+ * @throws Error if script loading fails or Pulse doesn't initialize correctly
12
+ */
13
+ async function loadAutotracker(window, provider, version) {
14
+ await new Promise((resolve, reject) => {
15
+ const element = window.document.createElement('script');
16
+ element.src = `//sdk.pulse.schibsted.com/versioned/${version}/pulse.min.js`;
17
+ element.async = true;
18
+ element.onload = resolve;
19
+ element.onerror = () => {
20
+ reject(new Error(`Failed to load autotracker script from src: ${element.src}`));
21
+ };
22
+ window.document.head.appendChild(element);
23
+ });
24
+ return new Promise((resolve, reject) => {
25
+ if (!window.pulse) {
26
+ reject(new Error('Pulse autotracker did not load correctly!'));
27
+ return;
28
+ }
29
+ window.pulse('init', provider);
30
+ window.pulse((tracker) => {
31
+ resolve(tracker);
32
+ });
33
+ });
34
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendMessage = sendMessage;
4
+ const consts_1 = require("./consts");
5
+ /**
6
+ * Sends a message to a target window and returns a promise that resolves with the response.
7
+ *
8
+ * This function creates a MessageChannel to facilitate two-way communication with the target.
9
+ * It handles timeouts and error responses appropriately.
10
+ *
11
+ * @param message - The message object to send to the target window
12
+ * @param options - Configuration options for sending the message
13
+ * @param options.target - The target window to send the message to (e.g., iframe.contentWindow)
14
+ * @param options.timeout - Optional timeout in milliseconds after which the promise will reject
15
+ * @param options.debug - Optional debug function for logging message events
16
+ * @returns A promise that resolves with the response message or rejects with an error
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { sendMessage } from './send-message';
21
+ *
22
+ * const message = { type: 'SOME_MESSAGE_TYPE', payload: { ... } };
23
+ * sendMessage(message, {
24
+ * target: document.querySelector('iframe').contentWindow,
25
+ * timeout: 5000,
26
+ * debug: console.log
27
+ * }).then(response => {
28
+ * // Handle response
29
+ * }).catch(error => {
30
+ * // Handle error
31
+ * });
32
+ * ```
33
+ */
34
+ function sendMessage(message, { target, timeout, debug = () => { } }) {
35
+ return new Promise((resolve, reject) => {
36
+ const channel = new MessageChannel();
37
+ const requestTimeout = timeout
38
+ ? setTimeout(() => {
39
+ channel.port1.close();
40
+ reject(new Error(`Message of type ${message.type} timed out`));
41
+ }, timeout)
42
+ : undefined;
43
+ const responseHandler = (event) => {
44
+ debug('[Pulse Tracker Proxy] Received the response on the MessageChannel', event);
45
+ if (!(0, consts_1.isPulseProxyMessage)(event.data)) {
46
+ debug('[Pulse Tracker Proxy] Response was not a valid Proxy Message', event.data);
47
+ return;
48
+ }
49
+ if (event.data.type === consts_1.MESSAGE_TYPE_RETURN_ERROR) {
50
+ debug('[Pulse Tracker Proxy] Received an return error response', event.data.error);
51
+ clearTimeout(requestTimeout);
52
+ channel.port1.close();
53
+ reject(new Error(event.data.error));
54
+ }
55
+ debug('[Pulse Tracker Proxy] Received a valid response', event.data);
56
+ clearTimeout(requestTimeout);
57
+ channel.port1.close();
58
+ resolve(event.data);
59
+ };
60
+ channel.port1.onmessage = responseHandler;
61
+ debug('[Pulse Tracker Proxy] Sending message to target window', message);
62
+ target.postMessage(message, '*', [channel.port2]);
63
+ });
64
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PulseTrackerProxyImpl = void 0;
7
+ const consts_1 = require("./consts");
8
+ const send_message_1 = require("./send-message");
9
+ const version_json_1 = __importDefault(require("./version.json"));
10
+ /**
11
+ * Adds proxy version information to the event input.
12
+ * This helps with debugging and allows tracking which proxy version was used for the event.
13
+ *
14
+ * @param input - The original event input to be decorated
15
+ * @returns The input with added proxyVersion in the tracker object
16
+ */
17
+ function decorateWithProxyVersion(input) {
18
+ const tracker = 'tracker' in input && typeof input.tracker === 'object' ? input.tracker : {};
19
+ return {
20
+ ...input,
21
+ tracker: { ...tracker, proxyVersion: version_json_1.default.PROXY_VERSION },
22
+ };
23
+ }
24
+ /**
25
+ * Implementation of the Pulse Tracker Proxy that forwards tracking calls to a target window.
26
+ * This class handles the communication between frames/windows and ensures proper message handling.
27
+ */
28
+ class PulseTrackerProxyImpl {
29
+ constructor(target, debug) {
30
+ this.target = target;
31
+ this.debug = debug;
32
+ }
33
+ /**
34
+ * Tracks an event by forwarding the call to the target window.
35
+ * Automatically decorates the event with proxy version information.
36
+ *
37
+ * @param args - Arguments that would normally be passed to the Pulse tracker's track method
38
+ * @returns A promise that resolves with the response from the target window
39
+ */
40
+ track(...args) {
41
+ this.debug('[Pulse Tracker Proxy] Calling `track`', args);
42
+ const [eventType, input, ...rest] = args;
43
+ const modifiedArgs = [eventType, decorateWithProxyVersion(input), ...rest];
44
+ return this.invoke({ method: 'track', args: modifiedArgs });
45
+ }
46
+ /**
47
+ * Invokes a remote function in the target window and handles the response.
48
+ *
49
+ * @param invocation - The invocation details including method name and arguments
50
+ * @returns A promise that resolves with the response payload or rejects with an error
51
+ * @private
52
+ */
53
+ async invoke(invocation) {
54
+ this.debug('[Pulse Tracker Proxy] Invoking remote function', invocation);
55
+ const response = await (0, send_message_1.sendMessage)({ type: consts_1.MESSAGE_TYPE_INVOKE, payload: invocation }, { target: this.target });
56
+ if (!(0, consts_1.isPulseProxyMessage)(response)) {
57
+ this.debug('[Pulse Tracker Proxy] Invalid response from proxy', response);
58
+ throw new Error('Invalid response from proxy');
59
+ }
60
+ if (response.type === consts_1.MESSAGE_TYPE_RETURN_ERROR) {
61
+ this.debug('[Pulse Tracker Proxy] Error response from proxy', response.error);
62
+ throw new Error(response.error);
63
+ }
64
+ if (response.type === consts_1.MESSAGE_TYPE_RETURN) {
65
+ this.debug('[Pulse Tracker Proxy] Successful response from proxy', response.payload);
66
+ return response.payload;
67
+ }
68
+ this.debug('[Pulse Tracker Proxy] Unhandled response from proxy', response);
69
+ throw new Error('Unexpected response from proxy');
70
+ }
71
+ }
72
+ exports.PulseTrackerProxyImpl = PulseTrackerProxyImpl;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ { "PROXY_VERSION": "1.0.0", "GIT_COMMIT": "6ca09707" }
@@ -0,0 +1,15 @@
1
+ // Sync the content between files below this line:
2
+ export const MESSAGE_PREFIX = 'pulse-tracker-proxy';
3
+ export const MESSAGE_TYPE_REQUEST = `${MESSAGE_PREFIX}:request`;
4
+ export const MESSAGE_TYPE_ACK = `${MESSAGE_PREFIX}:ack`;
5
+ export const MESSAGE_TYPE_INVOKE = `${MESSAGE_PREFIX}:invoke`;
6
+ export const MESSAGE_TYPE_RETURN = `${MESSAGE_PREFIX}:return`;
7
+ export const MESSAGE_TYPE_RETURN_ERROR = `${MESSAGE_PREFIX}:return-error`;
8
+ export const ALLOWED_METHODS = ['track'];
9
+ export function isPulseProxyMessage(message) {
10
+ return (message !== null &&
11
+ typeof message === 'object' &&
12
+ 'type' in message &&
13
+ typeof message.type === 'string' &&
14
+ message.type.startsWith(MESSAGE_PREFIX));
15
+ }
@@ -0,0 +1,90 @@
1
+ import { MESSAGE_TYPE_REQUEST } from './consts';
2
+ import { loadAutotracker } from './load-autotracker';
3
+ import { sendMessage } from './send-message';
4
+ import { PulseTrackerProxyImpl } from './tracker-proxy';
5
+ /**
6
+ * Pulse Tracker Proxy System Architecture
7
+ * --------------------------------------
8
+ * The proxy system allows for cross-frame/cross-window tracking events without directly exposing
9
+ * the tracking API to child frames. It works in two modes:
10
+ *
11
+ * 1. Mobile App Webview Mode:
12
+ * - Detects if running in a mobile app webview by checking for `pulseEventHandler`
13
+ * - Loads the Pulse autotracker with the specified version and provider
14
+ * - Directly communicates with the native app's tracking system
15
+ *
16
+ * 2. Iframe/Cross-window Mode:
17
+ * - Establishes a connection with the parent window (or specified target)
18
+ * - Uses postMessage for communication
19
+ * - Handles timeouts, retries, and error cases for reliable messaging
20
+ * - Proxies tracking calls to the parent window which can then handle them
21
+ *
22
+ * The system adds a proxy version to all events to help with debugging and tracking.
23
+ */
24
+ /**
25
+ * Establishes a connection with the target frame and returns a pulse tracker proxy object.
26
+ * This function automatically detects whether it's running in a mobile app webview or
27
+ * needs to use iframe/cross-window communication.
28
+ *
29
+ * Usage:
30
+ * ```typescript
31
+ * const tracker = await getPulseTrackerProxy({
32
+ * provider: 'my-provider',
33
+ * debug: console.log
34
+ * });
35
+ *
36
+ * tracker.track('trackerEvent', { ... });
37
+ * ```
38
+ *
39
+ * @param options - Configuration options for the pulse tracker proxy
40
+ * @returns A promise that resolves to a proxy object implementing the Pulse tracker interface
41
+ * @throws Error if connection fails, timeout occurs, or required options are missing
42
+ */
43
+ export function getPulseTrackerProxy({ provider, autotrackerVersion = '3', window = globalThis.window, target = window.parent, timeout = 5000, retries = 3, retryTimeout = 1000, debug = () => { }, } = {}) {
44
+ debug('[Pulse Tracker Proxy] Checking if `pulseEventHandler` is provided in the current window...');
45
+ if (window.pulseEventHandler) {
46
+ debug(`[Pulse Tracker Proxy] \`pulseEventHandler\` found in the current window. Assuming it is the app webview. Loading autotracker version ${autotrackerVersion}...`);
47
+ if (!provider) {
48
+ debug('[Pulse Tracker Proxy] `provider` is not set - cannot create pulse tracker.');
49
+ return Promise.reject('`provider` is not set - cannot create pulse tracker.');
50
+ }
51
+ return loadAutotracker(window, provider, autotrackerVersion);
52
+ }
53
+ debug('[Pulse Tracker Proxy] `pulseEventHandler` not found in the current window - not a mobile app webview.');
54
+ debug('[Pulse Tracker Proxy] Trying to establish connection...', { target, timeout, retries });
55
+ if (!target) {
56
+ return Promise.reject(new Error('Target window not provided. Possibly window.parent is not available.'));
57
+ }
58
+ let ackTimeout;
59
+ let alreadyTimedOut = false;
60
+ const timeoutPromise = new Promise((_, reject) => {
61
+ ackTimeout = setTimeout(() => {
62
+ alreadyTimedOut = true;
63
+ debug('[Pulse Tracker Proxy] Ack timeout. Sorry, target seems to not support the proxy.');
64
+ reject(new Error(`Pulse tracker proxy not found within ${timeout} ms`));
65
+ }, timeout);
66
+ });
67
+ const connectionPromise = (async () => {
68
+ for (let i = 0; i < retries; i++) {
69
+ if (alreadyTimedOut) {
70
+ debug('[Pulse Tracker Proxy] Configured timeout has passed before the next attempt');
71
+ throw new Error('Configured timeout has passed before the next attempt.');
72
+ }
73
+ try {
74
+ debug(`[Pulse Tracker Proxy] Sending request to target window (attempt: ${i + 1})...`);
75
+ await sendMessage({ type: MESSAGE_TYPE_REQUEST }, { target, timeout: retryTimeout });
76
+ }
77
+ catch (error) {
78
+ debug(`[Pulse Tracker Proxy] Ack Request failed: ${error}`);
79
+ continue;
80
+ }
81
+ clearTimeout(ackTimeout);
82
+ debug(`[Pulse Tracker Proxy] Ack Request successful (attempt: ${i + 1})`);
83
+ return new PulseTrackerProxyImpl(target, debug);
84
+ }
85
+ clearTimeout(ackTimeout);
86
+ debug('[Pulse Tracker Proxy] Retries exhausted. Target did not respond. Failing.');
87
+ throw new Error('Failed to establish connection with pulse tracker proxy');
88
+ })();
89
+ return Promise.race([timeoutPromise, connectionPromise]);
90
+ }
@@ -0,0 +1 @@
1
+ export * from './get-proxy';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Loads the Pulse autotracker script onto the page and initializes it with the provided configuration.
3
+ *
4
+ * @param window - The browser window object to attach the script to
5
+ * @param provider - The provider ID to initialize the Pulse tracker with
6
+ * @param version - The version of the Pulse autotracker to load
7
+ * @returns A promise that resolves to the configured Tracker instance
8
+ * @throws Error if script loading fails or Pulse doesn't initialize correctly
9
+ */
10
+ export async function loadAutotracker(window, provider, version) {
11
+ await new Promise((resolve, reject) => {
12
+ const element = window.document.createElement('script');
13
+ element.src = `//sdk.pulse.schibsted.com/versioned/${version}/pulse.min.js`;
14
+ element.async = true;
15
+ element.onload = resolve;
16
+ element.onerror = () => {
17
+ reject(new Error(`Failed to load autotracker script from src: ${element.src}`));
18
+ };
19
+ window.document.head.appendChild(element);
20
+ });
21
+ return new Promise((resolve, reject) => {
22
+ if (!window.pulse) {
23
+ reject(new Error('Pulse autotracker did not load correctly!'));
24
+ return;
25
+ }
26
+ window.pulse('init', provider);
27
+ window.pulse((tracker) => {
28
+ resolve(tracker);
29
+ });
30
+ });
31
+ }
@@ -0,0 +1,61 @@
1
+ import { isPulseProxyMessage, MESSAGE_TYPE_RETURN_ERROR } from './consts';
2
+ /**
3
+ * Sends a message to a target window and returns a promise that resolves with the response.
4
+ *
5
+ * This function creates a MessageChannel to facilitate two-way communication with the target.
6
+ * It handles timeouts and error responses appropriately.
7
+ *
8
+ * @param message - The message object to send to the target window
9
+ * @param options - Configuration options for sending the message
10
+ * @param options.target - The target window to send the message to (e.g., iframe.contentWindow)
11
+ * @param options.timeout - Optional timeout in milliseconds after which the promise will reject
12
+ * @param options.debug - Optional debug function for logging message events
13
+ * @returns A promise that resolves with the response message or rejects with an error
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { sendMessage } from './send-message';
18
+ *
19
+ * const message = { type: 'SOME_MESSAGE_TYPE', payload: { ... } };
20
+ * sendMessage(message, {
21
+ * target: document.querySelector('iframe').contentWindow,
22
+ * timeout: 5000,
23
+ * debug: console.log
24
+ * }).then(response => {
25
+ * // Handle response
26
+ * }).catch(error => {
27
+ * // Handle error
28
+ * });
29
+ * ```
30
+ */
31
+ export function sendMessage(message, { target, timeout, debug = () => { } }) {
32
+ return new Promise((resolve, reject) => {
33
+ const channel = new MessageChannel();
34
+ const requestTimeout = timeout
35
+ ? setTimeout(() => {
36
+ channel.port1.close();
37
+ reject(new Error(`Message of type ${message.type} timed out`));
38
+ }, timeout)
39
+ : undefined;
40
+ const responseHandler = (event) => {
41
+ debug('[Pulse Tracker Proxy] Received the response on the MessageChannel', event);
42
+ if (!isPulseProxyMessage(event.data)) {
43
+ debug('[Pulse Tracker Proxy] Response was not a valid Proxy Message', event.data);
44
+ return;
45
+ }
46
+ if (event.data.type === MESSAGE_TYPE_RETURN_ERROR) {
47
+ debug('[Pulse Tracker Proxy] Received an return error response', event.data.error);
48
+ clearTimeout(requestTimeout);
49
+ channel.port1.close();
50
+ reject(new Error(event.data.error));
51
+ }
52
+ debug('[Pulse Tracker Proxy] Received a valid response', event.data);
53
+ clearTimeout(requestTimeout);
54
+ channel.port1.close();
55
+ resolve(event.data);
56
+ };
57
+ channel.port1.onmessage = responseHandler;
58
+ debug('[Pulse Tracker Proxy] Sending message to target window', message);
59
+ target.postMessage(message, '*', [channel.port2]);
60
+ });
61
+ }
@@ -0,0 +1,65 @@
1
+ import { isPulseProxyMessage, MESSAGE_TYPE_INVOKE, MESSAGE_TYPE_RETURN, MESSAGE_TYPE_RETURN_ERROR, } from './consts';
2
+ import { sendMessage } from './send-message';
3
+ import version from './version.json';
4
+ /**
5
+ * Adds proxy version information to the event input.
6
+ * This helps with debugging and allows tracking which proxy version was used for the event.
7
+ *
8
+ * @param input - The original event input to be decorated
9
+ * @returns The input with added proxyVersion in the tracker object
10
+ */
11
+ function decorateWithProxyVersion(input) {
12
+ const tracker = 'tracker' in input && typeof input.tracker === 'object' ? input.tracker : {};
13
+ return {
14
+ ...input,
15
+ tracker: { ...tracker, proxyVersion: version.PROXY_VERSION },
16
+ };
17
+ }
18
+ /**
19
+ * Implementation of the Pulse Tracker Proxy that forwards tracking calls to a target window.
20
+ * This class handles the communication between frames/windows and ensures proper message handling.
21
+ */
22
+ export class PulseTrackerProxyImpl {
23
+ constructor(target, debug) {
24
+ this.target = target;
25
+ this.debug = debug;
26
+ }
27
+ /**
28
+ * Tracks an event by forwarding the call to the target window.
29
+ * Automatically decorates the event with proxy version information.
30
+ *
31
+ * @param args - Arguments that would normally be passed to the Pulse tracker's track method
32
+ * @returns A promise that resolves with the response from the target window
33
+ */
34
+ track(...args) {
35
+ this.debug('[Pulse Tracker Proxy] Calling `track`', args);
36
+ const [eventType, input, ...rest] = args;
37
+ const modifiedArgs = [eventType, decorateWithProxyVersion(input), ...rest];
38
+ return this.invoke({ method: 'track', args: modifiedArgs });
39
+ }
40
+ /**
41
+ * Invokes a remote function in the target window and handles the response.
42
+ *
43
+ * @param invocation - The invocation details including method name and arguments
44
+ * @returns A promise that resolves with the response payload or rejects with an error
45
+ * @private
46
+ */
47
+ async invoke(invocation) {
48
+ this.debug('[Pulse Tracker Proxy] Invoking remote function', invocation);
49
+ const response = await sendMessage({ type: MESSAGE_TYPE_INVOKE, payload: invocation }, { target: this.target });
50
+ if (!isPulseProxyMessage(response)) {
51
+ this.debug('[Pulse Tracker Proxy] Invalid response from proxy', response);
52
+ throw new Error('Invalid response from proxy');
53
+ }
54
+ if (response.type === MESSAGE_TYPE_RETURN_ERROR) {
55
+ this.debug('[Pulse Tracker Proxy] Error response from proxy', response.error);
56
+ throw new Error(response.error);
57
+ }
58
+ if (response.type === MESSAGE_TYPE_RETURN) {
59
+ this.debug('[Pulse Tracker Proxy] Successful response from proxy', response.payload);
60
+ return response.payload;
61
+ }
62
+ this.debug('[Pulse Tracker Proxy] Unhandled response from proxy', response);
63
+ throw new Error('Unexpected response from proxy');
64
+ }
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ { "PROXY_VERSION": "1.0.0", "GIT_COMMIT": "6ca09707" }