UNPKG

@shopware-ag/meteor-admin-sdk

Version:

The Meteor SDK for the Shopware Administration.

446 lines 17.9 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { generateUniqueId } from './_internals/utils'; import MissingPrivilegesError from './_internals/privileges/missing-privileges-error'; import SerializerFactory from './_internals/serializer'; import createError from './_internals/error-handling/error-factory'; import validate from './_internals/validator'; import { selectData } from './_internals/data/selectData'; import sdkVersion from './_internals/sdkVersion'; const packageVersion = sdkVersion; const { serialize, deserialize } = SerializerFactory({ handle, send, }); // This can't be exported and used in other files as it leads to circular dependencies. Use window._swsdk.adminExtensions instead const adminExtensions = {}; export function setExtensions(extensions) { Object.entries(extensions).forEach(([key, value]) => { // @ts-expect-error = we fill up the values later adminExtensions[key] = {}; Object.entries(value).forEach(([valueKey, valueContent]) => { // @ts-expect-error = we defined the key beforehand adminExtensions[key][valueKey] = valueContent; }); }); } /** * ---------------- * DATA STORES FOR REGISTRIES * ---------------- */ const sourceRegistry = new Set(); const subscriberRegistry = new Set(); /** * ---------------- * MAIN FUNCTIONS FOR EXPORT * ---------------- */ /** * With this method you can send actions or you can request data: * * @param type Choose a type of action from the send-types * @param data The matching data for the type * @returns A promise with the response data in the given responseType */ export function send(type, data, _targetWindow, _origin) { // Generate a unique callback ID used to match the response for this request const callbackId = generateUniqueId(); // Set fallback data when no data is defined const sendData = data !== null && data !== void 0 ? data : {}; // Generate the message with the callbackId const messageData = { _type: type, _data: sendData, _callbackId: callbackId, }; // Serialize the message data to transform Entity, EntityCollection, Criteria etc. to a JSON serializable format let serializedData = serialize(messageData); // Validate if send value contains entity data where the app has no privileges for if (_origin) { const validationErrors = validate({ serializedData: serializedData, origin: _origin, privilegesToCheck: ['read'], type: type, }); if (validationErrors) { // Datasets need the id for matching the response if ([ 'datasetSubscribe', 'datasetUpdate', 'datasetRegistration', 'datasetGet', ].includes(serializedData._type)) { serializedData = serialize({ _type: serializedData._type, _callbackId: serializedData._callbackId, _data: { // @ts-expect-error - We know with the includes that it has an ID // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment id: serializedData._data.id, data: validationErrors, }, }); } // Everything else can overwrite the response else { serializedData = serialize({ _type: serializedData._type, _callbackId: serializedData._callbackId, _data: validationErrors, }); } } } // Convert message data to string for message sending const message = JSON.stringify(serializedData); // Set value if send was resolved let isResolved = false; const timeoutMs = 7000; // Return a promise which resolves when the response is received return new Promise((resolve, reject) => { const callbackHandler = function (event) { if (typeof event.data !== 'string') { return; } // Only execute when callbackId matches if (event.data.indexOf(`"_callbackId":"${callbackId}"`) === -1) { return; } let shopwareResponseData; // Try to parse the json response try { shopwareResponseData = JSON.parse(event.data); } catch (_a) { // Fail silently when message is not a valid json file return; } // Check if messageData is valid if (!isMessageResponseData(shopwareResponseData)) { return; } // Only execute if response value exists if (!shopwareResponseData.hasOwnProperty('_response')) { return; } // Deserialize methods etc. so that they are callable in JS const deserializedResponseData = deserialize(shopwareResponseData, event); // Remove event so that in only execute once window.removeEventListener('message', callbackHandler); // Only return the data if the request is not timed out if (!isResolved) { isResolved = true; const response = deserializedResponseData._response; // @ts-expect-error To not specify a possible error on every message type ignore it here. if (response instanceof Error) { reject(response); return; } // Return the data resolve(response); } }; window.addEventListener('message', callbackHandler); let corsRestriction = true; try { corsRestriction = !window.parent.origin; } catch (_a) { // Silent catch to prevent cross origin frame exception } let targetOrigin = corsRestriction ? document.referrer : window.parent.origin; // If _origin was provided then update the targetOrigin if (_origin) { targetOrigin = _origin; } // Send the data to the target window _targetWindow ? _targetWindow.postMessage(message, targetOrigin) : window.parent.postMessage(message, targetOrigin); // Send timeout when no one sends data back or handler freezes setTimeout(() => { // Only runs when is not resolved if (isResolved) { return; } reject('Send timeout expired. It could be possible that no handler for the postMessage request exists or that the handler freezed.'); }, timeoutMs); }); } /** * @param type Choose a type of action from the {@link send-types} * @param method This method should return the response value * @returns The return value is a cancel function to stop listening to the events */ export function handle(type, method) { const handleListener = function (event) { var _a; return __awaiter(this, void 0, void 0, function* () { if (typeof event.data !== 'string') { return; } // Check if messageData type matches the type argument if (event.data.indexOf(`"_type":"${type}"`) === -1) { return; } let shopwareMessageData; // Try to parse the json file try { shopwareMessageData = JSON.parse(event.data); } catch (_b) { // Fail silently when message is not a valid json file return; } // Check if messageData is valid if (!isMessageData(shopwareMessageData)) { return; } // Deserialize methods etc. so that they are callable in JS const deserializedMessageData = deserialize(shopwareMessageData, event); // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const responseValue = yield Promise.resolve((() => { const responseValidationTypes = [ 'datasetSubscribe', 'datasetGet', 'datasetUpdate', // Test value '_collectionTest', ]; // Message type is not dataset related so just execute the method if (!responseValidationTypes.includes(type)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return method(deserializedMessageData._data, { _event_: event }); } /* * Validate incoming handle messages for privileges * in Entity and Entity Collection */ const validationErrors = validate({ serializedData: shopwareMessageData, origin: event.origin, type: type, privilegesToCheck: ['create', 'delete', 'update', 'read'], }); // If validation errors exists then return them as the response value if (validationErrors) { return validationErrors; } // eslint-disable-next-line @typescript-eslint/no-unsafe-return return method(deserializedMessageData._data, { _event_: event }); })()).catch(e => createError(type, e)); const responseMessage = { _callbackId: deserializedMessageData._callbackId, _type: deserializedMessageData._type, _response: responseValue !== null && responseValue !== void 0 ? responseValue : null, }; // Replace methods etc. so that they are working in JSON format const serializedResponseMessage = (() => { let serializedMessage = serialize(responseMessage); const messageValidationTypes = [ 'datasetSubscribe', 'datasetGet', // Test value '_collectionTest', ]; if (!messageValidationTypes.includes(type)) { return serializedMessage; } // Validate if response value contains entity data where the app has no privileges for const validationErrors = validate({ serializedData: serializedMessage, origin: event.origin, privilegesToCheck: ['read'], type: type, }); if (validationErrors) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment serializedMessage._response = validationErrors; serializedMessage = serialize(serializedMessage); } return serializedMessage; })(); const stringifiedResponseMessage = JSON.stringify(serializedResponseMessage); if (event.source) { // If event source exists then send it back to original source event.source.postMessage(stringifiedResponseMessage, { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment targetOrigin: (_a = event.origin) !== null && _a !== void 0 ? _a : '*', }); } else { // If no event source exists then it should send to same window window.postMessage(stringifiedResponseMessage, window.origin); } }); }; // Start listening directly window.addEventListener('message', handleListener); // Return a cancel method return () => window.removeEventListener('message', handleListener); } export function publish(type, data, sources = [...sourceRegistry].map(({ source, origin, sdkVersion }) => ({ source, origin, sdkVersion, }))) { sources.forEach(({ source, origin }) => { // Disable error handling because not every window need to react to the data // eslint-disable-next-line @typescript-eslint/no-empty-function return send(type, data, source, origin).catch(() => { }); }); } export function subscribe(type, method) { return handle(type, method); } // MAIN FUNCTION WHICH INCLUDES ALL POSSIBILITES export function createSender(messageType, baseMessageOptions) { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type return (messageOptions) => { return send(messageType, Object.assign(Object.assign({}, baseMessageOptions), messageOptions)); }; } /** * Factory method which creates a handler so that the type don't need to be * defined and can be hidden. */ export function createHandler(messageType) { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type return (method) => { return handle(messageType, method); }; } /** * Factory method which creates a handler so that the type doesn't need to be * defined and can be hidden. */ export function createSubscriber(messageType) { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type return (method, id) => { if (!id) { return subscribe(messageType, method); } const wrapper = (data) => { if (data.id === id) { void method(data); } }; return subscribe(messageType, wrapper); }; } const datasets = new Map(); /** * ---------------- * IIFE for default handler * ---------------- */ (() => __awaiter(void 0, void 0, void 0, function* () { // Handle registrations at current window handle('__registerWindow__', ({ sdkVersion }, additionalOptions) => { let source; let origin; if (additionalOptions._event_.source) { source = additionalOptions._event_.source; origin = additionalOptions._event_.origin; } else { source = window; origin = window.origin; } sourceRegistry.add({ source, origin, sdkVersion, }); }); handle('datasetSubscribeRegistration', (data, { _event_ }) => { let source; let origin; if (_event_.source) { source = _event_.source; origin = _event_.origin; } else { source = window; origin = window.origin; } subscriberRegistry.add({ id: data.id, source: source, origin: origin, selectors: data.selectors, }); // When initial data exists directly send it to the subscriber const dataset = datasets.get(data.id); if (dataset) { const selectedData = selectData(dataset, data.selectors, 'datasetSubscribe', origin); if (selectedData instanceof MissingPrivilegesError) { console.error(selectedData); return; } void send('datasetSubscribe', { id: data.id, data: selectedData, selectors: data.selectors, }, source, origin); } }); // Register at parent window yield send('__registerWindow__', { sdkVersion: packageVersion, }); }))().catch((e) => console.error(e)); // New dataset registered export function processDataRegistration(data) { return __awaiter(this, void 0, void 0, function* () { datasets.set(data.id, data.data); // Publish selected data to sources that are inside the subscriberRegistry subscriberRegistry.forEach(({ id, selectors, source, origin }) => { if (id !== data.id) { return; } const selectedData = selectData(data.data, selectors, 'datasetSubscribe', origin); if (selectedData instanceof MissingPrivilegesError) { console.error(selectedData); } // eslint-disable-next-line @typescript-eslint/no-empty-function send('datasetSubscribe', { id, data: selectedData, selectors }, source, origin).catch(() => { }); }); return Promise.resolve(); }); } window._swsdk = { sourceRegistry, subscriberRegistry, datasets, adminExtensions, }; /** * ---------------- * INTERNAL HELPER FUNCTIONS * ---------------- */ /** * Check if the data is valid message data */ function isMessageData(eventData) { const shopwareMessageData = eventData; return !!shopwareMessageData._type && !!shopwareMessageData._data && !!shopwareMessageData._callbackId; } // ShopwareMessageTypes[MESSAGE_TYPE]['responseType'] function isMessageResponseData(eventData) { const shopwareMessageData = eventData; return !!shopwareMessageData._type && !!shopwareMessageData.hasOwnProperty('_response') && !!shopwareMessageData._callbackId; } //# sourceMappingURL=channel.js.map