UNPKG

@shopify/app-bridge-core

Version:

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

239 lines (236 loc) 9.08 kB
import { LifecycleHook } from '../client/types.js'; import { removeFromCollection } from '../util/collection.js'; import { throwError, Action, isErrorEventName } from './Error/index.js'; import { Group } from './types.js'; import { generateUuid } from './uuid.js'; import { forEachInEnum, getEventNameSpace } from './helper.js'; class ActionSet { app; type; group; id; defaultGroup; subgroups = []; subscriptions = []; constructor(app, type, group, id) { this.app = app; this.type = type; this.group = group; if (!app) { throwError(Action.INVALID_ACTION, 'Missing required `app`'); } this.id = id || generateUuid(); this.defaultGroup = group; const defaultSet = this.set; this.set = (...args) => { if (!this.app.hooks) { return defaultSet.apply(this, args); } return this.app.hooks.run(LifecycleHook.UpdateAction, defaultSet, this, ...args); }; } set(..._) { } get component() { return { id: this.id, subgroups: this.subgroups, type: this.type, }; } updateSubscription(subscriptionToRemove, group, subgroups) { const { eventType, callback, component } = subscriptionToRemove; let currentIndex; currentIndex = this.subscriptions.findIndex((subscription) => subscription === subscriptionToRemove); if (currentIndex >= 0) { this.subscriptions[currentIndex].unsubscribe(); } else { currentIndex = undefined; } this.group = group; this.subgroups = subgroups; Object.assign(component, { subgroups: this.subgroups }); return this.subscribe(eventType, callback, component, currentIndex); } error(callback) { const subscriptionIndices = []; forEachInEnum(Action, (eventNameSpace) => { // Keep track of subscription index so we can call unsubscribe later // This ensure it will continue to work even when the subscription has been updated subscriptionIndices.push(this.subscriptions.length); this.subscribe(eventNameSpace, callback); }); return () => { const subscriptionsToRemove = subscriptionIndices.map((index) => this.subscriptions[index]); subscriptionsToRemove.forEach((toRemove) => { removeFromCollection(this.subscriptions, toRemove, (removed) => { removed.unsubscribe(); }); }); }; } subscribe(eventName, callback, component, currentIndex) { const eventComponent = component || this.component; const eventType = eventName.toUpperCase(); const boundedCallback = typeof currentIndex === 'number' ? callback : callback.bind(this); let eventNameSpace; if (isErrorEventName(eventName)) { eventNameSpace = getEventNameSpace(Group.Error, eventName, { ...eventComponent, type: '', }); } else { eventNameSpace = getEventNameSpace(this.group, eventName, eventComponent); } const unsubscribe = this.app.subscribe(eventNameSpace, boundedCallback, component ? component.id : this.id); const subscription = { eventType, unsubscribe, callback: boundedCallback, component: eventComponent, updateSubscribe: (group, subgroups) => this.updateSubscription(subscription, group, subgroups), }; if (typeof currentIndex === 'number' && currentIndex >= 0 && currentIndex < this.subscriptions.length) { this.subscriptions[currentIndex] = subscription; } else { this.subscriptions.push(subscription); } return unsubscribe; } unsubscribe(resetOnly = false) { unsubscribeActions(this.subscriptions, this.defaultGroup, resetOnly); return this; } } class ActionSetWithChildren extends ActionSet { children = []; unsubscribe(unsubscribeChildren = true, resetParentOnly = false) { unsubscribeActions(this.subscriptions, this.defaultGroup, resetParentOnly); this.children.forEach((child) => { if (child instanceof ActionSetWithChildren) { child.unsubscribe(unsubscribeChildren, !unsubscribeChildren); } else { child.unsubscribe(!unsubscribeChildren); } }); return this; } getChild(id) { const childIndex = this.children.findIndex((child) => child.id === id); return childIndex >= 0 ? this.children[childIndex] : undefined; } getChildIndex(id) { return this.children.findIndex((child) => child.id === id); } getChildSubscriptions(id, eventType) { return this.subscriptions.filter((sub) => sub.component.id === id && (!eventType || eventType === sub.eventType)); } addChild(child, group, subgroups) { const { subscriptions } = child; const existingChild = this.getChild(child.id); // Add child if it doesn't already exist if (!existingChild) { this.children.push(child); } if (!subscriptions || (group === child.group && subgroups === child.subgroups)) { return this; } subscriptions.forEach((subscription) => { const { updateSubscribe } = subscription; updateSubscribe(group, subgroups); }); // Update child's group and subgroups Object.assign(child, { group, subgroups }); // Update child's children subscriptions if (child instanceof ActionSetWithChildren) { child.children.forEach((childIter) => this.addChild(childIter, group, subgroups)); } return this; } removeChild(id) { removeFromCollection(this.children, this.getChild(id), () => { const toBeRemoved = this.subscriptions.filter((subs) => subs.component.id === id); toBeRemoved.forEach((toRemove) => { removeFromCollection(this.subscriptions, toRemove, (removed) => { removed.unsubscribe(); }); }); }); return this; } subscribeToChild(child, eventName, callback) { const boundedCallback = callback.bind(this); if (eventName instanceof Array) { eventName.forEach((eventNameIter) => this.subscribeToChild(child, eventNameIter, callback)); return this; } if (typeof eventName !== 'string') { return this; } const eventType = eventName.toUpperCase(); const currentSubscriptions = this.getChildSubscriptions(child.id, eventType); if (currentSubscriptions.length > 0) { // Subscription is already there, simply update it currentSubscriptions.forEach((subs) => subs.updateSubscribe(this.group, child.subgroups)); } else { const childComponent = { id: child.id, subgroups: child.subgroups, type: child.type, }; this.subscribe(eventType, boundedCallback, childComponent); } return this; } getUpdatedChildActions(newActions, currentActions) { if (newActions.length === 0) { while (currentActions.length > 0) { const action = currentActions.pop(); if (!action) { break; } this.removeChild(action.id); } return undefined; } // Only allow unique actions const uniqueActions = newActions.filter((action, index, actionsArr) => index === actionsArr.indexOf(action)); const newActionIds = uniqueActions.map((action) => action.id); // Remove unused actions const unusedActions = currentActions.filter((action) => { return newActionIds.indexOf(action.id) < 0; }); while (unusedActions.length > 0) { const action = unusedActions.pop(); if (!action) { break; } this.removeChild(action.id); } return uniqueActions; } } function unsubscribeActions(subscriptions, defaultGroup, reassign = false) { subscriptions.forEach((subscription) => { if (reassign) { const { updateSubscribe } = subscription; // eslint-disable-next-line no-warning-comments // TODO: Support cases where we don't wipe out group and subgroups to defaults updateSubscribe(defaultGroup, []); } else { const { unsubscribe } = subscription; unsubscribe(); } }); if (!reassign) { subscriptions.length = 0; } } export { ActionSet, ActionSetWithChildren, unsubscribeActions };