|
|
@@ -1,40 +1,14 @@
|
|
|
// Fork from https://github.com/dollarshaveclub/postmate
|
|
|
|
|
|
-/**
|
|
|
- * The type of messages our frames our sending
|
|
|
- * @type {String}
|
|
|
- */
|
|
|
export const messageType = 'application/x-postmate-v1+json'
|
|
|
-
|
|
|
-/**
|
|
|
- * The maximum number of attempts to send a handshake request to the parent
|
|
|
- * @type {Number}
|
|
|
- */
|
|
|
+export const defaultRequestTimeout = 10_000
|
|
|
export const maxHandshakeRequests = 5
|
|
|
|
|
|
-/**
|
|
|
- * A unique message ID that is used to ensure responses are sent to the correct requests
|
|
|
- * @type {Number}
|
|
|
- */
|
|
|
let _messageId = 0
|
|
|
|
|
|
-/**
|
|
|
- * Increments and returns a message ID
|
|
|
- * @return {Number} A unique ID for a message
|
|
|
- */
|
|
|
-export const generateNewMessageId = () => ++_messageId
|
|
|
-
|
|
|
-/**
|
|
|
- * Postmate logging function that enables/disables via config
|
|
|
- */
|
|
|
-export const log = (...args) => (Postmate.debug ? console.log(...args) : null)
|
|
|
-
|
|
|
-/**
|
|
|
- * Takes a URL and returns the origin
|
|
|
- * @param {String} url The full URL being requested
|
|
|
- * @return {String} The URLs origin
|
|
|
- */
|
|
|
-export const resolveOrigin = (url) => {
|
|
|
+const generateNewMessageId = () => ++_messageId
|
|
|
+const log = (...args: any) => (Postmate.debug ? console.log(...args) : null)
|
|
|
+const resolveOrigin = (url: string) => {
|
|
|
const a = document.createElement('a')
|
|
|
a.href = url
|
|
|
const protocol = a.protocol.length > 4 ? a.protocol : window.location.protocol
|
|
|
@@ -55,13 +29,7 @@ const messageTypes = {
|
|
|
request: 1,
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * Ensures that a message is safe to interpret
|
|
|
- * @param {Object} message The postmate message being sent
|
|
|
- * @param {String|Boolean} allowedOrigin The whitelisted origin or false to skip origin check
|
|
|
- * @return {Boolean}
|
|
|
- */
|
|
|
-export const sanitize = (message, allowedOrigin) => {
|
|
|
+export const sanitize = (message: any, allowedOrigin: any) => {
|
|
|
if (typeof allowedOrigin === 'string' && message.origin !== allowedOrigin)
|
|
|
return false
|
|
|
if (!message.data) return false
|
|
|
@@ -72,14 +40,8 @@ export const sanitize = (message, allowedOrigin) => {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * Takes a model, and searches for a value by the property
|
|
|
- * @param {Object} model The dictionary to search against
|
|
|
- * @param {String} property A path within a dictionary (i.e. 'window.location.href')
|
|
|
- * passed to functions in the child model
|
|
|
- * @return {Promise}
|
|
|
- */
|
|
|
-export const resolveValue = (model, property, args) => {
|
|
|
+export const resolveValue = (model: any, property: string, args: Array<any>) => {
|
|
|
+ // args arguments passed from parent to child function
|
|
|
const unwrappedContext =
|
|
|
typeof model[property] === 'function'
|
|
|
? model[property].apply(null, args)
|
|
|
@@ -98,12 +60,42 @@ export class ParentAPI {
|
|
|
public events = {}
|
|
|
public childOrigin: string
|
|
|
public listener: (e: any) => void
|
|
|
+ private readonly messagePort?: MessagePort
|
|
|
+
|
|
|
+ private addTransportListener(handler: (e: any) => void) {
|
|
|
+ if (this.messagePort) {
|
|
|
+ // MessagePort delivers MessageEvent too, but without origin/source.
|
|
|
+ this.messagePort.addEventListener('message', handler as any)
|
|
|
+ // Some browsers require start() when using addEventListener.
|
|
|
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
|
+ ;(this.messagePort as any).start?.()
|
|
|
+ } else {
|
|
|
+ this.parent.addEventListener('message', handler, false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private removeTransportListener(handler: (e: any) => void) {
|
|
|
+ if (this.messagePort) {
|
|
|
+ this.messagePort.removeEventListener('message', handler as any)
|
|
|
+ } else {
|
|
|
+ this.parent.removeEventListener('message', handler, false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private postToChild(payload: any) {
|
|
|
+ if (this.messagePort) {
|
|
|
+ this.messagePort.postMessage(payload)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.child.postMessage(payload, this.childOrigin)
|
|
|
+ }
|
|
|
|
|
|
constructor(info: Postmate) {
|
|
|
this.parent = info.parent
|
|
|
this.frame = info.frame
|
|
|
this.child = info.child
|
|
|
this.childOrigin = info.childOrigin
|
|
|
+ this.messagePort = info.messagePort
|
|
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log('Parent: Registering API')
|
|
|
@@ -111,7 +103,15 @@ export class ParentAPI {
|
|
|
}
|
|
|
|
|
|
this.listener = (e) => {
|
|
|
- if (!sanitize(e, this.childOrigin)) return false
|
|
|
+ // Port messages don't have origin/source, so we only enforce postmate/type.
|
|
|
+ if (this.messagePort) {
|
|
|
+ if (!e?.data) return false
|
|
|
+ if (typeof e.data === 'object' && !('postmate' in e.data)) return false
|
|
|
+ if (e.data.type !== messageType) return false
|
|
|
+ if (!messageTypes[e.data.postmate]) return false
|
|
|
+ } else {
|
|
|
+ if (!sanitize(e, this.childOrigin)) return false
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* the assignments below ensures that e, data, and value are all defined
|
|
|
@@ -123,26 +123,33 @@ export class ParentAPI {
|
|
|
log(`Parent: Received event emission: ${name}`)
|
|
|
}
|
|
|
if (name in this.events) {
|
|
|
- this.events[name].forEach((callback) => {
|
|
|
+ this.events[name].forEach((callback: Function) => {
|
|
|
callback.call(this, data)
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- this.parent.addEventListener('message', this.listener, false)
|
|
|
+ this.addTransportListener(this.listener)
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log('Parent: Awaiting event emissions from Child')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- get(property, ...args) {
|
|
|
+ get(property: string, ...args: any) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
// Extract data from response and kill listeners
|
|
|
const uid = generateNewMessageId()
|
|
|
+ const timeoutMs =
|
|
|
+ typeof (Postmate as any).requestTimeout === 'number'
|
|
|
+ ? (Postmate as any).requestTimeout
|
|
|
+ : defaultRequestTimeout
|
|
|
+
|
|
|
+ let timer: any
|
|
|
const transact = (e) => {
|
|
|
- if (e.data.uid === uid && e.data.postmate === 'reply') {
|
|
|
- this.parent.removeEventListener('message', transact, false)
|
|
|
+ if (e?.data?.uid === uid && e.data.postmate === 'reply') {
|
|
|
+ this.removeTransportListener(transact)
|
|
|
+ if (timer) clearTimeout(timer)
|
|
|
if (e.data.error) {
|
|
|
reject(e.data.error)
|
|
|
} else {
|
|
|
@@ -152,36 +159,37 @@ export class ParentAPI {
|
|
|
}
|
|
|
|
|
|
// Prepare for response from Child...
|
|
|
- this.parent.addEventListener('message', transact, false)
|
|
|
+ this.addTransportListener(transact)
|
|
|
+
|
|
|
+ if (timeoutMs > 0) {
|
|
|
+ timer = setTimeout(() => {
|
|
|
+ this.removeTransportListener(transact)
|
|
|
+ reject(new Error(`Postmate: request timeout (${timeoutMs}ms)`))
|
|
|
+ }, timeoutMs)
|
|
|
+ }
|
|
|
|
|
|
// Then ask child for information
|
|
|
- this.child.postMessage(
|
|
|
- {
|
|
|
- postmate: 'request',
|
|
|
- type: messageType,
|
|
|
- property,
|
|
|
- args,
|
|
|
- uid,
|
|
|
- },
|
|
|
- this.childOrigin
|
|
|
- )
|
|
|
+ this.postToChild({
|
|
|
+ postmate: 'request',
|
|
|
+ type: messageType,
|
|
|
+ property,
|
|
|
+ args,
|
|
|
+ uid,
|
|
|
+ })
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- call(property, data) {
|
|
|
+ call(property: string, data: any) {
|
|
|
// Send information to the child
|
|
|
- this.child.postMessage(
|
|
|
- {
|
|
|
- postmate: 'call',
|
|
|
- type: messageType,
|
|
|
- property,
|
|
|
- data,
|
|
|
- },
|
|
|
- this.childOrigin
|
|
|
- )
|
|
|
+ this.postToChild({
|
|
|
+ postmate: 'call',
|
|
|
+ type: messageType,
|
|
|
+ property,
|
|
|
+ data,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
- on(eventName, callback) {
|
|
|
+ on(eventName: string, callback: Function) {
|
|
|
if (!this.events[eventName]) {
|
|
|
this.events[eventName] = []
|
|
|
}
|
|
|
@@ -192,37 +200,69 @@ export class ParentAPI {
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log('Parent: Destroying Postmate instance')
|
|
|
}
|
|
|
- window.removeEventListener('message', this.listener, false)
|
|
|
+ this.removeTransportListener(this.listener)
|
|
|
+ try {
|
|
|
+ this.messagePort?.close()
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
this.frame.parentNode.removeChild(this.frame)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * Composes an API to be used by the child
|
|
|
- * @param {Object} info Information on the consumer
|
|
|
- */
|
|
|
+// Composes an API to be used by the child
|
|
|
export class ChildAPI {
|
|
|
- private model: any
|
|
|
+ private readonly model: any
|
|
|
private parent: Window
|
|
|
- private parentOrigin: string
|
|
|
+ private readonly parentOrigin: string
|
|
|
private child: Window
|
|
|
+ private readonly messagePort?: MessagePort
|
|
|
+ private readonly listener: (e: any) => void
|
|
|
+
|
|
|
+ private addTransportListener(handler: (e: any) => void) {
|
|
|
+ if (this.messagePort) {
|
|
|
+ this.messagePort.addEventListener('message', handler as any)
|
|
|
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
|
+ ;(this.messagePort as any).start?.()
|
|
|
+ } else {
|
|
|
+ this.child.addEventListener('message', handler, false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private postToParent(payload: any, fallbackEvent?: MessageEvent<any>) {
|
|
|
+ if (this.messagePort) {
|
|
|
+ this.messagePort.postMessage(payload)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // reply uses the event source/origin, others use stored parentOrigin.
|
|
|
+ if (fallbackEvent?.source) {
|
|
|
+ ;(fallbackEvent.source as WindowProxy).postMessage(payload, fallbackEvent.origin)
|
|
|
+ } else {
|
|
|
+ this.parent.postMessage(payload, this.parentOrigin)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
constructor(info: Model) {
|
|
|
this.model = info.model
|
|
|
this.parent = info.parent
|
|
|
this.parentOrigin = info.parentOrigin
|
|
|
this.child = info.child
|
|
|
+ this.messagePort = info.messagePort
|
|
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log('Child: Registering API')
|
|
|
log('Child: Awaiting messages...')
|
|
|
}
|
|
|
|
|
|
- this.child.addEventListener('message', (e) => {
|
|
|
- if (!sanitize(e, this.parentOrigin)) return
|
|
|
-
|
|
|
- if (process.env.NODE_ENV !== 'production') {
|
|
|
- log('Child: Received request', e.data)
|
|
|
+ this.listener = (e) => {
|
|
|
+ // Port messages don't have origin/source, so we only enforce postmate/type.
|
|
|
+ if (this.messagePort) {
|
|
|
+ if (!e?.data) return
|
|
|
+ if (typeof e.data === 'object' && !('postmate' in e.data)) return
|
|
|
+ if (e.data.type !== messageType) return
|
|
|
+ if (!messageTypes[e.data.postmate]) return
|
|
|
+ } else {
|
|
|
+ if (!sanitize(e, this.parentOrigin)) return
|
|
|
}
|
|
|
|
|
|
const { property, uid, data, args } = e.data
|
|
|
@@ -238,47 +278,48 @@ export class ChildAPI {
|
|
|
}
|
|
|
|
|
|
// Reply to Parent
|
|
|
- resolveValue(this.model, property, args).then((value) => {
|
|
|
- ;(e.source as WindowProxy).postMessage(
|
|
|
- {
|
|
|
- property,
|
|
|
- postmate: 'reply',
|
|
|
- type: messageType,
|
|
|
- uid,
|
|
|
- value,
|
|
|
- },
|
|
|
- e.origin
|
|
|
- )
|
|
|
- }).catch((error) => {
|
|
|
- ;(e.source as WindowProxy).postMessage(
|
|
|
- {
|
|
|
- property,
|
|
|
- postmate: 'reply',
|
|
|
- type: messageType,
|
|
|
- uid,
|
|
|
- error,
|
|
|
- },
|
|
|
- e.origin
|
|
|
- )
|
|
|
- })
|
|
|
- })
|
|
|
+ resolveValue(this.model, property, args)
|
|
|
+ .then((value) => {
|
|
|
+ this.postToParent(
|
|
|
+ {
|
|
|
+ property,
|
|
|
+ postmate: 'reply',
|
|
|
+ type: messageType,
|
|
|
+ uid,
|
|
|
+ value,
|
|
|
+ },
|
|
|
+ e
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ this.postToParent(
|
|
|
+ {
|
|
|
+ property,
|
|
|
+ postmate: 'reply',
|
|
|
+ type: messageType,
|
|
|
+ uid,
|
|
|
+ error,
|
|
|
+ },
|
|
|
+ e
|
|
|
+ )
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ this.addTransportListener(this.listener)
|
|
|
}
|
|
|
|
|
|
- emit(name, data) {
|
|
|
+ emit(name: string, data: any) {
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log(`Child: Emitting Event "${name}"`, data)
|
|
|
}
|
|
|
- this.parent.postMessage(
|
|
|
- {
|
|
|
- postmate: 'emit',
|
|
|
- type: messageType,
|
|
|
- value: {
|
|
|
- name,
|
|
|
- data,
|
|
|
- },
|
|
|
+ this.postToParent({
|
|
|
+ postmate: 'emit',
|
|
|
+ type: messageType,
|
|
|
+ value: {
|
|
|
+ name,
|
|
|
+ data,
|
|
|
},
|
|
|
- this.parentOrigin
|
|
|
- )
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -290,6 +331,11 @@ export type PostMateOptions = {
|
|
|
name?: string
|
|
|
model?: any,
|
|
|
allow?: string
|
|
|
+ /**
|
|
|
+ * Prefer using MessageChannel/MessagePort after handshake.
|
|
|
+ * Defaults to false to keep backward-compatible behavior with older SDKs.
|
|
|
+ */
|
|
|
+ enableMessageChannel?: boolean
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -297,6 +343,7 @@ export type PostMateOptions = {
|
|
|
*/
|
|
|
export class Postmate {
|
|
|
static debug = false // eslint-disable-line no-undef
|
|
|
+ static requestTimeout: number = defaultRequestTimeout
|
|
|
public container?: HTMLElement
|
|
|
public parent: Window
|
|
|
public frame: HTMLIFrameElement
|
|
|
@@ -306,9 +353,10 @@ export class Postmate {
|
|
|
public model: any
|
|
|
static Model: any
|
|
|
|
|
|
- /**
|
|
|
- * @param opts
|
|
|
- */
|
|
|
+ // Preferred transport after handshake.
|
|
|
+ public messagePort?: MessagePort
|
|
|
+ private readonly enableMessageChannel: boolean
|
|
|
+
|
|
|
constructor(opts: PostMateOptions) {
|
|
|
this.container = opts.container
|
|
|
this.url = opts.url
|
|
|
@@ -324,19 +372,42 @@ export class Postmate {
|
|
|
this.container.appendChild(this.frame)
|
|
|
this.child = this.frame.contentWindow
|
|
|
this.model = opts.model || {}
|
|
|
+ this.enableMessageChannel = !!opts.enableMessageChannel
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Begins the handshake strategy
|
|
|
- * @param {String} url The URL to send a handshake request to
|
|
|
- * @return {Promise} Promise that resolves when the handshake is complete
|
|
|
- */
|
|
|
sendHandshake(url?: string) {
|
|
|
url = url || this.url
|
|
|
const childOrigin = resolveOrigin(url)
|
|
|
let attempt = 0
|
|
|
- let responseInterval
|
|
|
+ let responseInterval: any
|
|
|
return new Promise((resolve, reject) => {
|
|
|
+ const runtimeSupportsMessageChannel =
|
|
|
+ typeof MessageChannel !== 'undefined' &&
|
|
|
+ typeof (MessageChannel as any) === 'function'
|
|
|
+
|
|
|
+ const shouldUseMessageChannel =
|
|
|
+ this.enableMessageChannel && runtimeSupportsMessageChannel
|
|
|
+
|
|
|
+ // Prefer MessageChannel if available. We transfer port2 to child.
|
|
|
+ // Important: once a port is transferred, it becomes neutered in this context,
|
|
|
+ // so we must create a fresh channel per handshake attempt.
|
|
|
+ let channel: MessageChannel | null = null
|
|
|
+ let port1: MessagePort | undefined
|
|
|
+ let port2: MessagePort | undefined
|
|
|
+
|
|
|
+ const ensureChannel = () => {
|
|
|
+ if (!shouldUseMessageChannel) return
|
|
|
+ // If we already have an active port from previous run, keep it.
|
|
|
+ if (this.messagePort) return
|
|
|
+ channel = new MessageChannel()
|
|
|
+ port1 = channel.port1
|
|
|
+ port2 = channel.port2
|
|
|
+ this.messagePort = port1
|
|
|
+ ;(port1 as any).start?.()
|
|
|
+ }
|
|
|
+
|
|
|
+ ensureChannel()
|
|
|
+
|
|
|
const reply = (e: any) => {
|
|
|
if (!sanitize(e, childOrigin)) return false
|
|
|
if (e.data.postmate === 'handshake-reply') {
|
|
|
@@ -346,6 +417,24 @@ export class Postmate {
|
|
|
}
|
|
|
this.parent.removeEventListener('message', reply, false)
|
|
|
this.childOrigin = e.origin
|
|
|
+
|
|
|
+ // If child didn't accept/return channel, fallback to window messages.
|
|
|
+ // Note: MessageChannel port is already set on this instance.
|
|
|
+ // Some browsers deliver the transferred port via e.ports.
|
|
|
+ if (e?.ports?.length) {
|
|
|
+ // Prefer the port child returned (if any); otherwise keep existing.
|
|
|
+ const returnedPort = e.ports[0]
|
|
|
+ if (returnedPort) {
|
|
|
+ try {
|
|
|
+ this.messagePort?.close()
|
|
|
+ } catch (err) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ this.messagePort = returnedPort
|
|
|
+ ;(this.messagePort as any).start?.()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log('Parent: Saving Child origin', this.childOrigin)
|
|
|
}
|
|
|
@@ -367,14 +456,40 @@ export class Postmate {
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log(`Parent: Sending handshake attempt ${attempt}`, { childOrigin })
|
|
|
}
|
|
|
- this.child.postMessage(
|
|
|
- {
|
|
|
- postmate: 'handshake',
|
|
|
- type: messageType,
|
|
|
- model: this.model,
|
|
|
- },
|
|
|
- childOrigin
|
|
|
- )
|
|
|
+ // port2 can be transferred only once. Create a new channel for retries.
|
|
|
+ if (shouldUseMessageChannel) {
|
|
|
+ // close any previous un-used port before re-creating.
|
|
|
+ if (!this.messagePort || port2 === undefined) {
|
|
|
+ // nothing
|
|
|
+ }
|
|
|
+ if (!port2) {
|
|
|
+ // We already transferred it in a previous attempt; create a fresh pair.
|
|
|
+ try {
|
|
|
+ this.messagePort?.close()
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ this.messagePort = undefined
|
|
|
+ ensureChannel()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const payload: any = {
|
|
|
+ postmate: 'handshake',
|
|
|
+ type: messageType,
|
|
|
+ model: this.model,
|
|
|
+ // hint for debugging / future extension
|
|
|
+ channel: port2 ? 1 : 0,
|
|
|
+ enableMessageChannel: shouldUseMessageChannel ? 1 : 0,
|
|
|
+ }
|
|
|
+
|
|
|
+ if (port2) {
|
|
|
+ this.child.postMessage(payload, childOrigin, [port2])
|
|
|
+ // Mark as transferred; next retry needs a new channel.
|
|
|
+ port2 = undefined
|
|
|
+ } else {
|
|
|
+ this.child.postMessage(payload, childOrigin)
|
|
|
+ }
|
|
|
|
|
|
if (attempt === maxHandshakeRequests) {
|
|
|
clearInterval(responseInterval)
|
|
|
@@ -399,6 +514,11 @@ export class Postmate {
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log('Postmate: Destroying Postmate instance')
|
|
|
}
|
|
|
+ try {
|
|
|
+ this.messagePort?.close()
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
this.frame.parentNode.removeChild(this.frame)
|
|
|
}
|
|
|
}
|
|
|
@@ -411,22 +531,17 @@ export class Model {
|
|
|
public model: any
|
|
|
public parent: Window
|
|
|
public parentOrigin: string
|
|
|
+ public messagePort?: MessagePort
|
|
|
+ private enableMessageChannel: boolean
|
|
|
|
|
|
- /**
|
|
|
- * Initializes the child, model, parent, and responds to the Parents handshake
|
|
|
- * @param {Object} model Hash of values, functions, or promises
|
|
|
- * @return {Promise} The Promise that resolves when the handshake has been received
|
|
|
- */
|
|
|
- constructor(model) {
|
|
|
+ constructor(model: any) {
|
|
|
this.child = window
|
|
|
this.model = model
|
|
|
this.parent = this.child.parent
|
|
|
+ // Child side is controlled by what parent sends in handshake; default false.
|
|
|
+ this.enableMessageChannel = false
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Responds to a handshake initiated by the Parent
|
|
|
- * @return {Promise} Resolves an object that exposes an API for the Child
|
|
|
- */
|
|
|
sendHandshakeReply() {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const shake = (e: MessageEvent<any>) => {
|
|
|
@@ -438,9 +553,23 @@ export class Model {
|
|
|
log('Child: Received handshake from Parent')
|
|
|
}
|
|
|
this.child.removeEventListener('message', shake, false)
|
|
|
+
|
|
|
+ // Only enable MessagePort transport when parent explicitly opted-in AND actually transferred a port.
|
|
|
+ this.enableMessageChannel = !!e.data?.enableMessageChannel
|
|
|
+ const transferredPort = e?.ports?.[0]
|
|
|
+ if (this.enableMessageChannel && transferredPort) {
|
|
|
+ this.messagePort = transferredPort
|
|
|
+ ;(this.messagePort as any).start?.()
|
|
|
+ } else {
|
|
|
+ this.messagePort = undefined
|
|
|
+ }
|
|
|
+
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
log('Child: Sending handshake reply to Parent')
|
|
|
}
|
|
|
+ // Reply back. If we want a dedicated child->parent port, we can
|
|
|
+ // create one and transfer it. For now, we only ack and rely on the
|
|
|
+ // transferred port (if any). Keep window reply for compatibility.
|
|
|
;(e.source as WindowProxy).postMessage(
|
|
|
{
|
|
|
postmate: 'handshake-reply',
|