@shopify/app-bridge-host
Version:
App Bridge Host contains components and middleware to be consumed by the app's host, as well as the host itself. The middleware and `Frame` component are responsible for facilitating communication between the client and host, and used to act on actions se
143 lines (140 loc) • 6.11 kB
JavaScript
import { __rest, __assign } from 'tslib';
import React, { createContext, useState, useRef, useMemo, useEffect, useCallback } from 'react';
import { Provider } from 'react-redux';
import { throwError, AppActionType } from '@shopify/app-bridge-core/actions/Error';
import { buildMiddleware } from './Middleware.js';
import { createStore, createAddReducer, APP_BRIDGE_KEY } from './store/index.js';
import { StoreReadyAction } from './store/reducers/embeddedApp/appBridge/actions.js';
import Host from './Host.js';
import { createApiContext } from './api.js';
/**
* The context provider for the app, config and addReducer method
* @public
* */
var HostContext = createContext(null);
/**
* The context provider for the router.
* Keeps track of the current location and
* handles history push/replace
* @public
* */
var RouterContext = createContext(null);
/**
* A component that creates a dynamic Redux store
* and renders the Host
* @public
* */
function HostProvider(props) {
var _a = useState(false), initialized = _a[0], setInitialized = _a[1];
var config = props.config, configureApi = props.configureApi, debug = props.debug, dispatchClientEventHandler = props.dispatchClientEventHandler, initialState = props.initialState, middleware = props.middleware, router = props.router, children = props.children, errorHandler = props.errorHandler, hostProps = __rest(props, ["config", "configureApi", "debug", "dispatchClientEventHandler", "initialState", "middleware", "router", "children", "errorHandler"]);
var apiKey = config && config.apiKey;
var previousApiKey = usePrevious(apiKey);
var apiKeyChanged = (!previousApiKey && apiKey !== undefined) || apiKey !== previousApiKey;
var previousInitialState = usePrevious(JSON.stringify(initialState));
var initialStateChanged = JSON.stringify(initialState) !== previousInitialState;
var _b = useState(), addReducer = _b[0], setAddReducer = _b[1];
var _c = useState(), app = _c[0], setApp = _c[1];
var dispatchClientEventHandlerRef = useRef(dispatchClientEventHandler);
dispatchClientEventHandlerRef.current = dispatchClientEventHandler;
var appBridgeMiddleware = useMemo(function () { return buildMiddleware(APP_BRIDGE_KEY, dispatchClientEventHandlerRef, errorHandler); }, []);
useEffect(function () {
if (!appBridgeMiddleware || typeof appBridgeMiddleware.load !== 'function') {
throwError(AppActionType.MISSING_APP_BRIDGE_MIDDLEWARE, 'Missing required context `appBridgeMiddleware`. Maybe you forgot the App Bridge `<Provider>` component?');
}
}, [appBridgeMiddleware]);
var storeCreated = useRef(false);
var store = useMemo(function () {
if (storeCreated.current) {
throwError(AppActionType.REDUX_REINSTANTIATED, 'Store cannot be instantiated twice. Redux middleware has changed or debug mode was toggled.');
}
storeCreated.current = true;
var hostMiddleware = middleware
? middleware.concat([appBridgeMiddleware])
: [appBridgeMiddleware];
// Only take the features state as features is the only default reducer
var defaultState = initialState.features && { features: initialState.features };
return createStore(hostMiddleware, defaultState, debug);
}, [appBridgeMiddleware, debug, middleware]);
useEffect(function () {
if (initialized) {
return;
}
store.dispatch(StoreReadyAction);
setInitialized(true);
}, [initialized, store]);
useEffect(function () {
if (!config || !apiKeyChanged) {
return;
}
var newApp = appBridgeMiddleware.load({
config: config,
type: 'application',
});
setApp(newApp);
}, [appBridgeMiddleware, config, apiKeyChanged]);
useEffect(function () {
if (!initialStateChanged) {
return;
}
setAddReducer(function () { return createAddReducer(store, initialState); });
}, [store, initialState, initialStateChanged]);
var onRouteChangeListeners = useRef(new Set());
var addRouteChangeListener = useCallback(function (onRouteChangeListener) {
onRouteChangeListeners.current.add(onRouteChangeListener);
return function () {
onRouteChangeListeners.current.delete(onRouteChangeListener);
};
}, []);
var notifyRouteChange = useCallback(function (options) {
onRouteChangeListeners.current.forEach(function (onRouteChangeListener) {
onRouteChangeListener(options);
});
}, []);
var hostContext = useMemo(function () {
if (!config || !app || !addReducer) {
return;
}
var context = {
app: app,
addReducer: addReducer,
config: config,
store: store,
addRouteChangeListener: addRouteChangeListener,
notifyRouteChange: notifyRouteChange,
errorHandler: errorHandler,
};
if (!configureApi) {
return context;
}
return __assign(__assign({}, context), { api: createApiContext() });
}, [
app,
addReducer,
config,
configureApi,
store,
addRouteChangeListener,
notifyRouteChange,
errorHandler,
]);
if (!hostContext) {
// eslint-disable-next-line react/no-children-prop
return React.createElement(Provider, { store: store, children: undefined });
}
var hostContent = (React.createElement(HostContext.Provider, { value: hostContext },
React.createElement(Provider, { store: store },
React.createElement(Host, __assign({}, hostProps)),
children)));
if (!router) {
return hostContent;
}
return React.createElement(RouterContext.Provider, { value: router }, hostContent);
}
function usePrevious(value) {
var ref = useRef();
useEffect(function () {
ref.current = value;
}, [value]);
return ref.current;
}
export { HostContext, RouterContext, HostProvider as default };