feat: move to nextjs
This commit is contained in:
120
helpers/communicator.ts
Normal file
120
helpers/communicator.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
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<MethodToResponse[Methods] | ErrorResponse | void>;
|
||||
|
||||
export enum LegacyMethods {
|
||||
getEnvInfo = "getEnvInfo",
|
||||
}
|
||||
|
||||
type SDKMethods = Methods | LegacyMethods;
|
||||
|
||||
class AppCommunicator {
|
||||
private iframeRef: MutableRefObject<HTMLIFrameElement | null>;
|
||||
private handlers = new Map<SDKMethods, MessageHandler>();
|
||||
|
||||
constructor(iframeRef: MutableRefObject<HTMLIFrameElement | null>) {
|
||||
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<void> => {
|
||||
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<HTMLIFrameElement | null>
|
||||
): AppCommunicator | undefined => {
|
||||
const [communicator, setCommunicator] = useState<AppCommunicator | undefined>(
|
||||
undefined
|
||||
);
|
||||
useEffect(() => {
|
||||
let communicatorInstance: AppCommunicator;
|
||||
const initCommunicator = (
|
||||
iframeRef: MutableRefObject<HTMLIFrameElement>
|
||||
) => {
|
||||
communicatorInstance = new AppCommunicator(iframeRef);
|
||||
setCommunicator(communicatorInstance);
|
||||
};
|
||||
|
||||
initCommunicator(iframeRef as MutableRefObject<HTMLIFrameElement>);
|
||||
|
||||
return () => {
|
||||
communicatorInstance?.clear();
|
||||
};
|
||||
}, [iframeRef]);
|
||||
|
||||
return communicator;
|
||||
};
|
||||
|
||||
export { useAppCommunicator };
|
||||
51
helpers/messageFormatter.ts
Normal file
51
helpers/messageFormatter.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
ErrorResponse,
|
||||
SDKRequestData,
|
||||
RequestId,
|
||||
SuccessResponse,
|
||||
MethodToResponse,
|
||||
Methods,
|
||||
} from "../types";
|
||||
import { getSDKVersion, generateRequestId } from "./utils";
|
||||
|
||||
class MessageFormatter {
|
||||
static makeRequest = <M extends Methods = Methods, P = unknown>(
|
||||
method: M,
|
||||
params: P
|
||||
): SDKRequestData<M, P> => {
|
||||
const id = generateRequestId();
|
||||
|
||||
return {
|
||||
id,
|
||||
method,
|
||||
params,
|
||||
env: {
|
||||
sdkVersion: getSDKVersion(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
static makeResponse = (
|
||||
id: RequestId,
|
||||
data: MethodToResponse[Methods],
|
||||
version: string
|
||||
): SuccessResponse => ({
|
||||
id,
|
||||
success: true,
|
||||
version,
|
||||
data,
|
||||
});
|
||||
|
||||
static makeErrorResponse = (
|
||||
id: RequestId,
|
||||
error: string,
|
||||
version: string
|
||||
): ErrorResponse => ({
|
||||
id,
|
||||
success: false,
|
||||
error,
|
||||
version,
|
||||
});
|
||||
}
|
||||
|
||||
export { MessageFormatter };
|
||||
20
helpers/utils.ts
Normal file
20
helpers/utils.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export const getSDKVersion = () => {
|
||||
return "7.6.0"; // IMPORTANT: needs to be >= 1.0.0
|
||||
};
|
||||
|
||||
// i.e. 0-255 -> '00'-'ff'
|
||||
const dec2hex = (dec: number): string => dec.toString(16).padStart(2, "0");
|
||||
|
||||
const generateId = (len: number): string => {
|
||||
const arr = new Uint8Array((len || 40) / 2);
|
||||
window.crypto.getRandomValues(arr);
|
||||
return Array.from(arr, dec2hex).join("");
|
||||
};
|
||||
|
||||
export const generateRequestId = (): string => {
|
||||
if (typeof window !== "undefined") {
|
||||
return generateId(10);
|
||||
}
|
||||
|
||||
return new Date().getTime().toString(36);
|
||||
};
|
||||
Reference in New Issue
Block a user