diff --git a/docs-src/docs/releases/16.0.0.md b/docs-src/docs/releases/16.0.0.md index c75de199f07..1e5a25074ef 100644 --- a/docs-src/docs/releases/16.0.0.md +++ b/docs-src/docs/releases/16.0.0.md @@ -76,6 +76,7 @@ I completely rewrote them and improved performance especially on initial load wh - If the handler of a [RxPipeline](../rx-pipeline.md) throws an error, block the whole pipeline and emit the error to the outside. - Throw error when dexie.js RxStorage is used with optional index fields [#6643](https://github.com/pubkey/rxdb/pull/6643#issuecomment-2505310082). - Fix IndexedDB bug: Some people had problems with the IndexedDB RxStorage that opened up collections very slowly. If you had this problem, please try out this new version. +- Add check to ensure remote instances are build with the same RxDB version. This is to ensure if you update RxDB and forget to rebuild your workers, it will throw instead of causing strange problems. ## Other diff --git a/src/plugins/dev-mode/error-messages.ts b/src/plugins/dev-mode/error-messages.ts index 3ef84905678..3efdd565067 100644 --- a/src/plugins/dev-mode/error-messages.ts +++ b/src/plugins/dev-mode/error-messages.ts @@ -104,7 +104,7 @@ export const ERROR_MESSAGES = { COL21: 'The RxCollection is closed or removed already, either from this JavaScript realm or from another, like a browser tab', CONFLICT: 'Document update conflict. When changing a document you must work on the previous revision', COL22: '.bulkInsert() and .bulkUpsert() cannot be run with multiple documents that have the same primary key', - COL23: 'In the open-source version of RxDB, the amount of collections that can exist in parallel is limited to '+NON_PREMIUM_COLLECTION_LIMIT+'. If you already purchased the premium access, you can remove this limit: https://rxdb.info/rx-collection.html#faq', + COL23: 'In the open-source version of RxDB, the amount of collections that can exist in parallel is limited to ' + NON_PREMIUM_COLLECTION_LIMIT + '. If you already purchased the premium access, you can remove this limit: https://rxdb.info/rx-collection.html#faq', // rx-document.js DOC1: 'RxDocument.get$ cannot get observable of in-array fields because order cannot be guessed', @@ -256,6 +256,9 @@ export const ERROR_MESSAGES = { DXE1: 'non-required index fields are not possible with the dexie.js RxStorage: https://github.com/pubkey/rxdb/pull/6643#issuecomment-2505310082', // removed in 15.0.0, added boolean index support to dexie storage - DXE1: 'The dexie.js RxStorage does not support boolean indexes, see https://rxdb.info/rx-storage-dexie.html#boolean-index', + // plugins/storage-remote + RM1: 'Cannot communicate with a remote that was build on a different RxDB version. Did you forget to rebuild your workers when updating RxDB?', + /** * Should never be thrown, use this for * null checks etc. so you do not have to increase the diff --git a/src/plugins/storage-remote-websocket/index.ts b/src/plugins/storage-remote-websocket/index.ts index 84589634f86..c2c97604ff0 100644 --- a/src/plugins/storage-remote-websocket/index.ts +++ b/src/plugins/storage-remote-websocket/index.ts @@ -39,7 +39,8 @@ export function startRxStorageRemoteWebsocketServer( send(msg) { const ws = getFromMapOrThrow(websocketByConnectionId, msg.connectionId); ws.send(JSON.stringify(msg)); - } + }, + fakeVersion: options.fakeVersion }; const exposeState = exposeRxStorageRemote(exposeSettings); diff --git a/src/plugins/storage-remote-websocket/types.ts b/src/plugins/storage-remote-websocket/types.ts index c886e3b98f0..1f2c41b83fa 100644 --- a/src/plugins/storage-remote-websocket/types.ts +++ b/src/plugins/storage-remote-websocket/types.ts @@ -14,6 +14,11 @@ export type RxStorageRemoteWebsocketServerOptions = ServerOptions & { storage?: RxStorage; database?: RxDatabase; customRequestHandler?: CustomRequestHandler; + /** + * Used in tests to simulate what happens if the remote + * was build on a different RxDB version. + */ + fakeVersion?: string; }; export type RxStorageRemoteWebsocketServerState = { diff --git a/src/plugins/storage-remote/remote.ts b/src/plugins/storage-remote/remote.ts index c5c60816227..5e0650cfbe5 100644 --- a/src/plugins/storage-remote/remote.ts +++ b/src/plugins/storage-remote/remote.ts @@ -5,7 +5,8 @@ import type { } from '../../types/index.d.ts'; import { deepEqual, - ensureNotFalsy + ensureNotFalsy, + RXDB_VERSION } from '../../plugins/utils/index.ts'; import { createAnswer, createErrorAnswer } from './storage-remote-helpers.ts'; import type { @@ -17,6 +18,7 @@ import type { RxStorageRemoteExposeType } from './storage-remote-types.ts'; import { getChangedDocumentsSince } from '../../rx-storage-helper.ts'; +import { newRxError } from '../../rx-error.ts'; /** * Run this on the 'remote' part, @@ -79,9 +81,20 @@ export function exposeRxStorageRemote(settings: RxStorageRemoteExposeSettings): } } + const mustBeRxDBVersion = settings.fakeVersion ? settings.fakeVersion : RXDB_VERSION; settings.messages$.pipe( filter(msg => msg.method === 'create') ).subscribe(async (msg) => { + if (msg.version !== mustBeRxDBVersion) { + settings.send(createErrorAnswer(msg, newRxError('RM1', { + args: { + mainVersion: msg.version, + remoteVersion: mustBeRxDBVersion + } + }))); + return; + } + const connectionId = msg.connectionId; /** diff --git a/src/plugins/storage-remote/rx-storage-remote.ts b/src/plugins/storage-remote/rx-storage-remote.ts index ea15390b975..56bbac0322e 100644 --- a/src/plugins/storage-remote/rx-storage-remote.ts +++ b/src/plugins/storage-remote/rx-storage-remote.ts @@ -30,7 +30,6 @@ import type { RxStorageRemoteSettings } from './storage-remote-types.ts'; import { closeMessageChannel, getMessageChannel } from './message-channel-cache.ts'; -import { ensureRxStorageInstanceParamsAreCorrect } from '../../rx-storage-helper.ts'; export class RxStorageRemote implements RxStorage { @@ -90,6 +89,7 @@ export class RxStorageRemote implements RxStorage messageChannel.send({ connectionId, method: 'create', + version: RXDB_VERSION, requestId, params }); @@ -124,6 +124,7 @@ export class RxStorageRemote implements RxStorage messageChannel.send({ connectionId, method: 'custom', + version: RXDB_VERSION, requestId, params: data }); @@ -203,6 +204,7 @@ export class RxStorageInstanceRemote implements RxStorageInstance | 'create' | 'custom'; + /** + * We send the RxDB version to the remote + * to ensure we are communicating with an RxDB instance + * of the same version. This is to prevent bugs + * when people forget to rebuild their workers. + */ + version: string; params: RxStorageInstanceCreationParams | // used in the create call any[] | // used to call RxStorageInstance methods @@ -76,6 +83,11 @@ export type RxStorageRemoteExposeSettingsBase = { send(msg: MessageFromRemote): void; messages$: Observable; customRequestHandler?: CustomRequestHandler; + /** + * Used in tests to simulate what happens if the remote + * was build on a different RxDB version. + */ + fakeVersion?: string; }; export type RxStorageRemoteExposeSettingsRxDatabase = RxStorageRemoteExposeSettingsBase & { diff --git a/test/unit/rx-storage-remote.test.ts b/test/unit/rx-storage-remote.test.ts index 5c93456d8cf..91800af2882 100644 --- a/test/unit/rx-storage-remote.test.ts +++ b/test/unit/rx-storage-remote.test.ts @@ -20,6 +20,7 @@ import { } from '../../plugins/storage-remote-websocket/index.mjs'; import { getRxStorageMemory } from '../../plugins/storage-memory/index.mjs'; import { wrappedValidateAjvStorage } from '../../plugins/validate-ajv/index.mjs'; +import { assertThrows } from 'async-test-util'; describeParallel('rx-storage-remote.test.ts', () => { /** @@ -350,6 +351,57 @@ describeParallel('rx-storage-remote.test.ts', () => { await database.close(); }); }); + describe('other', () => { + /** + * Many people forgot to rebuild their webworkers and shared workers when updating + * RxDB which lead to strange bugs. + * To prevent this, the remote storage itself should ensure that it only communicates + * with remote instances that have the same RxDB version. + */ + it('should throw when the remote was build on a different RxDB version', async () => { + const port = await nextPort(); + + const database = await createRxDatabase({ + name: randomToken(10), + storage: memoryStorageWithValidation + }); + await database.addCollections({ + one: { + schema: schemas.human + }, + two: { + schema: schemas.human + } + }); + const server = await startRxStorageRemoteWebsocketServer({ + port, + database, + fakeVersion: 'wrong-version' + }); + assert.ok(server); + + const storage = getRxStorageRemoteWebsocket({ + url: 'ws://localhost:' + port, + mode: 'collection' + }); + + await assertThrows( + () => storage.createStorageInstance({ + databaseInstanceToken: randomToken(10), + databaseName: randomToken(10), + collectionName: 'one', + devMode: true, + multiInstance: false, + options: {}, + schema: fillWithDefaultSettings(schemas.human) + }), + Error, + ['RM1', 'wrong-version'] + ); + + await database.close(); + }); + }); describe('custom requests', () => { it('should send the message and get the answer', async () => { const port = await nextPort();