@pairlobby/server-core 0.23.0 → 0.25.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/README.md +1 -1
- package/dist/admission.d.ts +34 -11
- package/dist/admission.d.ts.map +1 -1
- package/dist/admission.js +49 -24
- package/dist/admission.js.map +1 -1
- package/dist/auto-close-store.d.ts +1 -0
- package/dist/auto-close-store.d.ts.map +1 -1
- package/dist/auto-close-store.js +3 -0
- package/dist/auto-close-store.js.map +1 -1
- package/dist/changes.d.ts +1 -0
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +3 -0
- package/dist/changes.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/ports.d.ts +34 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +48 -0
- package/dist/ports.js.map +1 -0
- package/dist/relay.d.ts +146 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +488 -0
- package/dist/relay.js.map +1 -0
- package/dist/router.d.ts +4 -97
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +5 -376
- package/dist/router.js.map +1 -1
- package/dist/service.d.ts +35 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +136 -9
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +9 -1
- package/dist/store.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/service.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_ROOM_POLICY } from '@pairlobby/protocol';
|
|
2
|
-
import type { AdapterCapabilities, Attachment, AutoClosePolicy, FileRecord, MessageRequest, RequestPage, ExportResponse, ListFilesResponse, ParticipantKind, PermissionChange, RenameSelfRequest, NameSource, ParticipantRole, ReadEventsResponse, RelayLimits, RoomEvent, RoomInvitationChange, RoomPolicy, RoomSnapshot, ResolveRequestRequest, SendEventRequest } from '@pairlobby/protocol';
|
|
2
|
+
import type { AdapterCapabilities, Attachment, AutoClosePolicy, FileRecord, MessageRequest, RequestPage, ExportResponse, ListFilesResponse, ParticipantKind, PermissionChange, RenameSelfRequest, NameSource, ParticipantRole, ReadEventsResponse, ReceiptMode, ReceiptResponse, ReceiptUpdate, RelayLimits, RoomRecord, RoomEvent, RoomInvitationChange, RoomPolicy, RoomSnapshot, ResolveRequestRequest, SendEventRequest } from '@pairlobby/protocol';
|
|
3
3
|
import { TurnCoordinator } from './turns.js';
|
|
4
4
|
import type { RoomStore } from './store.js';
|
|
5
5
|
type InviteDirectory = {
|
|
@@ -39,6 +39,8 @@ export interface Identity {
|
|
|
39
39
|
kind: ParticipantKind;
|
|
40
40
|
sessionId?: string | undefined;
|
|
41
41
|
capabilities?: AdapterCapabilities | undefined;
|
|
42
|
+
/** The relay member joining, verified by the relay from a personal token. Never taken from a client request. */
|
|
43
|
+
member?: string | undefined;
|
|
42
44
|
}
|
|
43
45
|
export interface CreateRoomInput extends Identity {
|
|
44
46
|
name: string;
|
|
@@ -51,6 +53,8 @@ export interface CreateRoomInput extends Identity {
|
|
|
51
53
|
* relay with one Durable Object per room. Never taken from a client request.
|
|
52
54
|
*/
|
|
53
55
|
roomId?: string;
|
|
56
|
+
/** The relay member creating the room, verified by the relay from a personal token. Never taken from a client request. */
|
|
57
|
+
owner?: string | undefined;
|
|
54
58
|
}
|
|
55
59
|
export interface CreatedRoom {
|
|
56
60
|
roomId: string;
|
|
@@ -87,6 +91,18 @@ export type Clock = () => number;
|
|
|
87
91
|
export type RelayProfile = {
|
|
88
92
|
files: boolean;
|
|
89
93
|
limits: RelayLimits;
|
|
94
|
+
receiptMode: ReceiptMode;
|
|
95
|
+
};
|
|
96
|
+
export type RoomServiceOptions = {
|
|
97
|
+
/** How plain receipts are kept (`RECEIPT_MODES`); `both` until every client in use reads watermarks. */
|
|
98
|
+
receiptMode?: ReceiptMode;
|
|
99
|
+
};
|
|
100
|
+
/** The room with this participant's watermark moved up to `throughSeq`, never past the last event; the same room when it would not move. */
|
|
101
|
+
export declare function withReceipt(room: RoomRecord, participantId: string, throughSeq: number, at: number): RoomRecord;
|
|
102
|
+
/** The watermarks that moved after `after`, for a read that asked for them. */
|
|
103
|
+
export declare function receiptsSince(room: RoomRecord, after: number): {
|
|
104
|
+
receipts: ReceiptUpdate[];
|
|
105
|
+
receiptRevision: number;
|
|
90
106
|
};
|
|
91
107
|
/** The limits a new room gets under `policy`, as a relay reports them; file limits only where files are kept. */
|
|
92
108
|
export declare function relayLimits(policy: RoomPolicy, files: boolean): RelayLimits;
|
|
@@ -96,11 +112,12 @@ export declare class RoomService {
|
|
|
96
112
|
private readonly store;
|
|
97
113
|
private readonly now;
|
|
98
114
|
readonly turns: TurnCoordinator;
|
|
115
|
+
readonly receiptMode: ReceiptMode;
|
|
99
116
|
/**
|
|
100
117
|
* `roomPolicy` is what a new room gets when the caller names no policy, such as a
|
|
101
118
|
* self-hosted relay's configured limits. Existing rooms keep the policy they were made with.
|
|
102
119
|
*/
|
|
103
|
-
constructor(store: RoomStore, now?: Clock, directory?: InviteDirectory | undefined, roomPolicy?: RoomPolicy);
|
|
120
|
+
constructor(store: RoomStore, now?: Clock, directory?: InviteDirectory | undefined, roomPolicy?: RoomPolicy, options?: RoomServiceOptions);
|
|
104
121
|
private ctx;
|
|
105
122
|
private view;
|
|
106
123
|
/** What the room looks like, plus what this relay can do that depends on its store rather than on the room. */
|
|
@@ -126,9 +143,24 @@ export declare class RoomService {
|
|
|
126
143
|
* crash between the two steps leaves a resumable reservation rather than an
|
|
127
144
|
* invite a different stranger can claim.
|
|
128
145
|
*/
|
|
146
|
+
/** The room an invite's digest belongs to, for routing before the room checks the code; null when no invite has it. */
|
|
147
|
+
inviteRoom(digest: string): Promise<string | null>;
|
|
129
148
|
redeemInvite(input: RedeemInput): Promise<RedeemResult>;
|
|
130
149
|
send(roomId: string, credential: string, request: SendEventRequest): Promise<SentEventResult>;
|
|
131
150
|
acknowledgeMessage(roomId: string, credential: string, eventId: string): Promise<MessageRequest | null>;
|
|
151
|
+
/**
|
|
152
|
+
* `POST /v1/rooms/:id/receipts`: this participant has received every event up to `throughSeq`.
|
|
153
|
+
* Moves their watermark and marks their requests up to there received, with no event.
|
|
154
|
+
*/
|
|
155
|
+
receipt(roomId: string, credential: string, throughSeq: number): Promise<ReceiptResponse>;
|
|
156
|
+
/** A plain receipt with no event: the request it answers when it is addressed to this participant, else their watermark. */
|
|
157
|
+
private plainReceipt;
|
|
158
|
+
/** Writes the moved watermark and the requests it marks received, in one write without an event. */
|
|
159
|
+
private moveWatermark;
|
|
160
|
+
/** This participant's requests whose message is at or before `throughSeq` and not yet received, marked received at `now`. */
|
|
161
|
+
private receivedThrough;
|
|
162
|
+
/** The transcript seq of a request's message; 0 when that message is no longer retained, which puts it behind any watermark. */
|
|
163
|
+
private messageSeqOf;
|
|
132
164
|
private publicRequest;
|
|
133
165
|
requests(roomId: string, credential: string, after?: number, limit?: number, recipientId?: string, supportsTurns?: boolean): Promise<RequestPage>;
|
|
134
166
|
request(roomId: string, credential: string, eventId: string): Promise<MessageRequest>;
|
|
@@ -174,7 +206,7 @@ export declare class RoomService {
|
|
|
174
206
|
*/
|
|
175
207
|
closeDueRooms(limit?: number): Promise<AutoCloseSweep>;
|
|
176
208
|
close(roomId: string, credential: string): Promise<RoomEvent>;
|
|
177
|
-
read(roomId: string, credential: string, after: number, limit: number): Promise<ReadEventsResponse>;
|
|
209
|
+
read(roomId: string, credential: string, after: number, limit: number, receiptsAfter?: number): Promise<ReadEventsResponse>;
|
|
178
210
|
/** Older group events kept the all flag only in their canonical idempotency request. */
|
|
179
211
|
private restoreAudience;
|
|
180
212
|
snapshot(roomId: string, credential: string): Promise<RoomSnapshot>;
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAmF,mBAAmB,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/dist/service.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
//! Room operations shared by every transport. The Cloudflare Durable Object and
|
|
2
2
|
//! the Node server both run this; only the store and the socket layer differ.
|
|
3
|
-
import { ABSOLUTE_MAX_FILE_BYTES, DEFAULT_MAX_FILE_BYTES, DEFAULT_MAX_RETAINED_FILE_BYTES, DEFAULT_ROOM_POLICY, ProtocolError, UNATTACHED_FILE_LIFETIME_MS, formatFileSize, hashCredential, newId, newInviteCode, normalizeInviteCode, safeFileName } from '@pairlobby/protocol';
|
|
3
|
+
import { ABSOLUTE_MAX_FILE_BYTES, DEFAULT_MAX_FILE_BYTES, DEFAULT_MAX_RETAINED_FILE_BYTES, DEFAULT_ROOM_POLICY, PROTOCOL_VERSION, ProtocolError, UNATTACHED_FILE_LIFETIME_MS, formatFileSize, hashCredential, newId, newInviteCode, normalizeInviteCode, safeFileName } from '@pairlobby/protocol';
|
|
4
4
|
import { assertRoomWritable, appendEvent, assertRoomJoinable, assertCanWrite, assertCanAcknowledge, assertActiveMember, assertController, assertPermitted, authenticate, closeRoom, createRoom, joinAsGuest, joinOnLocalNetwork, recordInvitation, joinRoom, leaveRoom, rejoinRoom, renameRoom, renameSelf, requestControl, revokeParticipant, sendEvent, setExpiry, setAutoClose, setInviteRole, setJoinPolicy, setLocalJoin, setLocked, setMuted, setParticipantPermissions, setParticipantRole, setRoomPermissions, emptyMutation, toSnapshot } from '@pairlobby/room-core';
|
|
5
5
|
import { stableStringify } from './stable-json.js';
|
|
6
6
|
import { TurnCoordinator, assertTurn } from './turns.js';
|
|
7
7
|
import { AutoCloseStore } from './auto-close-store.js';
|
|
8
|
+
/** Requests a watermark marks received in one write; the rest wait for the next one. */
|
|
9
|
+
const WATERMARK_REQUEST_LIMIT = 2000;
|
|
10
|
+
/** A receipt that only says the message arrived: no Read stage, no action. */
|
|
11
|
+
function isPlainReceipt(request) {
|
|
12
|
+
return request.type === 'message.received' && !request.payload.stage && !request.payload.action;
|
|
13
|
+
}
|
|
14
|
+
/** The room with this participant's watermark moved up to `throughSeq`, never past the last event; the same room when it would not move. */
|
|
15
|
+
export function withReceipt(room, participantId, throughSeq, at) {
|
|
16
|
+
const through = Math.min(throughSeq, room.nextSeq - 1);
|
|
17
|
+
if (through <= (room.receipts?.[participantId]?.throughSeq ?? 0)) {
|
|
18
|
+
return room;
|
|
19
|
+
}
|
|
20
|
+
const revision = (room.receiptRevision ?? 0) + 1;
|
|
21
|
+
return { ...room, receipts: { ...room.receipts, [participantId]: { throughSeq: through, revision, at } }, receiptRevision: revision };
|
|
22
|
+
}
|
|
23
|
+
/** The watermarks that moved after `after`, for a read that asked for them. */
|
|
24
|
+
export function receiptsSince(room, after) {
|
|
25
|
+
const receipts = Object.entries(room.receipts ?? {}).filter(([, mark]) => mark.revision > after).map(([participantId, mark]) => ({ participantId, ...mark }));
|
|
26
|
+
return { receipts: receipts.sort((a, b) => a.revision - b.revision), receiptRevision: room.receiptRevision ?? 0 };
|
|
27
|
+
}
|
|
8
28
|
/** The limits a new room gets under `policy`, as a relay reports them; file limits only where files are kept. */
|
|
9
29
|
export function relayLimits(policy, files) {
|
|
10
30
|
return {
|
|
@@ -21,14 +41,16 @@ export class RoomService {
|
|
|
21
41
|
store;
|
|
22
42
|
now;
|
|
23
43
|
turns;
|
|
44
|
+
receiptMode;
|
|
24
45
|
/**
|
|
25
46
|
* `roomPolicy` is what a new room gets when the caller names no policy, such as a
|
|
26
47
|
* self-hosted relay's configured limits. Existing rooms keep the policy they were made with.
|
|
27
48
|
*/
|
|
28
|
-
constructor(store, now = () => Date.now(), directory, roomPolicy = DEFAULT_ROOM_POLICY) {
|
|
49
|
+
constructor(store, now = () => Date.now(), directory, roomPolicy = DEFAULT_ROOM_POLICY, options = {}) {
|
|
29
50
|
this.directory = directory;
|
|
30
51
|
this.roomPolicy = roomPolicy;
|
|
31
52
|
this.now = now;
|
|
53
|
+
this.receiptMode = options.receiptMode ?? 'both';
|
|
32
54
|
this.store = new AutoCloseStore(store, () => this.now());
|
|
33
55
|
this.turns = new TurnCoordinator(this.store, now);
|
|
34
56
|
}
|
|
@@ -44,12 +66,12 @@ export class RoomService {
|
|
|
44
66
|
}
|
|
45
67
|
/** What the room looks like, plus what this relay can do that depends on its store rather than on the room. */
|
|
46
68
|
snapshotOf(view) {
|
|
47
|
-
return { ...toSnapshot(view), filesSupported: this.store.files !== undefined };
|
|
69
|
+
return { ...toSnapshot(view), filesSupported: this.store.files !== undefined, receiptMode: this.receiptMode };
|
|
48
70
|
}
|
|
49
71
|
/** Whether rooms here keep files, and the limits a new room gets: what `GET /v1/server` reports. */
|
|
50
72
|
relayProfile() {
|
|
51
73
|
const files = this.store.files !== undefined;
|
|
52
|
-
return { files, limits: relayLimits(this.roomPolicy, files) };
|
|
74
|
+
return { files, limits: relayLimits(this.roomPolicy, files), receiptMode: this.receiptMode };
|
|
53
75
|
}
|
|
54
76
|
fileStore() {
|
|
55
77
|
if (!this.store.files) {
|
|
@@ -147,6 +169,8 @@ export class RoomService {
|
|
|
147
169
|
nameSource: input.nameSource,
|
|
148
170
|
sessionId: input.sessionId ?? null,
|
|
149
171
|
capabilities: input.capabilities ?? null,
|
|
172
|
+
...(input.member ? { member: input.member } : {}),
|
|
173
|
+
...(input.owner ? { owner: input.owner } : {}),
|
|
150
174
|
...(input.expiresAt !== undefined ? { expiresAt: input.expiresAt } : {}),
|
|
151
175
|
policy: input.policy ?? this.roomPolicy
|
|
152
176
|
}, this.ctx());
|
|
@@ -205,6 +229,10 @@ export class RoomService {
|
|
|
205
229
|
* crash between the two steps leaves a resumable reservation rather than an
|
|
206
230
|
* invite a different stranger can claim.
|
|
207
231
|
*/
|
|
232
|
+
/** The room an invite's digest belongs to, for routing before the room checks the code; null when no invite has it. */
|
|
233
|
+
async inviteRoom(digest) {
|
|
234
|
+
return (await this.store.inviteByDigest(digest))?.roomId ?? null;
|
|
235
|
+
}
|
|
208
236
|
async redeemInvite(input) {
|
|
209
237
|
const normalized = normalizeInviteCode(input.code);
|
|
210
238
|
if (!normalized) {
|
|
@@ -274,7 +302,8 @@ export class RoomService {
|
|
|
274
302
|
kind: input.kind,
|
|
275
303
|
nameSource: input.nameSource,
|
|
276
304
|
sessionId: input.sessionId ?? null,
|
|
277
|
-
capabilities: input.capabilities ?? null
|
|
305
|
+
capabilities: input.capabilities ?? null,
|
|
306
|
+
...(input.member ? { member: input.member } : {})
|
|
278
307
|
}, this.ctx());
|
|
279
308
|
if (previousOccupant?.muted) {
|
|
280
309
|
joined.participant.muted = true;
|
|
@@ -304,6 +333,13 @@ export class RoomService {
|
|
|
304
333
|
const target = await this.turns.resolve(roomId, request.payload.eventId, participant.participantId);
|
|
305
334
|
const eventId = target?.conversationId ?? request.payload.eventId;
|
|
306
335
|
request = { ...request, payload: { ...request.payload, eventId }, ...(!request.payload.action ? { idempotencyKey: `${request.payload.stage === 'read' ? 'read' : 'receipt'}-${eventId}-${participant.participantId}` } : {}) };
|
|
336
|
+
if (this.receiptMode === 'watermark' && isPlainReceipt(request)) {
|
|
337
|
+
// An older client's plain receipt: kept as the watermark, answered as an event that already existed.
|
|
338
|
+
assertRoomWritable(view, now);
|
|
339
|
+
await this.plainReceipt(view, participant, eventId, now);
|
|
340
|
+
const event = { protocolVersion: PROTOCOL_VERSION, roomId, seq: Math.max(1, view.room.nextSeq - 1), eventId: newId('event'), senderId: participant.participantId, idempotencyKey: request.idempotencyKey, recipientId: null, replyTo: null, at: now, type: 'message.received', payload: request.payload };
|
|
341
|
+
return { event, deduplicated: true };
|
|
342
|
+
}
|
|
307
343
|
}
|
|
308
344
|
const requestDigest = stableStringify({ type: request.type, payload: request.payload, recipientId: request.recipientId ?? null, replyTo: request.replyTo ?? null,
|
|
309
345
|
...(request.recipientIds ? { recipientIds: request.recipientIds } : {}), ...(request.allRecipients ? { allRecipients: true } : {}), ...(request.quoteOf ? { quoteOf: request.quoteOf } : {}) });
|
|
@@ -369,6 +405,8 @@ export class RoomService {
|
|
|
369
405
|
}
|
|
370
406
|
const updates = [];
|
|
371
407
|
let replyTarget = null;
|
|
408
|
+
// A plain receipt for a message not addressed to this participant also moves their watermark, in `both`.
|
|
409
|
+
let watermarkSeq = null;
|
|
372
410
|
if (request.type === 'message.received') {
|
|
373
411
|
const participant = assertCanAcknowledge(actor);
|
|
374
412
|
const target = await this.turns.resolve(roomId, request.payload.eventId, participant.participantId);
|
|
@@ -379,6 +417,9 @@ export class RoomService {
|
|
|
379
417
|
if ((target?.from ?? original?.senderId) === participant.participantId) {
|
|
380
418
|
throw new ProtocolError('unauthorized', 'a sender cannot acknowledge their own message');
|
|
381
419
|
}
|
|
420
|
+
if (this.receiptMode === 'both' && isPlainReceipt(request) && target?.to !== participant.participantId) {
|
|
421
|
+
watermarkSeq = original?.seq ?? (await this.messageSeqOf(roomId, target));
|
|
422
|
+
}
|
|
382
423
|
if (target?.to === participant.participantId) {
|
|
383
424
|
updates.push({ ...target, receivedAt: target.receivedAt ?? now, ...(request.payload.stage === 'read' ? { readAt: target.readAt ?? now } : {}) });
|
|
384
425
|
}
|
|
@@ -488,6 +529,9 @@ export class RoomService {
|
|
|
488
529
|
}
|
|
489
530
|
const outgoing = replyTarget?.conversationId ? { ...request, replyTo: replyTarget.conversationId } : request;
|
|
490
531
|
const mutation = sendEvent(view, await hashCredential(credential), outgoing, { now, newEventId: () => newId('event') });
|
|
532
|
+
if (watermarkSeq !== null && participantId) {
|
|
533
|
+
mutation.room = withReceipt(mutation.room, participantId, watermarkSeq, now);
|
|
534
|
+
}
|
|
491
535
|
if (attached.length) {
|
|
492
536
|
mutation.attachFiles = attached;
|
|
493
537
|
}
|
|
@@ -524,7 +568,7 @@ export class RoomService {
|
|
|
524
568
|
let seq = Math.max(view.room.nextRequestSeq ?? 0, mutation.appendEvent.seq);
|
|
525
569
|
for (const recipient of recipients) {
|
|
526
570
|
updates.push({
|
|
527
|
-
roomId, eventId: group ? newId('event') : mutation.appendEvent.eventId, seq: seq++,
|
|
571
|
+
roomId, eventId: group ? newId('event') : mutation.appendEvent.eventId, seq: seq++, messageSeq: mutation.appendEvent.seq,
|
|
528
572
|
from: mutation.appendEvent.senderId, to: recipient, text: quoteContext + request.payload.text, at: mutation.appendEvent.at,
|
|
529
573
|
requiresReply: !request.replyTo, receivedAt: null, responseEventId: null, respondedAt: null, progressAt: null,
|
|
530
574
|
...(group ? { conversationId: mutation.appendEvent.eventId, turnRequired: true } : {}),
|
|
@@ -542,12 +586,20 @@ export class RoomService {
|
|
|
542
586
|
return { event: mutation.appendEvent, deduplicated: false };
|
|
543
587
|
}
|
|
544
588
|
async acknowledgeMessage(roomId, credential, eventId) {
|
|
545
|
-
const
|
|
589
|
+
const view = await this.view(roomId);
|
|
590
|
+
const now = this.now();
|
|
591
|
+
const actor = authenticate(view, await hashCredential(credential), now);
|
|
546
592
|
const participant = assertCanAcknowledge(actor);
|
|
547
593
|
const target = await this.turns.resolve(roomId, eventId, participant.participantId);
|
|
548
594
|
if (target?.to === participant.participantId && target.receivedAt !== null) {
|
|
549
595
|
return this.publicRequest(target);
|
|
550
596
|
}
|
|
597
|
+
if (this.receiptMode === 'watermark') {
|
|
598
|
+
assertRoomWritable(view, now);
|
|
599
|
+
await this.plainReceipt(view, participant, eventId, now);
|
|
600
|
+
const received = await this.turns.resolve(roomId, eventId, participant.participantId);
|
|
601
|
+
return received ? this.publicRequest(received) : null;
|
|
602
|
+
}
|
|
551
603
|
const root = target?.conversationId ?? eventId;
|
|
552
604
|
const key = `receipt-${root}-${participant.participantId}`;
|
|
553
605
|
const saved = await this.store.idempotencyRecord(roomId, key);
|
|
@@ -561,6 +613,81 @@ export class RoomService {
|
|
|
561
613
|
const updated = await this.turns.resolve(roomId, eventId, participant.participantId);
|
|
562
614
|
return updated ? this.publicRequest(updated) : null;
|
|
563
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* `POST /v1/rooms/:id/receipts`: this participant has received every event up to `throughSeq`.
|
|
618
|
+
* Moves their watermark and marks their requests up to there received, with no event.
|
|
619
|
+
*/
|
|
620
|
+
async receipt(roomId, credential, throughSeq) {
|
|
621
|
+
if (this.receiptMode === 'events') {
|
|
622
|
+
throw new ProtocolError('unsupported_capability', 'this relay keeps receipts as events; acknowledge each message instead');
|
|
623
|
+
}
|
|
624
|
+
const view = await this.view(roomId);
|
|
625
|
+
const now = this.now();
|
|
626
|
+
const participant = assertCanAcknowledge(authenticate(view, await hashCredential(credential), now));
|
|
627
|
+
assertRoomWritable(view, now);
|
|
628
|
+
const room = await this.moveWatermark(view, participant.participantId, throughSeq, now);
|
|
629
|
+
return { throughSeq: room.receipts?.[participant.participantId]?.throughSeq ?? 0, receiptRevision: room.receiptRevision ?? 0 };
|
|
630
|
+
}
|
|
631
|
+
/** A plain receipt with no event: the request it answers when it is addressed to this participant, else their watermark. */
|
|
632
|
+
async plainReceipt(view, participant, eventId, now) {
|
|
633
|
+
const roomId = view.room.roomId;
|
|
634
|
+
const target = await this.turns.resolve(roomId, eventId, participant.participantId);
|
|
635
|
+
const original = target ? null : await this.store.eventById(roomId, eventId);
|
|
636
|
+
if (!target && original?.type !== 'message') {
|
|
637
|
+
throw new ProtocolError('invalid_request', 'no such retained message in this room');
|
|
638
|
+
}
|
|
639
|
+
if ((target?.from ?? original?.senderId) === participant.participantId) {
|
|
640
|
+
throw new ProtocolError('unauthorized', 'a sender cannot acknowledge their own message');
|
|
641
|
+
}
|
|
642
|
+
if (target?.to === participant.participantId) {
|
|
643
|
+
// A receiver may confirm one request out of order, so it says nothing about earlier messages.
|
|
644
|
+
if (target.receivedAt === null) {
|
|
645
|
+
await this.store.updateTurns(view.room, [{ ...target, receivedAt: now }], view.room.turnRevision ?? 0);
|
|
646
|
+
}
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
// Everything else is confirmed by a reader going through the room in order.
|
|
650
|
+
await this.moveWatermark(view, participant.participantId, original?.seq ?? (await this.messageSeqOf(roomId, target)), now);
|
|
651
|
+
}
|
|
652
|
+
/** Writes the moved watermark and the requests it marks received, in one write without an event. */
|
|
653
|
+
async moveWatermark(view, participantId, throughSeq, now) {
|
|
654
|
+
const room = withReceipt(view.room, participantId, throughSeq, now);
|
|
655
|
+
if (room === view.room) {
|
|
656
|
+
return room;
|
|
657
|
+
}
|
|
658
|
+
const requests = await this.receivedThrough(room.roomId, participantId, room.receipts[participantId].throughSeq, now);
|
|
659
|
+
await this.store.updateTurns(room, requests, view.room.turnRevision ?? 0);
|
|
660
|
+
return room;
|
|
661
|
+
}
|
|
662
|
+
/** This participant's requests whose message is at or before `throughSeq` and not yet received, marked received at `now`. */
|
|
663
|
+
async receivedThrough(roomId, participantId, throughSeq, now) {
|
|
664
|
+
const page = (after) => this.store.unreceivedRequests ? this.store.unreceivedRequests(roomId, participantId, after, 200) : this.store.messageRequests(roomId, after, 200, participantId);
|
|
665
|
+
const marked = [];
|
|
666
|
+
let after = 0;
|
|
667
|
+
while (marked.length < WATERMARK_REQUEST_LIMIT) {
|
|
668
|
+
const { requests, hasMore } = await page(after);
|
|
669
|
+
for (const request of requests) {
|
|
670
|
+
if (request.receivedAt !== null) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
// Requests are numbered in message order, so the first one past the watermark ends the search.
|
|
674
|
+
if ((await this.messageSeqOf(roomId, request)) > throughSeq) {
|
|
675
|
+
return marked;
|
|
676
|
+
}
|
|
677
|
+
marked.push({ ...request, receivedAt: now });
|
|
678
|
+
}
|
|
679
|
+
const last = requests.at(-1)?.seq;
|
|
680
|
+
if (!hasMore || last === undefined || last <= after) {
|
|
681
|
+
return marked;
|
|
682
|
+
}
|
|
683
|
+
after = last;
|
|
684
|
+
}
|
|
685
|
+
return marked;
|
|
686
|
+
}
|
|
687
|
+
/** The transcript seq of a request's message; 0 when that message is no longer retained, which puts it behind any watermark. */
|
|
688
|
+
async messageSeqOf(roomId, request) {
|
|
689
|
+
return request.messageSeq ?? (await this.store.eventById(roomId, request.conversationId ?? request.eventId))?.seq ?? 0;
|
|
690
|
+
}
|
|
564
691
|
publicRequest(request) {
|
|
565
692
|
const result = { ...request };
|
|
566
693
|
delete result.turnToken;
|
|
@@ -828,7 +955,7 @@ export class RoomService {
|
|
|
828
955
|
async close(roomId, credential) {
|
|
829
956
|
return this.applyOne(closeRoom(await this.view(roomId), await hashCredential(credential), this.ctx()));
|
|
830
957
|
}
|
|
831
|
-
async read(roomId, credential, after, limit) {
|
|
958
|
+
async read(roomId, credential, after, limit, receiptsAfter) {
|
|
832
959
|
const view = await this.view(roomId);
|
|
833
960
|
authenticate(view, await hashCredential(credential), this.now());
|
|
834
961
|
if (after > 0 && after < view.earliestSeq - 1) {
|
|
@@ -836,7 +963,7 @@ export class RoomService {
|
|
|
836
963
|
}
|
|
837
964
|
const page = await this.store.readEvents(roomId, after, limit);
|
|
838
965
|
const events = await Promise.all(page.events.map((event) => this.restoreAudience(event)));
|
|
839
|
-
return { events, earliestSeq: view.earliestSeq, latestSeq: view.room.nextSeq - 1, hasMore: page.hasMore };
|
|
966
|
+
return { events, earliestSeq: view.earliestSeq, latestSeq: view.room.nextSeq - 1, hasMore: page.hasMore, ...(receiptsAfter !== undefined && this.receiptMode !== 'events' ? receiptsSince(view.room, receiptsAfter) : {}) };
|
|
840
967
|
}
|
|
841
968
|
/** Older group events kept the all flag only in their canonical idempotency request. */
|
|
842
969
|
async restoreAudience(event) {
|