From 3132283eb4a51cc00c12ef74b4f9ac4b3c1090c4 Mon Sep 17 00:00:00 2001 From: Bill Goehrig <33036725+wgoehrig@users.noreply.github.com> Date: Fri, 20 May 2022 17:15:30 -0400 Subject: [PATCH] Fix initial pending request always being treated as an error. --- common/api/core-common.api.md | 4 ++- ...lg-integration-tests_2022-05-20-21-13.json | 10 ++++++++ core/common/src/rpc/core/RpcProtocol.ts | 3 +++ core/common/src/rpc/core/RpcRequest.ts | 1 + core/common/src/rpc/web/WebAppRpcProtocol.ts | 11 ++++---- core/common/src/rpc/web/WebAppRpcRequest.ts | 4 --- .../rpc/src/frontend/_Setup.test.ts | 25 ++++++++++++++++--- 7 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 common/changes/@itwin/core-common/billg-integration-tests_2022-05-20-21-13.json diff --git a/common/api/core-common.api.md b/common/api/core-common.api.md index ba0390f148ad..840ece2d35fd 100644 --- a/common/api/core-common.api.md +++ b/common/api/core-common.api.md @@ -7544,6 +7544,8 @@ export abstract class RpcProtocol { getOperationFromPath(path: string): SerializedRpcOperation; getStatus(code: number): RpcRequestStatus; inflateToken(tokenFromBody: IModelRpcProps, _request: SerializedRpcRequest): IModelRpcProps; + // (undocumented) + initialize(_token?: IModelRpcProps): Promise; readonly invocationType: typeof RpcInvocation; // (undocumented) onRpcClientInitialized(_definition: RpcInterfaceDefinition, _client: RpcInterface): void; @@ -9526,7 +9528,7 @@ export abstract class WebAppRpcProtocol extends RpcProtocol { handleOperationPostRequest(req: HttpServerRequest, res: HttpServerResponse): Promise; abstract info: OpenAPIInfo; // (undocumented) - initialize(): Promise; + initialize(token?: IModelRpcProps): Promise; isTimeout(code: number): boolean; get openAPIDescription(): RpcOpenAPIDescription; pathPrefix: string; diff --git a/common/changes/@itwin/core-common/billg-integration-tests_2022-05-20-21-13.json b/common/changes/@itwin/core-common/billg-integration-tests_2022-05-20-21-13.json new file mode 100644 index 000000000000..d1ac065f5d72 --- /dev/null +++ b/common/changes/@itwin/core-common/billg-integration-tests_2022-05-20-21-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-common", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/core-common" +} \ No newline at end of file diff --git a/core/common/src/rpc/core/RpcProtocol.ts b/core/common/src/rpc/core/RpcProtocol.ts index 317f020adde7..4ade3f44e094 100644 --- a/core/common/src/rpc/core/RpcProtocol.ts +++ b/core/common/src/rpc/core/RpcProtocol.ts @@ -186,6 +186,9 @@ export abstract class RpcProtocol { return new (this.invocationType)(this, request).fulfillment; } + /** @internal */ + public async initialize(_token?: IModelRpcProps): Promise { } + /** Serializes a request. */ public async serialize(request: RpcRequest): Promise { const serializedContext = await RpcConfiguration.requestContext.serialize(request); diff --git a/core/common/src/rpc/core/RpcRequest.ts b/core/common/src/rpc/core/RpcRequest.ts index db8e6804167d..0234d797bed7 100644 --- a/core/common/src/rpc/core/RpcRequest.ts +++ b/core/common/src/rpc/core/RpcRequest.ts @@ -323,6 +323,7 @@ export abstract class RpcRequest { this._connecting = true; RpcRequest._activeRequests.set(this.id, this); this.protocol.events.raiseEvent(RpcProtocolEvent.RequestCreated, this); + await this.protocol.initialize(this.operation.policy.token(this)); this._sending = new Cancellable(this.setHeaders().then(async () => this.send())); this.operation.policy.sentCallback(this); diff --git a/core/common/src/rpc/web/WebAppRpcProtocol.ts b/core/common/src/rpc/web/WebAppRpcProtocol.ts index b3e9aefcb2fe..3fd64ac74831 100644 --- a/core/common/src/rpc/web/WebAppRpcProtocol.ts +++ b/core/common/src/rpc/web/WebAppRpcProtocol.ts @@ -19,13 +19,14 @@ import { CommonLoggerCategory } from "../../CommonLoggerCategory"; import { RpcInterface } from "../../RpcInterface"; import { RpcManager } from "../../RpcManager"; import { RpcRoutingToken } from "../core/RpcRoutingToken"; +import { IModelRpcProps } from "../../IModel"; class InitializeInterface extends RpcInterface { public static readonly interfaceName = "InitializeInterface"; public static readonly interfaceVersion = "1.0.0"; - public async initialize() { return this.forward(arguments); } + public async initialize(_token?: IModelRpcProps) { return this.forward(arguments); } - public static createRequest(protocol: WebAppRpcProtocol) { + public static createRequest(protocol: WebAppRpcProtocol, token?: IModelRpcProps) { const routing = RpcRoutingToken.generate(); const config = class extends RpcConfiguration { @@ -39,7 +40,7 @@ class InitializeInterface extends RpcInterface { RpcConfiguration.initializeInterfaces(instance); const client = RpcManager.getClientForInterface(InitializeInterface, routing); - return new (protocol.requestType)(client, "initialize", []); + return new (protocol.requestType)(client, "initialize", [token]); } } @@ -91,14 +92,14 @@ export abstract class WebAppRpcProtocol extends RpcProtocol { public allowedHeaders: Set = new Set(); /** @internal */ - public async initialize() { + public override async initialize(token?: IModelRpcProps) { if (this._initialized) { return this._initialized; } return this._initialized = new Promise(async (resolve) => { try { - const request = InitializeInterface.createRequest(this); + const request = InitializeInterface.createRequest(this, token); const response = await request.preflight(); if (response && response.ok) { (response.headers.get("Access-Control-Allow-Headers") || "").split(",").forEach((v) => this.allowedHeaders.add(v.trim())); diff --git a/core/common/src/rpc/web/WebAppRpcRequest.ts b/core/common/src/rpc/web/WebAppRpcRequest.ts index 1247dd266e09..5a2e10309f22 100644 --- a/core/common/src/rpc/web/WebAppRpcRequest.ts +++ b/core/common/src/rpc/web/WebAppRpcRequest.ts @@ -183,10 +183,6 @@ export class WebAppRpcRequest extends RpcRequest { /** Sends the request. */ protected async send(): Promise { - if (this.method !== "options") { - await this.protocol.initialize(); - } - this._loading = true; await this.setupTransport(); diff --git a/full-stack-tests/rpc/src/frontend/_Setup.test.ts b/full-stack-tests/rpc/src/frontend/_Setup.test.ts index b9a3049a1c3b..05d8e90b2860 100644 --- a/full-stack-tests/rpc/src/frontend/_Setup.test.ts +++ b/full-stack-tests/rpc/src/frontend/_Setup.test.ts @@ -4,13 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { executeBackendCallback } from "@itwin/certa/lib/utils/CallbackUtils"; -import { BentleyCloudRpcConfiguration, BentleyCloudRpcManager, RpcConfiguration } from "@itwin/core-common"; +import { Logger, LogLevel } from "@itwin/core-bentley"; +import { BentleyCloudRpcConfiguration, BentleyCloudRpcManager, RpcConfiguration, WebAppRpcProtocol } from "@itwin/core-common"; import { ElectronApp } from "@itwin/core-electron/lib/cjs/ElectronFrontend"; import { MobileRpcManager } from "@itwin/core-mobile/lib/cjs/MobileFrontend"; import { BackendTestCallbacks } from "../common/SideChannels"; -import { AttachedInterface, MobileTestInterface, MultipleClientsInterface, rpcInterfaces } from "../common/TestRpcInterface"; +import { AttachedInterface, MobileTestInterface, MultipleClientsInterface, rpcInterfaces, TestRpcInterface } from "../common/TestRpcInterface"; +import { assert } from "chai"; -RpcConfiguration.disableRoutingValidation = true; +Logger.initializeToConsole(); +Logger.setLevelDefault(LogLevel.Warning); +RpcConfiguration.disableRoutingValidation = false; function initializeCloud(protocol: string) { const port = Number(window.location.port) + 2000; @@ -62,3 +66,18 @@ before(async () => { return ElectronApp.startup({ iModelApp: { rpcInterfaces } }); } }); + +describe("BentleyCloudRpcManager", () => { + it("should initialize correctly when routing validation is enabled", async () => { + if (currentEnvironment === "http") { + const protocol = TestRpcInterface.getClient().configuration.protocol as WebAppRpcProtocol; + assert.equal(protocol.allowedHeaders.size, 0); + await TestRpcInterface.getClient().op16({ iModelId: "foo", key: "bar" }, { iModelId: "foo", key: "bar" }); + assert.isAtLeast(protocol.allowedHeaders.size, 1); + } + }); + + after(() => { + RpcConfiguration.disableRoutingValidation = true; + }); +});