{"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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"}