From c96318d4157bb78a58678a20a967858c7ddeaa6e Mon Sep 17 00:00:00 2001 From: Alexey Zinoviev Date: Wed, 18 Sep 2024 07:01:27 +0400 Subject: [PATCH] UBERF-8139: Check server version when connecting from client (#6608) --- .vscode/launch.json | 1 + plugins/client-resources/src/connection.ts | 22 +++++++++++++++--- plugins/client/src/index.ts | 1 + plugins/guest-resources/src/connect.ts | 26 ++++++++++++++++++++++ plugins/workbench-resources/src/connect.ts | 26 ++++++++++++++++++++++ pods/server/package.json | 2 +- server/rpc/src/rpc.ts | 1 + server/ws/src/__tests__/server.test.ts | 6 ++--- server/ws/src/server.ts | 4 +++- 9 files changed, 81 insertions(+), 8 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2c27625582..0e62f4c7eb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -73,6 +73,7 @@ // "SERVER_PROVIDER":"uweb" "SERVER_PROVIDER":"ws", "MODEL_VERSION": "0.6.287", + // "VERSION": "0.6.289", "ELASTIC_INDEX_NAME": "local_storage_index", "UPLOAD_URL": "/files", diff --git a/plugins/client-resources/src/connection.ts b/plugins/client-resources/src/connection.ts index 13d81464f2..030d7e1acb 100644 --- a/plugins/client-resources/src/connection.ts +++ b/plugins/client-resources/src/connection.ts @@ -232,6 +232,10 @@ class Connection implements ClientConnection { } handleMsg (socketId: number, resp: Response): void { + if (this.closed) { + return + } + if (resp.error !== undefined) { if (resp.error?.code === UNAUTHORIZED.code || resp.terminate === true) { Analytics.handleError(new PlatformError(resp.error)) @@ -252,9 +256,24 @@ class Connection implements ClientConnection { return } if (resp.result === 'hello') { + const helloResp = resp as HelloResponse + if (helloResp.binary) { + this.binaryMode = true + } + // We need to clear dial timer, since we recieve hello response. clearTimeout(this.dialTimer) this.dialTimer = null + + const serverVersion = helloResp.serverVersion + console.log('Connected to server:', serverVersion) + + if (this.opt?.onHello !== undefined && !this.opt.onHello(serverVersion)) { + this.closed = true + this.websocket?.close() + return + } + this.helloRecieved = true if (this.upgrading) { // We need to call upgrade since connection is upgraded @@ -262,9 +281,6 @@ class Connection implements ClientConnection { } this.upgrading = false - if ((resp as HelloResponse).binary) { - this.binaryMode = true - } // Notify all waiting connection listeners const handlers = this.onConnectHandlers.splice(0, this.onConnectHandlers.length) for (const h of handlers) { diff --git a/plugins/client/src/index.ts b/plugins/client/src/index.ts index ee37f855d1..8eac225b84 100644 --- a/plugins/client/src/index.ts +++ b/plugins/client/src/index.ts @@ -56,6 +56,7 @@ export enum ClientSocketReadyState { } export interface ClientFactoryOptions { + onHello?: (serverVersion?: string) => boolean onUpgrade?: () => void onUnauthorized?: () => void onConnect?: (event: ClientConnectEvent, data: any) => void diff --git a/plugins/guest-resources/src/connect.ts b/plugins/guest-resources/src/connect.ts index 1face2ec82..6f0c47839a 100644 --- a/plugins/guest-resources/src/connect.ts +++ b/plugins/guest-resources/src/connect.ts @@ -67,6 +67,32 @@ export async function connect (title: string): Promise { let version: Version | undefined const clientFactory = await getResource(client.function.GetClient) _client = await clientFactory(token, workspaceLoginInfo.endpoint, { + onHello: (serverVersion?: string) => { + const frontVersion = getMetadata(presentation.metadata.FrontVersion) + if ( + serverVersion !== undefined && + serverVersion !== '' && + frontVersion !== undefined && + frontVersion !== serverVersion + ) { + const reloaded = localStorage.getItem(`versionUpgrade:s${serverVersion}:f${frontVersion}`) + + if (reloaded === null) { + localStorage.setItem(`versionUpgrade:s${serverVersion}:f${frontVersion}`, 't') + location.reload() + return false + } else { + versionError.set(`Front version ${frontVersion} is not in sync with server version ${serverVersion}`) + + setTimeout(() => { + location.reload() + }, 5000) + return false + } + } + + return true + }, onUpgrade: () => { location.reload() }, diff --git a/plugins/workbench-resources/src/connect.ts b/plugins/workbench-resources/src/connect.ts index 29b6e3519a..629d28b96c 100644 --- a/plugins/workbench-resources/src/connect.ts +++ b/plugins/workbench-resources/src/connect.ts @@ -153,6 +153,32 @@ export async function connect (title: string): Promise { {}, async (ctx) => await clientFactory(token, endpoint, { + onHello: (serverVersion?: string) => { + const frontVersion = getMetadata(presentation.metadata.FrontVersion) + if ( + serverVersion !== undefined && + serverVersion !== '' && + frontVersion !== undefined && + frontVersion !== serverVersion + ) { + const reloaded = localStorage.getItem(`versionUpgrade:s${serverVersion}:f${frontVersion}`) + + if (reloaded === null) { + localStorage.setItem(`versionUpgrade:s${serverVersion}:f${frontVersion}`, 't') + location.reload() + return false + } else { + versionError.set(`Front version ${frontVersion} is not in sync with server version ${serverVersion}`) + + setTimeout(() => { + location.reload() + }, 5000) + return false + } + } + + return true + }, onUpgrade: () => { location.reload() }, diff --git a/pods/server/package.json b/pods/server/package.json index 6311b85590..6398406901 100644 --- a/pods/server/package.json +++ b/pods/server/package.json @@ -15,7 +15,7 @@ "_phase:bundle": "rushx bundle", "_phase:docker-build": "rushx docker:build", "_phase:docker-staging": "rushx docker:staging", - "bundle": "mkdir -p bundle && esbuild src/__start.ts --sourcemap=inline --bundle --keep-names --platform=node --external:*.node --external:bufferutil --external:snappy --external:utf-8-validate --external:msgpackr-extract --define:process.env.MODEL_VERSION=$(node ../../common/scripts/show_version.js) --define:process.env.GIT_REVISION=$(../../common/scripts/git_version.sh) --outfile=bundle/bundle.js --log-level=error --sourcemap=external", + "bundle": "mkdir -p bundle && esbuild src/__start.ts --sourcemap=inline --bundle --keep-names --platform=node --external:*.node --external:bufferutil --external:snappy --external:utf-8-validate --external:msgpackr-extract --define:process.env.MODEL_VERSION=$(node ../../common/scripts/show_version.js) --define:process.env.VERSION=$(node ../../common/scripts/show_tag.js) --define:process.env.GIT_REVISION=$(../../common/scripts/git_version.sh) --outfile=bundle/bundle.js --log-level=error --sourcemap=external", "docker:build": "../../common/scripts/docker_build.sh hardcoreeng/transactor", "docker:tbuild": "docker build -t hardcoreeng/transactor . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/transactor", "docker:abuild": "docker build -t hardcoreeng/transactor . --platform=linux/arm64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/transactor", diff --git a/server/rpc/src/rpc.ts b/server/rpc/src/rpc.ts index 6a1c8c5939..56809a9519 100644 --- a/server/rpc/src/rpc.ts +++ b/server/rpc/src/rpc.ts @@ -45,6 +45,7 @@ export interface HelloRequest extends Request { export interface HelloResponse extends Response { binary: boolean reconnect?: boolean + serverVersion: string } function replacer (key: string, value: any): any { diff --git a/server/ws/src/__tests__/server.test.ts b/server/ws/src/__tests__/server.test.ts index 13c919d199..29f6448972 100644 --- a/server/ws/src/__tests__/server.test.ts +++ b/server/ws/src/__tests__/server.test.ts @@ -15,7 +15,7 @@ // import { UNAUTHORIZED } from '@hcengineering/platform' -import { RPCHandler } from '@hcengineering/rpc' +import { RPCHandler, type Response } from '@hcengineering/rpc' import { generateToken } from '@hcengineering/server-token' import WebSocket from 'ws' import { start } from '../server' @@ -129,7 +129,7 @@ describe('server', () => { conn.close(1000) }) conn.on('message', (msg: string) => { - const resp = handler.readResponse(msg, false) + const resp: Response = handler.readResponse(msg, false) expect(resp.result === 'hello') expect(resp.error?.code).toBe(UNAUTHORIZED.code) conn.close(1000) @@ -240,7 +240,7 @@ describe('server', () => { newConn.on('message', (msg: Buffer) => { try { console.log('resp:', msg.toString()) - const parsedMsg = handler.readResponse(msg.toString(), false) // Hello + const parsedMsg: Response = handler.readResponse(msg.toString(), false) // Hello if (!helloReceived) { expect(parsedMsg.result === 'hello') helloReceived = true diff --git a/server/ws/src/server.ts b/server/ws/src/server.ts index 6a9567ac00..2f4591dfcf 100644 --- a/server/ws/src/server.ts +++ b/server/ws/src/server.ts @@ -91,6 +91,7 @@ class TSessionManager implements SessionManager { timeMinutes = 0 modelVersion = process.env.MODEL_VERSION ?? '' + serverVersion = process.env.VERSION ?? '' oldClientErrors: number = 0 clientErrors: number = 0 @@ -914,7 +915,8 @@ class TSessionManager implements SessionManager { id: -1, result: 'hello', binary: service.binaryMode, - reconnect + reconnect, + serverVersion: this.serverVersion } ws.send(requestCtx, helloResponse, false, false) return