UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

264 lines • 12.6 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module RpcInterface */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RpcInvocation = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const CommonLoggerCategory_1 = require("../../CommonLoggerCategory"); const IModelError_1 = require("../../IModelError"); const RpcInterface_1 = require("../../RpcInterface"); const RpcConfiguration_1 = require("./RpcConfiguration"); const RpcConstants_1 = require("./RpcConstants"); const RpcControl_1 = require("./RpcControl"); const RpcMarshaling_1 = require("./RpcMarshaling"); const RpcOperation_1 = require("./RpcOperation"); const RpcProtocol_1 = require("./RpcProtocol"); const RpcRegistry_1 = require("./RpcRegistry"); /** An RPC operation invocation in response to a request. * @internal */ class RpcInvocation { static runActivity = async (_activity, fn) => fn(); _threw = false; _pending = false; _notFound = false; _noContent = false; _timeIn = 0; _timeOut = 0; /** The protocol for this invocation. */ protocol; /** The received request. */ request; /** The operation of the request. */ operation = undefined; /** The implementation response. */ result; /** The fulfillment for this request. */ fulfillment; /** The status for this request. */ get status() { return this._threw ? RpcConstants_1.RpcRequestStatus.Rejected : this._pending ? RpcConstants_1.RpcRequestStatus.Pending : this._notFound ? RpcConstants_1.RpcRequestStatus.NotFound : this._noContent ? RpcConstants_1.RpcRequestStatus.NoContent : RpcConstants_1.RpcRequestStatus.Resolved; } /** The elapsed time for this invocation. */ get elapsed() { return this._timeOut - this._timeIn; } /** * The invocation for the current RPC operation. * @note The return value of this function is only reliable in an RPC impl class member function where program control was received from the RpcInvocation constructor function. */ static current(rpcImpl) { return rpcImpl[RpcRegistry_1.CURRENT_INVOCATION]; } /** Constructs an invocation. */ constructor(protocol, request) { this._timeIn = new Date().getTime(); this.protocol = protocol; this.request = request; try { try { this.operation = RpcOperation_1.RpcOperation.lookup(this.request.operation.interfaceDefinition, this.request.operation.operationName); const backend = this.operation.interfaceVersion; const frontend = this.request.operation.interfaceVersion; if (!RpcInterface_1.RpcInterface.isVersionCompatible(backend, frontend)) { throw new IModelError_1.IModelError(core_bentley_1.RpcInterfaceStatus.IncompatibleVersion, `Backend version ${backend} does not match frontend version ${frontend} for RPC interface ${this.operation.operationName}.`); } } catch (error) { if (this.handleUnknownOperation(error)) { this.operation = RpcOperation_1.RpcOperation.lookup(this.request.operation.interfaceDefinition, this.request.operation.operationName); } else { throw error; } } this.result = this.resolve(); } catch (error) { this.result = this.reject(error); } this.fulfillment = this.result.then(async (value) => this._threw ? this.fulfillRejected(value) : this.fulfillResolved(value), async (reason) => this.fulfillRejected(reason)); } handleUnknownOperation(error) { RpcControl_1.RpcControlChannel.ensureInitialized(); return this.protocol.configuration.controlChannel.handleUnknownOperation(this, error); } static sanitizeForLog(activity) { /* eslint-disable @typescript-eslint/naming-convention */ return activity ? { ActivityId: activity.activityId, SessionId: activity.sessionId, ApplicationId: activity.applicationId, ApplicationVersion: activity.applicationVersion, rpcMethod: activity.rpcMethod, } : undefined; /* eslint-enable @typescript-eslint/naming-convention */ } async resolve() { const request = this.request; const activity = { activityId: request.id, applicationId: request.applicationId, applicationVersion: request.applicationVersion, sessionId: request.sessionId, user: request.user, accessToken: request.authorization, rpcMethod: request.operation.operationName, }; try { this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.RequestReceived, this); const parameters = request.parametersOverride || RpcMarshaling_1.RpcMarshaling.deserialize(this.protocol, request.parameters); this.applyPolicies(parameters); const impl = RpcRegistry_1.RpcRegistry.instance.getImplForInterface(this.operation.interfaceDefinition); impl[RpcRegistry_1.CURRENT_INVOCATION] = this; const op = this.lookupOperationFunction(impl); return await RpcInvocation.runActivity(activity, async () => op.call(impl, ...parameters) .catch(async (error) => { // this catch block is intentionally placed inside `runActivity` to attach the right logging metadata and use the correct openTelemetry span. if (!(error instanceof RpcControl_1.RpcPendingResponse)) { core_bentley_1.Logger.logError(CommonLoggerCategory_1.CommonLoggerCategory.RpcInterfaceBackend, "Error in RPC operation", { error: core_bentley_1.BentleyError.getErrorProps(error) }); core_bentley_1.Tracing.recordException(error); } throw error; })); } catch (error) { return this.reject(error); } } applyPolicies(parameters) { if (!parameters || !Array.isArray(parameters)) return; for (let i = 0; i !== parameters.length; ++i) { const parameter = parameters[i]; const isToken = typeof (parameter) === "object" && parameter !== null && parameter.hasOwnProperty("iModelId") && parameter.hasOwnProperty("iTwinId"); if (isToken && this.protocol.checkToken && !this.operation.policy.allowTokenMismatch) { const inflated = this.protocol.inflateToken(parameter, this.request); parameters[i] = inflated; if (!RpcInvocation.compareTokens(parameter, inflated)) { if (RpcConfiguration_1.RpcConfiguration.throwOnTokenMismatch) { throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "IModelRpcProps mismatch detected for this request."); } else { core_bentley_1.Logger.logWarning(CommonLoggerCategory_1.CommonLoggerCategory.RpcInterfaceBackend, "IModelRpcProps mismatch detected for this request."); } } } } } static compareTokens(a, b) { return a.key === b.key && a.iTwinId === b.iTwinId && a.iModelId === b.iModelId && (undefined === a.changeset || (a.changeset.id === b.changeset?.id)); } async reject(error) { this._threw = true; return error; } async fulfillResolved(value) { this._timeOut = new Date().getTime(); this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.BackendResponseCreated, this); const result = RpcMarshaling_1.RpcMarshaling.serialize(this.protocol, value); return this.fulfill(result, value); } async fulfillRejected(reason) { this._timeOut = new Date().getTime(); if (!RpcConfiguration_1.RpcConfiguration.developmentMode) reason.stack = undefined; const result = RpcMarshaling_1.RpcMarshaling.serialize(this.protocol, reason); if (reason instanceof RpcControl_1.RpcPendingResponse) { this._pending = true; this._threw = false; result.objects = reason.message; this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.BackendReportedPending, this); } else if (this.supportsNoContent() && reason?.errorNumber === core_bentley_1.IModelStatus.NoContent) { this._noContent = true; this._threw = false; this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.BackendReportedNoContent, this); } else if (reason instanceof RpcControl_1.RpcNotFoundResponse) { this._notFound = true; this._threw = false; this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.BackendReportedNotFound, this); } else { this._threw = true; this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.BackendErrorOccurred, this); } return this.fulfill(result, reason); } supportsNoContent() { if (!this.request.protocolVersion) { return false; } return RpcProtocol_1.RpcProtocol.protocolVersion >= RpcProtocol_1.RpcProtocolVersion.IntroducedNoContent && this.request.protocolVersion >= RpcProtocol_1.RpcProtocolVersion.IntroducedNoContent; } supportsStatusCategory() { if (!this.request.protocolVersion) { return false; } if (!this.protocol.supportsStatusCategory) { return false; } return RpcProtocol_1.RpcProtocol.protocolVersion >= RpcProtocol_1.RpcProtocolVersion.IntroducedStatusCategory && this.request.protocolVersion >= RpcProtocol_1.RpcProtocolVersion.IntroducedStatusCategory; } fulfill(result, rawResult) { const fulfillment = { result, rawResult, status: this.protocol.getCode(this.status), id: this.request.id, interfaceName: (typeof (this.operation) === "undefined") ? "" : this.operation.interfaceDefinition.interfaceName, allowCompression: this.operation ? this.operation.policy.allowResponseCompression : true, }; this.transformResponseStatus(fulfillment, rawResult); try { const impl = RpcRegistry_1.RpcRegistry.instance.getImplForInterface(this.operation.interfaceDefinition); if (impl[RpcRegistry_1.CURRENT_INVOCATION] === this) { impl[RpcRegistry_1.CURRENT_INVOCATION] = undefined; } } catch { } return fulfillment; } lookupOperationFunction(implementation) { const func = implementation[this.operation.operationName]; if (!func || typeof (func) !== "function") throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `RPC interface class "${implementation.constructor.name}" does not implement operation "${this.operation.operationName}".`); return func; } transformResponseStatus(fulfillment, rawResult) { if (!this.supportsStatusCategory()) { return; } let managedStatus; if (this._pending) { managedStatus = "pending"; } else if (this._notFound) { managedStatus = "notFound"; } else if (this._noContent) { managedStatus = "noContent"; } if (managedStatus) { const responseValue = fulfillment.result.objects; const status = { iTwinRpcCoreResponse: true, managedStatus, responseValue }; fulfillment.result.objects = JSON.stringify(status); status.responseValue = rawResult; // for ipc case fulfillment.rawResult = status; } if (rawResult instanceof Error) { fulfillment.status = core_bentley_1.StatusCategory.for(rawResult).code; } } } exports.RpcInvocation = RpcInvocation; //# sourceMappingURL=RpcInvocation.js.map