UNPKG

@sprucelabs/mercury-client

Version:

The simple way to interact with the Spruce Experience Platform

390 lines (389 loc) • 16.6 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 { AbstractEventEmitter } from '@sprucelabs/mercury-event-emitter'; import { eventContractUtil, eventNameUtil, eventResponseUtil, } from '@sprucelabs/spruce-event-utils'; import clone from 'just-clone'; import SpruceError from '../errors/SpruceError.js'; import { authenticateFqen } from './MercurySocketIoClient.js'; import MutableContractClient from './MutableContractClient.js'; import { connectionStatusContract } from './statusChangePayloadSchema.js'; class InternalEmitter extends AbstractEventEmitter { doesHandleEvent(eventName) { try { eventContractUtil.getSignatureByName(this.eventContract, eventName); return true; } catch (_a) { return false; } } validateEmitPayload(schema, actualPayload, eventName) { const payload = Object.assign({}, actualPayload); delete payload.source; return super.validateEmitPayload(schema, payload, eventName); } mixinOnlyUniqueSignatures(contract) { const fqens = Object.keys(contract.eventSignatures); for (const fqen of fqens) { if (!this.eventContract.eventSignatures[fqen]) { this.eventContract.eventSignatures[fqen] = contract.eventSignatures[fqen]; } } } overrideSignatures(contract) { const fqens = Object.keys(contract.eventSignatures); for (const fqen of fqens) { this.eventContract.eventSignatures[fqen] = contract.eventSignatures[fqen]; } } getContract() { return this.eventContract; } setContract(contract) { this.eventContract = contract; } } class MercuryTestClient extends MutableContractClient { get eventContract() { return MercuryTestClient.emitter.getContract(); } set eventContract(contract) { } static setShouldCheckPermissionsOnLocalEvents(should) { this.shouldCheckPermissionsOnLocalEvents = should; } static setNamespacesThatMustBeHandledLocally(namespaces) { this.namespacesThatHaveToBeHandledLocally = namespaces; } static getNamespacesThatMustBeHandledLocally() { return this.namespacesThatHaveToBeHandledLocally; } constructor(options) { const contract = options.eventContract; super(Object.assign(Object.assign({}, options), { eventContract: contract })); this._isConnected = false; this.isConnectedToApi = false; this.shouldHandleAuthenticateLocallyIfListenerSet = true; this.shouldWaitForDelayedConnectIfAuthing = true; if (!MercuryTestClient.emitter) { MercuryTestClient.getInternalEmitter(contract); } else if (contract) { MercuryTestClient.emitter.overrideSignatures(contract); } } /** @ts-ignore */ static getInternalEmitter(contract) { if (!MercuryTestClient.emitter) { MercuryTestClient.emitter = new InternalEmitter({ eventSignatures: {}, }); } const mixed = mixinConnectionEvents(contract); MercuryTestClient.emitter.mixinOnlyUniqueSignatures(mixed); /** @ts-ignore */ return MercuryTestClient.emitter; } off(eventName, cb) { const _super = Object.create(null, { off: { get: () => super.off } }); return __awaiter(this, void 0, void 0, function* () { var _a, _b; yield ((_a = MercuryTestClient.emitter) === null || _a === void 0 ? void 0 : _a.off(eventName, cb)); if (((_b = MercuryTestClient.emitter) === null || _b === void 0 ? void 0 : _b.listenCount(eventName)) === 0) { return _super.off.call(this, eventName); } else { return 1; } }); } static mixinContract(contract) { MutableContractClient.mixinContract(contract); MercuryTestClient.emitter.mixinContract(contract); } mixinContract(contract) { MutableContractClient.mixinContract(contract); MercuryTestClient.emitter.mixinContract(contract); } doesHandleEvent(eventName) { var _a; return (super.doesHandleEvent(eventName) || ((_a = MercuryTestClient.emitter) === null || _a === void 0 ? void 0 : _a.doesHandleEvent(eventName))); } on(...args) { return __awaiter(this, void 0, void 0, function* () { //@ts-ignore return MercuryTestClient.emitter.on(...args); }); } emit(...args) { const _super = Object.create(null, { emit: { get: () => super.emit } }); return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; const fqen = args[0]; try { if (this.shouldHandleEventLocally(fqen)) { return this.handleEventLocally(args); } else { if (MercuryTestClient.shouldRequireLocalListeners && fqen !== 'connection-status-change') { throw new SpruceError({ code: 'MUST_HANDLE_LOCALLY', fqen, friendlyMessage: `You need to listen to, fake a response to '${fqen}', or boot your skill. Try 'spruce create.listener', 'eventFaker.on('${fqen}')', or 'await this.bootSkill()'!`, }); } yield this.connectIfNotConnected(fqen); //@ts-ignore const results = yield _super.emit.call(this, ...args); const firstError = (_c = (_b = (_a = results.responses) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.errors) === null || _c === void 0 ? void 0 : _c[0]; if (firstError && ((_d = firstError.options) === null || _d === void 0 ? void 0 : _d.code) === 'INVALID_EVENT_NAME') { firstError.message = `Event not found! Make sure you are booting your skill in your test with \`await this.bootSkill()\`. If you haven't, you'll need to create a listener with \`spruce create.listener\`.\n\nOriginal Error:\n\n${firstError.options.friendlyMessage}`; } return results; } } catch (err) { if (((_e = err.options) === null || _e === void 0 ? void 0 : _e.code) === 'INVALID_EVENT_NAME') { err.message = `${err.message} Double check it's spelled correctly (types are passing) and that you've run \`spruce create.event\` to create the event.`; } throw err; } }); } shouldHandleEventLocally(fqen) { const emitter = MercuryTestClient.emitter; if (!this.shouldHandleAuthenticateLocallyIfListenerSet && fqen === authenticateFqen) { return false; } if (fqen === 'connection-status-change') { return true; } const { eventNamespace } = eventNameUtil.split(fqen); if (eventNamespace && MercuryTestClient.namespacesThatHaveToBeHandledLocally.indexOf(eventNamespace) > -1) { return true; } return emitter.listenCount(fqen) > 0; } handleEventLocally(args) { return __awaiter(this, void 0, void 0, function* () { var _a; const emitter = MercuryTestClient.emitter; const fqen = args[0]; const payload = args[1]; if (!MercuryTestClient.emitter.doesHandleEvent(fqen)) { throw new SpruceError({ code: 'MUST_CREATE_EVENT', fqen, }); } if (MercuryTestClient.shouldRequireLocalListeners !== false && MercuryTestClient.emitter.listenCount(fqen) === 0) { if (fqen === 'connection-status-change') { return { responses: [], totalContracts: 0, totalErrors: 0, totalResponses: 0, }; } throw new SpruceError({ code: 'MUST_HANDLE_LOCALLY', fqen, }); } this.assertValidEmitTargetAndPayload(fqen, payload); let { argsWithSource } = this.buildSource(args); const contract = emitter.getContract(); const sig = eventContractUtil.getSignatureByName(contract, fqen); const { eventNamespace } = eventNameUtil.split(fqen); if (eventNamespace) { this.assertValidEventSignature(sig, fqen); } if (sig.emitPermissionContract && eventNamespace) { const doesHonor = yield this.optionallyCheckPermissions(args, sig.emitPermissionContract.id, fqen); if (typeof doesHonor !== 'boolean') { return doesHonor; } if (!doesHonor) { return { totalContracts: 1, totalErrors: 1, totalResponses: 1, responses: [ { errors: [ new SpruceError({ code: 'UNAUTHORIZED_ACCESS', fqen, action: 'emit', target: (_a = args[1]) !== null && _a !== void 0 ? _a : {}, permissionContractId: sig.emitPermissionContract.id, }), ], }, ], }; } } //@ts-ignore const results = (yield emitter.emit(...argsWithSource)); return clone(results); }); } assertValidEventSignature(sig, fqen) { var _a, _b; if (!sig.isGlobal && !((_b = (_a = sig.emitPayloadSchema) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.target)) { throw new SpruceError({ code: 'INVALID_EVENT_SIGNATURE', fqen, instructions: 'Oh no! You have to either create an event using `spruce create.event`, set your event to global (event.options.ts, which requires special permissions) or add a target that includes an organizationId or locationId. Choose wisely!', }); } } optionallyCheckPermissions(args, permissionContractId, fqen) { return __awaiter(this, void 0, void 0, function* () { var _a; if (!MercuryTestClient.shouldCheckPermissionsOnLocalEvents) { return true; } let { target } = (_a = args[1]) !== null && _a !== void 0 ? _a : {}; const permTarget = {}; if (target === null || target === void 0 ? void 0 : target.organizationId) { permTarget.organizationId = target.organizationId; } if (target === null || target === void 0 ? void 0 : target.locationId) { throw new Error('checking permissions against a location is not supported. Add to mercury-workspace -> mercury-client'); } const { eventNamespace } = eventNameUtil.split(fqen); const results = yield this.emit('does-honor-permission-contract::v2020_12_25', { target: permTarget, payload: { id: `${eventNamespace}.${permissionContractId}`, }, }); if (results.totalErrors > 0) { return results; } const { doesHonor } = eventResponseUtil.getFirstResponseOrThrow(results); return doesHonor; }); } buildSource(args) { var _a, _b, _c; let source = Object.assign({}, (_a = args[1]) === null || _a === void 0 ? void 0 : _a.source); if ((_b = this.auth) === null || _b === void 0 ? void 0 : _b.person) { source.personId = this.auth.person.id; } if ((_c = this.auth) === null || _c === void 0 ? void 0 : _c.skill) { source.skillId = this.auth.skill.id; } if (args[0] !== 'authenticate::v2020_12_25' && !source.proxyToken && this.getProxyToken()) { source.proxyToken = this.getProxyToken(); } const argsWithSource = [...args]; if (typeof argsWithSource[1] !== 'function' && Object.keys(source).length > 0) { argsWithSource[1] = Object.assign(Object.assign({}, argsWithSource[1]), { source }); } return { source, argsWithSource: clone(argsWithSource) }; } connectIfNotConnected(fqen) { return __awaiter(this, void 0, void 0, function* () { if (!this.isConnectedToApi) { this.isConnectedToApi = true; this.connectPromise = this.delayedConnectAndAuth(fqen); } if (!this.shouldWaitForDelayedConnectIfAuthing && fqen === authenticateFqen) { return; } yield this.connectPromise; }); } delayedConnectAndAuth(fqen) { const _super = Object.create(null, { connect: { get: () => super.connect } }); return __awaiter(this, void 0, void 0, function* () { yield _super.connect.call(this); if (this.lastAuthOptions && fqen !== authenticateFqen) { this.authPromise = undefined; this.shouldHandleAuthenticateLocallyIfListenerSet = false; this.shouldWaitForDelayedConnectIfAuthing = false; yield this.authenticate(this.lastAuthOptions); } }); } connect() { const _super = Object.create(null, { connect: { get: () => super.connect } }); return __awaiter(this, void 0, void 0, function* () { if (this._isConnected) { yield _super.connect.call(this); } this._isConnected = true; }); } isConnected() { return this._isConnected; } disconnect() { const _super = Object.create(null, { disconnect: { get: () => super.disconnect } }); return __awaiter(this, void 0, void 0, function* () { if (this.isConnectedToApi) { yield _super.disconnect.call(this); } this._isConnected = false; }); } static reset() { MutableContractClient.reset(); //@ts-ignore MercuryTestClient.emitter = undefined; //@ts-ignore MercuryTestClient.emitter = MercuryTestClient.getInternalEmitter({ eventSignatures: {}, }); } getIsTestClient() { return true; } static setShouldRequireLocalListeners(shouldRequire) { this.shouldRequireLocalListeners = shouldRequire; } static getShouldRequireLocalListeners() { return this.shouldRequireLocalListeners; } } MercuryTestClient.shouldCheckPermissionsOnLocalEvents = false; MercuryTestClient.namespacesThatHaveToBeHandledLocally = []; MercuryTestClient.shouldRequireLocalListeners = true; export default MercuryTestClient; function mixinConnectionEvents(contract) { return eventContractUtil.unifyContracts([ contract !== null && contract !== void 0 ? contract : { eventSignatures: {} }, connectionStatusContract, ]); }