@actuate-media/cms-core 0.21.0 → 0.23.0
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/__tests__/actions/document-crud.test.js +74 -2
- package/dist/__tests__/actions/document-crud.test.js.map +1 -1
- package/dist/__tests__/api/cache-headers.test.d.ts +2 -0
- package/dist/__tests__/api/cache-headers.test.d.ts.map +1 -0
- package/dist/__tests__/api/cache-headers.test.js +135 -0
- package/dist/__tests__/api/cache-headers.test.js.map +1 -0
- package/dist/__tests__/api/collections-discovery.test.d.ts +2 -0
- package/dist/__tests__/api/collections-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/api/collections-discovery.test.js +129 -0
- package/dist/__tests__/api/collections-discovery.test.js.map +1 -0
- package/dist/__tests__/api/comments-routes.test.d.ts +2 -0
- package/dist/__tests__/api/comments-routes.test.d.ts.map +1 -0
- package/dist/__tests__/api/comments-routes.test.js +266 -0
- package/dist/__tests__/api/comments-routes.test.js.map +1 -0
- package/dist/__tests__/api/notifications-routes.test.d.ts +2 -0
- package/dist/__tests__/api/notifications-routes.test.d.ts.map +1 -0
- package/dist/__tests__/api/notifications-routes.test.js +225 -0
- package/dist/__tests__/api/notifications-routes.test.js.map +1 -0
- package/dist/__tests__/api/preview-and-scheduling.test.js +53 -0
- package/dist/__tests__/api/preview-and-scheduling.test.js.map +1 -1
- package/dist/__tests__/fields/relations.test.js +62 -8
- package/dist/__tests__/fields/relations.test.js.map +1 -1
- package/dist/__tests__/preview-event-bus.test.d.ts +2 -0
- package/dist/__tests__/preview-event-bus.test.d.ts.map +1 -0
- package/dist/__tests__/preview-event-bus.test.js +89 -0
- package/dist/__tests__/preview-event-bus.test.js.map +1 -0
- package/dist/__tests__/preview-sse-stream.test.d.ts +2 -0
- package/dist/__tests__/preview-sse-stream.test.d.ts.map +1 -0
- package/dist/__tests__/preview-sse-stream.test.js +117 -0
- package/dist/__tests__/preview-sse-stream.test.js.map +1 -0
- package/dist/__tests__/realtime/comments.test.d.ts +2 -0
- package/dist/__tests__/realtime/comments.test.d.ts.map +1 -0
- package/dist/__tests__/realtime/comments.test.js +508 -0
- package/dist/__tests__/realtime/comments.test.js.map +1 -0
- package/dist/__tests__/realtime/gateway-ws-integration.test.d.ts +2 -0
- package/dist/__tests__/realtime/gateway-ws-integration.test.d.ts.map +1 -0
- package/dist/__tests__/realtime/gateway-ws-integration.test.js +170 -0
- package/dist/__tests__/realtime/gateway-ws-integration.test.js.map +1 -0
- package/dist/__tests__/realtime/gateway.test.d.ts +2 -0
- package/dist/__tests__/realtime/gateway.test.d.ts.map +1 -0
- package/dist/__tests__/realtime/gateway.test.js +402 -0
- package/dist/__tests__/realtime/gateway.test.js.map +1 -0
- package/dist/__tests__/realtime/notifications.test.d.ts +2 -0
- package/dist/__tests__/realtime/notifications.test.d.ts.map +1 -0
- package/dist/__tests__/realtime/notifications.test.js +317 -0
- package/dist/__tests__/realtime/notifications.test.js.map +1 -0
- package/dist/__tests__/realtime/persistence.test.d.ts +2 -0
- package/dist/__tests__/realtime/persistence.test.d.ts.map +1 -0
- package/dist/__tests__/realtime/persistence.test.js +124 -0
- package/dist/__tests__/realtime/persistence.test.js.map +1 -0
- package/dist/__tests__/schema-preview-features.test.d.ts +2 -0
- package/dist/__tests__/schema-preview-features.test.d.ts.map +1 -0
- package/dist/__tests__/schema-preview-features.test.js +42 -0
- package/dist/__tests__/schema-preview-features.test.js.map +1 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +71 -26
- package/dist/actions.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +772 -188
- package/dist/api/handlers.js.map +1 -1
- package/dist/cache/__tests__/http.test.d.ts +2 -0
- package/dist/cache/__tests__/http.test.d.ts.map +1 -0
- package/dist/cache/__tests__/http.test.js +285 -0
- package/dist/cache/__tests__/http.test.js.map +1 -0
- package/dist/cache/http.d.ts +119 -0
- package/dist/cache/http.d.ts.map +1 -0
- package/dist/cache/http.js +188 -0
- package/dist/cache/http.js.map +1 -0
- package/dist/config/types.d.ts +92 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/fields/relations.d.ts.map +1 -1
- package/dist/fields/relations.js +43 -11
- package/dist/fields/relations.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/page-builder/schema.d.ts +2 -2
- package/dist/preview/event-bus.d.ts +39 -0
- package/dist/preview/event-bus.d.ts.map +1 -0
- package/dist/preview/event-bus.js +73 -0
- package/dist/preview/event-bus.js.map +1 -0
- package/dist/preview/sse-stream.d.ts +50 -0
- package/dist/preview/sse-stream.d.ts.map +1 -0
- package/dist/preview/sse-stream.js +120 -0
- package/dist/preview/sse-stream.js.map +1 -0
- package/dist/realtime/comments.d.ts +217 -0
- package/dist/realtime/comments.d.ts.map +1 -0
- package/dist/realtime/comments.js +282 -0
- package/dist/realtime/comments.js.map +1 -0
- package/dist/realtime/gateway.d.ts +107 -0
- package/dist/realtime/gateway.d.ts.map +1 -0
- package/dist/realtime/gateway.js +215 -0
- package/dist/realtime/gateway.js.map +1 -0
- package/dist/realtime/index.d.ts +18 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +18 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/notifications.d.ts +209 -0
- package/dist/realtime/notifications.d.ts.map +1 -0
- package/dist/realtime/notifications.js +209 -0
- package/dist/realtime/notifications.js.map +1 -0
- package/dist/realtime/persistence.d.ts +72 -0
- package/dist/realtime/persistence.d.ts.map +1 -0
- package/dist/realtime/persistence.js +53 -0
- package/dist/realtime/persistence.js.map +1 -0
- package/package.json +13 -4
- package/prisma/cms-schema.prisma +135 -4
- package/prisma/schema.prisma +17 -3
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test against the real `ws` library — confirms the duck-typed
|
|
3
|
+
* `WebSocketLike` interface works against a production-grade WebSocket
|
|
4
|
+
* server, including binary frame handling (ws emits Node `Buffer`s, which
|
|
5
|
+
* extend `Uint8Array`).
|
|
6
|
+
*
|
|
7
|
+
* This is the canary that catches "works in mocks, breaks on real ws"
|
|
8
|
+
* regressions — for example if we accidentally start requiring methods
|
|
9
|
+
* that ws.WebSocket doesn't expose.
|
|
10
|
+
*/
|
|
11
|
+
import { createServer } from 'node:http';
|
|
12
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
13
|
+
import { encodeSyncStep1, encodeUpdateMessage, readMessage, Awareness, } from '@actuate-media/realtime';
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
15
|
+
import * as Y from 'yjs';
|
|
16
|
+
import { createRealtimeGateway, } from '../../realtime/gateway.js';
|
|
17
|
+
/**
|
|
18
|
+
* Adapt `ws.WebSocket` to the `WebSocketLike` shape. Real consumers paste
|
|
19
|
+
* this snippet (or one like it) into their upgrade handler.
|
|
20
|
+
*/
|
|
21
|
+
function adaptWsSocket(ws) {
|
|
22
|
+
return {
|
|
23
|
+
send: (data) => ws.send(data),
|
|
24
|
+
close: (code, reason) => ws.close(code, reason),
|
|
25
|
+
on: (event, handler) => {
|
|
26
|
+
if (event === 'message') {
|
|
27
|
+
ws.on('message', (data) => handler(toUint8(data)));
|
|
28
|
+
}
|
|
29
|
+
else if (event === 'close') {
|
|
30
|
+
ws.on('close', () => handler());
|
|
31
|
+
}
|
|
32
|
+
else if (event === 'error') {
|
|
33
|
+
ws.on('error', (err) => handler(err));
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function toUint8(data) {
|
|
39
|
+
if (Buffer.isBuffer(data))
|
|
40
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
41
|
+
if (data instanceof ArrayBuffer)
|
|
42
|
+
return new Uint8Array(data);
|
|
43
|
+
// Buffer[] — concatenate.
|
|
44
|
+
return new Uint8Array(Buffer.concat(data));
|
|
45
|
+
}
|
|
46
|
+
async function startHarness(opts) {
|
|
47
|
+
const server = createServer();
|
|
48
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
49
|
+
let connSeq = 0;
|
|
50
|
+
const gateway = createRealtimeGateway({
|
|
51
|
+
authenticate: opts.authenticate ??
|
|
52
|
+
(async () => ({ documentId: opts.documentId, connectionId: `conn-${++connSeq}` })),
|
|
53
|
+
});
|
|
54
|
+
server.on('upgrade', (req, socket, head) => {
|
|
55
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
56
|
+
const reqInfo = {
|
|
57
|
+
url: req.url ?? '/',
|
|
58
|
+
headers: req.headers,
|
|
59
|
+
};
|
|
60
|
+
void gateway.handleConnection(adaptWsSocket(ws), reqInfo);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
await new Promise((resolve) => server.listen(0, resolve));
|
|
64
|
+
const port = server.address().port;
|
|
65
|
+
return { server, wss, gateway, port };
|
|
66
|
+
}
|
|
67
|
+
async function stopHarness(h) {
|
|
68
|
+
await h.gateway.shutdown();
|
|
69
|
+
h.wss.close();
|
|
70
|
+
await new Promise((resolve) => h.server.close(() => resolve()));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Open a ws client AND register the message listener inline before waiting
|
|
74
|
+
* for the open event. This avoids a race where the server's immediate
|
|
75
|
+
* SyncStep1 fires before the test code attaches its `on('message')` handler.
|
|
76
|
+
*/
|
|
77
|
+
async function openClient(port, documentId) {
|
|
78
|
+
const ws = new WebSocket(`ws://localhost:${port}/api/cms/realtime/sync?documentId=${documentId}`);
|
|
79
|
+
ws.binaryType = 'nodebuffer';
|
|
80
|
+
const inbox = [];
|
|
81
|
+
ws.on('message', (data) => inbox.push(new Uint8Array(data)));
|
|
82
|
+
await new Promise((resolve, reject) => {
|
|
83
|
+
ws.once('open', () => resolve());
|
|
84
|
+
ws.once('error', (err) => reject(err));
|
|
85
|
+
});
|
|
86
|
+
return { ws, inbox };
|
|
87
|
+
}
|
|
88
|
+
/** Wait until the predicate returns truthy or the timeout fires. */
|
|
89
|
+
async function waitFor(predicate, timeoutMs = 1_000) {
|
|
90
|
+
const start = Date.now();
|
|
91
|
+
while (Date.now() - start < timeoutMs) {
|
|
92
|
+
if (predicate())
|
|
93
|
+
return;
|
|
94
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`waitFor: predicate did not become truthy within ${timeoutMs}ms`);
|
|
97
|
+
}
|
|
98
|
+
describe('realtime gateway over real ws sockets', () => {
|
|
99
|
+
let harness;
|
|
100
|
+
beforeEach(async () => {
|
|
101
|
+
harness = await startHarness({ documentId: 'doc-1' });
|
|
102
|
+
});
|
|
103
|
+
afterEach(async () => {
|
|
104
|
+
await stopHarness(harness);
|
|
105
|
+
});
|
|
106
|
+
it('completes a SyncStep1 handshake with a real ws client', async () => {
|
|
107
|
+
const { ws, inbox } = await openClient(harness.port, 'doc-1');
|
|
108
|
+
await waitFor(() => inbox.length >= 1);
|
|
109
|
+
expect(inbox[0][0]).toBe(0 /* MESSAGE_SYNC */);
|
|
110
|
+
expect(inbox[0][1]).toBe(0 /* SyncStep1 */);
|
|
111
|
+
ws.close();
|
|
112
|
+
});
|
|
113
|
+
it('relays an update from one client to another', async () => {
|
|
114
|
+
const { ws: wsA, inbox: inboxA } = await openClient(harness.port, 'doc-1');
|
|
115
|
+
const { ws: wsB, inbox: inboxB } = await openClient(harness.port, 'doc-1');
|
|
116
|
+
// Drain initial SyncStep1 from both clients.
|
|
117
|
+
await waitFor(() => inboxA.length >= 1 && inboxB.length >= 1);
|
|
118
|
+
inboxA.length = 0;
|
|
119
|
+
inboxB.length = 0;
|
|
120
|
+
// A pushes an update.
|
|
121
|
+
const probe = new Y.Doc();
|
|
122
|
+
probe.getText('t').insert(0, 'real-ws-edit');
|
|
123
|
+
wsA.send(encodeUpdateMessage(Y.encodeStateAsUpdate(probe)));
|
|
124
|
+
await waitFor(() => inboxB.length > 0);
|
|
125
|
+
const docB = new Y.Doc();
|
|
126
|
+
const awB = new Awareness(docB);
|
|
127
|
+
for (const m of inboxB)
|
|
128
|
+
readMessage(m, docB, awB, 'test');
|
|
129
|
+
expect(docB.getText('t').toString()).toBe('real-ws-edit');
|
|
130
|
+
wsA.close();
|
|
131
|
+
wsB.close();
|
|
132
|
+
});
|
|
133
|
+
it('closes connections cleanly on gateway shutdown', async () => {
|
|
134
|
+
const { ws } = await openClient(harness.port, 'doc-1');
|
|
135
|
+
const closed = new Promise((resolve) => ws.once('close', () => resolve()));
|
|
136
|
+
await harness.gateway.shutdown();
|
|
137
|
+
await closed;
|
|
138
|
+
// After shutdown the gateway should refuse new business.
|
|
139
|
+
expect(harness.gateway.roomCount).toBe(0);
|
|
140
|
+
expect(harness.gateway.connectionCount).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
it('returns 4401 to clients whose auth check fails', async () => {
|
|
143
|
+
await stopHarness(harness);
|
|
144
|
+
harness = await startHarness({
|
|
145
|
+
documentId: 'doc-1',
|
|
146
|
+
authenticate: async () => null,
|
|
147
|
+
});
|
|
148
|
+
const ws = new WebSocket(`ws://localhost:${harness.port}/api/cms/realtime/sync?documentId=doc-1`);
|
|
149
|
+
const closeInfo = await new Promise((resolve) => {
|
|
150
|
+
ws.once('close', (code) => resolve({ code }));
|
|
151
|
+
});
|
|
152
|
+
expect(closeInfo.code).toBe(4401);
|
|
153
|
+
});
|
|
154
|
+
it('replies to a client-initiated SyncStep1 with the persisted state', async () => {
|
|
155
|
+
// Round-trip a fresh client sending its own SyncStep1: the gateway should
|
|
156
|
+
// reply with the current state (initially empty, but the sync step 2
|
|
157
|
+
// envelope is still returned).
|
|
158
|
+
const { ws, inbox } = await openClient(harness.port, 'doc-1');
|
|
159
|
+
await waitFor(() => inbox.length >= 1);
|
|
160
|
+
inbox.length = 0;
|
|
161
|
+
const clientDoc = new Y.Doc();
|
|
162
|
+
ws.send(encodeSyncStep1(clientDoc));
|
|
163
|
+
await waitFor(() => inbox.length >= 1);
|
|
164
|
+
expect(inbox[0][0]).toBe(0 /* MESSAGE_SYNC */);
|
|
165
|
+
// SyncStep2 sub-type = 1.
|
|
166
|
+
expect(inbox[0][1]).toBe(1);
|
|
167
|
+
ws.close();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
//# sourceMappingURL=gateway-ws-integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-ws-integration.test.js","sourceRoot":"","sources":["../../../src/__tests__/realtime/gateway-ws-integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/realtime/gateway.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import * as Y from 'yjs';
|
|
3
|
+
import { createMemoryPersistence, encodeAwarenessUpdate, encodeUpdateMessage, Awareness, MESSAGE_SYNC, readMessage, } from '@actuate-media/realtime';
|
|
4
|
+
import { createRealtimeGateway, } from '../../realtime/gateway.js';
|
|
5
|
+
function makeFakeSocket() {
|
|
6
|
+
const handlers = {
|
|
7
|
+
message: new Set(),
|
|
8
|
+
close: new Set(),
|
|
9
|
+
error: new Set(),
|
|
10
|
+
};
|
|
11
|
+
const sock = {
|
|
12
|
+
sent: [],
|
|
13
|
+
closed: false,
|
|
14
|
+
send(data) {
|
|
15
|
+
if (this.closed)
|
|
16
|
+
return;
|
|
17
|
+
this.sent.push(data);
|
|
18
|
+
},
|
|
19
|
+
close(code, reason) {
|
|
20
|
+
if (this.closed)
|
|
21
|
+
return;
|
|
22
|
+
this.closed = true;
|
|
23
|
+
this.closeCode = code;
|
|
24
|
+
this.closeReason = reason;
|
|
25
|
+
for (const h of handlers.close)
|
|
26
|
+
h();
|
|
27
|
+
},
|
|
28
|
+
on(event, handler) {
|
|
29
|
+
// Using `as never` for parameter erasure — handlers.message etc. carry the
|
|
30
|
+
// right shape and a runtime branch picks the right set.
|
|
31
|
+
const bucket = handlers[event];
|
|
32
|
+
bucket?.add(handler);
|
|
33
|
+
},
|
|
34
|
+
dispatch(event, payload) {
|
|
35
|
+
if (event === 'message') {
|
|
36
|
+
for (const h of handlers.message)
|
|
37
|
+
h(payload);
|
|
38
|
+
}
|
|
39
|
+
else if (event === 'close') {
|
|
40
|
+
if (!this.closed) {
|
|
41
|
+
this.closed = true;
|
|
42
|
+
for (const h of handlers.close)
|
|
43
|
+
h();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (event === 'error') {
|
|
47
|
+
for (const h of handlers.error)
|
|
48
|
+
h(payload);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
return sock;
|
|
53
|
+
}
|
|
54
|
+
function req(documentId, headers = {}) {
|
|
55
|
+
return {
|
|
56
|
+
url: `/api/cms/realtime/sync?documentId=${encodeURIComponent(documentId)}`,
|
|
57
|
+
headers,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Build an authenticator that maps documentId → fixed connection id (with optional reject). */
|
|
61
|
+
function staticAuth(decisions) {
|
|
62
|
+
return async (r) => {
|
|
63
|
+
const url = new URL(`http://x${r.url}`);
|
|
64
|
+
const documentId = url.searchParams.get('documentId');
|
|
65
|
+
if (!documentId)
|
|
66
|
+
return null;
|
|
67
|
+
return decisions[documentId] ?? null;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
describe('createRealtimeGateway — auth gating', () => {
|
|
71
|
+
let gateway;
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
gateway = createRealtimeGateway({
|
|
74
|
+
authenticate: staticAuth({
|
|
75
|
+
'doc-ok': { documentId: 'doc-ok', connectionId: 'c1' },
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it('closes the socket with 4401 when authenticate returns null', async () => {
|
|
80
|
+
const socket = makeFakeSocket();
|
|
81
|
+
await gateway.handleConnection(socket, req('doc-denied'));
|
|
82
|
+
expect(socket.closed).toBe(true);
|
|
83
|
+
expect(socket.closeCode).toBe(4401);
|
|
84
|
+
expect(gateway.connectionCount).toBe(0);
|
|
85
|
+
expect(gateway.roomCount).toBe(0);
|
|
86
|
+
});
|
|
87
|
+
it('closes with 4401 when authenticate throws', async () => {
|
|
88
|
+
const g = createRealtimeGateway({
|
|
89
|
+
authenticate: async () => {
|
|
90
|
+
throw new Error('boom');
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const socket = makeFakeSocket();
|
|
94
|
+
await g.handleConnection(socket, req('doc-1'));
|
|
95
|
+
expect(socket.closed).toBe(true);
|
|
96
|
+
expect(socket.closeCode).toBe(4401);
|
|
97
|
+
});
|
|
98
|
+
it('rejects duplicate connection ids with 4409', async () => {
|
|
99
|
+
const g = createRealtimeGateway({
|
|
100
|
+
authenticate: staticAuth({
|
|
101
|
+
'doc-1': { documentId: 'doc-1', connectionId: 'shared' },
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
const a = makeFakeSocket();
|
|
105
|
+
const b = makeFakeSocket();
|
|
106
|
+
await g.handleConnection(a, req('doc-1'));
|
|
107
|
+
expect(a.closed).toBe(false);
|
|
108
|
+
await g.handleConnection(b, req('doc-1'));
|
|
109
|
+
expect(b.closed).toBe(true);
|
|
110
|
+
expect(b.closeCode).toBe(4409);
|
|
111
|
+
});
|
|
112
|
+
it('does not create a room when auth fails', async () => {
|
|
113
|
+
const socket = makeFakeSocket();
|
|
114
|
+
await gateway.handleConnection(socket, req('doc-denied'));
|
|
115
|
+
expect(gateway.roomCount).toBe(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('createRealtimeGateway — sync handshake', () => {
|
|
119
|
+
it('sends SyncStep1 immediately after accepting a connection', async () => {
|
|
120
|
+
const gateway = createRealtimeGateway({
|
|
121
|
+
authenticate: staticAuth({
|
|
122
|
+
'doc-1': { documentId: 'doc-1', connectionId: 'c1' },
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
const socket = makeFakeSocket();
|
|
126
|
+
await gateway.handleConnection(socket, req('doc-1'));
|
|
127
|
+
expect(socket.sent.length).toBeGreaterThanOrEqual(1);
|
|
128
|
+
expect(socket.sent[0][0]).toBe(MESSAGE_SYNC);
|
|
129
|
+
});
|
|
130
|
+
it('relays a client update to a second connection in the same room', async () => {
|
|
131
|
+
const gateway = createRealtimeGateway({
|
|
132
|
+
authenticate: staticAuth({
|
|
133
|
+
'doc-1': { documentId: 'doc-1', connectionId: '' },
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
const socketA = makeFakeSocket();
|
|
137
|
+
const socketB = makeFakeSocket();
|
|
138
|
+
// staticAuth shares a single connectionId — patch the auth to vary per-call.
|
|
139
|
+
const idMap = new Map();
|
|
140
|
+
const g2 = createRealtimeGateway({
|
|
141
|
+
authenticate: async (r) => {
|
|
142
|
+
const id = idMap.get(r) ?? 'unknown';
|
|
143
|
+
return { documentId: 'doc-1', connectionId: id };
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const reqA = req('doc-1');
|
|
147
|
+
const reqB = req('doc-1');
|
|
148
|
+
idMap.set(reqA, 'A');
|
|
149
|
+
idMap.set(reqB, 'B');
|
|
150
|
+
await g2.handleConnection(socketA, reqA);
|
|
151
|
+
await g2.handleConnection(socketB, reqB);
|
|
152
|
+
// Drop SyncStep1 messages from the inboxes.
|
|
153
|
+
socketA.sent.length = 0;
|
|
154
|
+
socketB.sent.length = 0;
|
|
155
|
+
// A pushes an update; B should receive it.
|
|
156
|
+
const probe = new Y.Doc();
|
|
157
|
+
probe.getText('t').insert(0, 'from A');
|
|
158
|
+
socketA.dispatch('message', encodeUpdateMessage(Y.encodeStateAsUpdate(probe)));
|
|
159
|
+
expect(socketB.sent.length).toBeGreaterThan(0);
|
|
160
|
+
// The forwarded message should also be a SYNC envelope.
|
|
161
|
+
expect(socketB.sent[0][0]).toBe(MESSAGE_SYNC);
|
|
162
|
+
// Decode B's inbox into a fresh client doc and confirm content.
|
|
163
|
+
const clientB = new Y.Doc();
|
|
164
|
+
const awarenessB = new Awareness(clientB);
|
|
165
|
+
for (const message of socketB.sent) {
|
|
166
|
+
readMessage(message, clientB, awarenessB, 'test');
|
|
167
|
+
}
|
|
168
|
+
expect(clientB.getText('t').toString()).toBe('from A');
|
|
169
|
+
gateway.shutdown();
|
|
170
|
+
await g2.shutdown();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('createRealtimeGateway — persistence wiring', () => {
|
|
174
|
+
it('loads initial state from persistence on first connection', async () => {
|
|
175
|
+
const persistence = createMemoryPersistence();
|
|
176
|
+
// Seed persistence with a doc that already has content.
|
|
177
|
+
const seed = new Y.Doc();
|
|
178
|
+
seed.getText('t').insert(0, 'persisted content');
|
|
179
|
+
await persistence.save('doc-1', Y.encodeStateAsUpdate(seed));
|
|
180
|
+
seed.destroy();
|
|
181
|
+
const gateway = createRealtimeGateway({
|
|
182
|
+
authenticate: staticAuth({ 'doc-1': { documentId: 'doc-1', connectionId: 'c1' } }),
|
|
183
|
+
persistence,
|
|
184
|
+
});
|
|
185
|
+
const socket = makeFakeSocket();
|
|
186
|
+
await gateway.handleConnection(socket, req('doc-1'));
|
|
187
|
+
// The server's SyncStep1 carries the state vector, but to confirm content
|
|
188
|
+
// we read the initial messages into a client doc, then send a SyncStep1
|
|
189
|
+
// from the client and read its reply.
|
|
190
|
+
const client = new Y.Doc();
|
|
191
|
+
const awareness = new Awareness(client);
|
|
192
|
+
// Process server's SyncStep1: produces a reply (SyncStep2) the gateway
|
|
193
|
+
// will send back, but since we're driving the client manually, we
|
|
194
|
+
// simulate the round-trip by handling the message and then sending a
|
|
195
|
+
// fresh SyncStep1 from the client to ask for the server's content.
|
|
196
|
+
for (const m of socket.sent)
|
|
197
|
+
readMessage(m, client, awareness, 'test');
|
|
198
|
+
// Client sends SyncStep1; gateway should reply with SyncStep2 carrying
|
|
199
|
+
// the persisted content.
|
|
200
|
+
socket.sent.length = 0;
|
|
201
|
+
const { encodeSyncStep1 } = await import('@actuate-media/realtime');
|
|
202
|
+
socket.dispatch('message', encodeSyncStep1(client));
|
|
203
|
+
// The gateway's reply lands in socket.sent. Feed everything into the
|
|
204
|
+
// client and assert content.
|
|
205
|
+
for (const m of socket.sent)
|
|
206
|
+
readMessage(m, client, awareness, 'test');
|
|
207
|
+
expect(client.getText('t').toString()).toBe('persisted content');
|
|
208
|
+
await gateway.shutdown();
|
|
209
|
+
});
|
|
210
|
+
it('flushes pending writes on shutdown', async () => {
|
|
211
|
+
const save = vi.fn(async (_id, _state) => { });
|
|
212
|
+
const persistence = {
|
|
213
|
+
async load() {
|
|
214
|
+
return null;
|
|
215
|
+
},
|
|
216
|
+
save,
|
|
217
|
+
};
|
|
218
|
+
const gateway = createRealtimeGateway({
|
|
219
|
+
authenticate: staticAuth({ 'doc-1': { documentId: 'doc-1', connectionId: 'c1' } }),
|
|
220
|
+
persistence,
|
|
221
|
+
persistenceDebounceMs: 10_000, // long debounce so no auto-flush
|
|
222
|
+
});
|
|
223
|
+
const socket = makeFakeSocket();
|
|
224
|
+
await gateway.handleConnection(socket, req('doc-1'));
|
|
225
|
+
const probe = new Y.Doc();
|
|
226
|
+
probe.getText('t').insert(0, 'unsaved edit');
|
|
227
|
+
socket.dispatch('message', encodeUpdateMessage(Y.encodeStateAsUpdate(probe)));
|
|
228
|
+
expect(save).not.toHaveBeenCalled();
|
|
229
|
+
await gateway.shutdown();
|
|
230
|
+
expect(save).toHaveBeenCalledTimes(1);
|
|
231
|
+
expect(save.mock.calls[0][0]).toBe('doc-1');
|
|
232
|
+
});
|
|
233
|
+
it('uses a noop persistence by default (no persistence option)', async () => {
|
|
234
|
+
const gateway = createRealtimeGateway({
|
|
235
|
+
authenticate: staticAuth({ 'doc-1': { documentId: 'doc-1', connectionId: 'c1' } }),
|
|
236
|
+
});
|
|
237
|
+
const socket = makeFakeSocket();
|
|
238
|
+
await gateway.handleConnection(socket, req('doc-1'));
|
|
239
|
+
// Edits should not throw; the doc just doesn't survive a restart.
|
|
240
|
+
const probe = new Y.Doc();
|
|
241
|
+
probe.getText('t').insert(0, 'x');
|
|
242
|
+
socket.dispatch('message', encodeUpdateMessage(Y.encodeStateAsUpdate(probe)));
|
|
243
|
+
await gateway.shutdown();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
describe('createRealtimeGateway — room lifecycle', () => {
|
|
247
|
+
it('increments roomCount on first connection per documentId', async () => {
|
|
248
|
+
const gateway = createRealtimeGateway({
|
|
249
|
+
authenticate: staticAuth({
|
|
250
|
+
'doc-1': { documentId: 'doc-1', connectionId: 'A' },
|
|
251
|
+
'doc-2': { documentId: 'doc-2', connectionId: 'B' },
|
|
252
|
+
}),
|
|
253
|
+
});
|
|
254
|
+
await gateway.handleConnection(makeFakeSocket(), req('doc-1'));
|
|
255
|
+
expect(gateway.roomCount).toBe(1);
|
|
256
|
+
expect(gateway.connectionCount).toBe(1);
|
|
257
|
+
await gateway.handleConnection(makeFakeSocket(), req('doc-2'));
|
|
258
|
+
expect(gateway.roomCount).toBe(2);
|
|
259
|
+
expect(gateway.connectionCount).toBe(2);
|
|
260
|
+
await gateway.shutdown();
|
|
261
|
+
});
|
|
262
|
+
it('reaps an idle room after idleTimeoutMs', async () => {
|
|
263
|
+
const onClose = vi.fn();
|
|
264
|
+
vi.useFakeTimers();
|
|
265
|
+
try {
|
|
266
|
+
const gateway = createRealtimeGateway({
|
|
267
|
+
authenticate: staticAuth({ 'doc-1': { documentId: 'doc-1', connectionId: 'A' } }),
|
|
268
|
+
idleTimeoutMs: 1_000,
|
|
269
|
+
onRoomClose: onClose,
|
|
270
|
+
});
|
|
271
|
+
const socket = makeFakeSocket();
|
|
272
|
+
await gateway.handleConnection(socket, req('doc-1'));
|
|
273
|
+
expect(gateway.roomCount).toBe(1);
|
|
274
|
+
socket.dispatch('close');
|
|
275
|
+
expect(gateway.connectionCount).toBe(0);
|
|
276
|
+
// Room is still alive until the reap timer fires.
|
|
277
|
+
expect(gateway.roomCount).toBe(1);
|
|
278
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
279
|
+
// Allow the reap's async chain (flush -> unbind -> destroy) to settle.
|
|
280
|
+
await vi.runAllTimersAsync();
|
|
281
|
+
expect(gateway.roomCount).toBe(0);
|
|
282
|
+
expect(onClose).toHaveBeenCalledWith('doc-1');
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
vi.useRealTimers();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
it('cancels a pending reap when a new connection arrives', async () => {
|
|
289
|
+
vi.useFakeTimers();
|
|
290
|
+
try {
|
|
291
|
+
const gateway = createRealtimeGateway({
|
|
292
|
+
authenticate: staticAuth({ 'doc-1': { documentId: 'doc-1', connectionId: 'A' } }),
|
|
293
|
+
idleTimeoutMs: 1_000,
|
|
294
|
+
});
|
|
295
|
+
const socket = makeFakeSocket();
|
|
296
|
+
await gateway.handleConnection(socket, req('doc-1'));
|
|
297
|
+
socket.dispatch('close');
|
|
298
|
+
// Almost-but-not-quite the reap horizon.
|
|
299
|
+
await vi.advanceTimersByTimeAsync(900);
|
|
300
|
+
// A new connection should cancel the reap.
|
|
301
|
+
const reauth = createRealtimeGateway({
|
|
302
|
+
authenticate: staticAuth({ 'doc-1': { documentId: 'doc-1', connectionId: 'A2' } }),
|
|
303
|
+
idleTimeoutMs: 1_000,
|
|
304
|
+
});
|
|
305
|
+
// Use the same gateway; just authenticate as a new connection id.
|
|
306
|
+
// For that we need a fresh gateway-like shape, but the simpler test is
|
|
307
|
+
// to demonstrate that re-using the doc resets the timer.
|
|
308
|
+
await reauth.handleConnection(makeFakeSocket(), req('doc-1'));
|
|
309
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
310
|
+
// The second gateway's room should still be alive.
|
|
311
|
+
expect(reauth.roomCount).toBe(1);
|
|
312
|
+
await reauth.shutdown();
|
|
313
|
+
await gateway.shutdown();
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
vi.useRealTimers();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
it('fires onRoomOpen / onRoomClose hooks', async () => {
|
|
320
|
+
const onOpen = vi.fn();
|
|
321
|
+
const onClose = vi.fn();
|
|
322
|
+
const gateway = createRealtimeGateway({
|
|
323
|
+
authenticate: staticAuth({ 'doc-1': { documentId: 'doc-1', connectionId: 'A' } }),
|
|
324
|
+
onRoomOpen: onOpen,
|
|
325
|
+
onRoomClose: onClose,
|
|
326
|
+
});
|
|
327
|
+
const socket = makeFakeSocket();
|
|
328
|
+
await gateway.handleConnection(socket, req('doc-1'));
|
|
329
|
+
expect(onOpen).toHaveBeenCalledWith('doc-1');
|
|
330
|
+
await gateway.shutdown();
|
|
331
|
+
expect(onClose).toHaveBeenCalledWith('doc-1');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
describe('createRealtimeGateway — shutdown', () => {
|
|
335
|
+
it('closes existing sockets and rejects new ones', async () => {
|
|
336
|
+
const gateway = createRealtimeGateway({
|
|
337
|
+
authenticate: staticAuth({ 'doc-1': { documentId: 'doc-1', connectionId: 'A' } }),
|
|
338
|
+
});
|
|
339
|
+
const socket = makeFakeSocket();
|
|
340
|
+
await gateway.handleConnection(socket, req('doc-1'));
|
|
341
|
+
await gateway.shutdown();
|
|
342
|
+
expect(socket.closed).toBe(true);
|
|
343
|
+
expect(gateway.roomCount).toBe(0);
|
|
344
|
+
expect(gateway.connectionCount).toBe(0);
|
|
345
|
+
const second = makeFakeSocket();
|
|
346
|
+
await gateway.handleConnection(second, req('doc-1'));
|
|
347
|
+
expect(second.closed).toBe(true);
|
|
348
|
+
expect(second.closeCode).toBe(1001);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
describe('createRealtimeGateway — malformed frames', () => {
|
|
352
|
+
it('closes the offending socket without affecting others', async () => {
|
|
353
|
+
const gateway = createRealtimeGateway({
|
|
354
|
+
authenticate: async (r) => {
|
|
355
|
+
const url = new URL(`http://x${r.url}`);
|
|
356
|
+
const documentId = url.searchParams.get('documentId');
|
|
357
|
+
return { documentId, connectionId: r.headers['x-test-id'] };
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
const socketA = makeFakeSocket();
|
|
361
|
+
const socketB = makeFakeSocket();
|
|
362
|
+
await gateway.handleConnection(socketA, { ...req('doc-1'), headers: { 'x-test-id': 'A' } });
|
|
363
|
+
await gateway.handleConnection(socketB, { ...req('doc-1'), headers: { 'x-test-id': 'B' } });
|
|
364
|
+
socketA.sent.length = 0;
|
|
365
|
+
socketB.sent.length = 0;
|
|
366
|
+
// Empty frame triggers `readMessage` to throw.
|
|
367
|
+
socketA.dispatch('message', new Uint8Array(0));
|
|
368
|
+
expect(socketA.closed).toBe(true);
|
|
369
|
+
expect(socketA.closeCode).toBe(1003);
|
|
370
|
+
expect(socketB.closed).toBe(false);
|
|
371
|
+
await gateway.shutdown();
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
describe('createRealtimeGateway — awareness broadcast', () => {
|
|
375
|
+
it('forwards awareness state from one client to another', async () => {
|
|
376
|
+
const gateway = createRealtimeGateway({
|
|
377
|
+
authenticate: async (r) => {
|
|
378
|
+
const url = new URL(`http://x${r.url}`);
|
|
379
|
+
return { documentId: url.searchParams.get('documentId'), connectionId: r.headers['x-id'] };
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
const socketA = makeFakeSocket();
|
|
383
|
+
const socketB = makeFakeSocket();
|
|
384
|
+
await gateway.handleConnection(socketA, { ...req('doc-1'), headers: { 'x-id': 'A' } });
|
|
385
|
+
await gateway.handleConnection(socketB, { ...req('doc-1'), headers: { 'x-id': 'B' } });
|
|
386
|
+
socketA.sent.length = 0;
|
|
387
|
+
socketB.sent.length = 0;
|
|
388
|
+
const clientDoc = new Y.Doc();
|
|
389
|
+
const clientAw = new Awareness(clientDoc);
|
|
390
|
+
clientAw.setLocalState({ user: 'alice' });
|
|
391
|
+
socketA.dispatch('message', encodeAwarenessUpdate(clientAw, [clientAw.clientID]));
|
|
392
|
+
// B should receive an awareness message.
|
|
393
|
+
expect(socketB.sent.length).toBeGreaterThan(0);
|
|
394
|
+
const docB = new Y.Doc();
|
|
395
|
+
const awB = new Awareness(docB);
|
|
396
|
+
for (const m of socketB.sent)
|
|
397
|
+
readMessage(m, docB, awB, 'test');
|
|
398
|
+
expect(awB.getStates().get(clientAw.clientID)).toEqual({ user: 'alice' });
|
|
399
|
+
await gateway.shutdown();
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
//# sourceMappingURL=gateway.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.test.js","sourceRoot":"","sources":["../../../src/__tests__/realtime/gateway.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/realtime/notifications.test.ts"],"names":[],"mappings":""}
|