@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
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) {
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;
;