diff --git a/src/enums/events.ts b/src/enums/events.ts index f99afd5..79ff369 100644 --- a/src/enums/events.ts +++ b/src/enums/events.ts @@ -1,5 +1,8 @@ export enum Events { ALL = '*', + // Topper ORDER_PLACED = 'orderPlaced', - WIDGET_CONTINUE_BUTTON_CLICKED = 'widgetContinueButtonClicked' + WIDGET_CONTINUE_BUTTON_CLICKED = 'widgetContinueButtonClicked', + // Internal + RESOLVE_HANDLER = 'resolveHandler' } diff --git a/src/enums/handlers.ts b/src/enums/handlers.ts new file mode 100644 index 0000000..c1311f7 --- /dev/null +++ b/src/enums/handlers.ts @@ -0,0 +1,3 @@ +export enum Handlers { + ASSETS = 'assets' +} diff --git a/src/enums/index.ts b/src/enums/index.ts index c2c138d..ab4e9c3 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -1,4 +1,6 @@ export * from './environments'; export * from './events'; +export * from './handlers'; +export * from './sources'; export * from './urls'; export * from './variants'; diff --git a/src/enums/sources.ts b/src/enums/sources.ts new file mode 100644 index 0000000..140a7c6 --- /dev/null +++ b/src/enums/sources.ts @@ -0,0 +1,4 @@ +export enum Sources { + EVENT = '@topper-web-sdk-event', + HANDLER = '@topper-web-sdk-handler' +} diff --git a/src/interfaces/config.ts b/src/interfaces/config.ts index 244d160..06ec00d 100644 --- a/src/interfaces/config.ts +++ b/src/interfaces/config.ts @@ -5,6 +5,7 @@ export interface Config { environment?: Environments; is_android_app?: boolean; is_ios_app?: boolean; - variant?: Variants; theme?: string; + use_assets?: boolean; + variant?: Variants; } diff --git a/src/topper-web-sdk.ts b/src/topper-web-sdk.ts index d26c169..b8ea3ea 100644 --- a/src/topper-web-sdk.ts +++ b/src/topper-web-sdk.ts @@ -1,12 +1,12 @@ import { Config } from './interfaces'; -import { Environments, Events, Urls, Variants } from './enums'; -import { EventHandler } from 'types'; +import { Environments, Events, Handlers, Sources, Urls, Variants } from './enums'; +import { EventHandler, Handler } from 'types'; +import isPromise from 'utils/isPromise'; import queryString from 'query-string'; -const TOPPER_WEB_SDK_EVENT_SOURCE = '@topper-web-sdk'; - class TopperWebSdk { private eventHandlers: { [key in Events]?: EventHandler[] } = {}; + private handlers: { [key in Handlers]?: Handler } = {}; private handleMessage: ((event: MessageEvent) => void) | null = null; private isInitialized: boolean = false; private targetWindow: Window | null = null; @@ -18,13 +18,39 @@ class TopperWebSdk { environment: Environments.PRODUCTION, is_android_app: false, is_ios_app: false, + use_assets: false, variant: Variants.NEW_TAB, ...config }; } + static getMainWindow() { + return window.self !== window.top ? window.parent : window.opener; + } + + static resolveHandler(handlerName: Handlers): Promise { + const mainWindow = this.getMainWindow(); + + mainWindow.postMessage( + { + name: Events.RESOLVE_HANDLER, + payload: handlerName, + source: Sources.HANDLER + }, + '*' + ); + + return new Promise(resolve => { + window.addEventListener('message', event => { + if (event.data.name === Events.RESOLVE_HANDLER && event.data.payload && event.data.source === Sources.HANDLER) { + resolve(event.data.payload); + } + }); + }); + } + static triggerEvent(eventName: Events, data?: any): void { - const mainWindow = window.self !== window.top ? window.parent : window.opener; + const mainWindow = this.getMainWindow(); if (!mainWindow) { return; @@ -34,7 +60,7 @@ class TopperWebSdk { { name: eventName, payload: data, - source: TOPPER_WEB_SDK_EVENT_SOURCE + source: Sources.EVENT }, '*' ); @@ -54,9 +80,13 @@ class TopperWebSdk { return; } - if (event.data && event.data.name && event.data.source === TOPPER_WEB_SDK_EVENT_SOURCE) { + if (event.data.name && event.data.source === Sources.EVENT) { this.triggerEvent(event.data.name as Events, event.data.payload); } + + if (event.data.name === Events.RESOLVE_HANDLER && event.data.payload && event.data.source === Sources.HANDLER) { + this.resolveHandler(event.data.payload as Handlers); + } }; window.addEventListener('message', this.handleMessage); @@ -82,7 +112,8 @@ class TopperWebSdk { ...(isTopperSelfEmbed && { embed: 1 }), ...(this.config.theme && { theme: this.config.theme }), ...(this.config.is_android_app && { is_android_app: 1 }), - ...(this.config.is_ios_app && { is_ios_app: 1 }) + ...(this.config.is_ios_app && { is_ios_app: 1 }), + ...(this.config.use_assets && { use_assets: 1 }) }; const url = queryString.stringifyUrl({ query: queryParams, url: `${baseUrl}/` }); @@ -112,6 +143,29 @@ class TopperWebSdk { this.eventHandlers[eventName]?.push(handler); } + + registerHandler(handlerName: Handlers, handler: Handler): void { + if (!(typeof handler === 'function' || isPromise(handler))) { + throw new Error('Handler must be a Promise or an async function.'); + } + + this.handlers[handlerName] = handler; + } + + async resolveHandler(handlerName: Handlers) { + const handler = this.handlers[handlerName]; + + const handlerResult = isPromise(handler) ? await handler : await handler?.(); + + this.targetWindow?.postMessage( + { + name: Events.RESOLVE_HANDLER, + payload: handlerResult, + source: Sources.HANDLER + }, + '*' + ); + } } export { TopperWebSdk }; diff --git a/src/types/handler.ts b/src/types/handler.ts new file mode 100644 index 0000000..5cc47b9 --- /dev/null +++ b/src/types/handler.ts @@ -0,0 +1 @@ +export type Handler = () => Promise; diff --git a/src/types/index.ts b/src/types/index.ts index a54ccf8..9d52f70 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1 +1,2 @@ export * from './event-handler'; +export * from './handler'; diff --git a/src/utils/isPromise.ts b/src/utils/isPromise.ts new file mode 100644 index 0000000..b19ce4b --- /dev/null +++ b/src/utils/isPromise.ts @@ -0,0 +1,7 @@ +const isObject = (value: any) => value !== null && (typeof value === 'object' || typeof value === 'function'); + +const isPromise = (value: any) => + value instanceof Promise || + (isObject(value) && typeof value.then === 'function' && typeof value.catch === 'function'); + +export default isPromise;