@pairlobby/room-core 0.11.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/LICENSE ADDED
@@ -0,0 +1,93 @@
1
+ Elastic License 2.0
2
+
3
+ URL: https://www.elastic.co/licensing/elastic-license
4
+
5
+ ## Acceptance
6
+
7
+ By using the software, you agree to all of the terms and conditions below.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject to
14
+ the limitations and conditions below.
15
+
16
+ ## Limitations
17
+
18
+ You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set of
20
+ the features or functionality of the software.
21
+
22
+ You may not move, change, disable, or circumvent the license key functionality
23
+ in the software, and you may not remove or obscure any functionality in the
24
+ software that is protected by the license key.
25
+
26
+ You may not alter, remove, or obscure any licensing, copyright, or other notices
27
+ of the licensor in the software. Any use of the licensor’s trademarks is subject
28
+ to applicable law.
29
+
30
+ ## Patents
31
+
32
+ The licensor grants you a license, under any patent claims the licensor can
33
+ license, or becomes able to license, to make, have made, use, sell, offer for
34
+ sale, import and have imported the software, in each case subject to the
35
+ limitations and conditions in this license. This license does not cover any
36
+ patent claims that you cause to be infringed by modifications or additions to
37
+ the software. If you or your company make any written claim that the software
38
+ infringes or contributes to infringement of any patent, your patent license for
39
+ the software granted under these terms ends immediately. If your company makes
40
+ such a claim, your patent license ends immediately for work on behalf of your
41
+ company.
42
+
43
+ ## Notices
44
+
45
+ You must ensure that anyone who gets a copy of any part of the software from you
46
+ also gets a copy of these terms.
47
+
48
+ If you modify the software, you must include in any modified copies of the
49
+ software prominent notices stating that you have modified the software.
50
+
51
+ ## No Other Rights
52
+
53
+ These terms do not imply any licenses other than those expressly granted in
54
+ these terms.
55
+
56
+ ## Termination
57
+
58
+ If you use the software in violation of these terms, such use is not licensed,
59
+ and your licenses will automatically terminate. If the licensor provides you
60
+ with a notice of your violation, and you cease all violation of this license no
61
+ later than 30 days after you receive that notice, your licenses will be
62
+ reinstated retroactively. However, if you violate these terms after such
63
+ reinstatement, any additional violation of these terms will cause your licenses
64
+ to terminate automatically and permanently.
65
+
66
+ ## No Liability
67
+
68
+ *As far as the law allows, the software comes as is, without any warranty or
69
+ condition, and the licensor will not be liable to you for any damages arising
70
+ out of these terms or the use or nature of the software, under any kind of
71
+ legal claim.*
72
+
73
+ ## Definitions
74
+
75
+ The **licensor** is the entity offering these terms, and the **software** is the
76
+ software the licensor makes available under these terms, including any portion
77
+ of it.
78
+
79
+ **you** refers to the individual or entity agreeing to these terms.
80
+
81
+ **your company** is any legal entity, sole proprietorship, or other kind of
82
+ organization that you work for, plus all organizations that have control over,
83
+ are under the control of, or are under common control with that
84
+ organization. **control** means ownership of substantially all the assets of an
85
+ entity, or the power to direct its management and policies by vote, contract, or
86
+ otherwise. Control can be direct or indirect.
87
+
88
+ **your licenses** are all the licenses granted to you for the software under
89
+ these terms.
90
+
91
+ **use** means anything you do with the software requiring one of your licenses.
92
+
93
+ **trademark** means trademarks, service marks, and similar rights.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @pairlobby/room-core
2
+
3
+ Part of [PairLobby](https://github.com/PairLobby/app), built from `packages/room-core` at version 0.11.0. See that repository for documentation.
4
+
5
+ Licensed under the [Elastic License 2.0](LICENSE): you may use, copy, modify and distribute it, but may not offer it to others as a hosted or managed service.
@@ -0,0 +1,18 @@
1
+ import { DEFAULT_ROOM_POLICY } from '@pairlobby/protocol';
2
+ import type { EventBody, RoomEvent, RoomRecord } from '@pairlobby/protocol';
3
+ import type { CoreContext } from './state.js';
4
+ export interface AppendInput {
5
+ senderId: string | null;
6
+ idempotencyKey: string | null;
7
+ recipientId: string | null;
8
+ replyTo: string | null;
9
+ quoteOf?: string;
10
+ body: EventBody;
11
+ }
12
+ export interface Appended {
13
+ room: RoomRecord;
14
+ event: RoomEvent;
15
+ }
16
+ export declare function appendEvent(room: RoomRecord, input: AppendInput, ctx: CoreContext): Appended;
17
+ export { DEFAULT_ROOM_POLICY };
18
+ //# sourceMappingURL=append.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"append.d.ts","sourceRoot":"","sources":["../src/append.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,mBAAmB,EAA6C,MAAM,qBAAqB,CAAC;AACpG,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAE,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAE1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,WAAW;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;CACpB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,GAAG,QAAQ,CAkC5F;AAED,OAAO,EAAC,mBAAmB,EAAC,CAAC"}
package/dist/append.js ADDED
@@ -0,0 +1,40 @@
1
+ //! Sequence assignment and quota accounting. The server assigns sender identity
2
+ //! and sequence; a client never supplies either.
3
+ import { DEFAULT_ROOM_POLICY, ProtocolError, isQuotaExempt, payloadBytes } from '@pairlobby/protocol';
4
+ export function appendEvent(room, input, ctx) {
5
+ const bytes = payloadBytes(input.body);
6
+ if (bytes > room.policy.maxEventPayloadBytes) {
7
+ throw new ProtocolError('payload_too_large', `payload is ${bytes} bytes; the limit is ${room.policy.maxEventPayloadBytes}`);
8
+ }
9
+ const response = input.body.type === 'message' && input.replyTo !== null;
10
+ if (!isQuotaExempt(input.body.type) && !response) {
11
+ if (room.retainedEvents + 1 > room.policy.maxRetainedEvents) {
12
+ throw new ProtocolError('quota_exceeded', 'this room has reached its retained event limit; control, close, and export remain available');
13
+ }
14
+ if (room.retainedEventBytes + bytes > room.policy.maxRetainedEventBytes) {
15
+ throw new ProtocolError('quota_exceeded', 'this room has reached its retained byte limit; control, close, and export remain available');
16
+ }
17
+ }
18
+ const event = {
19
+ protocolVersion: 1,
20
+ roomId: room.roomId,
21
+ seq: room.nextSeq,
22
+ eventId: ctx.newEventId(),
23
+ senderId: input.senderId,
24
+ idempotencyKey: input.idempotencyKey,
25
+ recipientId: input.recipientId,
26
+ replyTo: input.replyTo,
27
+ ...(input.quoteOf ? { quoteOf: input.quoteOf } : {}),
28
+ at: ctx.now,
29
+ ...input.body
30
+ };
31
+ const next = {
32
+ ...room,
33
+ nextSeq: room.nextSeq + 1,
34
+ retainedEvents: room.retainedEvents + 1,
35
+ retainedEventBytes: room.retainedEventBytes + bytes
36
+ };
37
+ return { room: next, event };
38
+ }
39
+ export { DEFAULT_ROOM_POLICY };
40
+ //# sourceMappingURL=append.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"append.js","sourceRoot":"","sources":["../src/append.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,iDAAiD;AAEjD,OAAO,EAAC,mBAAmB,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAC,MAAM,qBAAqB,CAAC;AAmBpG,MAAM,UAAU,WAAW,CAAC,IAAgB,EAAE,KAAkB,EAAE,GAAgB;IAC9E,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC;QAC3C,MAAM,IAAI,aAAa,CAAC,mBAAmB,EAAE,cAAc,KAAK,wBAAwB,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAChI,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC;IACzE,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/C,IAAI,IAAI,CAAC,cAAc,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC1D,MAAM,IAAI,aAAa,CAAC,gBAAgB,EAAE,6FAA6F,CAAC,CAAC;QAC7I,CAAC;QACD,IAAI,IAAI,CAAC,kBAAkB,GAAG,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAC;YACtE,MAAM,IAAI,aAAa,CAAC,gBAAgB,EAAE,4FAA4F,CAAC,CAAC;QAC5I,CAAC;IACL,CAAC;IACD,MAAM,KAAK,GAAG;QACV,eAAe,EAAE,CAAC;QAClB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,EAAE,IAAI,CAAC,OAAO;QACjB,OAAO,EAAE,GAAG,CAAC,UAAU,EAAE;QACzB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,EAAE,EAAE,GAAG,CAAC,GAAG;QACX,GAAG,KAAK,CAAC,IAAI;KACH,CAAC;IACf,MAAM,IAAI,GAAe;QACrB,GAAG,IAAI;QACP,OAAO,EAAE,IAAI,CAAC,OAAO,GAAG,CAAC;QACzB,cAAc,EAAE,IAAI,CAAC,cAAc,GAAG,CAAC;QACvC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,GAAG,KAAK;KACtD,CAAC;IACF,OAAO,EAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAC,CAAC;AAC/B,CAAC;AAED,OAAO,EAAC,mBAAmB,EAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { ParticipantRecord } from '@pairlobby/protocol';
2
+ import { type RoomView } from './state.js';
3
+ export type Actor = {
4
+ kind: 'participant';
5
+ participant: ParticipantRecord;
6
+ } | {
7
+ kind: 'controller';
8
+ };
9
+ /** Room lifecycle is checked at request time; physical cleanup is maintenance, not the access boundary. */
10
+ export declare function assertRoomReadable(view: RoomView, now: number): void;
11
+ export declare function assertRoomWritable(view: RoomView, now: number): void;
12
+ export declare function authenticate(view: RoomView, credentialHash: string, now: number): Actor;
13
+ export declare function assertController(actor: Actor): void;
14
+ /**
15
+ * A guest may read and leave. Participant writes go through here; receipt-only
16
+ * acknowledgements use assertCanAcknowledge so muting does not block reading.
17
+ */
18
+ export declare function assertCanWrite(actor: Actor): ParticipantRecord;
19
+ /** Muting blocks speech, not confirmation that a member received a message. */
20
+ export declare function assertCanAcknowledge(actor: Actor): ParticipantRecord;
21
+ export declare function assertActiveMember(actor: Actor): ParticipantRecord;
22
+ export declare function assertRecipientExists(view: RoomView, recipientId: string): ParticipantRecord;
23
+ export declare function assertRoomJoinable(view: RoomView, now: number): void;
24
+ //# sourceMappingURL=authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../src/authorize.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAA4B,KAAK,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEpE,MAAM,MAAM,KAAK,GAAG;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,iBAAiB,CAAA;CAAC,GAAG;IAAC,IAAI,EAAE,YAAY,CAAA;CAAC,CAAC;AAEjG,2GAA2G;AAC3G,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAUpE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAKpE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,CAgBvF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CASnD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,iBAAiB,CAM9D;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,iBAAiB,CAMpE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,GAAG,iBAAiB,CAQlE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB,CAM5F;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAKpE"}
@@ -0,0 +1,91 @@
1
+ //! Authorization runs on every operation, including events arriving on a socket
2
+ //! that was authorized when it opened.
3
+ import { ProtocolError, digestsEqual } from '@pairlobby/protocol';
4
+ import { findParticipant, isActive } from './state.js';
5
+ /** Room lifecycle is checked at request time; physical cleanup is maintenance, not the access boundary. */
6
+ export function assertRoomReadable(view, now) {
7
+ if (view.room.lifecycle === 'deleted') {
8
+ throw new ProtocolError('room_not_found', 'this room no longer exists');
9
+ }
10
+ if (view.room.lifecycle === 'expired' || (view.room.expiresAt !== null && now >= view.room.expiresAt)) {
11
+ throw new ProtocolError('room_expired', 'this room has expired');
12
+ }
13
+ if (view.room.lifecycle === 'closed' && view.room.closedAt !== null && now >= view.room.closedAt + view.room.policy.exportWindowMs) {
14
+ throw new ProtocolError('room_expired', 'this room was closed and its export window has ended');
15
+ }
16
+ }
17
+ export function assertRoomWritable(view, now) {
18
+ assertRoomReadable(view, now);
19
+ if (view.room.lifecycle === 'closed') {
20
+ throw new ProtocolError('room_closed', 'this room is closed to new activity');
21
+ }
22
+ }
23
+ export function authenticate(view, credentialHash, now) {
24
+ assertRoomReadable(view, now);
25
+ if (digestsEqual(credentialHash, view.room.controllerCredentialHash)) {
26
+ return { kind: 'controller' };
27
+ }
28
+ const participant = view.participants.find((candidate) => digestsEqual(candidate.credentialHash, credentialHash));
29
+ if (!participant) {
30
+ throw new ProtocolError('unauthorized', 'credential not recognized for this room');
31
+ }
32
+ if (participant.revokedAt !== null) {
33
+ throw new ProtocolError('participant_revoked', 'this participant was removed from the room');
34
+ }
35
+ if (participant.leftAt !== null && view.room.locked) {
36
+ throw new ProtocolError('room_locked', 'this room is locked; a participant who left cannot rejoin');
37
+ }
38
+ return { kind: 'participant', participant };
39
+ }
40
+ export function assertController(actor) {
41
+ if (actor.kind === 'controller') {
42
+ return;
43
+ }
44
+ if (actor.participant.role === 'controller') {
45
+ assertCanWrite(actor);
46
+ return;
47
+ }
48
+ throw new ProtocolError('unauthorized', 'this action requires the room controller credential');
49
+ }
50
+ /**
51
+ * A guest may read and leave. Participant writes go through here; receipt-only
52
+ * acknowledgements use assertCanAcknowledge so muting does not block reading.
53
+ */
54
+ export function assertCanWrite(actor) {
55
+ const participant = assertCanAcknowledge(actor);
56
+ if (participant.muted) {
57
+ throw new ProtocolError('participant_muted', 'you are muted in this room');
58
+ }
59
+ return participant;
60
+ }
61
+ /** Muting blocks speech, not confirmation that a member received a message. */
62
+ export function assertCanAcknowledge(actor) {
63
+ const participant = assertActiveMember(actor);
64
+ if (participant.role === 'guest') {
65
+ throw new ProtocolError('unauthorized', 'guests can read this room but cannot take part in it');
66
+ }
67
+ return participant;
68
+ }
69
+ export function assertActiveMember(actor) {
70
+ if (actor.kind !== 'participant') {
71
+ throw new ProtocolError('unauthorized', 'this action requires a room participant');
72
+ }
73
+ if (!isActive(actor.participant)) {
74
+ throw new ProtocolError('participant_revoked', 'this participant is no longer in the room');
75
+ }
76
+ return actor.participant;
77
+ }
78
+ export function assertRecipientExists(view, recipientId) {
79
+ const recipient = findParticipant(view, recipientId);
80
+ if (!recipient || !isActive(recipient)) {
81
+ throw new ProtocolError('invalid_request', 'recipient is not an active participant of this room');
82
+ }
83
+ return recipient;
84
+ }
85
+ export function assertRoomJoinable(view, now) {
86
+ assertRoomWritable(view, now);
87
+ if (view.room.locked) {
88
+ throw new ProtocolError('room_locked', 'this room is locked; joins and new invites are disabled');
89
+ }
90
+ }
91
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../src/authorize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { AutoClosePolicy, CloseReason, ParticipantRecord, RoomEvent, RoomRecord } from '@pairlobby/protocol';
2
+ import { type CoreContext, type Mutation, type RoomView } from './state.js';
3
+ export declare function autoClosePolicy(room: RoomRecord): AutoClosePolicy;
4
+ /**
5
+ * Brings the auto-close fields up to date after a mutation. `participants` is the
6
+ * membership after the mutation; `appended` is the event it adds, if any. Only an
7
+ * accepted `message` (replies and progress included) moves the inactivity clock.
8
+ */
9
+ export declare function refreshAutoClose(room: RoomRecord, participants: ParticipantRecord[], appended: RoomEvent | null, now: number): RoomRecord;
10
+ /** The reason an open room must close now, or null. An expired room is left to expiry. */
11
+ export declare function autoCloseDue(view: RoomView, now: number): CloseReason | null;
12
+ /** The server-authored close. Callers check `autoCloseDue` against freshly loaded state first. */
13
+ export declare function autoCloseRoom(view: RoomView, reason: CloseReason, ctx: CoreContext): Mutation;
14
+ export declare function closeRoom(view: RoomView, credentialHash: string, ctx: CoreContext): Mutation;
15
+ /** Owner or admin only. A policy that is already overdue closes the room on its next load. */
16
+ export declare function setAutoClose(view: RoomView, credentialHash: string, autoClose: AutoClosePolicy, ctx: CoreContext): Mutation;
17
+ //# sourceMappingURL=auto-close.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-close.d.ts","sourceRoot":"","sources":["../src/auto-close.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,eAAe,EAAE,WAAW,EAAE,iBAAiB,EAAE,SAAS,EAAE,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAIhH,OAAO,EAAgB,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAC,MAAM,YAAY,CAAC;AAIzF,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAEjE;AAUD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAezI;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAU5E;AAYD,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,GAAG,QAAQ,CAE7F;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,QAAQ,CAK5F;AAMD,8FAA8F;AAC9F,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,QAAQ,CAe3H"}
@@ -0,0 +1,92 @@
1
+ //! Rooms that close themselves. Every mutation recomputes one stored deadline, in
2
+ //! the same atomic write, so closing never depends on a timer surviving: the
3
+ //! scheduler, a restart or the next request all find the same overdue deadline.
4
+ //! The deadline is recomputed from current state, so a stale wake-up cannot close
5
+ //! a room whose policy or activity has since moved its deadline.
6
+ import { ProtocolError } from '@pairlobby/protocol';
7
+ import { appendEvent } from './append.js';
8
+ import { assertController, assertRoomWritable, authenticate } from './authorize.js';
9
+ import { emptyMutation } from './state.js';
10
+ const OFF = { mode: 'off' };
11
+ export function autoClosePolicy(room) {
12
+ return room.policy.autoClose ?? OFF;
13
+ }
14
+ function countsForDeparture(participant) {
15
+ return participant.kind === 'agent' || participant.role === 'guest';
16
+ }
17
+ function present(participant) {
18
+ return participant.leftAt === null && participant.revokedAt === null;
19
+ }
20
+ /**
21
+ * Brings the auto-close fields up to date after a mutation. `participants` is the
22
+ * membership after the mutation; `appended` is the event it adds, if any. Only an
23
+ * accepted `message` (replies and progress included) moves the inactivity clock.
24
+ */
25
+ export function refreshAutoClose(room, participants, appended, now) {
26
+ const lastMessageAt = appended?.type === 'message' ? appended.at : (room.lastMessageAt ?? null);
27
+ // Membership records outlive departures, so "has one ever joined" needs no extra history.
28
+ const armed = participants.some(countsForDeparture);
29
+ const policy = autoClosePolicy(room);
30
+ let deadline = null;
31
+ if (room.lifecycle === 'open') {
32
+ switch (policy.mode) {
33
+ case 'inactivity':
34
+ deadline = (lastMessageAt ?? room.createdAt) + policy.afterMs;
35
+ break;
36
+ case 'age':
37
+ deadline = room.createdAt + policy.afterMs;
38
+ break;
39
+ case 'agents_and_guests_left':
40
+ deadline = armed && !participants.some((participant) => present(participant) && countsForDeparture(participant)) ? (room.autoCloseAt ?? now) : null;
41
+ break;
42
+ case 'off':
43
+ deadline = null;
44
+ break;
45
+ }
46
+ }
47
+ return { ...room, lastMessageAt, autoCloseArmed: armed, autoCloseAt: deadline };
48
+ }
49
+ /** The reason an open room must close now, or null. An expired room is left to expiry. */
50
+ export function autoCloseDue(view, now) {
51
+ const { room } = view;
52
+ if (room.lifecycle !== 'open' || room.autoCloseAt === undefined || room.autoCloseAt === null || now < room.autoCloseAt) {
53
+ return null;
54
+ }
55
+ if (room.expiresAt !== null && now >= room.expiresAt) {
56
+ return null;
57
+ }
58
+ const policy = autoClosePolicy(room);
59
+ return policy.mode === 'off' ? null : policy.mode;
60
+ }
61
+ function close(view, senderId, reason, ctx) {
62
+ const closed = { ...view.room, lifecycle: 'closed', closedAt: ctx.now, closeReason: reason, autoCloseAt: null };
63
+ const { room, event } = appendEvent(closed, { senderId, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'room.closed', payload: { exportWindowEndsAt: ctx.now + view.room.policy.exportWindowMs, reason } } }, ctx);
64
+ return emptyMutation(room, event);
65
+ }
66
+ /** The server-authored close. Callers check `autoCloseDue` against freshly loaded state first. */
67
+ export function autoCloseRoom(view, reason, ctx) {
68
+ return close(view, null, reason, ctx);
69
+ }
70
+ export function closeRoom(view, credentialHash, ctx) {
71
+ const actor = authenticate(view, credentialHash, ctx.now);
72
+ assertController(actor);
73
+ assertRoomWritable(view, ctx.now);
74
+ return close(view, actor.kind === 'participant' ? actor.participant.participantId : null, 'manual', ctx);
75
+ }
76
+ function samePolicy(left, right) {
77
+ return left.mode === right.mode && ('afterMs' in left ? left.afterMs : null) === ('afterMs' in right ? right.afterMs : null);
78
+ }
79
+ /** Owner or admin only. A policy that is already overdue closes the room on its next load. */
80
+ export function setAutoClose(view, credentialHash, autoClose, ctx) {
81
+ const actor = authenticate(view, credentialHash, ctx.now);
82
+ assertController(actor);
83
+ assertRoomWritable(view, ctx.now);
84
+ const previous = autoClosePolicy(view.room);
85
+ if (samePolicy(previous, autoClose)) {
86
+ throw new ProtocolError('invalid_request', 'the room already uses that auto-close setting');
87
+ }
88
+ const updated = { ...view.room, policy: { ...view.room.policy, autoClose }, autoCloseAt: null };
89
+ const { room, event } = appendEvent(updated, { senderId: actor.kind === 'participant' ? actor.participant.participantId : null, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'room.auto_close_changed', payload: { autoClose, previous } } }, ctx);
90
+ return emptyMutation(room, event);
91
+ }
92
+ //# sourceMappingURL=auto-close.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-close.js","sourceRoot":"","sources":["../src/auto-close.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { HandoverDocument, HandoverRecord, ParticipantRecord } from '@pairlobby/protocol';
2
+ import type { CoreContext, RoomView } from './state.js';
3
+ interface OfferPayload {
4
+ handoverId: string;
5
+ revision: number;
6
+ document: HandoverDocument;
7
+ }
8
+ interface ResolvePayload {
9
+ handoverId: string;
10
+ revision: number;
11
+ }
12
+ export declare function applyHandoverOffered(view: RoomView, sender: ParticipantRecord, payload: OfferPayload, recipientId: string | null, ctx: CoreContext): HandoverRecord;
13
+ export declare function applyHandoverAccepted(view: RoomView, actor: ParticipantRecord, payload: ResolvePayload): HandoverRecord;
14
+ export declare function applyHandoverDeclined(view: RoomView, actor: ParticipantRecord, payload: ResolvePayload): HandoverRecord;
15
+ export {};
16
+ //# sourceMappingURL=handover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handover.d.ts","sourceRoot":"","sources":["../src/handover.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,gBAAgB,EAAE,cAAc,EAAE,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAG7F,OAAO,KAAK,EAAC,WAAW,EAAE,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEtD,UAAU,YAAY;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,gBAAgB,CAAC;CAC9B;AAED,UAAU,cAAc;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,WAAW,GAAG,cAAc,CAoCnK;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,cAAc,GAAG,cAAc,CAGvH;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,cAAc,GAAG,cAAc,CAGvH"}
@@ -0,0 +1,69 @@
1
+ //! Handover rules. A revision is immutable once offered; an amendment is the
2
+ //! next revision; acceptance names an exact revision so a stale one can never be
3
+ //! accepted by accident.
4
+ import { ProtocolError } from '@pairlobby/protocol';
5
+ import { assertRecipientExists } from './authorize.js';
6
+ export function applyHandoverOffered(view, sender, payload, recipientId, ctx) {
7
+ if (recipientId === null) {
8
+ throw new ProtocolError('invalid_request', 'a handover must name its recipient');
9
+ }
10
+ const recipient = assertRecipientExists(view, recipientId);
11
+ if (recipient.participantId === sender.participantId) {
12
+ throw new ProtocolError('invalid_request', 'a handover cannot be offered to its own sender');
13
+ }
14
+ const existing = view.handovers.find((handover) => handover.handoverId === payload.handoverId);
15
+ if (!existing) {
16
+ if (payload.revision !== 1) {
17
+ throw new ProtocolError('stale_handover_revision', 'a new handover must start at revision 1', { currentRevision: 0 });
18
+ }
19
+ return {
20
+ handoverId: payload.handoverId,
21
+ roomId: view.room.roomId,
22
+ senderId: sender.participantId,
23
+ recipientId,
24
+ revision: 1,
25
+ document: payload.document,
26
+ state: 'offered',
27
+ offeredEventId: '',
28
+ resolvedEventId: null,
29
+ resolvedAt: null
30
+ };
31
+ }
32
+ if (existing.senderId !== sender.participantId) {
33
+ throw new ProtocolError('unauthorized', 'only the original sender may amend this handover');
34
+ }
35
+ if (existing.state === 'accepted') {
36
+ throw new ProtocolError('handover_already_resolved', 'this handover was already accepted and cannot be amended');
37
+ }
38
+ if (payload.revision !== existing.revision + 1) {
39
+ throw new ProtocolError('stale_handover_revision', `the next revision of this handover is ${existing.revision + 1}`, { currentRevision: existing.revision });
40
+ }
41
+ return { ...existing, recipientId, revision: payload.revision, document: payload.document, state: 'offered', resolvedEventId: null, resolvedAt: null };
42
+ }
43
+ export function applyHandoverAccepted(view, actor, payload) {
44
+ const handover = requireResolvable(view, actor, payload);
45
+ return { ...handover, state: 'accepted' };
46
+ }
47
+ export function applyHandoverDeclined(view, actor, payload) {
48
+ const handover = requireResolvable(view, actor, payload);
49
+ return { ...handover, state: 'declined' };
50
+ }
51
+ function requireResolvable(view, actor, payload) {
52
+ const handover = view.handovers.find((candidate) => candidate.handoverId === payload.handoverId);
53
+ if (!handover) {
54
+ throw new ProtocolError('invalid_request', 'no such handover in this room');
55
+ }
56
+ if (handover.recipientId !== actor.participantId) {
57
+ throw new ProtocolError('unauthorized', 'only the named recipient may resolve this handover');
58
+ }
59
+ // Resolution is terminal for its revision. Reversing a decline needs the sender
60
+ // to amend, so the sender never discovers a handover they were told was refused.
61
+ if (handover.state !== 'offered') {
62
+ throw new ProtocolError('handover_already_resolved', `this handover revision was already ${handover.state}; ask the sender to amend it`);
63
+ }
64
+ if (payload.revision !== handover.revision) {
65
+ throw new ProtocolError('stale_handover_revision', `this handover is at revision ${handover.revision}`, { currentRevision: handover.revision });
66
+ }
67
+ return handover;
68
+ }
69
+ //# sourceMappingURL=handover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handover.js","sourceRoot":"","sources":["../src/handover.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,iFAAiF;AACjF,yBAAyB;AAEzB,OAAO,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAGlD,OAAO,EAAC,qBAAqB,EAAC,MAAM,gBAAgB,CAAC;AAcrD,MAAM,UAAU,oBAAoB,CAAC,IAAc,EAAE,MAAyB,EAAE,OAAqB,EAAE,WAA0B,EAAE,GAAgB;IAC/I,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,aAAa,CAAC,iBAAiB,EAAE,oCAAoC,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,SAAS,GAAG,qBAAqB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC3D,IAAI,SAAS,CAAC,aAAa,KAAK,MAAM,CAAC,aAAa,EAAE,CAAC;QACnD,MAAM,IAAI,aAAa,CAAC,iBAAiB,EAAE,gDAAgD,CAAC,CAAC;IACjG,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/F,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,aAAa,CAAC,yBAAyB,EAAE,yCAAyC,EAAE,EAAC,eAAe,EAAE,CAAC,EAAC,CAAC,CAAC;QACxH,CAAC;QACD,OAAO;YACH,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;YACxB,QAAQ,EAAE,MAAM,CAAC,aAAa;YAC9B,WAAW;YACX,QAAQ,EAAE,CAAC;YACX,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,KAAK,EAAE,SAAS;YAChB,cAAc,EAAE,EAAE;YAClB,eAAe,EAAE,IAAI;YACrB,UAAU,EAAE,IAAI;SACnB,CAAC;IACN,CAAC;IACD,IAAI,QAAQ,CAAC,QAAQ,KAAK,MAAM,CAAC,aAAa,EAAE,CAAC;QAC7C,MAAM,IAAI,aAAa,CAAC,cAAc,EAAE,kDAAkD,CAAC,CAAC;IAChG,CAAC;IACD,IAAI,QAAQ,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,aAAa,CAAC,2BAA2B,EAAE,0DAA0D,CAAC,CAAC;IACrH,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,aAAa,CAAC,yBAAyB,EAAE,yCAAyC,QAAQ,CAAC,QAAQ,GAAG,CAAC,EAAE,EAAE,EAAC,eAAe,EAAE,QAAQ,CAAC,QAAQ,EAAC,CAAC,CAAC;IAC/J,CAAC;IACD,OAAO,EAAC,GAAG,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAC,CAAC;AACzJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAc,EAAE,KAAwB,EAAE,OAAuB;IACnG,MAAM,QAAQ,GAAG,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACzD,OAAO,EAAC,GAAG,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAc,EAAE,KAAwB,EAAE,OAAuB;IACnG,MAAM,QAAQ,GAAG,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACzD,OAAO,EAAC,GAAG,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAC,CAAC;AAC5C,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAc,EAAE,KAAwB,EAAE,OAAuB;IACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACjG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,MAAM,IAAI,aAAa,CAAC,iBAAiB,EAAE,+BAA+B,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,QAAQ,CAAC,WAAW,KAAK,KAAK,CAAC,aAAa,EAAE,CAAC;QAC/C,MAAM,IAAI,aAAa,CAAC,cAAc,EAAE,oDAAoD,CAAC,CAAC;IAClG,CAAC;IACD,gFAAgF;IAChF,iFAAiF;IACjF,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,IAAI,aAAa,CAAC,2BAA2B,EAAE,sCAAsC,QAAQ,CAAC,KAAK,8BAA8B,CAAC,CAAC;IAC7I,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACzC,MAAM,IAAI,aAAa,CAAC,yBAAyB,EAAE,gCAAgC,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAC,eAAe,EAAE,QAAQ,CAAC,QAAQ,EAAC,CAAC,CAAC;IAClJ,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from './append.js';
2
+ export * from './auto-close.js';
3
+ export * from './authorize.js';
4
+ export * from './handover.js';
5
+ export * from './state.js';
6
+ export * from './transitions.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from './append.js';
2
+ export * from './auto-close.js';
3
+ export * from './authorize.js';
4
+ export * from './handover.js';
5
+ export * from './state.js';
6
+ export * from './transitions.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { ControlRecord, MessageRequest, HandoverRecord, ParticipantRecord, ParticipantView, RoomEvent, RoomRecord, RoomSnapshot } from '@pairlobby/protocol';
2
+ export interface RoomView {
3
+ room: RoomRecord;
4
+ participants: ParticipantRecord[];
5
+ handovers: HandoverRecord[];
6
+ controls: ControlRecord[];
7
+ earliestSeq: number;
8
+ }
9
+ /** Applied by a storage adapter in one transaction, together with the idempotency record. */
10
+ export interface Mutation {
11
+ room: RoomRecord;
12
+ appendEvent: RoomEvent;
13
+ upsertParticipants: ParticipantRecord[];
14
+ upsertHandovers: HandoverRecord[];
15
+ upsertControls: ControlRecord[];
16
+ upsertRequests?: MessageRequest[];
17
+ expectedTurnRevision?: number;
18
+ }
19
+ export interface CoreContext {
20
+ now: number;
21
+ newEventId: () => string;
22
+ }
23
+ export declare function emptyMutation(room: RoomRecord, appendEvent: RoomEvent): Mutation;
24
+ export declare function findParticipant(view: RoomView, participantId: string): ParticipantRecord | undefined;
25
+ export declare function controlFor(view: RoomView, participantId: string): ControlRecord | undefined;
26
+ export declare function isActive(participant: ParticipantRecord): boolean;
27
+ export declare function activeParticipants(view: RoomView): ParticipantRecord[];
28
+ /**
29
+ * Resolves a display name to a participant. Duplicate names are never routed by
30
+ * guess: an ambiguous name returns every match so the caller can show ids.
31
+ */
32
+ export declare function participantsByDisplayName(view: RoomView, displayName: string): ParticipantRecord[];
33
+ export declare function toParticipantView(view: RoomView, participant: ParticipantRecord): ParticipantView;
34
+ export declare function toSnapshot(view: RoomView): RoomSnapshot;
35
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAC,MAAM,qBAAqB,CAAC;AAEhK,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,6FAA6F;AAC7F,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,SAAS,CAAC;IACvB,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,cAAc,EAAE,CAAC;IAClC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,GAAG,QAAQ,CAEhF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAEpG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAE3F;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAEhE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,EAAE,CAEtE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAGlG;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,GAAG,eAAe,CAmBjG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,YAAY,CA+BvD"}
package/dist/state.js ADDED
@@ -0,0 +1,79 @@
1
+ //! The bounded slice of a room a transition needs, and the atomic mutation it
2
+ //! produces. Events are appended, never materialized in full: a room may retain
3
+ //! twenty thousand of them.
4
+ export function emptyMutation(room, appendEvent) {
5
+ return { room, appendEvent, upsertParticipants: [], upsertHandovers: [], upsertControls: [] };
6
+ }
7
+ export function findParticipant(view, participantId) {
8
+ return view.participants.find((participant) => participant.participantId === participantId);
9
+ }
10
+ export function controlFor(view, participantId) {
11
+ return view.controls.find((control) => control.targetParticipantId === participantId);
12
+ }
13
+ export function isActive(participant) {
14
+ return participant.revokedAt === null && participant.leftAt === null;
15
+ }
16
+ export function activeParticipants(view) {
17
+ return view.participants.filter(isActive);
18
+ }
19
+ /**
20
+ * Resolves a display name to a participant. Duplicate names are never routed by
21
+ * guess: an ambiguous name returns every match so the caller can show ids.
22
+ */
23
+ export function participantsByDisplayName(view, displayName) {
24
+ const wanted = displayName.trim().toLowerCase();
25
+ return activeParticipants(view).filter((participant) => participant.displayName.toLowerCase() === wanted);
26
+ }
27
+ export function toParticipantView(view, participant) {
28
+ const control = controlFor(view, participant.participantId);
29
+ return {
30
+ participantId: participant.participantId,
31
+ displayName: participant.displayName,
32
+ ...(participant.nameSource ? { nameSource: participant.nameSource } : {}),
33
+ kind: participant.kind,
34
+ role: participant.role,
35
+ capabilities: participant.capabilities,
36
+ joinedAt: participant.joinedAt,
37
+ revoked: participant.revokedAt !== null,
38
+ left: participant.leftAt !== null,
39
+ paused: control?.paused ?? false,
40
+ muted: participant.muted ?? false,
41
+ controlRevision: control?.revision ?? 0,
42
+ acknowledgedOutcome: control?.acknowledgedOutcome ?? null,
43
+ acknowledgedRevision: control?.acknowledgedRevision ?? 0,
44
+ interruptRequested: Boolean(control?.paused && control.interruptRequested)
45
+ };
46
+ }
47
+ export function toSnapshot(view) {
48
+ return {
49
+ roomId: view.room.roomId,
50
+ messageReceiptScope: 'members',
51
+ messageStagesSupported: true,
52
+ rejoinSupported: true,
53
+ renameSelfSupported: true,
54
+ inviteRoleSupported: true,
55
+ autoCloseSupported: true,
56
+ interruptSupported: true,
57
+ adminRolesSupported: true,
58
+ quotedMessagesSupported: true,
59
+ groupTurnsSupported: true,
60
+ workingStatusSupported: true,
61
+ turnMode: view.room.turnMode ?? 'sequential',
62
+ name: view.room.name,
63
+ lifecycle: view.room.lifecycle,
64
+ locked: view.room.locked ?? false,
65
+ createdAt: view.room.createdAt,
66
+ expiresAt: view.room.expiresAt,
67
+ closedAt: view.room.closedAt,
68
+ lastMessageAt: view.room.lastMessageAt ?? null,
69
+ autoCloseArmed: view.room.autoCloseArmed ?? false,
70
+ autoCloseAt: view.room.autoCloseAt ?? null,
71
+ ...(view.room.closeReason ? { closeReason: view.room.closeReason } : {}),
72
+ controlRevision: view.room.controlRevision,
73
+ latestSeq: view.room.nextSeq - 1,
74
+ earliestSeq: view.earliestSeq,
75
+ participants: view.participants.map((participant) => toParticipantView(view, participant)),
76
+ policy: view.room.policy
77
+ };
78
+ }
79
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { RenameSelfRequest } from '@pairlobby/protocol';
2
+ import type { AdapterCapabilities, ParticipantKind, NameSource, ParticipantRecord, ParticipantRole, RoomInvitationChange, RoomPolicy, RoomRecord, SendEventRequest } from '@pairlobby/protocol';
3
+ import { type Actor } from './authorize.js';
4
+ import { type CoreContext, type Mutation, type RoomView } from './state.js';
5
+ export interface Identity {
6
+ displayName: string;
7
+ nameSource?: NameSource | undefined;
8
+ kind: ParticipantKind;
9
+ sessionId: string | null;
10
+ capabilities: AdapterCapabilities | null;
11
+ }
12
+ export interface CreateRoomInput extends Identity {
13
+ name: string;
14
+ roomId: string;
15
+ /** Explicit expiry, overriding the policy lifetime. Null means it never expires. */
16
+ expiresAt?: number | null;
17
+ controllerCredentialHash: string;
18
+ participantCredentialHash: string;
19
+ policy?: RoomPolicy;
20
+ }
21
+ export interface CreatedRoom {
22
+ room: RoomRecord;
23
+ participant: ParticipantRecord;
24
+ mutation: Mutation;
25
+ }
26
+ export declare function createRoom(input: CreateRoomInput, ctx: CoreContext): CreatedRoom;
27
+ export interface JoinRoomInput extends Identity {
28
+ role: ParticipantRole;
29
+ credentialHash: string;
30
+ }
31
+ export interface JoinedRoom {
32
+ participant: ParticipantRecord;
33
+ mutation: Mutation;
34
+ }
35
+ /** Called by invite redemption once the invite object has bound this attempt. */
36
+ export declare function joinRoom(view: RoomView, input: JoinRoomInput, ctx: CoreContext): JoinedRoom;
37
+ export declare function sendEvent(view: RoomView, credentialHash: string, request: SendEventRequest, ctx: CoreContext): Mutation;
38
+ /**
39
+ * Pause holds a participant's later work; `interrupt` also asks its receiver to stop
40
+ * the turn it is running now. Resume clears both. Owner or admin only.
41
+ */
42
+ export declare function requestControl(view: RoomView, credentialHash: string, targetParticipantId: string, paused: boolean, ctx: CoreContext, interrupt?: boolean): Mutation;
43
+ export declare function revokeParticipant(view: RoomView, credentialHash: string, targetParticipantId: string, ctx: CoreContext): Mutation;
44
+ export declare function leaveRoom(view: RoomView, credentialHash: string, ctx: CoreContext): Mutation;
45
+ /** Restore the same authenticated membership without creating a new identity or role. */
46
+ export declare function rejoinRoom(view: RoomView, credentialHash: string, ctx: CoreContext): Mutation;
47
+ /** Change only the authenticated participant's label, preserving membership and permissions. */
48
+ export declare function renameSelf(view: RoomView, credentialHash: string, input: RenameSelfRequest, ctx: CoreContext): Mutation | null;
49
+ export declare function renameRoom(view: RoomView, credentialHash: string, name: string, ctx: CoreContext): Mutation;
50
+ /** Delegated admins use their own membership; no controller secret is shared. */
51
+ export declare function setParticipantRole(view: RoomView, credentialHash: string, participantId: string, role: 'member' | 'controller', ctx: CoreContext): Mutation | null;
52
+ /** Sets or clears when a room expires. Null stops it expiring at all. */
53
+ /** Opens or closes guest entry. Controller only. */
54
+ export declare function setJoinPolicy(view: RoomView, credentialHash: string, joinPolicy: 'invite_only' | 'open_to_guests', ctx: CoreContext): Mutation;
55
+ /** Sets what a plain invitation admits: members who can speak, or read-only guests. Owner or admin only. */
56
+ export declare function setInviteRole(view: RoomView, credentialHash: string, inviteRole: 'member' | 'guest', ctx: CoreContext): Mutation;
57
+ /**
58
+ * Admits a read-only guest. There is no invite to redeem: the caller's claim is
59
+ * that they know the room id, which is only sufficient while the room says so.
60
+ */
61
+ export declare function joinAsGuest(view: RoomView, input: Omit<JoinRoomInput, 'role'>, ctx: CoreContext): JoinedRoom;
62
+ /** Lets devices on the relay's local network join by name, as members. Owner or admin only. */
63
+ export declare function setLocalJoin(view: RoomView, credentialHash: string, localJoin: boolean, ctx: CoreContext): Mutation;
64
+ /**
65
+ * Admits a member who found the room by name. The relay has already checked that
66
+ * the request came from its local network or tailnet; this checks the room allows it.
67
+ */
68
+ export declare function joinOnLocalNetwork(view: RoomView, input: Omit<JoinRoomInput, 'role'>, ctx: CoreContext): JoinedRoom;
69
+ /**
70
+ * Records that an account was invited by handle, or that the invitation was withdrawn,
71
+ * so the transcript explains who was asked in. The relay that holds accounts calls
72
+ * this after it has stored or removed the invitation; owner or admin only.
73
+ */
74
+ export declare function recordInvitation(view: RoomView, credentialHash: string, change: RoomInvitationChange, ctx: CoreContext): Mutation;
75
+ export declare function setExpiry(view: RoomView, credentialHash: string, expiresAt: number | null, ctx: CoreContext): Mutation;
76
+ export type { Actor };
77
+ export declare function setLocked(view: RoomView, credentialHash: string, locked: boolean, ctx: CoreContext): Mutation;
78
+ export declare function setMuted(view: RoomView, credentialHash: string, participantId: string, muted: boolean, ctx: CoreContext): Mutation;
79
+ //# sourceMappingURL=transitions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transitions.d.ts","sourceRoot":"","sources":["../src/transitions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,389 @@
1
+ //! Room state transitions. Each returns one atomic mutation for a storage
2
+ //! adapter to apply together with its idempotency record.
3
+ import { RenameSelfRequest, DEFAULT_ROOM_POLICY, ProtocolError, newId } from '@pairlobby/protocol';
4
+ import { appendEvent } from './append.js';
5
+ import { assertActiveMember, assertCanAcknowledge, assertCanWrite, assertRoomJoinable, assertController, assertRecipientExists, assertRoomWritable, authenticate } from './authorize.js';
6
+ import { applyHandoverAccepted, applyHandoverDeclined, applyHandoverOffered } from './handover.js';
7
+ import { activeParticipants, controlFor, emptyMutation, findParticipant, isActive } from './state.js';
8
+ export function createRoom(input, ctx) {
9
+ const policy = input.policy ?? DEFAULT_ROOM_POLICY;
10
+ const room = {
11
+ roomId: input.roomId,
12
+ name: input.name,
13
+ createdAt: ctx.now,
14
+ expiresAt: input.expiresAt !== undefined ? input.expiresAt : policy.roomLifetimeMs === null ? null : ctx.now + policy.roomLifetimeMs,
15
+ closedAt: null,
16
+ lifecycle: 'open',
17
+ policy,
18
+ controllerCredentialHash: input.controllerCredentialHash,
19
+ controlRevision: 0,
20
+ nextSeq: 1,
21
+ retainedEventBytes: 0,
22
+ retainedEvents: 0
23
+ };
24
+ const participant = buildParticipant(input.roomId, input, 'member', input.participantCredentialHash, ctx);
25
+ const { room: afterEvent, event } = appendEvent(room, {
26
+ senderId: participant.participantId,
27
+ idempotencyKey: null,
28
+ recipientId: null,
29
+ replyTo: null,
30
+ body: { type: 'participant.joined', payload: joinPayload(participant) }
31
+ }, ctx);
32
+ return { room: afterEvent, participant, mutation: { ...emptyMutation(afterEvent, event), upsertParticipants: [participant] } };
33
+ }
34
+ /** Called by invite redemption once the invite object has bound this attempt. */
35
+ export function joinRoom(view, input, ctx) {
36
+ assertRoomJoinable(view, ctx.now);
37
+ const existing = view.participants.find((candidate) => candidate.credentialHash === input.credentialHash);
38
+ if (existing) {
39
+ throw new ProtocolError('idempotency_conflict', 'this credential already belongs to a participant of this room');
40
+ }
41
+ if (activeParticipants(view).length >= view.room.policy.maxParticipants) {
42
+ throw new ProtocolError('participant_limit_reached', `this room already holds its limit of ${view.room.policy.maxParticipants} participants`);
43
+ }
44
+ const participant = buildParticipant(view.room.roomId, input, input.role, input.credentialHash, ctx);
45
+ const { room, event } = appendEvent(view.room, {
46
+ senderId: participant.participantId,
47
+ idempotencyKey: null,
48
+ recipientId: null,
49
+ replyTo: null,
50
+ body: { type: 'participant.joined', payload: joinPayload(participant) }
51
+ }, ctx);
52
+ return { participant, mutation: { ...emptyMutation(room, event), upsertParticipants: [participant] } };
53
+ }
54
+ export function sendEvent(view, credentialHash, request, ctx) {
55
+ const actor = authenticate(view, credentialHash, ctx.now);
56
+ assertRoomWritable(view, ctx.now);
57
+ const sender = request.type === 'message.received' ? assertCanAcknowledge(actor) : assertCanWrite(actor);
58
+ const recipientId = request.recipientId ?? null;
59
+ if (recipientId !== null) {
60
+ assertRecipientExists(view, recipientId);
61
+ }
62
+ const submission = { type: request.type, payload: request.payload };
63
+ const upsertHandovers = [];
64
+ const upsertControls = [];
65
+ switch (submission.type) {
66
+ case 'handover.offered':
67
+ upsertHandovers.push(applyHandoverOffered(view, sender, submission.payload, recipientId, ctx));
68
+ break;
69
+ case 'handover.accepted':
70
+ upsertHandovers.push(applyHandoverAccepted(view, sender, submission.payload));
71
+ break;
72
+ case 'handover.declined':
73
+ upsertHandovers.push(applyHandoverDeclined(view, sender, submission.payload));
74
+ break;
75
+ case 'control.ack':
76
+ upsertControls.push(applyControlAck(view, sender, submission.payload, ctx));
77
+ break;
78
+ case 'message':
79
+ break;
80
+ }
81
+ const { room, event } = appendEvent(view.room, {
82
+ senderId: sender.participantId,
83
+ idempotencyKey: request.idempotencyKey,
84
+ recipientId,
85
+ replyTo: request.replyTo ?? null,
86
+ ...(request.quoteOf ? { quoteOf: request.quoteOf } : {}),
87
+ body: submission
88
+ }, ctx);
89
+ // A handover offer records the event that carried it, so the offer is traceable from the record alone.
90
+ const handovers = upsertHandovers.map((handover) => submission.type === 'handover.offered' ? { ...handover, offeredEventId: event.eventId } : { ...handover, resolvedEventId: event.eventId, resolvedAt: ctx.now });
91
+ return { ...emptyMutation(room, event), upsertHandovers: handovers, upsertControls };
92
+ }
93
+ /**
94
+ * Pause holds a participant's later work; `interrupt` also asks its receiver to stop
95
+ * the turn it is running now. Resume clears both. Owner or admin only.
96
+ */
97
+ export function requestControl(view, credentialHash, targetParticipantId, paused, ctx, interrupt = false) {
98
+ const actor = authenticate(view, credentialHash, ctx.now);
99
+ assertController(actor);
100
+ assertRoomWritable(view, ctx.now);
101
+ const target = findParticipant(view, targetParticipantId);
102
+ if (!target || !isActive(target)) {
103
+ throw new ProtocolError('invalid_request', 'control target is not an active participant of this room');
104
+ }
105
+ if (interrupt && (!paused || target.kind !== 'agent')) {
106
+ throw new ProtocolError('invalid_request', 'only an agent can be interrupted');
107
+ }
108
+ const revision = view.room.controlRevision + 1;
109
+ const previous = controlFor(view, targetParticipantId);
110
+ const control = {
111
+ roomId: view.room.roomId,
112
+ targetParticipantId,
113
+ paused,
114
+ interruptRequested: interrupt,
115
+ revision,
116
+ requestedAt: ctx.now,
117
+ acknowledgedRevision: previous?.acknowledgedRevision ?? 0,
118
+ acknowledgedOutcome: previous?.acknowledgedOutcome ?? null,
119
+ acknowledgedAt: previous?.acknowledgedAt ?? null
120
+ };
121
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
122
+ const body = paused ? { type: 'control.pause', payload: { targetParticipantId, revision, ...(interrupt ? { interrupt: true } : {}) } } : { type: 'control.resume', payload: { targetParticipantId, revision } };
123
+ const { room, event } = appendEvent({ ...view.room, controlRevision: revision }, { senderId, idempotencyKey: null, recipientId: targetParticipantId, replyTo: null, body }, ctx);
124
+ return { ...emptyMutation(room, event), upsertControls: [control] };
125
+ }
126
+ export function revokeParticipant(view, credentialHash, targetParticipantId, ctx) {
127
+ const actor = authenticate(view, credentialHash, ctx.now);
128
+ assertController(actor);
129
+ assertRoomWritable(view, ctx.now);
130
+ const target = findParticipant(view, targetParticipantId);
131
+ if (!target) {
132
+ throw new ProtocolError('invalid_request', 'no such participant in this room');
133
+ }
134
+ if (target.revokedAt !== null) {
135
+ throw new ProtocolError('idempotency_conflict', 'this participant was already removed');
136
+ }
137
+ const revoked = { ...target, revokedAt: ctx.now };
138
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
139
+ const { room, event } = appendEvent(view.room, { senderId, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'participant.revoked', payload: { participantId: targetParticipantId } } }, ctx);
140
+ return { ...emptyMutation(room, event), upsertParticipants: [revoked] };
141
+ }
142
+ export function leaveRoom(view, credentialHash, ctx) {
143
+ const actor = authenticate(view, credentialHash, ctx.now);
144
+ const participant = assertActiveMember(actor);
145
+ assertRoomWritable(view, ctx.now);
146
+ const left = { ...participant, leftAt: ctx.now };
147
+ const { room, event } = appendEvent(view.room, {
148
+ senderId: participant.participantId,
149
+ idempotencyKey: null,
150
+ recipientId: null,
151
+ replyTo: null,
152
+ body: { type: 'participant.left', payload: { participantId: participant.participantId } }
153
+ }, ctx);
154
+ return { ...emptyMutation(room, event), upsertParticipants: [left] };
155
+ }
156
+ /** Restore the same authenticated membership without creating a new identity or role. */
157
+ export function rejoinRoom(view, credentialHash, ctx) {
158
+ const actor = authenticate(view, credentialHash, ctx.now);
159
+ if (actor.kind !== 'participant') {
160
+ throw new ProtocolError('unauthorized', 'rejoining requires the saved participant credential');
161
+ }
162
+ assertRoomJoinable(view, ctx.now);
163
+ const activeCount = activeParticipants(view).length;
164
+ if (activeCount >= view.room.policy.maxParticipants) {
165
+ throw new ProtocolError('participant_limit_reached', 'this room has reached its participant limit');
166
+ }
167
+ const participant = { ...actor.participant, leftAt: null };
168
+ const { room, event } = appendEvent(view.room, {
169
+ senderId: participant.participantId,
170
+ idempotencyKey: null,
171
+ recipientId: null,
172
+ replyTo: null,
173
+ body: { type: 'participant.joined', payload: joinPayload(participant) }
174
+ }, ctx);
175
+ return { ...emptyMutation(room, event), upsertParticipants: [participant] };
176
+ }
177
+ /** Change only the authenticated participant's label, preserving membership and permissions. */
178
+ export function renameSelf(view, credentialHash, input, ctx) {
179
+ const participant = assertCanWrite(authenticate(view, credentialHash, ctx.now));
180
+ assertRoomWritable(view, ctx.now);
181
+ const parsed = RenameSelfRequest.safeParse(input);
182
+ if (!parsed.success) {
183
+ throw new ProtocolError('invalid_request', 'Use a name of 1–64 characters without control characters; all is reserved.');
184
+ }
185
+ const { name, source } = parsed.data;
186
+ if (source === 'profile' && participant.nameSource === 'room') {
187
+ return null;
188
+ }
189
+ if (participant.displayName === name && participant.nameSource === source) {
190
+ return null;
191
+ }
192
+ const { room, event } = appendEvent(view.room, {
193
+ senderId: participant.participantId,
194
+ idempotencyKey: null,
195
+ recipientId: null,
196
+ replyTo: null,
197
+ body: { type: 'participant.renamed', payload: { participantId: participant.participantId, previousName: participant.displayName, name, source } }
198
+ }, ctx);
199
+ return { ...emptyMutation(room, event), upsertParticipants: [{ ...participant, displayName: name, nameSource: source }] };
200
+ }
201
+ export function renameRoom(view, credentialHash, name, ctx) {
202
+ const actor = authenticate(view, credentialHash, ctx.now);
203
+ assertController(actor);
204
+ assertRoomWritable(view, ctx.now);
205
+ const previousName = view.room.name;
206
+ if (previousName === name) {
207
+ throw new ProtocolError('invalid_request', 'the room already has that name');
208
+ }
209
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
210
+ const { room, event } = appendEvent({ ...view.room, name }, { senderId, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'room.renamed', payload: { name, previousName } } }, ctx);
211
+ return emptyMutation(room, event);
212
+ }
213
+ /** Delegated admins use their own membership; no controller secret is shared. */
214
+ export function setParticipantRole(view, credentialHash, participantId, role, ctx) {
215
+ const actor = authenticate(view, credentialHash, ctx.now);
216
+ assertController(actor);
217
+ assertRoomWritable(view, ctx.now);
218
+ const target = assertRecipientExists(view, participantId);
219
+ if (target.role === 'guest') {
220
+ throw new ProtocolError('invalid_request', 'Invite this observer as a member before granting admin access.');
221
+ }
222
+ if (target.role === role) {
223
+ return null;
224
+ }
225
+ if (role === 'member' && actor.kind === 'participant' && actor.participant.participantId === participantId && !view.participants.some((person) => person.participantId !== participantId && person.role === 'controller' && isActive(person) && !person.muted)) {
226
+ throw new ProtocolError('invalid_request', 'Grant another member admin access before removing your own, or use the room owner credential.');
227
+ }
228
+ const { room, event } = appendEvent(view.room, {
229
+ senderId: actor.kind === 'participant' ? actor.participant.participantId : null,
230
+ idempotencyKey: null, recipientId: null, replyTo: null,
231
+ body: { type: 'participant.role_changed', payload: { participantId, role, previousRole: target.role } }
232
+ }, ctx);
233
+ return { ...emptyMutation(room, event), upsertParticipants: [{ ...target, role }] };
234
+ }
235
+ /** Sets or clears when a room expires. Null stops it expiring at all. */
236
+ /** Opens or closes guest entry. Controller only. */
237
+ export function setJoinPolicy(view, credentialHash, joinPolicy, ctx) {
238
+ const actor = authenticate(view, credentialHash, ctx.now);
239
+ assertController(actor);
240
+ assertRoomWritable(view, ctx.now);
241
+ if (view.room.policy.joinPolicy === joinPolicy) {
242
+ throw new ProtocolError('invalid_request', joinPolicy === 'open_to_guests' ? 'this room is already open to guests' : 'this room is already invite only');
243
+ }
244
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
245
+ const room = { ...view.room, policy: { ...view.room.policy, joinPolicy } };
246
+ const { room: updated, event } = appendEvent(room, { senderId, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'room.access_changed', payload: { joinPolicy } } }, ctx);
247
+ return emptyMutation(updated, event);
248
+ }
249
+ /** Sets what a plain invitation admits: members who can speak, or read-only guests. Owner or admin only. */
250
+ export function setInviteRole(view, credentialHash, inviteRole, ctx) {
251
+ const actor = authenticate(view, credentialHash, ctx.now);
252
+ assertController(actor);
253
+ assertRoomWritable(view, ctx.now);
254
+ if ((view.room.policy.inviteRole ?? 'member') === inviteRole) {
255
+ throw new ProtocolError('invalid_request', inviteRole === 'guest' ? 'invitations already admit read-only observers' : 'invitations already admit members');
256
+ }
257
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
258
+ const room = { ...view.room, policy: { ...view.room.policy, inviteRole } };
259
+ const { room: updated, event } = appendEvent(room, { senderId, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'room.access_changed', payload: { joinPolicy: view.room.policy.joinPolicy, inviteRole } } }, ctx);
260
+ return emptyMutation(updated, event);
261
+ }
262
+ /**
263
+ * Admits a read-only guest. There is no invite to redeem: the caller's claim is
264
+ * that they know the room id, which is only sufficient while the room says so.
265
+ */
266
+ export function joinAsGuest(view, input, ctx) {
267
+ if (view.room.policy.joinPolicy !== 'open_to_guests') {
268
+ throw new ProtocolError('unauthorized', 'this room is invite only; ask its owner for a code');
269
+ }
270
+ return joinRoom(view, { ...input, role: 'guest' }, ctx);
271
+ }
272
+ /** Lets devices on the relay's local network join by name, as members. Owner or admin only. */
273
+ export function setLocalJoin(view, credentialHash, localJoin, ctx) {
274
+ const actor = authenticate(view, credentialHash, ctx.now);
275
+ assertController(actor);
276
+ assertRoomWritable(view, ctx.now);
277
+ if ((view.room.policy.localJoin ?? false) === localJoin) {
278
+ throw new ProtocolError('invalid_request', localJoin ? 'this room is already open to the local network' : 'this room is already closed to the local network');
279
+ }
280
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
281
+ const room = { ...view.room, policy: { ...view.room.policy, localJoin } };
282
+ const { room: updated, event } = appendEvent(room, { senderId, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'room.local_join_changed', payload: { localJoin } } }, ctx);
283
+ return emptyMutation(updated, event);
284
+ }
285
+ /**
286
+ * Admits a member who found the room by name. The relay has already checked that
287
+ * the request came from its local network or tailnet; this checks the room allows it.
288
+ */
289
+ export function joinOnLocalNetwork(view, input, ctx) {
290
+ if (!view.room.policy.localJoin) {
291
+ throw new ProtocolError('unauthorized', 'this room is not open to the local network; ask its owner for an invite code');
292
+ }
293
+ return joinRoom(view, { ...input, role: 'member' }, ctx);
294
+ }
295
+ /**
296
+ * Records that an account was invited by handle, or that the invitation was withdrawn,
297
+ * so the transcript explains who was asked in. The relay that holds accounts calls
298
+ * this after it has stored or removed the invitation; owner or admin only.
299
+ */
300
+ export function recordInvitation(view, credentialHash, change, ctx) {
301
+ const actor = authenticate(view, credentialHash, ctx.now);
302
+ assertController(actor);
303
+ assertRoomWritable(view, ctx.now);
304
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
305
+ const { room, event } = appendEvent(view.room, { senderId, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'room.invitation_changed', payload: change } }, ctx);
306
+ return emptyMutation(room, event);
307
+ }
308
+ export function setExpiry(view, credentialHash, expiresAt, ctx) {
309
+ const actor = authenticate(view, credentialHash, ctx.now);
310
+ assertController(actor);
311
+ assertRoomWritable(view, ctx.now);
312
+ if (expiresAt !== null && expiresAt <= ctx.now) {
313
+ throw new ProtocolError('invalid_request', 'that expiry time is already in the past');
314
+ }
315
+ const previousExpiresAt = view.room.expiresAt;
316
+ if (previousExpiresAt === expiresAt) {
317
+ throw new ProtocolError('invalid_request', expiresAt === null ? 'this room already does not expire' : 'the room already expires then');
318
+ }
319
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
320
+ const { room, event } = appendEvent({ ...view.room, expiresAt }, { senderId, idempotencyKey: null, recipientId: null, replyTo: null, body: { type: 'room.expiry_changed', payload: { expiresAt, previousExpiresAt } } }, ctx);
321
+ return emptyMutation(room, event);
322
+ }
323
+ function applyControlAck(view, sender, payload, ctx) {
324
+ if (payload.targetParticipantId !== sender.participantId) {
325
+ throw new ProtocolError('unauthorized', 'a participant may only acknowledge control requests addressed to itself');
326
+ }
327
+ const control = controlFor(view, sender.participantId);
328
+ if (!control) {
329
+ throw new ProtocolError('invalid_request', 'no control request is outstanding for this participant');
330
+ }
331
+ if (payload.revision > control.revision) {
332
+ throw new ProtocolError('invalid_request', 'acknowledged a control revision the server never issued');
333
+ }
334
+ // A late acknowledgement is still true history, so it is accepted and appended.
335
+ // It simply does not overwrite state that a newer revision already set.
336
+ if (payload.revision < control.revision || payload.revision < control.acknowledgedRevision) {
337
+ return control;
338
+ }
339
+ return { ...control, acknowledgedRevision: payload.revision, acknowledgedOutcome: payload.outcome, acknowledgedAt: ctx.now };
340
+ }
341
+ function buildParticipant(roomId, identity, role, credentialHash, ctx) {
342
+ return {
343
+ participantId: newId('participant'),
344
+ roomId,
345
+ displayName: identity.displayName,
346
+ ...(identity.nameSource ? { nameSource: identity.nameSource } : {}),
347
+ kind: identity.kind,
348
+ role,
349
+ sessionId: identity.sessionId,
350
+ credentialHash,
351
+ capabilities: identity.capabilities,
352
+ joinedAt: ctx.now,
353
+ revokedAt: null,
354
+ leftAt: null
355
+ };
356
+ }
357
+ function joinPayload(participant) {
358
+ return {
359
+ participantId: participant.participantId,
360
+ displayName: participant.displayName,
361
+ kind: participant.kind,
362
+ role: participant.role,
363
+ ...(participant.capabilities ? { capabilities: participant.capabilities } : {})
364
+ };
365
+ }
366
+ export function setLocked(view, credentialHash, locked, ctx) {
367
+ const actor = authenticate(view, credentialHash, ctx.now);
368
+ assertController(actor);
369
+ assertRoomWritable(view, ctx.now);
370
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
371
+ const { room, event } = appendEvent({ ...view.room, locked }, {
372
+ senderId, idempotencyKey: null, recipientId: null, replyTo: null,
373
+ body: { type: 'room.lock_changed', payload: { locked } }
374
+ }, ctx);
375
+ return emptyMutation(room, event);
376
+ }
377
+ export function setMuted(view, credentialHash, participantId, muted, ctx) {
378
+ const actor = authenticate(view, credentialHash, ctx.now);
379
+ assertController(actor);
380
+ assertRoomWritable(view, ctx.now);
381
+ const target = assertRecipientExists(view, participantId);
382
+ const senderId = actor.kind === 'participant' ? actor.participant.participantId : null;
383
+ const { room, event } = appendEvent(view.room, {
384
+ senderId, idempotencyKey: null, recipientId: null, replyTo: null,
385
+ body: { type: 'participant.mute_changed', payload: { participantId, muted } }
386
+ }, ctx);
387
+ return { ...emptyMutation(room, event), upsertParticipants: [{ ...target, muted }] };
388
+ }
389
+ //# sourceMappingURL=transitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transitions.js","sourceRoot":"","sources":["../src/transitions.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@pairlobby/room-core",
3
+ "version": "0.11.0",
4
+ "description": "PairLobby room-core: part of the PairLobby room protocol and relay",
5
+ "license": "Elastic-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "LICENSE",
18
+ "README.md"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/PairLobby/app.git",
23
+ "directory": "packages/room-core"
24
+ },
25
+ "homepage": "https://pairlobby.com",
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "dependencies": {
33
+ "@pairlobby/protocol": "0.11.0"
34
+ }
35
+ }