UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

162 lines • 7.56 kB
/*--------------------------------------------------------------------------------------------- * 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 */ import { RpcConfiguration } from "./rpc/core/RpcConfiguration"; import { CURRENT_REQUEST } from "./rpc/core/RpcRegistry"; import { aggregateLoad, RpcRequest } from "./rpc/core/RpcRequest"; import { RpcRoutingToken } from "./rpc/core/RpcRoutingToken"; import { IpcSession } from "./ipc/IpcSession"; import { IModelError, NoContentError } from "./IModelError"; import { RpcRequestEvent, RpcRequestStatus } from "./rpc/core/RpcConstants"; import { BeDuration, BentleyStatus } from "@itwin/core-bentley"; /** An RPC interface is a set of operations exposed by a service that a client can call, using configurable protocols, * in a platform-independent way. TheRpcInterface class is the base class for RPC interface definitions and implementations. * @public */ export class RpcInterface { static findDiff(backend, frontend) { return backend.major !== frontend.major ? "major" : backend.minor !== frontend.minor ? "minor" : backend.patch !== frontend.patch ? "patch" : backend.prerelease !== frontend.prerelease ? "prerelease" : "same"; } static parseVer(version) { // separate the version from the prerelease tag const parts = version.split(/[:-]/); // Split the major.minor.path into separate components const prefix = parts[0].split("."); const ver = { major: Number(prefix[0]), minor: Number(prefix[1]), patch: Number(prefix[2]) }; if (parts.length > 1) ver.prerelease = parts[1]; return ver; } /** Determines whether the backend version of an RPC interface is compatible (according to semantic versioning) with the frontend version of the interface. */ static isVersionCompatible(backend, frontend) { if (backend === frontend) return true; // most common case, versions are identical const backendSemver = this.parseVer(backend); const frontendSemver = this.parseVer(frontend); // if either has a prerelease tag, they are not compatible unless version strings are identical if (backendSemver.prerelease || frontendSemver.prerelease) return false; const difference = this.findDiff(backendSemver, frontendSemver); // If the major versions are different, the versions are not compatible if (difference === "major") return false; // special case for major version 0. If patch difference, backend patch must be greater than frontend patch if (backendSemver.major === 0) return (difference === "patch" && frontendSemver.patch < backendSemver.patch); // patch difference is fine. If minor versions differ, compatible as long as backend minor version is greater return difference === "patch" || (difference === "minor" && frontendSemver.minor < backendSemver.minor); } /** The configuration for the RPC interface. * @internal */ configuration; /** @internal */ routing; /** @beta */ constructor(routing = RpcRoutingToken.default) { this.routing = routing; this.configuration = RpcConfiguration.supply(this); } /** Obtains the implementation result for an RPC operation. */ async forward(parameters) { const parametersCompat = (arguments.length === 1 && typeof (parameters) === "object") ? parameters : arguments; const parametersArray = Array.isArray(parametersCompat) ? parametersCompat : Array.prototype.slice.call(parametersCompat); const operationName = parametersArray.pop(); const session = IpcSession.active; if (session) { return intercept(session, this, operationName, parametersArray); } else { const request = new this.configuration.protocol.requestType(this, operationName, parametersArray); request.submit(); // eslint-disable-line @typescript-eslint/no-floating-promises this[CURRENT_REQUEST] = request; return request.response; } } } RpcInterface.prototype.configurationSupplier = undefined; class InterceptedRequest extends RpcRequest { async load() { throw new Error(); } async send() { throw new Error(); } setHeader(_name, _value) { throw new Error(); } } async function intercept(session, client, operation, parameters) { const request = new InterceptedRequest(client, operation, []); client[CURRENT_REQUEST] = request; const context = await client.configuration.protocol.serialize(request); request.parameters = parameters; const info = { definition: { interfaceName: context.operation.interfaceDefinition, interfaceVersion: context.operation.interfaceVersion, }, operation, parameters, context: { applicationId: context.applicationId, applicationVersion: context.applicationVersion, id: context.id, sessionId: context.sessionId, protocolVersion: (context.protocolVersion || 0).toString(), }, }; const dispatch = async () => { aggregateLoad.lastRequest = new Date().getTime(); const response = await session.handleRpc(info); aggregateLoad.lastResponse = new Date().getTime(); if (typeof (response) === "object" && response.hasOwnProperty("iTwinRpcCoreResponse") && response.hasOwnProperty("managedStatus")) { const status = response; if (status.managedStatus === "pending") { return handlePending(request, status, dispatch); } else if (status.managedStatus === "notFound") { return handleNotFound(request, status, dispatch); } else if (status.managedStatus === "noContent") { return handleNoContent(); } } else { return response; } }; return dispatch(); } async function handlePending(request, status, dispatch) { request._status = RpcRequestStatus.Pending; request._extendedStatus = status.responseValue.message; RpcRequest.events.raiseEvent(RpcRequestEvent.PendingUpdateReceived, request); const delay = request.operation.policy.retryInterval(request.client.configuration); await BeDuration.wait(delay); return dispatch(); } async function handleNotFound(request, status, dispatch) { return new Promise((resolve, reject) => { let resubmitted = false; RpcRequest.notFoundHandlers.raiseEvent(request, status.responseValue, async () => { if (resubmitted) { throw new IModelError(BentleyStatus.ERROR, `Already resubmitted using this handler.`); } resubmitted = true; try { const response = await dispatch(); resolve(response); } catch (err) { reject(err); // eslint-disable-line @typescript-eslint/prefer-promise-reject-errors } }, reject); }); } async function handleNoContent() { throw new NoContentError(); } //# sourceMappingURL=RpcInterface.js.map