UNPKG

@itwin/core-frontend

Version:
132 lines • 7.35 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 IModelConnection */ import { BentleyError, BentleyStatus, Logger } from "@itwin/core-bentley"; import { IModelError, IModelReadRpcInterface, IModelVersion, RpcManager, RpcOperation, RpcRequest, RpcRequestEvent, } from "@itwin/core-common"; import { FrontendLoggerCategory } from "./common/FrontendLoggerCategory"; import { IModelApp } from "./IModelApp"; import { IModelConnection } from "./IModelConnection"; import { IModelRoutingContext } from "./IModelRoutingContext"; import { IpcApp } from "./IpcApp"; const loggerCategory = FrontendLoggerCategory.IModelConnection; /** * An IModelConnection to a Checkpoint of an iModel. * @see [CheckpointConnection]($docs/learning/frontend/IModelConnection) * @public */ export class CheckpointConnection extends IModelConnection { _fromIpc; /** The Guid that identifies the iTwin that owns this iModel. */ get iTwinId() { return super.iTwinId; } /** The Guid that identifies this iModel. */ get iModelId() { return super.iModelId; } /** Returns `true` if [[close]] has already been called. */ get isClosed() { return this._isClosed ? true : false; } _isClosed; constructor(props, fromIpc) { super(props); this._fromIpc = fromIpc; } /** Type guard for instanceof [[CheckpointConnection]] */ isCheckpointConnection() { return true; } /** * Open a readonly IModelConnection to a Checkpoint of an iModel. */ static async openRemote(iTwinId, iModelId, version = IModelVersion.latest()) { if (undefined === IModelApp.hubAccess) throw new Error("Missing an implementation of IModelApp.hubAccess"); const accessToken = await IModelApp.getAccessToken(); const changeset = await IModelApp.hubAccess.getChangesetFromVersion({ accessToken, iModelId, version }); let connection; const iModelProps = { iTwinId, iModelId, changeset }; if (IpcApp.isValid) { connection = new this(await IpcApp.appFunctionIpc.openCheckpoint(iModelProps), true); } else { const routingContext = IModelRoutingContext.current || IModelRoutingContext.default; connection = new this(await this.callOpen(iModelProps, routingContext), false); RpcManager.setIModel(connection); connection.routingContext = routingContext; RpcRequest.notFoundHandlers.addListener(connection._reopenConnectionHandler); } IModelConnection.onOpen.raiseEvent(connection); return connection; } static async callOpen(iModelToken, routingContext) { // Try opening the iModel repeatedly accommodating any pending responses from the backend. // Waits for an increasing amount of time (but within a range) before checking on the pending request again. const connectionRetryIntervalRange = { min: 100, max: 5000 }; // in milliseconds let connectionRetryInterval = Math.min(connectionRetryIntervalRange.min, IModelConnection.connectionTimeout); const openForReadOperation = RpcOperation.lookup(IModelReadRpcInterface, "getConnectionProps"); if (!openForReadOperation) throw new IModelError(BentleyStatus.ERROR, "IModelReadRpcInterface.getConnectionProps() is not available"); openForReadOperation.policy.retryInterval = () => connectionRetryInterval; Logger.logTrace(loggerCategory, `IModelConnection.open`, iModelToken); const startTime = Date.now(); const removeListener = RpcRequest.events.addListener((type, request) => { if (type !== RpcRequestEvent.PendingUpdateReceived) // eslint-disable-line @typescript-eslint/no-deprecated return; if (!(openForReadOperation && request.operation === openForReadOperation)) return; Logger.logTrace(loggerCategory, "Received pending open notification in IModelConnection.open", iModelToken); const connectionTimeElapsed = Date.now() - startTime; if (connectionTimeElapsed > IModelConnection.connectionTimeout) { Logger.logError(loggerCategory, `Timed out opening connection in IModelConnection.open (took longer than ${IModelConnection.connectionTimeout} milliseconds)`, iModelToken); throw new IModelError(BentleyStatus.ERROR, "Opening a connection was timed out"); // NEEDS_WORK: More specific error status } connectionRetryInterval = Math.min(connectionRetryIntervalRange.max, connectionRetryInterval * 2, IModelConnection.connectionTimeout - connectionTimeElapsed); if (request.retryInterval !== connectionRetryInterval) { request.retryInterval = connectionRetryInterval; Logger.logTrace(loggerCategory, `Adjusted open connection retry interval to ${request.retryInterval} milliseconds in IModelConnection.open`, iModelToken); } }); const openPromise = IModelReadRpcInterface.getClientForRouting(routingContext.token).getConnectionProps(iModelToken); let openResponse; try { openResponse = await openPromise; } finally { Logger.logTrace(loggerCategory, "Completed open request in IModelConnection.open", iModelToken); removeListener(); } return openResponse; } _reopenConnectionHandler = async (request, response, resubmit, reject) => { if (!response.hasOwnProperty("isIModelNotFoundResponse")) reject(); const iModelRpcProps = request.parameters[0]; if (this._fileKey !== iModelRpcProps.key) reject(); // The handler is called for a different connection than this Logger.logTrace(loggerCategory, "Attempting to reopen connection", () => iModelRpcProps); try { const openResponse = await CheckpointConnection.callOpen(iModelRpcProps, this.routingContext); // The new/reopened connection may have a new rpcKey and/or changesetId, but the other IModelRpcTokenProps should be the same this._fileKey = openResponse.key; this.changeset = openResponse.changeset; } catch (error) { reject(BentleyError.getErrorMessage(error)); } finally { } Logger.logTrace(loggerCategory, "Resubmitting original request after reopening connection", iModelRpcProps); request.parameters[0] = this.getRpcProps(); // Modify the token of the original request before resubmitting it. resubmit(); }; /** Close this CheckpointConnection */ async close() { if (this.isClosed) return; this.beforeClose(); if (this._fromIpc) await IpcApp.appFunctionIpc.closeIModel(this._fileKey); else RpcRequest.notFoundHandlers.removeListener(this._reopenConnectionHandler); this._isClosed = true; } } //# sourceMappingURL=CheckpointConnection.js.map