UNPKG

@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
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 };