From b2a0bfab4329fb5dd5563b1a22ef043b5f86ae31 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 22 May 2026 08:46:18 +0100 Subject: [PATCH] refactor(gateway): split connect assembly --- src/gateway/client.ts | 227 ++++++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 98 deletions(-) diff --git a/src/gateway/client.ts b/src/gateway/client.ts index 2fdfb59f3337..8e2996531299 100644 --- a/src/gateway/client.ts +++ b/src/gateway/client.ts @@ -89,6 +89,14 @@ type StoredDeviceAuth = { scopes?: string[]; }; +type AssembledConnect = { + params: ConnectParams; + authApprovalRuntimeToken: string | undefined; + resolvedDeviceToken: string | undefined; + storedToken: string | undefined; + usingStoredDeviceToken: boolean | undefined; +}; + type FingerprintCheckingClientOptions = Omit & { checkServerIdentity?: (servername: string, cert: CertMeta) => Error | undefined; }; @@ -538,100 +546,9 @@ export class GatewayClient { return; } const role = this.opts.role ?? "operator"; - let authApprovalRuntimeToken: string | undefined; - let resolvedDeviceToken: string | undefined; - let storedToken: string | undefined; - let usingStoredDeviceToken: boolean | undefined; - let params: ConnectParams; + let assembled: AssembledConnect; try { - const { - authToken, - authBootstrapToken, - authDeviceToken, - authPassword, - authApprovalRuntimeToken: selectedAuthApprovalRuntimeToken, - signatureToken, - resolvedDeviceToken: selectedResolvedDeviceToken, - storedToken: selectedStoredToken, - storedScopes, - usingStoredDeviceToken: selectedUsingStoredDeviceToken, - } = this.selectConnectAuth(role); - authApprovalRuntimeToken = selectedAuthApprovalRuntimeToken; - resolvedDeviceToken = selectedResolvedDeviceToken; - storedToken = selectedStoredToken; - usingStoredDeviceToken = selectedUsingStoredDeviceToken; - if (this.pendingDeviceTokenRetry && authDeviceToken) { - this.pendingDeviceTokenRetry = false; - } - const auth = - authToken || - authBootstrapToken || - authPassword || - resolvedDeviceToken || - authApprovalRuntimeToken - ? { - token: authToken, - bootstrapToken: authBootstrapToken, - deviceToken: authDeviceToken ?? resolvedDeviceToken, - password: authPassword, - approvalRuntimeToken: authApprovalRuntimeToken, - } - : undefined; - const signedAtMs = Date.now(); - const scopes = this.resolveConnectScopes({ - usingStoredDeviceToken, - storedScopes, - }); - const platform = this.opts.platform ?? process.platform; - const device = (() => { - if (!this.opts.deviceIdentity) { - return undefined; - } - const payload = buildDeviceAuthPayloadV3({ - deviceId: this.opts.deviceIdentity.deviceId, - clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, - clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND, - role, - scopes, - signedAtMs, - token: signatureToken ?? null, - nonce, - platform, - deviceFamily: this.opts.deviceFamily, - }); - const signature = signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload); - return { - id: this.opts.deviceIdentity.deviceId, - publicKey: publicKeyRawBase64UrlFromPem(this.opts.deviceIdentity.publicKeyPem), - signature, - signedAt: signedAtMs, - nonce, - }; - })(); - params = { - minProtocol: this.opts.minProtocol ?? MIN_CLIENT_PROTOCOL_VERSION, - maxProtocol: this.opts.maxProtocol ?? PROTOCOL_VERSION, - client: { - id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, - displayName: this.opts.clientDisplayName, - version: this.opts.clientVersion ?? VERSION, - platform, - deviceFamily: this.opts.deviceFamily, - mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND, - instanceId: this.opts.instanceId, - }, - caps: Array.isArray(this.opts.caps) ? this.opts.caps : [], - commands: Array.isArray(this.opts.commands) ? this.opts.commands : undefined, - permissions: - this.opts.permissions && typeof this.opts.permissions === "object" - ? this.opts.permissions - : undefined, - pathEnv: this.opts.pathEnv, - auth, - role, - scopes, - device, - }; + assembled = this.assembleConnectParams({ role, nonce }); } catch (err) { this.handleConnectFailure(err); return; @@ -640,7 +557,7 @@ export class GatewayClient { this.connectSent = true; this.clearConnectChallengeTimeout(); - void this.request("connect", params) + void this.request("connect", assembled.params) .then((helloOk) => { this.pendingDeviceTokenRetry = false; this.deviceTokenRetryBudgetUsed = false; @@ -674,12 +591,12 @@ export class GatewayClient { const shouldRetryWithDeviceToken = this.shouldRetryWithStoredDeviceToken({ error: err, explicitGatewayToken: normalizeOptionalString(this.opts.token), - resolvedDeviceToken, - storedToken: storedToken ?? undefined, + resolvedDeviceToken: assembled.resolvedDeviceToken, + storedToken: assembled.storedToken, }); if ( this.opts.deviceIdentity && - usingStoredDeviceToken && + assembled.usingStoredDeviceToken && err instanceof GatewayClientRequestError && readConnectErrorDetailCode(err.details) === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH @@ -709,7 +626,7 @@ export class GatewayClient { if ( this.shouldRetryWithoutApprovalRuntimeToken({ error: err, - authApprovalRuntimeToken, + authApprovalRuntimeToken: assembled.authApprovalRuntimeToken, }) ) { this.approvalRuntimeTokenCompatibilityDisabled = true; @@ -730,6 +647,120 @@ export class GatewayClient { }); } + private assembleConnectParams(params: { role: string; nonce: string }): AssembledConnect { + const { role, nonce } = params; + const selectedAuth = this.selectConnectAuth(role); + const { + authToken, + authBootstrapToken, + authDeviceToken, + authPassword, + authApprovalRuntimeToken, + signatureToken, + resolvedDeviceToken, + storedToken, + storedScopes, + usingStoredDeviceToken, + } = selectedAuth; + + if (this.pendingDeviceTokenRetry && authDeviceToken) { + this.pendingDeviceTokenRetry = false; + } + + const auth = + authToken || + authBootstrapToken || + authPassword || + resolvedDeviceToken || + authApprovalRuntimeToken + ? { + token: authToken, + bootstrapToken: authBootstrapToken, + deviceToken: authDeviceToken ?? resolvedDeviceToken, + password: authPassword, + approvalRuntimeToken: authApprovalRuntimeToken, + } + : undefined; + const signedAtMs = Date.now(); + const scopes = this.resolveConnectScopes({ + usingStoredDeviceToken, + storedScopes, + }); + const platform = this.opts.platform ?? process.platform; + + return { + params: { + minProtocol: this.opts.minProtocol ?? MIN_CLIENT_PROTOCOL_VERSION, + maxProtocol: this.opts.maxProtocol ?? PROTOCOL_VERSION, + client: { + id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, + displayName: this.opts.clientDisplayName, + version: this.opts.clientVersion ?? VERSION, + platform, + deviceFamily: this.opts.deviceFamily, + mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND, + instanceId: this.opts.instanceId, + }, + caps: Array.isArray(this.opts.caps) ? this.opts.caps : [], + commands: Array.isArray(this.opts.commands) ? this.opts.commands : undefined, + permissions: + this.opts.permissions && typeof this.opts.permissions === "object" + ? this.opts.permissions + : undefined, + pathEnv: this.opts.pathEnv, + auth, + role, + scopes, + device: this.buildDeviceConnectParams({ + nonce, + role, + scopes, + signatureToken, + signedAtMs, + platform, + }), + }, + authApprovalRuntimeToken, + resolvedDeviceToken, + storedToken, + usingStoredDeviceToken, + }; + } + + private buildDeviceConnectParams(params: { + nonce: string; + role: string; + scopes: string[]; + signatureToken: string | undefined; + signedAtMs: number; + platform: string; + }): ConnectParams["device"] { + if (!this.opts.deviceIdentity) { + return undefined; + } + const { nonce, role, scopes, signatureToken, signedAtMs, platform } = params; + const payload = buildDeviceAuthPayloadV3({ + deviceId: this.opts.deviceIdentity.deviceId, + clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, + clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND, + role, + scopes, + signedAtMs, + token: signatureToken ?? null, + nonce, + platform, + deviceFamily: this.opts.deviceFamily, + }); + const signature = signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload); + return { + id: this.opts.deviceIdentity.deviceId, + publicKey: publicKeyRawBase64UrlFromPem(this.opts.deviceIdentity.publicKeyPem), + signature, + signedAt: signedAtMs, + nonce, + }; + } + private handleConnectFailure(err: unknown) { const error = err instanceof Error ? err : new Error(String(err)); this.clearConnectChallengeTimeout();