UNPKG

@shopify/app-bridge-core

Version:

**[Join our team and work on libraries like this one.](https://www.shopify.ca/careers)**

193 lines (190 loc) 6.87 kB
import { fromAction, AppActionType, invalidOriginAction } from './actions/Error/index.js'; import { isAppMessage, isAppBridgeAction } from './actions/validator.js'; import { MessageType } from './client/types.js'; import { addAndRemoveFromCollection } from './util/collection.js'; import { isUnframed } from './util/env.js'; var Context; (function (Context) { Context["Modal"] = "Modal"; Context["Main"] = "Main"; })(Context || (Context = {})); /** * Create a MessageTransport from a Frame. * @remarks * Used on the host-side to create a postMessage MessageTransport. * @beta */ function fromFrame(frame, localOrigin, context) { const handlers = []; const { host, window: frameWindow } = frame; if (!host) { throw fromAction('App frame is undefined', AppActionType.WINDOW_UNDEFINED); } if (isUnframed && window.MobileWebView) { Object.assign(window.MobileWebView, { postMessageToIframe(message, origin) { frameWindow?.postMessage(message, origin); if (isDispatchAction(message)) { host.postMessage(JSON.stringify(message.payload), location.origin); } }, updateIframeUrl(newUrl) { const currentWindowLocation = window.location; const { location: frameWindowLocation } = frame.window || {}; try { const newUrlOrigin = new URL(newUrl).origin; if (newUrlOrigin === localOrigin && frameWindowLocation) { frameWindowLocation.replace(newUrl); } else { currentWindowLocation.href = newUrl; } } catch (_) { // Noop } }, }); } host.addEventListener('message', (event) => { if (event.source === host || !isAppMessage(event)) { return; } if (event.origin !== localOrigin) { const errorMessage = `Message origin '${event.origin}' does not match app origin '${localOrigin}'.`; const payload = invalidOriginAction(errorMessage); const message = { type: 'dispatch', payload, }; frameWindow?.postMessage(message, event.origin); return; } if (isUnframed && window.MobileWebView) { const payload = JSON.stringify({ id: 'unframed://fromClient', origin: localOrigin, data: event.data, }); window.MobileWebView.postMessage(payload); return; } for (const handler of handlers) { handler(event); } }); return { context, localOrigin, frameWindow, hostFrame: host, dispatch(message) { frameWindow?.postMessage(message, localOrigin); }, subscribe(handler) { return addAndRemoveFromCollection(handlers, handler); }, }; } /** * Create a MessageTransport from a parent window. * @remarks * Used on the client-side to create a postMessage MessageTransport. * @internalremarks * In unframed mode, message should be dispatched via MobileWebView.postMessage instead of postMessage. * @beta */ function fromWindow(contentWindow, localOrigin) { const handlers = []; if (typeof window !== undefined) { window.addEventListener('message', (event) => { if ((window === contentWindow && !isUnframed) || event.source !== contentWindow || !(isAppBridgeAction(event.data.payload) || isAppMessage(event))) { return; } for (const handler of handlers) { handler(event); } }); } return { localOrigin, hostFrame: contentWindow, dispatch(message) { if (!message.source?.host) { return; } if (isUnframed && window && window.MobileWebView) { const payload = JSON.stringify({ id: 'unframed://fromClient', origin: localOrigin, data: message, }); window.MobileWebView.postMessage(payload); return; } const messageOrigin = new URL(`https://${message.source.host}`).origin; contentWindow.postMessage(message, messageOrigin); }, subscribe(handler) { return addAndRemoveFromCollection(handlers, handler); }, }; } function createTransportListener() { const listeners = []; const actionListeners = {}; function createSubscribeHandler(dispatcher) { function subscribe() { if (arguments.length < 2) { // eslint-disable-next-line prefer-rest-params return addAndRemoveFromCollection(listeners, { callback: arguments[0] }); } // eslint-disable-next-line prefer-rest-params const [type, callback, id] = Array.from(arguments); const actionCallback = { callback, id }; const payload = { type, id }; if (!Object.prototype.hasOwnProperty.call(actionListeners, type)) { actionListeners[type] = []; } if (dispatcher) { dispatcher(MessageType.Subscribe, payload); } return addAndRemoveFromCollection(actionListeners[type], actionCallback, () => { if (dispatcher) { dispatcher(MessageType.Unsubscribe, payload); } }); } return subscribe; } return { createSubscribeHandler, handleMessage(message) { listeners.forEach((listener) => listener.callback(message)); }, handleActionDispatch({ type, payload }) { let hasCallback = false; if (Object.prototype.hasOwnProperty.call(actionListeners, type)) { for (const listener of actionListeners[type]) { const { id, callback } = listener; const matchId = payload && payload.id === id; if (matchId || !id) { callback(payload); hasCallback = true; } } } return hasCallback; }, }; } function isDispatchAction(message) { return (message !== null && typeof message === 'object' && !Array.isArray(message) && message.type === 'dispatch' && typeof message.payload === 'object'); } export { Context, createTransportListener, fromFrame, fromWindow };