@itwin/core-common
Version:
iTwin.js components common to frontend and backend
264 lines • 12.6 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* 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