@shopify/app-bridge
Version:
> **Maintenance Mode:** Although apps using this package will continue to function, it is no longer receiving updates. For new projects, please use the [CDN version of App Bridge](https://shopify.dev/docs/api/app-home?accordionItem=getting-started-build-y
263 lines (262 loc) • 10.8 kB
JavaScript
;
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, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
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 = (0, redirect_1.getLocation)();
if (env_1.isUnframed || !location || !apiKey || !host || !forceRedirect || !(0, redirect_1.shouldRedirect)(hostFrame)) {
return false;
}
var url = "https://".concat(host, "/apps/").concat(apiKey).concat(location.pathname).concat(location.search || '');
(0, redirect_1.redirect)(url);
return true;
}
var actionWrapper = function (next) {
return function (action) {
return next(__assign(__assign({}, action), { version: (0, helper_1.getVersion)(), clientInterface: {
name: (0, helper_1.getPackageName)(),
version: (0, 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((0, Client_1.initialize)());
try {
(0, 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 = (0, 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 = (0, helper_1.findMatchInEnum)(Error_1.Action, payload.type);
if (errorType) {
(0, 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://".concat(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, true) : 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 = [];
(0, 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 (0, Error_1.fromAction)('host must be provided', Error_1.AppActionType.INVALID_CONFIG);
}
if (!config.apiKey) {
throw (0, 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 (0, Error_1.fromAction)(message, Error_1.AppActionType.INVALID_CONFIG);
}
}
/**
* @public
*/
function createAppWrapper(frame, localOrigin, middleware) {
if (middleware === void 0) { middleware = []; }
if (!frame) {
throw (0, Error_1.fromAction)(exports.WINDOW_UNDEFINED_MESSAGE, Error_1.AppActionType.WINDOW_UNDEFINED);
}
var location = (0, redirect_1.getLocation)();
var origin = localOrigin || (location && location.origin);
if (!origin) {
throw (0, Error_1.fromAction)('local origin cannot be blank', Error_1.AppActionType.MISSING_LOCAL_ORIGIN);
}
var transport = (0, MessageTransport_1.fromWindow)(frame, origin);
var appCreator = (0, exports.createClientApp)(transport, __spreadArray([actionWrappingMiddleware], middleware, true));
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 = (0, 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;