@sprucelabs/mercury-client
Version:
The simple way to interact with the Spruce Experience Platform
390 lines (389 loc) • 16.6 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());
});
};
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,
]);
}