import { MutableRefObject, useEffect, useState } from "react"; import { MessageFormatter } from "./messageFormatter"; import { SDKMessageEvent, MethodToResponse, Methods, ErrorResponse, RequestId, } from "../types"; import { getSDKVersion } from "./utils"; type MessageHandler = ( msg: SDKMessageEvent ) => | void | MethodToResponse[Methods] | ErrorResponse | Promise; export enum LegacyMethods { getEnvInfo = "getEnvInfo", } type SDKMethods = Methods | LegacyMethods; class AppCommunicator { private iframeRef: MutableRefObject; private handlers = new Map(); constructor(iframeRef: MutableRefObject) { this.iframeRef = iframeRef; window.addEventListener("message", this.handleIncomingMessage); } on = (method: SDKMethods, handler: MessageHandler): void => { this.handlers.set(method, handler); }; private isValidMessage = (msg: SDKMessageEvent): boolean => { if (msg.data.hasOwnProperty("isCookieEnabled")) { return true; } const sentFromIframe = this.iframeRef.current?.contentWindow === msg.source; const knownMethod = Object.values(Methods).includes(msg.data.method); return sentFromIframe && knownMethod; }; private canHandleMessage = (msg: SDKMessageEvent): boolean => { return Boolean(this.handlers.get(msg.data.method)); }; send = (data: unknown, requestId: RequestId, error = false): void => { const sdkVersion = getSDKVersion(); const msg = error ? MessageFormatter.makeErrorResponse( requestId, data as string, sdkVersion ) : MessageFormatter.makeResponse(requestId, data, sdkVersion); // console.log("send", { msg }); this.iframeRef.current?.contentWindow?.postMessage(msg, "*"); }; handleIncomingMessage = async (msg: SDKMessageEvent): Promise => { const validMessage = this.isValidMessage(msg); const hasHandler = this.canHandleMessage(msg); if (validMessage && hasHandler) { // console.log("incoming", { msg: msg.data }); const handler = this.handlers.get(msg.data.method); try { // @ts-expect-error Handler existence is checked in this.canHandleMessage const response = await handler(msg); // If response is not returned, it means the response will be send somewhere else if (typeof response !== "undefined") { this.send(response, msg.data.id); } } catch (err: any) { this.send(err.message, msg.data.id, true); } } }; clear = (): void => { window.removeEventListener("message", this.handleIncomingMessage); }; } const useAppCommunicator = ( iframeRef: MutableRefObject ): AppCommunicator | undefined => { const [communicator, setCommunicator] = useState( undefined ); useEffect(() => { let communicatorInstance: AppCommunicator; const initCommunicator = ( iframeRef: MutableRefObject ) => { communicatorInstance = new AppCommunicator(iframeRef); setCommunicator(communicatorInstance); }; initCommunicator(iframeRef as MutableRefObject); return () => { communicatorInstance?.clear(); }; }, [iframeRef]); return communicator; }; export { useAppCommunicator };