@itwin/core-common
Version:
iTwin.js components common to frontend and backend
502 lines • 21.3 kB
JavaScript
"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.initializeRpcRequest = exports.RpcRequest = exports.ResponseLike = exports.aggregateLoad = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const IModelError_1 = require("../../IModelError");
const RpcConfiguration_1 = require("./RpcConfiguration");
const RpcConstants_1 = require("./RpcConstants");
const RpcMarshaling_1 = require("./RpcMarshaling");
const RpcOperation_1 = require("./RpcOperation");
const RpcProtocol_1 = require("./RpcProtocol");
const RpcRegistry_1 = require("./RpcRegistry");
/* eslint-disable @typescript-eslint/naming-convention */
// cspell:ignore csrf
/* eslint-disable @typescript-eslint/no-deprecated */
/** @internal */
exports.aggregateLoad = { lastRequest: 0, lastResponse: 0 };
/** @internal */
class ResponseLike {
_data;
get body() { return null; }
async arrayBuffer() { return this._data; }
async blob() { throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Not implemented."); }
async formData() { throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Not implemented."); }
async json() { return this._data; }
async text() { return this._data; }
get bodyUsed() { return false; }
get headers() { throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Not implemented."); }
get ok() { return this.status >= 200 && this.status <= 299; }
get redirected() { return false; }
get status() { return 200; }
get statusText() { return ""; }
get trailer() { throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Not implemented."); }
get type() { return "basic"; }
get url() { return ""; }
clone() { return { ...this }; }
constructor(data) {
this._data = Promise.resolve(data);
}
}
exports.ResponseLike = ResponseLike;
class Cancellable {
promise;
cancel() { }
constructor(task) {
this.promise = new Promise((resolve, reject) => {
this.cancel = () => resolve(undefined);
task.then(resolve, reject);
});
}
}
/** A RPC operation request.
* @internal
*/
class RpcRequest {
static _activeRequests = new Map();
_resolve = () => undefined;
_resolveRaw = () => undefined;
_reject = () => undefined;
_rejectRaw = () => undefined;
_created = 0;
_lastSubmitted = 0;
_lastUpdated = 0;
/** @internal */
_status = RpcConstants_1.RpcRequestStatus.Unknown;
/** @internal */
_extendedStatus = "";
_connecting = false;
_active = true;
_hasRawListener = false;
_raw = undefined;
_sending;
_attempts = 0;
_retryAfter = null;
_transientFaults = 0;
_response = undefined;
_rawPromise;
responseProtocolVersion = RpcProtocol_1.RpcProtocolVersion.None;
/** All RPC requests that are currently in flight. */
static get activeRequests() { return this._activeRequests; }
/** Events raised by RpcRequest. See [[RpcRequestEvent]] */
static events = new core_bentley_1.BeEvent();
/** Resolvers for "not found" requests. See [[RpcRequestNotFoundHandler]] */
static notFoundHandlers = new core_bentley_1.BeEvent();
/** The aggregate operations profile of all active RPC interfaces. */
static get aggregateLoad() { return exports.aggregateLoad; }
/**
* The request for the current RPC operation.
* @note The return value of this function is only reliable if program control was received from a RPC interface class member function that directly returns the result of calling RpcInterface.forward.
*/
static current(context) {
return context[RpcRegistry_1.CURRENT_REQUEST];
}
/** The unique identifier of this request. */
id;
/** The operation for this request. */
operation;
/** The parameters for this request. */
parameters;
/** The RPC client instance for this request. */
client;
/** Convenience access to the protocol of this request. */
protocol;
/** The implementation response for this request. */
response;
/** The status of this request. */
get status() { return this._status; }
/** Extended status information for this request (if available). */
get extendedStatus() { return this._extendedStatus; }
/** The last submission for this request. */
get lastSubmitted() { return this._lastSubmitted; }
/** The last status update received for this request. */
get lastUpdated() { return this._lastUpdated; }
/** The target interval (in milliseconds) between submission attempts for this request. */
retryInterval;
/** Whether a connection is active for this request. */
get connecting() { return this._connecting; }
/** Whether this request is pending. */
get pending() {
switch (this.status) {
case RpcConstants_1.RpcRequestStatus.Submitted:
case RpcConstants_1.RpcRequestStatus.Pending: {
return true;
}
default: {
return false;
}
}
}
/** The elapsed time for this request. */
get elapsed() {
return this._lastUpdated - this._created;
}
/** A protocol-specific path identifier for this request. */
path;
/** A protocol-specific method identifier for this request. */
method;
/** An attempt-specific value for when to next retry this request. */
get retryAfter() { return this._retryAfter; }
/** Finds the first parameter of a given structural type if present. */
findParameterOfType(requiredProperties) {
for (const param of this.parameters) {
if (typeof (param) === "object" && param !== null) {
for (const prop of Object.getOwnPropertyNames(requiredProperties)) {
if (prop in param && typeof (param[prop]) === requiredProperties[prop]) {
return param;
}
}
}
}
return undefined;
}
/** Finds the first IModelRpcProps parameter if present. */
findTokenPropsParameter() {
return this.findParameterOfType({ iModelId: "string" });
}
/** The raw implementation response for this request. */
get rawResponse() {
this._hasRawListener = true;
return this._rawPromise;
}
/** Constructs an RPC request. */
constructor(client, operation, parameters) {
this._created = new Date().getTime();
this.path = "";
this.method = "";
this.client = client;
this.protocol = client.configuration.protocol;
this.operation = RpcOperation_1.RpcOperation.lookup(client.constructor, operation);
this.parameters = parameters;
this.retryInterval = this.operation.policy.retryInterval(client.configuration);
this.response = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
this._rawPromise = new Promise((resolve, reject) => {
this._resolveRaw = resolve;
this._rejectRaw = reject;
});
this.id = RpcConfiguration_1.RpcConfiguration.requestContext.getId(this) || core_bentley_1.Guid.createValue();
this.setStatus(RpcConstants_1.RpcRequestStatus.Created);
this.operation.policy.requestCallback(this);
}
/** Sets the last updated time for the request. */
setLastUpdatedTime() {
this._lastUpdated = new Date().getTime();
}
computeRetryAfter(attempts) {
return (((Math.pow(2, attempts) - 1) / 2) * 500) + 500;
}
recordTransientFault() {
++this._transientFaults;
}
resetTransientFaultCount() {
this._transientFaults = 0;
}
supportsStatusCategory() {
if (!this.protocol.supportsStatusCategory) {
return false;
}
return RpcProtocol_1.RpcProtocol.protocolVersion >= RpcProtocol_1.RpcProtocolVersion.IntroducedStatusCategory && this.responseProtocolVersion >= RpcProtocol_1.RpcProtocolVersion.IntroducedStatusCategory;
}
/* @internal */
cancel() {
if (typeof (this._sending) === "undefined") {
return;
}
this._sending.cancel();
this._sending = undefined;
this._connecting = false;
RpcRequest._activeRequests.delete(this.id);
this.setStatus(RpcConstants_1.RpcRequestStatus.Cancelled);
}
/* @internal */
async submit() {
if (!this._active)
return;
this._lastSubmitted = new Date().getTime();
this._retryAfter = null;
++this._attempts;
if (this.status === RpcConstants_1.RpcRequestStatus.Created || this.status === RpcConstants_1.RpcRequestStatus.NotFound || this.status === RpcConstants_1.RpcRequestStatus.Cancelled) {
this.setStatus(RpcConstants_1.RpcRequestStatus.Submitted);
}
try {
this._connecting = true;
RpcRequest._activeRequests.set(this.id, this);
this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.RequestCreated, this);
this._sending = new Cancellable(this.setHeaders().then(async () => this.send()));
this.operation.policy.sentCallback(this);
const response = await this._sending.promise;
if (typeof (response) === "undefined") {
return;
}
this._sending = undefined;
const status = this.protocol.getStatus(response);
if (this._hasRawListener && status === RpcConstants_1.RpcRequestStatus.Resolved && typeof (this._response) !== "undefined") {
this._connecting = false;
RpcRequest._activeRequests.delete(this.id);
this.resolveRaw();
}
else {
this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.ResponseLoading, this);
if (status === RpcConstants_1.RpcRequestStatus.Unknown) {
this._connecting = false;
RpcRequest._activeRequests.delete(this.id);
this.handleUnknownResponse(response);
return;
}
const value = await this.load();
this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.ResponseLoaded, this);
RpcRequest._activeRequests.delete(this.id);
this._connecting = false;
this.handleResponse(response, value);
}
}
catch (err) {
this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.ConnectionErrorReceived, this, err);
RpcRequest._activeRequests.delete(this.id);
this._connecting = false;
this.reject(err);
}
}
handleUnknownResponse(code) {
this.reject(new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `Unknown response ${code}.`));
}
handleResponse(code, value) {
const protocolStatus = this.protocol.getStatus(code);
const status = this.transformResponseStatus(protocolStatus, value);
if (RpcConstants_1.RpcRequestStatus.isTransientError(status)) {
return this.handleTransientError(status);
}
switch (status) {
case RpcConstants_1.RpcRequestStatus.Resolved: {
return this.handleResolved(value);
}
case RpcConstants_1.RpcRequestStatus.Rejected: {
return this.handleRejected(value);
}
case RpcConstants_1.RpcRequestStatus.Pending: {
return this.setPending(status, value.objects);
}
case RpcConstants_1.RpcRequestStatus.NotFound: {
return this.handleNotFound(status, value);
}
case RpcConstants_1.RpcRequestStatus.NoContent: {
return this.handleNoContent();
}
}
}
transformResponseStatus(protocolStatus, value) {
if (!this.supportsStatusCategory()) {
return protocolStatus;
}
let status = protocolStatus;
if (protocolStatus === RpcConstants_1.RpcRequestStatus.Pending) {
status = RpcConstants_1.RpcRequestStatus.Rejected;
}
else if (protocolStatus === RpcConstants_1.RpcRequestStatus.NotFound) {
status = RpcConstants_1.RpcRequestStatus.Rejected;
}
else if (protocolStatus === RpcConstants_1.RpcRequestStatus.Unknown) {
status = RpcConstants_1.RpcRequestStatus.Rejected;
}
if (value.objects.indexOf("iTwinRpcCoreResponse") !== -1 && value.objects.indexOf("managedStatus") !== -1) {
const managedStatus = JSON.parse(value.objects);
value.objects = managedStatus.responseValue;
if (managedStatus.managedStatus === "pending") {
status = RpcConstants_1.RpcRequestStatus.Pending;
}
else if (managedStatus.managedStatus === "notFound") {
status = RpcConstants_1.RpcRequestStatus.NotFound;
}
}
return status;
}
handleResolved(value) {
try {
this._raw = value.objects;
const result = RpcMarshaling_1.RpcMarshaling.deserialize(this.protocol, value);
if (ArrayBuffer.isView(result)) {
this._raw = result.buffer;
}
return this.resolve(result);
}
catch (err) {
return this.reject(err);
}
}
handleRejected(value) {
this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.BackendErrorReceived, this);
try {
const error = RpcMarshaling_1.RpcMarshaling.deserialize(this.protocol, value);
const hasInfo = error && typeof (error) === "object" && error.hasOwnProperty("name") && error.hasOwnProperty("message");
const name = hasInfo ? error.name : "";
const message = hasInfo ? error.message : "";
const errorNumber = (hasInfo && error.hasOwnProperty("errorNumber")) ? error.errorNumber : core_bentley_1.BentleyStatus.ERROR;
return this.reject(new IModelError_1.BackendError(errorNumber, name, message, () => error));
}
catch (err) {
return this.reject(err);
}
}
handleNoContent() {
return this.reject(new IModelError_1.NoContentError());
}
handleNotFound(status, value) {
if (RpcRequest.notFoundHandlers.numberOfListeners === 0)
this.handleRejected(value);
const response = RpcMarshaling_1.RpcMarshaling.deserialize(this.protocol, value);
this.setStatus(status);
let resubmitted = false;
RpcRequest.notFoundHandlers.raiseEvent(this, response, () => {
if (resubmitted)
throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `Already resubmitted using this handler.`);
resubmitted = true;
void this.submit();
}, (reason) => reason ? this.reject(reason) : this.handleRejected(value));
return;
}
resolve(result) {
if (!this._active)
return;
this._active = false;
this.setLastUpdatedTime();
this._resolve(result);
if (this._hasRawListener) {
if (typeof (this._raw) === "undefined") {
throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Cannot access raw response.");
}
this._resolveRaw(new ResponseLike(this._raw));
}
this.setStatus(RpcConstants_1.RpcRequestStatus.Resolved);
this[Symbol.dispose]();
}
resolveRaw() {
if (typeof (this._response) === "undefined") {
throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Cannot access raw response.");
}
this._active = false;
this.setLastUpdatedTime();
this._resolveRaw(this._response);
this.setStatus(RpcConstants_1.RpcRequestStatus.Resolved);
this[Symbol.dispose]();
}
reject(reason) {
if (!this._active)
return;
this._active = false;
this.setLastUpdatedTime();
this._reject(reason);
if (this._hasRawListener) {
this._rejectRaw(reason);
}
this.setStatus(RpcConstants_1.RpcRequestStatus.Rejected);
this[Symbol.dispose]();
}
/** @internal */
[Symbol.dispose]() {
this.setStatus(RpcConstants_1.RpcRequestStatus.Disposed);
this._raw = undefined;
this._response = undefined;
const client = this.client;
if (client[RpcRegistry_1.CURRENT_REQUEST] === this) {
client[RpcRegistry_1.CURRENT_REQUEST] = undefined;
}
}
setPending(status, extendedStatus) {
if (!this._active)
return;
this.setLastUpdatedTime();
this._extendedStatus = extendedStatus;
this.setStatus(status);
RpcRequest.events.raiseEvent(RpcConstants_1.RpcRequestEvent.PendingUpdateReceived, this);
}
handleTransientError(status) {
if (!this._active)
return;
this.setLastUpdatedTime();
this._retryAfter = this.computeRetryAfter(this._attempts - 1);
if (this._transientFaults > this.protocol.configuration.transientFaultLimit) {
this.reject(new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `Exceeded transient fault limit.`));
}
else {
this.setStatus(status);
RpcRequest.events.raiseEvent(RpcConstants_1.RpcRequestEvent.TransientErrorReceived, this);
}
}
async setHeaders() {
const versionHeader = this.protocol.protocolVersionHeaderName;
if (versionHeader && RpcProtocol_1.RpcProtocol.protocolVersion)
this.setHeader(versionHeader, RpcProtocol_1.RpcProtocol.protocolVersion.toString());
const headerNames = this.protocol.serializedClientRequestContextHeaderNames;
const headerValues = await RpcConfiguration_1.RpcConfiguration.requestContext.serialize(this);
if (headerNames.id)
this.setHeader(headerNames.id, headerValues.id || this.id); // Cannot be empty
if (headerNames.applicationVersion)
this.setHeader(headerNames.applicationVersion, headerValues.applicationVersion);
if (headerNames.applicationId)
this.setHeader(headerNames.applicationId, headerValues.applicationId);
if (headerNames.sessionId)
this.setHeader(headerNames.sessionId, headerValues.sessionId);
if (headerNames.authorization && headerValues.authorization)
this.setHeader(headerNames.authorization, headerValues.authorization);
if (headerValues.csrfToken)
this.setHeader(headerValues.csrfToken.headerName, headerValues.csrfToken.headerValue);
}
setStatus(status) {
if (this._status === status)
return;
this._status = status;
RpcRequest.events.raiseEvent(RpcConstants_1.RpcRequestEvent.StatusChanged, this);
}
}
exports.RpcRequest = RpcRequest;
/** @internal */
exports.initializeRpcRequest = (() => {
let initialized = false;
return () => {
if (initialized) {
return;
}
initialized = true;
RpcRequest.events.addListener((type, request) => {
if (type !== RpcConstants_1.RpcRequestEvent.StatusChanged)
return;
switch (request.status) {
case RpcConstants_1.RpcRequestStatus.Submitted: {
exports.aggregateLoad.lastRequest = request.lastSubmitted;
break;
}
case RpcConstants_1.RpcRequestStatus.Pending:
case RpcConstants_1.RpcRequestStatus.Resolved:
case RpcConstants_1.RpcRequestStatus.Rejected: {
exports.aggregateLoad.lastResponse = request.lastUpdated;
break;
}
}
});
RpcProtocol_1.RpcProtocol.events.addListener((type) => {
const now = new Date().getTime();
switch (type) {
case RpcConstants_1.RpcProtocolEvent.RequestReceived: {
exports.aggregateLoad.lastRequest = now;
break;
}
case RpcConstants_1.RpcProtocolEvent.BackendReportedPending:
case RpcConstants_1.RpcProtocolEvent.BackendErrorOccurred:
case RpcConstants_1.RpcProtocolEvent.BackendResponseCreated: {
exports.aggregateLoad.lastResponse = now;
break;
}
}
});
};
})();
//# sourceMappingURL=RpcRequest.js.map