UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

240 lines • 10.5 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.WebAppRpcRequest = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const IModelError_1 = require("../../IModelError"); const RpcConstants_1 = require("../core/RpcConstants"); const RpcMarshaling_1 = require("../core/RpcMarshaling"); const RpcRequest_1 = require("../core/RpcRequest"); const RpcMultipartParser_1 = require("./multipart/RpcMultipartParser"); const RpcMultipart_1 = require("./RpcMultipart"); const WebAppRpcProtocol_1 = require("./WebAppRpcProtocol"); /** A web application RPC request. * @internal */ class WebAppRpcRequest extends RpcRequest_1.RpcRequest { _loading = false; _request = {}; _pathSuffix = ""; get _headers() { return this._request.headers; } /** The maximum size permitted for an encoded component in a URL. * Note that some backends limit the total cumulative request size. Our current node backends accept requests with a max size of 16 kb. * In addition to the url size, an authorization header may also add considerably to the request size. * @note This is used for features like encoding the payload of a cacheable request in the URL. */ static maxUrlComponentSize = 1024 * 8; /** The HTTP method for this request. */ method; /** Convenience access to the protocol of this request. */ protocol = this.client.configuration.protocol; /** Standardized access to metadata about the request (useful for purposes such as logging). */ metadata = { status: 0, message: "" }; /** Parses a request. */ static async parseRequest(protocol, req) { return this.backend.parseRequest(protocol, req); } /** Sends the response for a web request. */ static async sendResponse(protocol, request, fulfillment, req, res) { return this.backend.sendResponse(protocol, request, fulfillment, req, res); } /** Determines the most efficient transport type for an RPC value. */ static computeTransportType(value, source) { if (source instanceof Uint8Array || (Array.isArray(source) && source[0] instanceof Uint8Array)) { return RpcConstants_1.RpcContentType.Binary; } else if (value.data.length > 0) { return RpcConstants_1.RpcContentType.Multipart; } else if (value.stream) { return RpcConstants_1.RpcContentType.Stream; } else { return RpcConstants_1.RpcContentType.Text; } } /** Constructs a web application request. */ constructor(client, operation, parameters) { super(client, operation, parameters); this.path = this.protocol.supplyPathForOperation(this.operation, this); this.method = "head"; this._request.headers = {}; } /** Sets request header values. */ setHeader(name, value) { this._headers[name] = value; } /** Sends the request. */ async send() { this._loading = true; await this.setupTransport(); return new Promise(async (resolve, reject) => { try { resolve(await this.performFetch()); } catch (reason) { reject(new IModelError_1.ServerError(-1, typeof (reason) === "string" ? reason : "Server connection error.")); } }); } computeRetryAfter(attempts) { const retryAfter = this._response && this._response.headers.get("Retry-After"); if (retryAfter) { this.resetTransientFaultCount(); const r = Number(retryAfter); if (Number.isFinite(r)) { return r * 1000; } const d = Date.parse(retryAfter); if (!Number.isNaN(d)) { return d - Date.now(); } } else { this.recordTransientFault(); } return super.computeRetryAfter(attempts); } handleUnknownResponse(code) { if (this.protocol.isTimeout(code)) { this.reject(new IModelError_1.ServerTimeoutError("Request timeout.")); } else { this.reject(new IModelError_1.ServerError(code, "Unknown server response code.")); } } async load() { return new Promise(async (resolve, reject) => { try { if (!this._loading) return; const response = this._response; if (!response) { reject(new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Invalid state.")); return; } if (this.protocol.protocolVersionHeaderName) { const version = response.headers.get(this.protocol.protocolVersionHeaderName); if (version) { this.responseProtocolVersion = parseInt(version, 10); } } const contentType = response.headers.get(RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT); const responseType = WebAppRpcProtocol_1.WebAppRpcProtocol.computeContentType(contentType); if (responseType === RpcConstants_1.RpcContentType.Binary) { resolve(await this.loadBinary(response)); } else if (responseType === RpcConstants_1.RpcContentType.Multipart) { resolve(await this.loadMultipart(response, (0, core_bentley_1.expectNotNull)(contentType))); } else { resolve(await this.loadText(response)); } this._loading = false; this.setLastUpdatedTime(); this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.ResponseLoaded, this); } catch (reason) { if (!this._loading) return; this._loading = false; reject(new IModelError_1.ServerError(this.metadata.status, typeof (reason) === "string" ? reason : "Unknown server response error.")); } }); } /** Override to supply an alternate fetch function. */ supplyFetch() { return fetch; } /** Override to supply an alternate Request function. */ supplyRequest() { return Request; } async performFetch() { const requestClass = this.supplyRequest(); const fetchFunction = this.supplyFetch(); const path = new URL(this.path, typeof location !== "undefined" ? location.origin : undefined); if (this._pathSuffix) { const params = new URLSearchParams(); params.set("parameters", this._pathSuffix); path.search = `?${params.toString()}`; } const request = new requestClass(path.toString(), this._request); const response = await fetchFunction(request); this._response = response; this.metadata.status = response.status; return response.status; } async loadText(response) { const value = await response.text(); this.metadata.message = value; return RpcMarshaling_1.RpcSerializedValue.create(value); } async loadBinary(response) { const value = new Uint8Array(await response.arrayBuffer()); const objects = JSON.stringify(RpcMarshaling_1.MarshalingBinaryMarker.createDefault()); return RpcMarshaling_1.RpcSerializedValue.create(objects, [value]); } async loadMultipart(response, contentType) { const data = await response.arrayBuffer(); const value = new RpcMultipartParser_1.RpcMultipartParser(contentType, new Uint8Array(data)).parse(); return value; } async setupTransport() { const parameters = (await this.protocol.serialize(this)).parameters; const transportType = WebAppRpcRequest.computeTransportType(parameters, this.parameters); if (transportType === RpcConstants_1.RpcContentType.Binary) { this.setupBinaryTransport(parameters); } else if (transportType === RpcConstants_1.RpcContentType.Multipart) { this.setupMultipartTransport(parameters); } else { this.setupTextTransport(parameters); } } setupBinaryTransport(parameters) { this._headers[RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT] = RpcConstants_1.WEB_RPC_CONSTANTS.BINARY; this._request.method = "post"; this._request.body = parameters.data[0]; } setupMultipartTransport(parameters) { // IMPORTANT: do not set a multipart Content-Type header value. The browser does this automatically! delete this._headers[RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT]; this._request.method = "post"; this._request.body = RpcMultipart_1.RpcMultipart.createForm(parameters); } setupTextTransport(parameters) { if (this.operation.policy.allowResponseCaching(this)) { const encodedBody = btoa(parameters.objects); if (encodedBody.length <= WebAppRpcRequest.maxUrlComponentSize) { this._request.method = "get"; this._request.body = undefined; delete this._headers[RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT]; this._pathSuffix = encodedBody; return; } } this._pathSuffix = ""; this._headers[RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT] = RpcConstants_1.WEB_RPC_CONSTANTS.TEXT; this._request.method = "post"; this._request.body = parameters.objects; } /** @internal */ static backend = { sendResponse: async (_protocol, _request, _fulfillment, _req, _res) => { throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Not bound."); }, parseRequest: async (_protocol, _req) => { throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Not bound."); }, }; } exports.WebAppRpcRequest = WebAppRpcRequest; //# sourceMappingURL=WebAppRpcRequest.js.map