@tldraw/sync-core 4.1.0-next.b6dfe9bccde9 → 4.1.0-next.b9999db71010
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.
- package/dist-cjs/index.d.ts +605 -75
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/ClientWebSocketAdapter.js +144 -0
- package/dist-cjs/lib/ClientWebSocketAdapter.js.map +2 -2
- package/dist-cjs/lib/RoomSession.js +3 -0
- package/dist-cjs/lib/RoomSession.js.map +2 -2
- package/dist-cjs/lib/ServerSocketAdapter.js +23 -0
- package/dist-cjs/lib/ServerSocketAdapter.js.map +2 -2
- package/dist-cjs/lib/TLRemoteSyncError.js +8 -0
- package/dist-cjs/lib/TLRemoteSyncError.js.map +2 -2
- package/dist-cjs/lib/TLSocketRoom.js +280 -56
- package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
- package/dist-cjs/lib/TLSyncClient.js +45 -2
- package/dist-cjs/lib/TLSyncClient.js.map +2 -2
- package/dist-cjs/lib/TLSyncRoom.js +161 -16
- package/dist-cjs/lib/TLSyncRoom.js.map +2 -2
- package/dist-cjs/lib/chunk.js +30 -0
- package/dist-cjs/lib/chunk.js.map +2 -2
- package/dist-cjs/lib/diff.js.map +2 -2
- package/dist-cjs/lib/findMin.js.map +2 -2
- package/dist-cjs/lib/interval.js.map +2 -2
- package/dist-cjs/lib/protocol.js.map +2 -2
- package/dist-cjs/lib/server-types.js.map +1 -1
- package/dist-esm/index.d.mts +605 -75
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/ClientWebSocketAdapter.mjs +144 -0
- package/dist-esm/lib/ClientWebSocketAdapter.mjs.map +2 -2
- package/dist-esm/lib/RoomSession.mjs +3 -0
- package/dist-esm/lib/RoomSession.mjs.map +2 -2
- package/dist-esm/lib/ServerSocketAdapter.mjs +23 -0
- package/dist-esm/lib/ServerSocketAdapter.mjs.map +2 -2
- package/dist-esm/lib/TLRemoteSyncError.mjs +8 -0
- package/dist-esm/lib/TLRemoteSyncError.mjs.map +2 -2
- package/dist-esm/lib/TLSocketRoom.mjs +280 -56
- package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
- package/dist-esm/lib/TLSyncClient.mjs +45 -2
- package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
- package/dist-esm/lib/TLSyncRoom.mjs +161 -16
- package/dist-esm/lib/TLSyncRoom.mjs.map +2 -2
- package/dist-esm/lib/chunk.mjs +30 -0
- package/dist-esm/lib/chunk.mjs.map +2 -2
- package/dist-esm/lib/diff.mjs.map +2 -2
- package/dist-esm/lib/findMin.mjs.map +2 -2
- package/dist-esm/lib/interval.mjs.map +2 -2
- package/dist-esm/lib/protocol.mjs.map +2 -2
- package/package.json +6 -6
- package/src/lib/ClientWebSocketAdapter.test.ts +712 -129
- package/src/lib/ClientWebSocketAdapter.ts +240 -9
- package/src/lib/RoomSession.test.ts +97 -0
- package/src/lib/RoomSession.ts +105 -3
- package/src/lib/ServerSocketAdapter.test.ts +228 -0
- package/src/lib/ServerSocketAdapter.ts +124 -5
- package/src/lib/TLRemoteSyncError.ts +50 -1
- package/src/lib/TLSocketRoom.ts +377 -60
- package/src/lib/TLSyncClient.test.ts +828 -0
- package/src/lib/TLSyncClient.ts +251 -26
- package/src/lib/TLSyncRoom.ts +284 -24
- package/src/lib/chunk.ts +72 -1
- package/src/lib/diff.ts +128 -14
- package/src/lib/findMin.ts +6 -0
- package/src/lib/interval.ts +40 -0
- package/src/lib/protocol.ts +185 -7
- package/src/lib/server-types.test.ts +44 -0
- package/src/lib/server-types.ts +45 -1
- package/src/test/TLSocketRoom.test.ts +438 -29
- package/src/test/chunk.test.ts +200 -3
- package/src/test/diff.test.ts +396 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { InstancePresenceRecordType, PageRecordType } from '@tldraw/tlschema'
|
|
2
2
|
import { createTLSchema, createTLStore, ZERO_INDEX_KEY } from 'tldraw'
|
|
3
|
-
import { vi } from 'vitest'
|
|
4
|
-
import { WebSocketMinimal } from '../lib/ServerSocketAdapter'
|
|
5
|
-
import { TLSocketRoom } from '../lib/TLSocketRoom'
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
6
4
|
import { RecordOpType } from '../lib/diff'
|
|
7
5
|
import { getTlsyncProtocolVersion } from '../lib/protocol'
|
|
6
|
+
import { WebSocketMinimal } from '../lib/ServerSocketAdapter'
|
|
7
|
+
import { TLSocketRoom, TLSyncLog } from '../lib/TLSocketRoom'
|
|
8
|
+
import { TLSyncErrorCloseEventReason } from '../lib/TLSyncClient'
|
|
8
9
|
|
|
9
10
|
function getStore() {
|
|
10
11
|
const schema = createTLSchema()
|
|
@@ -12,18 +13,25 @@ function getStore() {
|
|
|
12
13
|
return store
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
// Mock WebSocket implementation for testing
|
|
17
|
+
function createMockSocket(overrides: Partial<WebSocketMinimal> = {}): WebSocketMinimal {
|
|
18
|
+
return {
|
|
19
|
+
send: vi.fn(),
|
|
20
|
+
close: vi.fn(),
|
|
21
|
+
readyState: WebSocket.OPEN,
|
|
22
|
+
addEventListener: vi.fn(),
|
|
23
|
+
removeEventListener: vi.fn(),
|
|
24
|
+
...overrides,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
// Helper to create test session metadata
|
|
29
|
+
interface TestSessionMeta {
|
|
30
|
+
userId: string
|
|
31
|
+
userName: string
|
|
32
|
+
}
|
|
26
33
|
|
|
34
|
+
describe(TLSocketRoom, () => {
|
|
27
35
|
it('allows being initialized with a non-empty TLStoreSnapshot', () => {
|
|
28
36
|
const store = getStore()
|
|
29
37
|
// populate with an empty document (document:document and page:page records)
|
|
@@ -103,22 +111,6 @@ describe(TLSocketRoom, () => {
|
|
|
103
111
|
`)
|
|
104
112
|
})
|
|
105
113
|
|
|
106
|
-
it('getPresenceRecords returns empty object when no presence records exist', () => {
|
|
107
|
-
const store = getStore()
|
|
108
|
-
// Don't add any presence records, just the default document
|
|
109
|
-
store.ensureStoreIsUsable()
|
|
110
|
-
|
|
111
|
-
const snapshot = store.getStoreSnapshot()
|
|
112
|
-
const room = new TLSocketRoom({
|
|
113
|
-
initialSnapshot: snapshot,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const presenceRecords = room.getPresenceRecords()
|
|
117
|
-
|
|
118
|
-
expect(presenceRecords).toEqual({})
|
|
119
|
-
expect(Object.keys(presenceRecords)).toHaveLength(0)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
114
|
it('getPresenceRecords correctly handles presence records', () => {
|
|
123
115
|
const store = getStore()
|
|
124
116
|
store.ensureStoreIsUsable()
|
|
@@ -407,4 +399,421 @@ describe(TLSocketRoom, () => {
|
|
|
407
399
|
expect(result.schema).toEqual(originalSchema)
|
|
408
400
|
})
|
|
409
401
|
})
|
|
402
|
+
|
|
403
|
+
describe('Constructor options', () => {
|
|
404
|
+
it('sets up logging with default console.error when log option missing', () => {
|
|
405
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
406
|
+
const room = new TLSocketRoom({})
|
|
407
|
+
// Create a session first, then send invalid message to trigger JSON parse error
|
|
408
|
+
const socket = createMockSocket()
|
|
409
|
+
room.handleSocketConnect({
|
|
410
|
+
sessionId: 'test-session',
|
|
411
|
+
socket,
|
|
412
|
+
})
|
|
413
|
+
// Send invalid JSON to trigger JSON parse error which should call console.error
|
|
414
|
+
room.handleSocketMessage('test-session', '{invalid json')
|
|
415
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
416
|
+
consoleSpy.mockRestore()
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('uses custom logger when provided', () => {
|
|
420
|
+
const mockLog: TLSyncLog = {
|
|
421
|
+
warn: vi.fn(),
|
|
422
|
+
error: vi.fn(),
|
|
423
|
+
}
|
|
424
|
+
const room = new TLSocketRoom({ log: mockLog })
|
|
425
|
+
// Create a session first, then send invalid message
|
|
426
|
+
const socket = createMockSocket()
|
|
427
|
+
room.handleSocketConnect({
|
|
428
|
+
sessionId: 'test-session',
|
|
429
|
+
socket,
|
|
430
|
+
})
|
|
431
|
+
// Send invalid JSON to trigger JSON parse error which should call log.error
|
|
432
|
+
room.handleSocketMessage('test-session', '{invalid json')
|
|
433
|
+
expect(mockLog.error).toHaveBeenCalled()
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
it('initializes with custom client timeout', () => {
|
|
437
|
+
const customTimeout = 15000
|
|
438
|
+
const room = new TLSocketRoom({ clientTimeout: customTimeout })
|
|
439
|
+
expect(room.opts.clientTimeout).toBe(customTimeout)
|
|
440
|
+
})
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
describe('Session management', () => {
|
|
444
|
+
let room: TLSocketRoom
|
|
445
|
+
let onSessionRemoved: ReturnType<typeof vi.fn>
|
|
446
|
+
|
|
447
|
+
beforeEach(() => {
|
|
448
|
+
onSessionRemoved = vi.fn()
|
|
449
|
+
room = new TLSocketRoom({ onSessionRemoved })
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
it('handles multiple concurrent sessions', () => {
|
|
453
|
+
const sessions = ['session1', 'session2', 'session3']
|
|
454
|
+
const sockets = sessions.map(() => createMockSocket())
|
|
455
|
+
|
|
456
|
+
sessions.forEach((sessionId, index) => {
|
|
457
|
+
room.handleSocketConnect({
|
|
458
|
+
sessionId,
|
|
459
|
+
socket: sockets[index],
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
const connectRequest = {
|
|
463
|
+
type: 'connect' as const,
|
|
464
|
+
connectRequestId: `connect-${index}`,
|
|
465
|
+
lastServerClock: 0,
|
|
466
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
467
|
+
schema: createTLSchema().serialize(),
|
|
468
|
+
}
|
|
469
|
+
room.handleSocketMessage(sessionId, JSON.stringify(connectRequest))
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
expect(room.getNumActiveSessions()).toBe(3)
|
|
473
|
+
|
|
474
|
+
const sessionInfo = room.getSessions()
|
|
475
|
+
expect(sessionInfo).toHaveLength(3)
|
|
476
|
+
expect(sessionInfo.every((s) => s.isConnected)).toBe(true)
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
it('handles readonly sessions correctly', () => {
|
|
480
|
+
const socket = createMockSocket()
|
|
481
|
+
room.handleSocketConnect({
|
|
482
|
+
sessionId: 'readonly-session',
|
|
483
|
+
socket,
|
|
484
|
+
isReadonly: true,
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
const connectRequest = {
|
|
488
|
+
type: 'connect' as const,
|
|
489
|
+
connectRequestId: 'connect-1',
|
|
490
|
+
lastServerClock: 0,
|
|
491
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
492
|
+
schema: createTLSchema().serialize(),
|
|
493
|
+
}
|
|
494
|
+
room.handleSocketMessage('readonly-session', JSON.stringify(connectRequest))
|
|
495
|
+
|
|
496
|
+
const sessions = room.getSessions()
|
|
497
|
+
expect(sessions[0].isReadonly).toBe(true)
|
|
498
|
+
})
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
describe('Message handling', () => {
|
|
502
|
+
let room: TLSocketRoom
|
|
503
|
+
let socket: WebSocketMinimal
|
|
504
|
+
let onBeforeSendMessage: ReturnType<typeof vi.fn>
|
|
505
|
+
let onAfterReceiveMessage: ReturnType<typeof vi.fn>
|
|
506
|
+
|
|
507
|
+
beforeEach(() => {
|
|
508
|
+
onBeforeSendMessage = vi.fn()
|
|
509
|
+
onAfterReceiveMessage = vi.fn()
|
|
510
|
+
room = new TLSocketRoom({
|
|
511
|
+
onBeforeSendMessage,
|
|
512
|
+
onAfterReceiveMessage,
|
|
513
|
+
})
|
|
514
|
+
socket = createMockSocket()
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
it('calls onBeforeSendMessage for outgoing messages', () => {
|
|
518
|
+
room.handleSocketConnect({
|
|
519
|
+
sessionId: 'test-session',
|
|
520
|
+
socket,
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
const connectRequest = {
|
|
524
|
+
type: 'connect' as const,
|
|
525
|
+
connectRequestId: 'connect-1',
|
|
526
|
+
lastServerClock: 0,
|
|
527
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
528
|
+
schema: createTLSchema().serialize(),
|
|
529
|
+
}
|
|
530
|
+
room.handleSocketMessage('test-session', JSON.stringify(connectRequest))
|
|
531
|
+
|
|
532
|
+
expect(onBeforeSendMessage).toHaveBeenCalled()
|
|
533
|
+
const call = onBeforeSendMessage.mock.calls[0][0]
|
|
534
|
+
expect(call.sessionId).toBe('test-session')
|
|
535
|
+
expect(call.message).toBeDefined()
|
|
536
|
+
expect(call.stringified).toBeDefined()
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
it('calls onAfterReceiveMessage for valid incoming messages', () => {
|
|
540
|
+
room.handleSocketConnect({
|
|
541
|
+
sessionId: 'test-session',
|
|
542
|
+
socket,
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
const connectRequest = {
|
|
546
|
+
type: 'connect' as const,
|
|
547
|
+
connectRequestId: 'connect-1',
|
|
548
|
+
lastServerClock: 0,
|
|
549
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
550
|
+
schema: createTLSchema().serialize(),
|
|
551
|
+
}
|
|
552
|
+
room.handleSocketMessage('test-session', JSON.stringify(connectRequest))
|
|
553
|
+
|
|
554
|
+
expect(onAfterReceiveMessage).toHaveBeenCalled()
|
|
555
|
+
const call = onAfterReceiveMessage.mock.calls[0][0]
|
|
556
|
+
expect(call.sessionId).toBe('test-session')
|
|
557
|
+
expect(call.message).toBeDefined()
|
|
558
|
+
expect(call.stringified).toBeDefined()
|
|
559
|
+
})
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
describe('WebSocket error handling', () => {
|
|
563
|
+
let room: TLSocketRoom
|
|
564
|
+
let socket: WebSocketMinimal
|
|
565
|
+
|
|
566
|
+
beforeEach(() => {
|
|
567
|
+
room = new TLSocketRoom({})
|
|
568
|
+
socket = createMockSocket()
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
it('handles socket errors correctly', () => {
|
|
572
|
+
room.handleSocketConnect({
|
|
573
|
+
sessionId: 'test-session',
|
|
574
|
+
socket,
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
const connectRequest = {
|
|
578
|
+
type: 'connect' as const,
|
|
579
|
+
connectRequestId: 'connect-1',
|
|
580
|
+
lastServerClock: 0,
|
|
581
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
582
|
+
schema: createTLSchema().serialize(),
|
|
583
|
+
}
|
|
584
|
+
room.handleSocketMessage('test-session', JSON.stringify(connectRequest))
|
|
585
|
+
|
|
586
|
+
expect(room.getSessions()).toHaveLength(1)
|
|
587
|
+
|
|
588
|
+
// Trigger socket error - should not throw
|
|
589
|
+
expect(() => room.handleSocketError('test-session')).not.toThrow()
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
it('handles socket close correctly', () => {
|
|
593
|
+
room.handleSocketConnect({
|
|
594
|
+
sessionId: 'test-session',
|
|
595
|
+
socket,
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
const connectRequest = {
|
|
599
|
+
type: 'connect' as const,
|
|
600
|
+
connectRequestId: 'connect-1',
|
|
601
|
+
lastServerClock: 0,
|
|
602
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
603
|
+
schema: createTLSchema().serialize(),
|
|
604
|
+
}
|
|
605
|
+
room.handleSocketMessage('test-session', JSON.stringify(connectRequest))
|
|
606
|
+
|
|
607
|
+
expect(room.getSessions()).toHaveLength(1)
|
|
608
|
+
|
|
609
|
+
// Trigger socket close - should not throw
|
|
610
|
+
expect(() => room.handleSocketClose('test-session')).not.toThrow()
|
|
611
|
+
})
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
describe('Custom messages', () => {
|
|
615
|
+
let room: TLSocketRoom
|
|
616
|
+
let socket: WebSocketMinimal
|
|
617
|
+
|
|
618
|
+
beforeEach(() => {
|
|
619
|
+
room = new TLSocketRoom({})
|
|
620
|
+
socket = createMockSocket()
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
it('sends custom messages to connected sessions', () => {
|
|
624
|
+
room.handleSocketConnect({
|
|
625
|
+
sessionId: 'test-session',
|
|
626
|
+
socket,
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
const connectRequest = {
|
|
630
|
+
type: 'connect' as const,
|
|
631
|
+
connectRequestId: 'connect-1',
|
|
632
|
+
lastServerClock: 0,
|
|
633
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
634
|
+
schema: createTLSchema().serialize(),
|
|
635
|
+
}
|
|
636
|
+
room.handleSocketMessage('test-session', JSON.stringify(connectRequest))
|
|
637
|
+
|
|
638
|
+
const customData = { type: 'notification', message: 'Hello World' }
|
|
639
|
+
room.sendCustomMessage('test-session', customData)
|
|
640
|
+
|
|
641
|
+
expect(socket.send).toHaveBeenCalledWith(JSON.stringify({ type: 'custom', data: customData }))
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
it('handles custom message to non-existent session gracefully', () => {
|
|
645
|
+
// Should not throw an error
|
|
646
|
+
expect(() => {
|
|
647
|
+
room.sendCustomMessage('nonexistent-session', { test: 'data' })
|
|
648
|
+
}).not.toThrow()
|
|
649
|
+
})
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
describe('Session closing', () => {
|
|
653
|
+
let room: TLSocketRoom
|
|
654
|
+
let socket: WebSocketMinimal
|
|
655
|
+
|
|
656
|
+
beforeEach(() => {
|
|
657
|
+
room = new TLSocketRoom({})
|
|
658
|
+
socket = createMockSocket()
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
it('closes session without fatal reason', () => {
|
|
662
|
+
room.handleSocketConnect({
|
|
663
|
+
sessionId: 'test-session',
|
|
664
|
+
socket,
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
const connectRequest = {
|
|
668
|
+
type: 'connect' as const,
|
|
669
|
+
connectRequestId: 'connect-1',
|
|
670
|
+
lastServerClock: 0,
|
|
671
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
672
|
+
schema: createTLSchema().serialize(),
|
|
673
|
+
}
|
|
674
|
+
room.handleSocketMessage('test-session', JSON.stringify(connectRequest))
|
|
675
|
+
|
|
676
|
+
room.closeSession('test-session')
|
|
677
|
+
|
|
678
|
+
// Session should be removed
|
|
679
|
+
expect(room.getSessions()).toHaveLength(0)
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
it('closes session with fatal reason', () => {
|
|
683
|
+
room.handleSocketConnect({
|
|
684
|
+
sessionId: 'test-session',
|
|
685
|
+
socket,
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
const connectRequest = {
|
|
689
|
+
type: 'connect' as const,
|
|
690
|
+
connectRequestId: 'connect-1',
|
|
691
|
+
lastServerClock: 0,
|
|
692
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
693
|
+
schema: createTLSchema().serialize(),
|
|
694
|
+
}
|
|
695
|
+
room.handleSocketMessage('test-session', JSON.stringify(connectRequest))
|
|
696
|
+
|
|
697
|
+
room.closeSession('test-session', TLSyncErrorCloseEventReason.FORBIDDEN)
|
|
698
|
+
|
|
699
|
+
// Session should be removed
|
|
700
|
+
expect(room.getSessions()).toHaveLength(0)
|
|
701
|
+
})
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
describe('Room lifecycle', () => {
|
|
705
|
+
it('closes room correctly', () => {
|
|
706
|
+
const room = new TLSocketRoom({})
|
|
707
|
+
const socket = createMockSocket()
|
|
708
|
+
|
|
709
|
+
room.handleSocketConnect({
|
|
710
|
+
sessionId: 'test-session',
|
|
711
|
+
socket,
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
const connectRequest = {
|
|
715
|
+
type: 'connect' as const,
|
|
716
|
+
connectRequestId: 'connect-1',
|
|
717
|
+
lastServerClock: 0,
|
|
718
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
719
|
+
schema: createTLSchema().serialize(),
|
|
720
|
+
}
|
|
721
|
+
room.handleSocketMessage('test-session', JSON.stringify(connectRequest))
|
|
722
|
+
|
|
723
|
+
expect(room.getSessions()).toHaveLength(1)
|
|
724
|
+
|
|
725
|
+
// Close the room
|
|
726
|
+
room.close()
|
|
727
|
+
|
|
728
|
+
// Room should be marked as closed
|
|
729
|
+
expect(room.isClosed()).toBe(true)
|
|
730
|
+
})
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
describe('Store updates', () => {
|
|
734
|
+
it('executes async store updates', async () => {
|
|
735
|
+
const store = getStore()
|
|
736
|
+
store.ensureStoreIsUsable()
|
|
737
|
+
const room = new TLSocketRoom({
|
|
738
|
+
initialSnapshot: store.getStoreSnapshot(),
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
const initialClock = room.getCurrentDocumentClock()
|
|
742
|
+
|
|
743
|
+
await room.updateStore(async (store) => {
|
|
744
|
+
const page = PageRecordType.create({
|
|
745
|
+
id: PageRecordType.createId('new-page'),
|
|
746
|
+
name: 'New Page',
|
|
747
|
+
index: ZERO_INDEX_KEY,
|
|
748
|
+
})
|
|
749
|
+
store.put(page)
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
expect(room.getCurrentDocumentClock()).toBeGreaterThan(initialClock)
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
it('handles errors in store updates', async () => {
|
|
756
|
+
const store = getStore()
|
|
757
|
+
store.ensureStoreIsUsable()
|
|
758
|
+
const room = new TLSocketRoom({
|
|
759
|
+
initialSnapshot: store.getStoreSnapshot(),
|
|
760
|
+
})
|
|
761
|
+
|
|
762
|
+
await expect(async () => {
|
|
763
|
+
await room.updateStore(() => {
|
|
764
|
+
throw new Error('Test error')
|
|
765
|
+
})
|
|
766
|
+
}).rejects.toThrow('Test error')
|
|
767
|
+
})
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
describe('Session metadata handling', () => {
|
|
771
|
+
it('handles sessions with metadata correctly', () => {
|
|
772
|
+
const roomWithMeta = new TLSocketRoom<any, TestSessionMeta>({})
|
|
773
|
+
const socket = createMockSocket()
|
|
774
|
+
const meta: TestSessionMeta = { userId: 'user123', userName: 'Alice' }
|
|
775
|
+
|
|
776
|
+
roomWithMeta.handleSocketConnect({
|
|
777
|
+
sessionId: 'meta-session',
|
|
778
|
+
socket,
|
|
779
|
+
meta,
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
const connectRequest = {
|
|
783
|
+
type: 'connect' as const,
|
|
784
|
+
connectRequestId: 'connect-1',
|
|
785
|
+
lastServerClock: 0,
|
|
786
|
+
protocolVersion: getTlsyncProtocolVersion(),
|
|
787
|
+
schema: createTLSchema().serialize(),
|
|
788
|
+
}
|
|
789
|
+
roomWithMeta.handleSocketMessage('meta-session', JSON.stringify(connectRequest))
|
|
790
|
+
|
|
791
|
+
const sessions = roomWithMeta.getSessions()
|
|
792
|
+
expect(sessions[0].meta).toEqual(meta)
|
|
793
|
+
})
|
|
794
|
+
})
|
|
795
|
+
|
|
796
|
+
describe('Clock operations', () => {
|
|
797
|
+
it('increments clock after store updates', async () => {
|
|
798
|
+
const store = getStore()
|
|
799
|
+
store.ensureStoreIsUsable()
|
|
800
|
+
const room = new TLSocketRoom({
|
|
801
|
+
initialSnapshot: store.getStoreSnapshot(),
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
const initialClock = room.getCurrentDocumentClock()
|
|
805
|
+
|
|
806
|
+
await room.updateStore((store) => {
|
|
807
|
+
store.put(
|
|
808
|
+
PageRecordType.create({
|
|
809
|
+
id: PageRecordType.createId('test-page'),
|
|
810
|
+
name: 'Test',
|
|
811
|
+
index: ZERO_INDEX_KEY,
|
|
812
|
+
})
|
|
813
|
+
)
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
expect(room.getCurrentDocumentClock()).toBeGreaterThan(initialClock)
|
|
817
|
+
})
|
|
818
|
+
})
|
|
410
819
|
})
|