@heyframe-ag/meteor-admin-sdk
Version:
The Meteor SDK for the HeyFrame Administration.
471 lines • 21.3 kB
JavaScript
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());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./_internals/utils", "./_internals/privileges/missing-privileges-error", "./_internals/serializer", "./_internals/error-handling/error-factory", "./_internals/validator", "./_internals/data/selectData", "./_internals/sdkVersion"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processDataRegistration = exports.createSubscriber = exports.createHandler = exports.createSender = exports.subscribe = exports.publish = exports.handle = exports.send = exports.setExtensions = void 0;
const utils_1 = require("./_internals/utils");
const missing_privileges_error_1 = __importDefault(require("./_internals/privileges/missing-privileges-error"));
const serializer_1 = __importDefault(require("./_internals/serializer"));
const error_factory_1 = __importDefault(require("./_internals/error-handling/error-factory"));
const validator_1 = __importDefault(require("./_internals/validator"));
const selectData_1 = require("./_internals/data/selectData");
const sdkVersion_1 = __importDefault(require("./_internals/sdkVersion"));
const packageVersion = sdkVersion_1.default;
const { serialize, deserialize } = (0, serializer_1.default)({
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 = {};
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;
});
});
}
exports.setExtensions = setExtensions;
/**
* ----------------
* 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
*/
function send(type, data, _targetWindow, _origin) {
// Generate a unique callback ID used to match the response for this request
const callbackId = (0, utils_1.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 = (0, validator_1.default)({
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 heyframeResponseData;
// Try to parse the json response
try {
heyframeResponseData = JSON.parse(event.data);
}
catch (_a) {
// Fail silently when message is not a valid json file
return;
}
// Check if messageData is valid
if (!isMessageResponseData(heyframeResponseData)) {
return;
}
// Only execute if response value exists
if (!heyframeResponseData.hasOwnProperty('_response')) {
return;
}
// Deserialize methods etc. so that they are callable in JS
const deserializedResponseData = deserialize(heyframeResponseData, 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);
});
}
exports.send = send;
/**
* @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
*/
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 heyframeMessageData;
// Try to parse the json file
try {
heyframeMessageData = JSON.parse(event.data);
}
catch (_b) {
// Fail silently when message is not a valid json file
return;
}
// Check if messageData is valid
if (!isMessageData(heyframeMessageData)) {
return;
}
// Deserialize methods etc. so that they are callable in JS
const deserializedMessageData = deserialize(heyframeMessageData, 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 = (0, validator_1.default)({
serializedData: heyframeMessageData,
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 => (0, error_factory_1.default)(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 = (0, validator_1.default)({
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);
}
exports.handle = handle;
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(() => { });
});
}
exports.publish = publish;
function subscribe(type, method) {
return handle(type, method);
}
exports.subscribe = subscribe;
// MAIN FUNCTION WHICH INCLUDES ALL POSSIBILITES
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));
};
}
exports.createSender = createSender;
/**
* Factory method which creates a handler so that the type don't need to be
* defined and can be hidden.
*/
function createHandler(messageType) {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
return (method) => {
return handle(messageType, method);
};
}
exports.createHandler = createHandler;
/**
* Factory method which creates a handler so that the type doesn't need to be
* defined and can be hidden.
*/
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);
};
}
exports.createSubscriber = createSubscriber;
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 = (0, selectData_1.selectData)(dataset, data.selectors, 'datasetSubscribe', origin);
if (selectedData instanceof missing_privileges_error_1.default) {
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
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 = (0, selectData_1.selectData)(data.data, selectors, 'datasetSubscribe', origin);
if (selectedData instanceof missing_privileges_error_1.default) {
console.error(selectedData);
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
send('datasetSubscribe', { id, data: selectedData, selectors }, source, origin).catch(() => { });
});
return Promise.resolve();
});
}
exports.processDataRegistration = processDataRegistration;
window._swsdk = {
sourceRegistry,
subscriberRegistry,
datasets,
adminExtensions,
};
/**
* ----------------
* INTERNAL HELPER FUNCTIONS
* ----------------
*/
/**
* Check if the data is valid message data
*/
function isMessageData(eventData) {
const heyframeMessageData = eventData;
return !!heyframeMessageData._type
&& !!heyframeMessageData._data
&& !!heyframeMessageData._callbackId;
}
// HeyFrameMessageTypes[MESSAGE_TYPE]['responseType']
function isMessageResponseData(eventData) {
const heyframeMessageData = eventData;
return !!heyframeMessageData._type
&& !!heyframeMessageData.hasOwnProperty('_response')
&& !!heyframeMessageData._callbackId;
}
});
//# sourceMappingURL=channel.js.map