UNPKG

@shopify/app-bridge

Version:

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

259 lines (258 loc) 10.5 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __spreadArray = (this && this.__spreadArray) || function (to, from) { for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) to[j] = from[i]; return to; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createApp = exports.createAppWrapper = exports.createClientApp = exports.WINDOW_UNDEFINED_MESSAGE = void 0; var helper_1 = require("../actions/helper"); var Print_1 = require("../actions/Print"); var Error_1 = require("../actions/Error"); var MessageTransport_1 = require("../MessageTransport"); var shared_1 = require("../util/shared"); var env_1 = require("../util/env"); var Client_1 = require("../actions/Client"); var WebVitals_1 = require("../actions/WebVitals"); var print_1 = require("./print"); var redirect_1 = require("./redirect"); var types_1 = require("./types"); var Hooks_1 = __importDefault(require("./Hooks")); exports.WINDOW_UNDEFINED_MESSAGE = 'window is not defined. Running an app outside a browser is not supported'; function redirectHandler(hostFrame, config) { var apiKey = config.apiKey, host = config.host, _a = config.forceRedirect, forceRedirect = _a === void 0 ? !env_1.isDevelopmentClient : _a; var location = redirect_1.getLocation(); if (env_1.isUnframed || !location || !apiKey || !host || !forceRedirect || !redirect_1.shouldRedirect(hostFrame)) { return false; } var url = "https://" + host + "/apps/" + apiKey + location.pathname + (location.search || ''); redirect_1.redirect(url); return true; } var actionWrapper = function (next) { return function (action) { return next(__assign(__assign({}, action), { version: helper_1.getVersion(), clientInterface: { name: helper_1.getPackageName(), version: helper_1.getVersion(), } })); }; }; var actionWrappingMiddleware = function (hooks) { hooks.set(types_1.LifecycleHook.DispatchAction, actionWrapper); }; function appSetUp(app) { app.subscribe(Print_1.Action.APP, print_1.handleAppPrint); app.dispatch(Client_1.initialize()); try { WebVitals_1.initializeWebVitals(app); } catch (err) { // eslint-disable-next-line no-console console.error('App-Bridge failed to initialize web-vitals', err); } } /** * @internal */ var createClientApp = function (transport, middlewares) { if (middlewares === void 0) { middlewares = []; } var getStateListeners = []; var transportListener = MessageTransport_1.createTransportListener(); var handler = function (event) { var message = event.data; var type = message.type, payload = message.payload; switch (type) { case 'getState': { var resolvers = getStateListeners.splice(0); resolvers.forEach(function (resolver) { return resolver(payload); }); break; } case 'dispatch': { transportListener.handleMessage(payload); var hasCallback = transportListener.handleActionDispatch(payload); if (hasCallback) { return; } // Throw an error if there are no subscriptions to this error var errorType = helper_1.findMatchInEnum(Error_1.Action, payload.type); if (errorType) { Error_1.throwError(errorType, payload); } break; } default: // Silently swallow unknown actions } }; transport.subscribe(handler); return function (config) { var decodedConfig = validateAndDecodeConfig(config); var isRedirecting = redirectHandler(transport.hostFrame, decodedConfig); if (isRedirecting) { return shared_1.mockAppBridge; } var dispatcher = createDispatcher(transport, decodedConfig); var subscribe = transportListener.createSubscribeHandler(dispatcher); // It is possible to initialize an app multiple times // Therefore we need to clear subscriptions to be safe dispatcher(types_1.MessageType.Unsubscribe); function dispatch(action) { dispatcher(types_1.MessageType.Dispatch, action); return action; } var hostOrigin = new URL("https://" + decodedConfig.host).origin; var hooks = new Hooks_1.default(); var app = { hostOrigin: hostOrigin, localOrigin: transport.localOrigin, hooks: hooks, dispatch: function (action) { if (!app.hooks) { return dispatch(action); } return app.hooks.run(types_1.LifecycleHook.DispatchAction, dispatch, app, action); }, featuresAvailable: function () { var features = []; for (var _i = 0; _i < arguments.length; _i++) { features[_i] = arguments[_i]; } var firstItem = features[0]; var parsedFeatures = Array.isArray(firstItem) ? __spreadArray([], firstItem) : features; return app.getState('features').then(function (state) { if (parsedFeatures.length) { return parsedFeatures.reduce(function (acc, feature) { if (Object.keys(state).includes(feature)) { acc[feature] = state[feature]; } return acc; }, {}); } return state; }); }, getState: function (query) { if (query && typeof query !== 'string') { return Promise.resolve(undefined); } return new Promise(function (resolve) { getStateListeners.push(resolve); dispatcher(types_1.MessageType.GetState); }).then(function (state) { var newState = state; if (query) { for (var _i = 0, _a = query.split('.'); _i < _a.length; _i++) { var key = _a[_i]; if (newState == null || typeof newState !== 'object' || Array.isArray(newState) || !Object.keys(newState).includes(key)) { return undefined; } newState = newState[key]; } } return newState; }); }, subscribe: subscribe, error: function (listener, id) { var unsubscribeCb = []; helper_1.forEachInEnum(Error_1.Action, function (eventNameSpace) { unsubscribeCb.push(subscribe(eventNameSpace, listener, id)); }); return function () { unsubscribeCb.forEach(function (unsubscribe) { return unsubscribe(); }); }; }, }; for (var _i = 0, middlewares_1 = middlewares; _i < middlewares_1.length; _i++) { var middleware = middlewares_1[_i]; middleware(hooks, app); } appSetUp(app); return app; }; }; exports.createClientApp = createClientApp; /** * @internal */ function validateAndDecodeConfig(config) { var _a; if (!config.host) { throw Error_1.fromAction('host must be provided', Error_1.AppActionType.INVALID_CONFIG); } if (!config.apiKey) { throw Error_1.fromAction('apiKey must be provided', Error_1.AppActionType.INVALID_CONFIG); } try { var host = atob((_a = config.host) === null || _a === void 0 ? void 0 : _a.replace(/_/g, '/').replace(/-/g, '+')); return __assign(__assign({}, config), { host: host }); } catch (_b) { var message = "not a valid host, please use the value provided by Shopify"; throw Error_1.fromAction(message, Error_1.AppActionType.INVALID_CONFIG); } } /** * @public */ function createAppWrapper(frame, localOrigin, middleware) { if (middleware === void 0) { middleware = []; } if (!frame) { throw Error_1.fromAction(exports.WINDOW_UNDEFINED_MESSAGE, Error_1.AppActionType.WINDOW_UNDEFINED); } var location = redirect_1.getLocation(); var origin = localOrigin || (location && location.origin); if (!origin) { throw Error_1.fromAction('local origin cannot be blank', Error_1.AppActionType.MISSING_LOCAL_ORIGIN); } var transport = MessageTransport_1.fromWindow(frame, origin); var appCreator = exports.createClientApp(transport, __spreadArray([actionWrappingMiddleware], middleware)); return appCreator; } exports.createAppWrapper = createAppWrapper; /** * Creates your application instance. * @param config - `apiKey` and `host` are both required. * @remarks * You will need to store `host` during the authentication process and then retrieve it for the code to work properly. To learn more about this process, see {@link https://help.shopify.com/api/embedded-apps/shop-origin | Getting and storing the shop origin}. * @public */ function createApp(config) { var currentWindow = redirect_1.getWindow(); if (!currentWindow || !currentWindow.top) { return shared_1.serverAppBridge; } return createAppWrapper(currentWindow.top)(config); } exports.createApp = createApp; function createDispatcher(transport, config) { return function (type, payload) { transport.dispatch({ payload: payload, source: config, type: type, }); }; } /** * {@inheritdocs createApp} * @public */ exports.default = createApp;