UNPKG

@hokodo/hokodo-js

Version:

Hokodo JS SDK

1 lines 21.7 kB
{"version":3,"file":"hokodo.mjs","sources":["../utils/eventListener/eventListener.ts","../utils/uuid/uuid.ts","../src/elements/dialog/dialog.ts","../src/elements/index.ts","../src/elements/dialog/builder.ts","../src/core/HokodoInstance/HokodoInstance.ts","../src/index.ts"],"sourcesContent":["/**\n * Hokodo post message data structure\n * @internal\n */\ntype HokodoData<Action> = {\n hokodoJs: boolean;\n frameId?: null; // populate once iframe init code is complete\n event: {\n action: Action;\n payload?: null; // future option\n };\n};\n\n/**\n * @internal\n * Type guard for Hokodo post message data\n * @param data - Pass unknown data to see if it is a Hokodo post message\n */\nconst isHokodoEvent = <Action>(data: unknown): data is HokodoData<Action> =>\n (data as HokodoData<Action>)?.hokodoJs === true;\n\n/**\n * @internal\n * Parse the data that is returned from a post message event\n * @param data - Data from a post message event\n */\nconst parseData = <Action>(data: unknown): HokodoData<Action> | null => {\n try {\n const dataObject: unknown = typeof data === \"string\" ? JSON.parse(data) : data;\n return isHokodoEvent<Action>(dataObject) ? dataObject : null;\n } catch (e) {\n // TODO: log error\n return null;\n }\n};\n\n/**\n * @internal\n * Event handle for post messages\n * @param handler - Function to handle data from Hokodo post message\n */\nexport const eventHandler =\n <Action>(handler: (data: HokodoData<Action>) => void, origin: string) =>\n (event: MessageEvent): void => {\n const data = parseData<Action>(event.data);\n\n if (data && event.origin === origin) {\n handler(data);\n }\n };\n","/* eslint-disable no-bitwise */\n/**\n * @internal\n * UUID generator\n */\nexport const uuid = (): string => {\n const ho = (n: number, p: number) => n.toString(16).padStart(p); /// Return the hexadecimal text representation of number `n`, padded with zeroes to be of length `p`\n const view = new DataView(new ArrayBuffer(16)); /// Create a view backed by a 16-byte buffer\n crypto.getRandomValues(new Uint8Array(view.buffer)); /// Fill the buffer with random data\n view.setUint8(6, (view.getUint8(6) & 0xf) | 0x40); /// Patch the 6th byte to reflect a version 4 UUID\n view.setUint8(8, (view.getUint8(8) & 0x3f) | 0x80); /// Patch the 8th byte to reflect a variant 1 UUID (version 4 UUIDs are)\n return `${ho(view.getUint32(0), 8)}-${ho(view.getUint16(4), 4)}-${ho(view.getUint16(6), 4)}-${ho(\n view.getUint16(8),\n 4\n )}-${ho(view.getUint32(10), 8)}${ho(view.getUint16(14), 4)}`; /// Compile the canonical textual form from the array data\n};\n","import { disableBodyScroll, clearAllBodyScrollLocks } from \"body-scroll-lock\";\nimport { eventHandler } from \"../../../utils/eventListener\";\nimport { uuid } from \"../../../utils/uuid\";\nimport { dialogBuilder } from \"./builder\";\n\n/**\n * @internal\n * Dialog event values\n */\nenum DialogEventValues {\n READY = \"ready\",\n SUCCESS = \"success\",\n FAILURE = \"failure\",\n CANCEL = \"cancel\",\n}\n\n/**\n * Dialog args\n *\n * @param paymentUrl - URL returned from the `/payment/offers/` endpoint\n */\nexport type DialogArgs = {\n paymentUrl: string;\n};\n\n/**\n * @internal\n * Dialog args type guard\n * @param args - Unknown args\n */\nexport const isDialogArgs = (args: unknown): args is DialogArgs =>\n !!(args as DialogArgs).paymentUrl;\n\n/**\n * #### Dialog\n *\n * @param options - The Dialog options\n */\nexport class Dialog {\n #wrapperId: string;\n\n #dialogId: string;\n\n #frameId: string;\n\n #target: HTMLElement | null;\n\n #mounted: boolean;\n\n #component: HTMLElement | null;\n\n #events: { [key in `${DialogEventValues}`]?: Array<() => void> };\n\n #eventHandler: (event: MessageEvent) => void;\n\n #paymentUrl: string;\n\n #onDestroyCallback: () => void;\n\n constructor({ paymentUrl }: DialogArgs, onDestroyCallback: () => void) {\n this.#wrapperId = `__privateHokodoDialog__Wrapper-${uuid()}`;\n this.#dialogId = `__privateHokodoDialog-${uuid()}`;\n this.#frameId = `__privateHokodoDialog__Frame-${uuid()}`;\n\n this.#target = null;\n this.#mounted = false;\n\n this.#events = {};\n\n this.#onDestroyCallback = onDestroyCallback;\n\n const urlRegex = new RegExp(/pay(-.*)?\\.hokodo.co/);\n\n this.#paymentUrl = (paymentUrl || \"https://pay.hokodo.co\").replace(\n urlRegex,\n \"i-pay$1.hokodo.co\"\n );\n\n if (this.#paymentUrl && !urlRegex.test(paymentUrl)) {\n this.#component = null;\n console.error(\"Integration Error - Incorrect payment URL passed to Dialog element\");\n } else {\n this.#component = dialogBuilder({\n wrapperId: this.#wrapperId,\n dialogId: this.#dialogId,\n frameId: this.#frameId,\n src: this.#paymentUrl,\n onLoad: () => this.#emit(DialogEventValues.READY),\n });\n }\n\n this.#eventHandler = eventHandler<`${DialogEventValues}`>((data) => {\n this.#emit(data.event.action);\n }, new URL(this.#paymentUrl).origin);\n }\n\n // TODO: update or prefetch method to allow loading of iFrame before it is needed\n\n /**\n * ##### Mount element\n * @param target - DOM target to mounting element\n * @example\n * ```js\n * dialog.mount();\n * ```\n */\n mount(target?: string): void {\n if (this.#mounted) {\n console.error(\"Integration Error - An element can only be mounted once.\");\n return;\n }\n\n if (target) {\n this.#target = document.querySelector(target);\n }\n\n if (target && !this.#target) {\n console.error(\"Integration error - The provided element target not found\");\n return;\n }\n\n if (!this.#component) {\n console.error(\n \"Integration error - The element has been destroyed, please recreate element to mount.\"\n );\n return;\n }\n\n window.addEventListener(\"message\", this.#eventHandler);\n\n if (this.#target) {\n this.#target.innerHTML = this.#component.innerHTML;\n } else {\n document.body.appendChild(this.#component);\n }\n\n this.#mounted = true;\n disableBodyScroll(this.#component);\n }\n\n /**\n * ##### Unmount element\n * @example\n * ```js\n * dialog.unmount();\n * ```\n */\n unmount(): void {\n if (this.#component && this.#mounted) {\n window.removeEventListener(\"message\", this.#eventHandler);\n\n if (this.#target) {\n this.#target.innerHTML = \"\";\n } else {\n document.body.removeChild(this.#component);\n }\n\n this.#mounted = false;\n clearAllBodyScrollLocks();\n } else {\n console.warn(\n \"Integration error - An element must be created and mounted before it can be unmounted.\"\n );\n }\n }\n\n /**\n * ##### Destroy element\n * @example\n * ```js\n * dialog.destroy();\n * ```\n */\n destroy(): void {\n this.unmount();\n this.#component = null;\n this.#mounted = false;\n this.#events = {};\n this.#onDestroyCallback();\n }\n\n /**\n * ##### Dialog event\n * @example #event:ready\n * ```js\n * dialog.on(\"ready\", function() {\n * console.log(\"dialog has loaded\")\n * });\n * ```\n * @example #event:success\n * ```js\n * dialog.on(\"success\", function() {\n * console.log(\"form has been completed successfully\")\n * });\n * ```\n * @example #event:failure\n * ```js\n * dialog.on(\"failure\", function() {\n * console.log(\"there has been a fatal error\")\n * });\n * ```\n * @example #event:cancel\n * ```js\n * dialog.on(\"cancel\", function() {\n * console.log(\"the user has exited the form\")\n * });\n * ```\n */\n on(event: `${DialogEventValues}`, fn: () => void): void {\n if (Object.values<string>(DialogEventValues).includes(event)) {\n this.#events[event] = this.#events[event] || [];\n this.#events[event]?.push(fn);\n } else {\n console.warn(\n `Integration error - The event \"${event}\" does not exist for the dialog element.`\n );\n }\n }\n\n // #off(event: `${DialogEventValues}`, fn: () => void) {\n // if (this.#events[event]) {\n // this.#events[event] = this.#events[event]?.filter((f) => f !== fn);\n // }\n // }\n\n #emit(event: `${DialogEventValues}`): void {\n if (this.#events[event]) {\n this.#events[event]?.forEach((fn) => {\n fn();\n });\n }\n }\n}\n","import { Dialog, DialogArgs, isDialogArgs } from \"./dialog\";\n\nenum ElementTypesValues {\n DIALOG = \"dialog\",\n}\n\ntype Components = Dialog;\n\nexport class Elements {\n // TODO: Consider having a static elements object instance so elements are only created/mounted once\n\n static #createdElements?: Partial<Record<`${ElementTypesValues}`, Components>>;\n\n /**\n * ### Get element\n *\n * @param element - Name of the element you wish to create\n * @example #element:dialog\n * ```js\n * const dialog = elements.getElement(\"dialog\");\n * ```\n */\n getElement(elementName: \"dialog\"): Dialog;\n // eslint-disable-next-line class-methods-use-this\n getElement(elementName: `${ElementTypesValues}`): Dialog | null {\n const element = Elements.#createdElements?.[elementName];\n\n if (element) {\n return element;\n }\n\n console.error(`Integration Error - Element \"${elementName}\" does not exist.`);\n return null;\n }\n\n static #create(elementName: \"dialog\", element: Dialog): Dialog;\n // eslint-disable-next-line class-methods-use-this\n static #create(elementName: `${ElementTypesValues}`, element: Components): Components | null {\n if (Elements.#createdElements?.[elementName]) {\n console.error(`Integration Error - Element \"${elementName}\" already exist.`);\n return null;\n }\n\n Elements.#createdElements = Elements.#createdElements || {};\n Elements.#createdElements[elementName] = element;\n\n return Elements.#createdElements[elementName] || null;\n }\n\n static #destroy(elementName: `${ElementTypesValues}`): () => void {\n return () => {\n if (Elements.#createdElements?.[elementName]) {\n delete Elements.#createdElements[elementName];\n } else {\n console.warn(`Element \"${elementName}\" was not destroyed as it does not exist.`);\n }\n };\n }\n\n /**\n * ### Create element\n *\n * @param element - Name of the element you wish to create\n * @param options - Options associated with that element\n * @example #element:dialog\n * ```js\n * const dialog = elements.create(\"dialog\");\n * ```\n */\n create(element: \"dialog\", options: DialogArgs): Dialog;\n // eslint-disable-next-line class-methods-use-this\n create(element: `${ElementTypesValues}`, options: DialogArgs): Components | null {\n const error = (elementName: string) =>\n console.error(`Integration error - Incorrect ${elementName} options passed.`);\n\n switch (element) {\n case ElementTypesValues.DIALOG:\n if (isDialogArgs(options)) {\n return Elements.#create(\n ElementTypesValues.DIALOG,\n new Dialog(options, Elements.#destroy(ElementTypesValues.DIALOG))\n );\n }\n error(\"Dialog\");\n break;\n default:\n console.warn(\"Integration Issue - No such element available\");\n }\n\n return null;\n }\n}\n","/**\n * @internal\n * @param wrapperId - ID of wrapper\n * @param dialogId - ID of dialog element\n * @param frameId - ID of iFrame element\n * @param src - iFrame source\n * @param onLoad - onLoad callback\n */\ntype DialogBuilderArgs = {\n wrapperId: string;\n dialogId: string;\n frameId: string;\n src?: string;\n onLoad: () => void;\n};\n\n/**\n * @internal\n * Dialog builder method\n * @param dialogBuildArgs - Ths arguments for the dialog builder\n */\nexport const dialogBuilder = ({\n wrapperId,\n dialogId,\n frameId,\n src,\n onLoad,\n}: DialogBuilderArgs): HTMLElement => {\n // create dialog\n const dialog = document.createElement(\"dialog\");\n dialog.setAttribute(\"id\", dialogId);\n dialog.setAttribute(\"open\", \"true\");\n dialog.setAttribute(\n \"style\",\n `\n z-index: 99999 !important;\n position: fixed !important;\n padding: 0 !important;\n border: none !important;\n box-shadow: 0px 0px 13px 0px rgb(0 0 0 / 50%) !important;\n width: 90vw !important;\n max-width: 1000px !important;\n height: calc(100% - 4rem) !important;\n top: 50% !important;\n left: 50% !important;\n transform: translate(-50%, -50%) !important;\n background-image: url(\"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='80px' height='80px' display='block' style='margin:auto' preserveAspectRatio='xMidYMid' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='50' cy='50' r='40' fill='none' stroke='%23ca8a2a' stroke-dasharray='188.49555921538757 64.83185307179586' stroke-width='14'%3E%3CanimateTransform attributeName='transform' dur='1s' keyTimes='0;1' repeatCount='indefinite' type='rotate' values='0 50 50;360 50 50'/%3E%3C/circle%3E%3C/svg%3E%0A\") !important;\n background-position: center !important;\n background-color: white !important;\n background-repeat: no-repeat !important;\n `\n .replace(/\\s\\s+/g, \" \")\n .trim()\n );\n\n // create wrapper\n const background = document.createElement(\"div\");\n background.setAttribute(\n \"style\",\n `\n z-index: 99998 !important;\n position: fixed !important;\n top: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n left: 0 !important;\n background: rgba(0, 0, 0, 0.4) !important;\n `\n .replace(/\\s\\s+/g, \" \")\n .trim()\n );\n\n // create dialog\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\"id\", frameId);\n iframe.setAttribute(\"title\", \"Hokodo Buy Now Pay Later Form\");\n iframe.setAttribute(\"frameBorder\", \"0\");\n iframe.setAttribute(\"scrolling\", \"yes\");\n iframe.setAttribute(\"allowTransparency\", \"true\");\n iframe.setAttribute(\"allow\", \"payment *\");\n iframe.setAttribute(\"loading\", \"lazy\");\n if (src) {\n iframe.setAttribute(\"src\", src);\n }\n iframe.onload = () => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const frame = document.getElementById(frameId);\n if (frame) {\n frame.style.opacity = \"1\";\n }\n onLoad();\n };\n // styling\n iframe.setAttribute(\n \"style\",\n `\n border: none !important;\n margin: 0px !important;\n padding: 0px !important;\n width: 1px !important;\n min-width: 100% !important;\n min-height: 100% !important;\n overflow: scroll !important;\n display: block !important;\n user-select: none !important;\n will-change: transform !important;\n opacity: 0 !important;\n transition: opacity 0.2s cubic-bezier(.42,0,.58,1)\n `\n .replace(/\\s\\s+/g, \" \")\n .trim()\n );\n\n // add iframe to dialog\n dialog.appendChild(iframe);\n\n // create wrapper and add dialog and background to it\n const wrapper = document.createElement(\"div\");\n wrapper.setAttribute(\"id\", wrapperId);\n wrapper.appendChild(background);\n wrapper.appendChild(dialog);\n\n return wrapper;\n};\n","import { Elements } from \"../../elements\";\n\n/**\n * HokodoInstance class\n */\nexport class HokodoInstance {\n static #instance?: HokodoInstance;\n\n static #elementsInstance?: Elements;\n\n /**\n * @internal\n * Get instance of Hokodo class\n */\n static getInstance(): HokodoInstance {\n if (!HokodoInstance.#instance) {\n HokodoInstance.#instance = new HokodoInstance();\n } else {\n console.error(\n \"Integration Error - Please only initialise the Hokodo instance only once per.\"\n );\n }\n\n return HokodoInstance.#instance;\n }\n\n // #apiKey?: string;\n\n // constructor(apiKey?: string) {\n // this.#apiKey = apiKey;\n // }\n\n /**\n * ## Initialise elements\n *\n * Initialise an instance of the elements object\n *\n * @example\n * ```js\n * const elements = hokodo.elements();\n * ```\n */\n // eslint-disable-next-line class-methods-use-this\n elements(): Elements {\n if (!HokodoInstance.#elementsInstance) {\n HokodoInstance.#elementsInstance = new Elements();\n } else {\n console.error(\n \"Integration Error - Please only initialise the Elements instance only once per.\"\n );\n }\n\n return HokodoInstance.#elementsInstance;\n }\n}\n","import pkg from \"../package.json\";\nimport { HokodoInstance } from \"./core/HokodoInstance\";\n\n/**\n * ### Hokodo()\n * To start using the Hokodo SDK you must first initialise an instance as per the example code. Once initialised you can then access the rest of the SDK.\n *\n * @example\n * ```js\n * const hokodo = Hokodo();\n * ```\n */\nconst Hokodo = (): HokodoInstance => HokodoInstance.getInstance();\n\nHokodo.version = pkg.version;\n\nexport default Hokodo;\n"],"names":["uuid","ho","n","p","toString","padStart","view","DataView","ArrayBuffer","crypto","getRandomValues","Uint8Array","buffer","setUint8","getUint8","getUint32","getUint16","DialogEventValues","ElementTypesValues","Dialog","constructor","paymentUrl","onDestroyCallback","_classPrivateFieldLooseBase","urlRegex","RegExp","handler","origin","replace","test","console","error","wrapperId","dialogId","frameId","src","onLoad","dialog","document","createElement","setAttribute","trim","background","iframe","onload","frame","getElementById","style","opacity","appendChild","wrapper","dialogBuilder","this","READY","data","event","action","URL","dataObject","JSON","parse","hokodoJs","isHokodoEvent","e","parseData","mount","target","querySelector","window","addEventListener","innerHTML","body","disableBodyScroll","unmount","removeEventListener","removeChild","clearAllBodyScrollLocks","warn","destroy","on","fn","Object","values","includes","push","forEach","Elements","getElement","elementName","_classPrivateFieldLoo","create","element","options","DIALOG","_classPrivateFieldLoo3","_classPrivateFieldLoo2","HokodoInstance","getInstance","elements","Hokodo","version"],"mappings":"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"}