-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[NO CHANGELOG][Checkout Widget] Refactor post message handler (#2077)
- Loading branch information
Showing
11 changed files
with
274 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './postMessageHandler'; | ||
export * from './postMessageEventTypes'; |
15 changes: 15 additions & 0 deletions
15
packages/checkout/sdk/src/postMessageHandler/postMessageEventTypes.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export enum PostMessageHandlerEventType { | ||
SYN = 'IMTBL_POST_MESSAGE_SYN', | ||
ACK = 'IMTBL_POST_MESSAGE_ACK', | ||
PROVIDER_RELAY = 'IMTBL_PROVIDER_RELAY', | ||
EIP_6963_EVENT = 'IMTBL_EIP_6963_EVENT', | ||
WIDGET_EVENT = 'IMTBL_CHECKOUT_WIDGET_EVENT', | ||
} | ||
|
||
export type PostMessageProviderRelayData = any; | ||
|
||
export type PostMessageEIP6963Data = any; | ||
|
||
export type PostMessagePayload = | ||
| PostMessageProviderRelayData | ||
| PostMessageEIP6963Data; |
158 changes: 105 additions & 53 deletions
158
packages/checkout/sdk/src/postMessageHandler/postMessageHandler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,148 @@ | ||
import { | ||
PostMessageHandlerEventType, | ||
PostMessagePayload, | ||
} from './postMessageEventTypes'; | ||
|
||
export type PostMessageHandlerConfiguration = { | ||
targetOrigin: string; | ||
eventTarget: MinimalEventTargetInterface; | ||
eventSource?: MinimalEventSourceInterface; | ||
eventTarget: WindowProxy; | ||
eventSource?: WindowProxy; | ||
}; | ||
// todo put these in a types file | ||
export enum PostMessageHandlerEventType { | ||
PROVIDER_RELAY = 'IMTBL_PROVIDER_RELAY', | ||
EIP_6963_EVENT = 'IMTBL_EIP_6963_EVENT', | ||
WIDGET_EVENT = 'IMTBL_CHECKOUT_WIDGET_EVENT', | ||
} | ||
|
||
export type PostMessageProviderRelayData = any; | ||
|
||
export type PostMessageEIP6963Data = any; | ||
|
||
export type PostMessagePayaload = | ||
| PostMessageProviderRelayData | ||
| PostMessageEIP6963Data; | ||
|
||
export type PostMessageData = { | ||
type: PostMessageHandlerEventType; | ||
payload: PostMessagePayaload; | ||
payload: PostMessagePayload; | ||
}; | ||
|
||
export interface MinimalEventSourceInterface { | ||
addEventListener( | ||
eventType: 'message', | ||
handler: (message: MessageEvent) => void | ||
): void; | ||
removeEventListener( | ||
eventType: 'message', | ||
handler: (message: MessageEvent) => void | ||
): void; | ||
} | ||
export class PostMessageHandler { | ||
private init: boolean = false; | ||
|
||
export interface MinimalEventTargetInterface { | ||
postMessage(message: any, targetOrigin?: string): void; | ||
} | ||
private haveSyn: boolean = false; | ||
|
||
export class PostMessageHandler { | ||
private eventHandlers: Map<PostMessageHandlerEventType, (data: any) => void> = new Map(); | ||
private subscribers: Array<(message: PostMessageData) => void> = []; | ||
|
||
private queue: PostMessageData[] = []; | ||
|
||
private targetOrigin!: string; | ||
|
||
private eventTarget!: MinimalEventTargetInterface; | ||
private eventTarget!: WindowProxy; | ||
|
||
private eventSource!: WindowProxy; | ||
|
||
private logger: (...args: any[]) => void; | ||
|
||
private eventSource!: MinimalEventSourceInterface; | ||
static isSynOrAck = (type: PostMessageHandlerEventType): boolean => type === PostMessageHandlerEventType.SYN | ||
|| type === PostMessageHandlerEventType.ACK; | ||
|
||
constructor({ | ||
targetOrigin, | ||
eventTarget, | ||
eventSource = window, | ||
}: PostMessageHandlerConfiguration) { | ||
this.handleMessage = this.handleMessage.bind(this); | ||
if (!targetOrigin) { | ||
throw new Error('targetOrigin is required'); | ||
} | ||
|
||
if (!eventTarget) { | ||
throw new Error('eventTarget is required'); | ||
} | ||
|
||
if (typeof eventTarget.postMessage !== 'function') { | ||
throw new Error( | ||
'eventTarget.postMessage is not a function. This class should only be instantiated in a Window.', | ||
); | ||
} | ||
|
||
this.targetOrigin = targetOrigin; | ||
this.eventSource = eventSource; | ||
this.eventTarget = eventTarget; | ||
this.eventHandlers = new Map(); | ||
this.logger = () => {}; | ||
|
||
this.eventSource.addEventListener('message', this.handleMessage); | ||
this.eventSource.addEventListener('message', this.onMessage); | ||
this.handshake(); | ||
} | ||
|
||
public sendMessage(type: PostMessageHandlerEventType, payload: any) { | ||
public setLogger(logger: any) { | ||
this.logger = logger; | ||
} | ||
|
||
private handshake = (): void => { | ||
this.postMessage(PostMessageHandlerEventType.SYN, null); | ||
}; | ||
|
||
private onMessage = (event: MessageEvent): void => { | ||
if (event.origin !== this.targetOrigin) return; | ||
|
||
if (this.init) { | ||
this.handleMessage(event); | ||
} else if (event.data?.type === PostMessageHandlerEventType.SYN) { | ||
this.haveSyn = true; | ||
this.postMessage(PostMessageHandlerEventType.ACK, null); | ||
} else if (event.data?.type === PostMessageHandlerEventType.ACK) { | ||
this.init = true; | ||
if (!this.haveSyn) { | ||
this.postMessage(PostMessageHandlerEventType.ACK, null); | ||
} | ||
this.flushQueue(); | ||
} | ||
}; | ||
|
||
private flushQueue(): void { | ||
while (this.queue.length > 0) { | ||
const message = this.queue.shift(); | ||
|
||
if (message) { | ||
this.logger('Flush message:', message); | ||
this.send(message.type, message.payload); | ||
} | ||
} | ||
} | ||
|
||
private postMessage(type: PostMessageHandlerEventType, payload: any): void { | ||
const message: PostMessageData = { type, payload }; | ||
this.eventTarget.postMessage(message, this.targetOrigin); | ||
|
||
if (!PostMessageHandler.isSynOrAck(type)) { | ||
this.logger('Send message:', { type, payload }); | ||
} | ||
} | ||
|
||
public addEventHandler( | ||
type: PostMessageHandlerEventType, | ||
handler: (data: any) => void, | ||
): void { | ||
this.eventHandlers.set(type, handler); | ||
public send(type: PostMessageHandlerEventType, payload: any): void { | ||
if (this.init || PostMessageHandler.isSynOrAck(type)) { | ||
this.postMessage(type, payload); | ||
return; | ||
} | ||
|
||
this.logger('Queue message:', { type, payload }); | ||
this.queue.push({ type, payload }); | ||
} | ||
|
||
public removeEventHandler(type: PostMessageHandlerEventType): void { | ||
this.eventHandlers.delete(type); | ||
public subscribe(handler: (message: PostMessageData) => void): () => void { | ||
this.subscribers.push(handler); | ||
|
||
return () => { | ||
this.unsubscribe(handler); | ||
}; | ||
} | ||
|
||
private handleMessage(event: MessageEvent) { | ||
if (event.origin !== this.targetOrigin) { | ||
return; | ||
private unsubscribe(handler: (message: PostMessageData) => void): void { | ||
const index = this.subscribers.indexOf(handler); | ||
if (index !== -1) { | ||
this.subscribers.splice(index, 1); | ||
} | ||
} | ||
|
||
private handleMessage = (event: MessageEvent) => { | ||
const message: PostMessageData = event.data; | ||
|
||
const handler = this.eventHandlers.get(message.type); | ||
if (handler) { | ||
handler(message.payload); | ||
if (!PostMessageHandler.isSynOrAck(message.type)) { | ||
this.logger('Received message:', message); | ||
} | ||
} | ||
|
||
this.subscribers.forEach((handler) => handler(message)); | ||
}; | ||
|
||
public destroy() { | ||
this.eventSource.removeEventListener('message', this.handleMessage); | ||
this.eventSource.removeEventListener('message', this.onMessage); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.