@tldraw/sync-core 5.5.0-canary.8e8b2aa09b36 → 5.5.0-canary.8ec56b5e7aff
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.js
CHANGED
|
@@ -61,7 +61,7 @@ var import_TLSyncRoom = require("./lib/TLSyncRoom");
|
|
|
61
61
|
var import_TLSyncStorage = require("./lib/TLSyncStorage");
|
|
62
62
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
63
63
|
"@tldraw/sync-core",
|
|
64
|
-
"5.5.0-canary.
|
|
64
|
+
"5.5.0-canary.8ec56b5e7aff",
|
|
65
65
|
"cjs"
|
|
66
66
|
);
|
|
67
67
|
//# sourceMappingURL=index.js.map
|
package/dist-esm/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/sync-core",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (multiplayer sync).",
|
|
4
|
-
"version": "5.5.0-canary.
|
|
4
|
+
"version": "5.5.0-canary.8ec56b5e7aff",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw GB Ltd.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"@types/uuid-readable": "^0.0.3",
|
|
57
57
|
"react": "^19.2.1",
|
|
58
58
|
"react-dom": "^19.2.1",
|
|
59
|
-
"tldraw": "5.5.0-canary.
|
|
59
|
+
"tldraw": "5.5.0-canary.8ec56b5e7aff",
|
|
60
60
|
"typescript": "^7.0.2",
|
|
61
61
|
"uuid-by-string": "^4.0.0",
|
|
62
62
|
"uuid-readable": "^0.0.2",
|
|
63
|
-
"vitest": "^
|
|
63
|
+
"vitest": "^5.0.0"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@tldraw/state": "5.5.0-canary.
|
|
67
|
-
"@tldraw/store": "5.5.0-canary.
|
|
68
|
-
"@tldraw/tlschema": "5.5.0-canary.
|
|
69
|
-
"@tldraw/utils": "5.5.0-canary.
|
|
66
|
+
"@tldraw/state": "5.5.0-canary.8ec56b5e7aff",
|
|
67
|
+
"@tldraw/store": "5.5.0-canary.8ec56b5e7aff",
|
|
68
|
+
"@tldraw/tlschema": "5.5.0-canary.8ec56b5e7aff",
|
|
69
|
+
"@tldraw/utils": "5.5.0-canary.8ec56b5e7aff",
|
|
70
70
|
"nanoevents": "^7.0.1",
|
|
71
71
|
"ws": "^8.18.0"
|
|
72
72
|
},
|
|
@@ -73,24 +73,38 @@ describe('ClientWebSocketAdapter', () => {
|
|
|
73
73
|
connectedServerSocket = socket
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
+
// Bound per test on an OS-assigned port. A fixed port raced against the previous test's
|
|
77
|
+
// server, which closes asynchronously, and intermittently failed with EADDRINUSE.
|
|
78
|
+
let port: number
|
|
79
|
+
|
|
76
80
|
let consoleWarnSpy: ReturnType<typeof vi.spyOn>
|
|
77
|
-
beforeEach(() => {
|
|
81
|
+
beforeEach(async () => {
|
|
78
82
|
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
wsServer = new WebSocketServer({ port: 0 })
|
|
84
|
+
// Attach before the adapter exists: the server is already listening by the time the
|
|
85
|
+
// adapter is constructed, so a handler added afterwards can miss the first connection.
|
|
81
86
|
wsServer.on('connection', connectMock as any)
|
|
87
|
+
await new Promise<void>((resolve, reject) => {
|
|
88
|
+
wsServer.once('listening', resolve)
|
|
89
|
+
wsServer.once('error', reject)
|
|
90
|
+
})
|
|
91
|
+
port = (wsServer.address() as { port: number }).port
|
|
92
|
+
adapter = new ClientWebSocketAdapter(() => `ws://localhost:${port}`)
|
|
82
93
|
})
|
|
83
94
|
|
|
84
95
|
afterEach(() => {
|
|
85
96
|
consoleWarnSpy.mockRestore()
|
|
86
97
|
adapter.close()
|
|
98
|
+
// Not awaited: the next test binds its own OS-assigned port, so a server still closing
|
|
99
|
+
// here can't collide with it. Awaiting would deliver in-flight messages to the socket
|
|
100
|
+
// adapter.close() just orphaned.
|
|
87
101
|
wsServer.close()
|
|
88
102
|
connectMock.mockClear()
|
|
89
103
|
})
|
|
90
104
|
|
|
91
105
|
describe('connection and initial state (CW1, CW2)', () => {
|
|
92
106
|
it('[CW2] starts with connectionStatus offline while the internal status is initial', () => {
|
|
93
|
-
const newAdapter = new ClientWebSocketAdapter(() =>
|
|
107
|
+
const newAdapter = new ClientWebSocketAdapter(() => `ws://localhost:${port}`)
|
|
94
108
|
try {
|
|
95
109
|
expect(newAdapter._connectionStatus.get()).toBe('initial')
|
|
96
110
|
expect(newAdapter.connectionStatus).toBe('offline')
|
|
@@ -113,7 +127,7 @@ describe('ClientWebSocketAdapter', () => {
|
|
|
113
127
|
let uriCallCount = 0
|
|
114
128
|
const dynamicAdapter = new ClientWebSocketAdapter(() => {
|
|
115
129
|
uriCallCount++
|
|
116
|
-
return `ws://localhost
|
|
130
|
+
return `ws://localhost:${port}?attempt=${uriCallCount}`
|
|
117
131
|
})
|
|
118
132
|
try {
|
|
119
133
|
await waitFor(() => dynamicAdapter._ws?.readyState === WebSocket.OPEN)
|
|
@@ -143,7 +157,7 @@ describe('ClientWebSocketAdapter', () => {
|
|
|
143
157
|
// no socket can be created until the promise resolves
|
|
144
158
|
expect(asyncAdapter._ws).toBeNull()
|
|
145
159
|
|
|
146
|
-
resolveUri!(
|
|
160
|
+
resolveUri!(`ws://localhost:${port}`)
|
|
147
161
|
|
|
148
162
|
await waitFor(() => asyncAdapter._ws?.readyState === WebSocket.OPEN)
|
|
149
163
|
expect(asyncAdapter.connectionStatus).toBe('online')
|
|
@@ -301,7 +315,7 @@ describe('ClientWebSocketAdapter', () => {
|
|
|
301
315
|
|
|
302
316
|
describe('URI conversion (CW1)', () => {
|
|
303
317
|
it('[CW1] converts http URIs to ws and connects', async () => {
|
|
304
|
-
const httpAdapter = new ClientWebSocketAdapter(() =>
|
|
318
|
+
const httpAdapter = new ClientWebSocketAdapter(() => `http://localhost:${port}`)
|
|
305
319
|
try {
|
|
306
320
|
await waitFor(() => httpAdapter._ws?.readyState === WebSocket.OPEN)
|
|
307
321
|
expect(httpAdapter._ws!.url).toMatch(/^ws:\/\//)
|
|
@@ -312,7 +326,7 @@ describe('ClientWebSocketAdapter', () => {
|
|
|
312
326
|
})
|
|
313
327
|
|
|
314
328
|
it('[CW1] converts https URIs to wss', async () => {
|
|
315
|
-
const httpsAdapter = new ClientWebSocketAdapter(() =>
|
|
329
|
+
const httpsAdapter = new ClientWebSocketAdapter(() => `https://localhost:${port}`)
|
|
316
330
|
try {
|
|
317
331
|
await waitFor(() => httpsAdapter._ws !== null)
|
|
318
332
|
expect(httpsAdapter._ws!.url).toMatch(/^wss:\/\//)
|
|
@@ -529,7 +543,7 @@ describe('ClientWebSocketAdapter', () => {
|
|
|
529
543
|
})
|
|
530
544
|
|
|
531
545
|
it('[CW9][RM5] closing immediately does not start another getUri call', async () => {
|
|
532
|
-
const getUri = vi.fn(() =>
|
|
546
|
+
const getUri = vi.fn(() => `ws://localhost:${port}`)
|
|
533
547
|
const testAdapter = new ClientWebSocketAdapter(getUri)
|
|
534
548
|
testAdapter.close()
|
|
535
549
|
const callsAtClose = getUri.mock.calls.length
|
|
@@ -544,7 +558,7 @@ describe('ClientWebSocketAdapter', () => {
|
|
|
544
558
|
let uriCallCount = 0
|
|
545
559
|
const testAdapter = new ClientWebSocketAdapter(() => {
|
|
546
560
|
uriCallCount++
|
|
547
|
-
return
|
|
561
|
+
return `ws://localhost:${port}`
|
|
548
562
|
})
|
|
549
563
|
const onStatusChange = vi.fn()
|
|
550
564
|
testAdapter.onStatusChange(onStatusChange)
|
|
@@ -606,17 +620,26 @@ describe('ReconnectManager', () => {
|
|
|
606
620
|
connectedServerSocket = socket
|
|
607
621
|
})
|
|
608
622
|
|
|
623
|
+
// See the note on the other suite's port: bound per test on an OS-assigned port.
|
|
624
|
+
let port: number
|
|
625
|
+
|
|
609
626
|
let consoleWarnSpy: ReturnType<typeof vi.spyOn>
|
|
610
|
-
beforeEach(() => {
|
|
627
|
+
beforeEach(async () => {
|
|
611
628
|
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
612
|
-
|
|
613
|
-
wsServer = new WebSocketServer({ port: 2234 })
|
|
629
|
+
wsServer = new WebSocketServer({ port: 0 })
|
|
614
630
|
wsServer.on('connection', connectMock as any)
|
|
631
|
+
await new Promise<void>((resolve, reject) => {
|
|
632
|
+
wsServer.once('listening', resolve)
|
|
633
|
+
wsServer.once('error', reject)
|
|
634
|
+
})
|
|
635
|
+
port = (wsServer.address() as { port: number }).port
|
|
636
|
+
adapter = new ClientWebSocketAdapter(() => `ws://localhost:${port}`)
|
|
615
637
|
})
|
|
616
638
|
|
|
617
639
|
afterEach(() => {
|
|
618
640
|
consoleWarnSpy.mockRestore()
|
|
619
641
|
adapter.close()
|
|
642
|
+
// Not awaited - see the note on the other suite's teardown.
|
|
620
643
|
wsServer.close()
|
|
621
644
|
connectMock.mockClear()
|
|
622
645
|
})
|
|
@@ -631,7 +654,7 @@ describe('ReconnectManager', () => {
|
|
|
631
654
|
})
|
|
632
655
|
|
|
633
656
|
it('[RM1] reconnection delays back off exponentially, bounded by the active delays', () => {
|
|
634
|
-
const testAdapter = new ClientWebSocketAdapter(() =>
|
|
657
|
+
const testAdapter = new ClientWebSocketAdapter(() => `ws://localhost:${port}`)
|
|
635
658
|
try {
|
|
636
659
|
const manager = testAdapter._reconnectManager
|
|
637
660
|
// make every disconnected() call take the "attempt now" branch
|
|
@@ -656,7 +679,7 @@ describe('ReconnectManager', () => {
|
|
|
656
679
|
|
|
657
680
|
it('[RM1] uses the inactive delay bounds when the tab is hidden', () => {
|
|
658
681
|
const hiddenMock = vi.spyOn(document, 'hidden', 'get').mockReturnValue(true)
|
|
659
|
-
const testAdapter = new ClientWebSocketAdapter(() =>
|
|
682
|
+
const testAdapter = new ClientWebSocketAdapter(() => `ws://localhost:${port}`)
|
|
660
683
|
try {
|
|
661
684
|
const manager = testAdapter._reconnectManager
|
|
662
685
|
;(manager as any).lastAttemptStart = Date.now() - 1_000_000
|
|
@@ -828,7 +851,7 @@ describe('ReconnectManager', () => {
|
|
|
828
851
|
if (failure === 'throws') throw error
|
|
829
852
|
return Promise.reject(error)
|
|
830
853
|
}
|
|
831
|
-
return
|
|
854
|
+
return `ws://localhost:${port}`
|
|
832
855
|
})
|
|
833
856
|
try {
|
|
834
857
|
await waitFor(() => testAdapter._ws?.readyState === WebSocket.OPEN)
|
|
@@ -843,7 +866,7 @@ describe('ReconnectManager', () => {
|
|
|
843
866
|
)
|
|
844
867
|
|
|
845
868
|
it('[RM5] close cancels timers and removes the reconnect event listeners', () => {
|
|
846
|
-
const testAdapter = new ClientWebSocketAdapter(() =>
|
|
869
|
+
const testAdapter = new ClientWebSocketAdapter(() => `ws://localhost:${port}`)
|
|
847
870
|
const manager = testAdapter._reconnectManager
|
|
848
871
|
|
|
849
872
|
testAdapter.close()
|
|
@@ -862,6 +885,10 @@ describe('ReconnectManager', () => {
|
|
|
862
885
|
})
|
|
863
886
|
|
|
864
887
|
describe('URI failure boundaries', () => {
|
|
888
|
+
// These tests never open a real socket - the adapter is closed first, or the socket is a
|
|
889
|
+
// mock - so the URI only has to parse. No server, hence no port to bind.
|
|
890
|
+
const UNUSED_URI = 'ws://localhost:1'
|
|
891
|
+
|
|
865
892
|
it.each(['resolves', 'rejects'])('ignores getUri that %s after close', async (outcome) => {
|
|
866
893
|
const uri = promiseWithResolve<string>()
|
|
867
894
|
const getUri = vi.fn(() => uri)
|
|
@@ -872,7 +899,7 @@ describe('URI failure boundaries', () => {
|
|
|
872
899
|
try {
|
|
873
900
|
await Promise.resolve()
|
|
874
901
|
testAdapter.close()
|
|
875
|
-
if (outcome === 'resolves') uri.resolve(
|
|
902
|
+
if (outcome === 'resolves') uri.resolve(UNUSED_URI)
|
|
876
903
|
else uri.reject(new Error('token endpoint unavailable'))
|
|
877
904
|
await vi.advanceTimersByTimeAsync(INACTIVE_MAX_DELAY)
|
|
878
905
|
|
|
@@ -890,7 +917,7 @@ describe('URI failure boundaries', () => {
|
|
|
890
917
|
it.each([
|
|
891
918
|
{ uri: 'not a URL', openSocket: false, error: 'Invalid URL' },
|
|
892
919
|
{
|
|
893
|
-
uri:
|
|
920
|
+
uri: UNUSED_URI,
|
|
894
921
|
openSocket: true,
|
|
895
922
|
error: 'There should be no connection attempts while already connected',
|
|
896
923
|
},
|