@tldraw/sync-core 5.2.0-canary.6421dbe4e25a → 5.2.0-canary.667cbd6bb6ff

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -838,6 +838,28 @@ export class TLSyncRoom<R extends UnknownRecord, SessionMeta> {
838
838
  this.broadcastPatch(diff)
839
839
  }
840
840
 
841
+ /**
842
+ * Work out whether a client we can't reconcile schemas with is running a newer or older SDK
843
+ * than us.
844
+ */
845
+ private getVersionMismatchReason(theirSchema: SerializedSchema) {
846
+ const ourSchema = this.serializedSchema
847
+
848
+ if (theirSchema.schemaVersion > ourSchema.schemaVersion) {
849
+ return TLSyncErrorCloseEventReason.SERVER_TOO_OLD
850
+ }
851
+
852
+ if (theirSchema.schemaVersion === 2 && ourSchema.schemaVersion === 2) {
853
+ for (const [sequenceId, theirVersion] of Object.entries(theirSchema.sequences)) {
854
+ const ourVersion = ourSchema.sequences[sequenceId]
855
+ if (ourVersion === undefined || theirVersion > ourVersion) {
856
+ return TLSyncErrorCloseEventReason.SERVER_TOO_OLD
857
+ }
858
+ }
859
+ }
860
+ return TLSyncErrorCloseEventReason.CLIENT_TOO_OLD
861
+ }
862
+
841
863
  private handleConnectRequest(
842
864
  session: RoomSession<R, SessionMeta>,
843
865
  message: Extract<TLSocketClientSentEvent<R>, { type: 'connect' }>
@@ -874,8 +896,13 @@ export class TLSyncRoom<R extends UnknownRecord, SessionMeta> {
874
896
  return
875
897
  }
876
898
  const migrations = this.schema.getMigrationsSince(message.schema)
877
- // if the client's store is at a different version to ours, we can't support them
878
- if (!migrations.ok || migrations.value.some((m) => m.scope !== 'record' || !m.down)) {
899
+ if (!migrations.ok) {
900
+ this.rejectSession(session.sessionId, this.getVersionMismatchReason(message.schema))
901
+ return
902
+ }
903
+ // The client's schema is older than ours, but we can't migrate our data down to their
904
+ // version (a migration isn't record-scoped or has no down migration), so they're too old.
905
+ if (migrations.value.some((m) => m.scope !== 'record' || !m.down)) {
879
906
  this.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)
880
907
  return
881
908
  }
@@ -845,6 +845,48 @@ describe('23. Connect handshake (HS)', () => {
845
845
  )
846
846
  })
847
847
 
848
+ it('rejects a client running a newer schema with SERVER_TOO_OLD, not CLIENT_TOO_OLD', () => {
849
+ // Regression test for #6169
850
+ // A client running a newer SDK than the server: its schema has a sequence version higher
851
+ // than anything this server knows about. The server can't reconcile it, but the server is
852
+ // the one that's behind, so it should report SERVER_TOO_OLD (not CLIENT_TOO_OLD).
853
+ const serializedSchema = schema.serialize()
854
+ const newerSerializedSchema: SerializedSchemaV2 = {
855
+ schemaVersion: 2,
856
+ sequences: {
857
+ ...serializedSchema.sequences,
858
+ 'com.tldraw.shape.arrow': 999,
859
+ },
860
+ }
861
+
862
+ const { room } = makeRoom()
863
+ const socket = makeSocket()
864
+
865
+ room.handleNewSession({
866
+ sessionId: 'newer-client-session',
867
+ socket,
868
+ meta: undefined,
869
+ isReadonly: false,
870
+ })
871
+
872
+ room.handleMessage('newer-client-session', {
873
+ connectRequestId: 'connect-1',
874
+ lastServerClock: 0,
875
+ protocolVersion: getTlsyncProtocolVersion(),
876
+ schema: newerSerializedSchema,
877
+ type: 'connect',
878
+ })
879
+
880
+ // Session should not be connected - it was rejected due to incompatible schema
881
+ expect(room.sessions.get('newer-client-session')?.state).not.toBe(RoomSessionState.Connected)
882
+
883
+ // Socket should be closed with SERVER_TOO_OLD, since the client is on a newer version
884
+ expect(socket.close).toHaveBeenCalledWith(
885
+ TLSyncErrorCloseEventCode,
886
+ TLSyncErrorCloseEventReason.SERVER_TOO_OLD
887
+ )
888
+ })
889
+
848
890
  it('[HS4] handles a client with lastServerClock greater than the current documentClock', () => {
849
891
  const { room } = makeRoom({ snapshot: makeSnapshot(records, { documentClock: 10 }) })
850
892
 
@@ -482,9 +482,9 @@ it('[HS3] when the client is too new it cannot connect', () => {
482
482
 
483
483
  expect(v2_socket.close).toHaveBeenCalledWith(
484
484
  4099,
485
- // this should really be 'serverTooOld' but our schema format is a bit too loose to
486
- // accurately determine that now.
487
- TLSyncErrorCloseEventReason.CLIENT_TOO_OLD
485
+ // the client is on a newer schema than the server, so from its perspective the server is
486
+ // the one that's too old.
487
+ TLSyncErrorCloseEventReason.SERVER_TOO_OLD
488
488
  )
489
489
  })
490
490