@pairlobby/client 0.0.0-stage → 0.12.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.
@@ -0,0 +1,212 @@
1
+ //! Per-device room registry and credential store.
2
+ //!
3
+ //! These are two files on purpose. The registry answers "which rooms have my
4
+ //! agents joined, in which sessions" and is safe to print; credentials live
5
+ //! apart so listing a room can never disclose one.
6
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
7
+ import { homedir, platform } from 'node:os';
8
+ import { join } from 'node:path';
9
+ export const DEFAULT_SETTINGS = {
10
+ confirmDelete: true,
11
+ pollIntervalMs: 700,
12
+ showIds: false,
13
+ defaultRoomLifetimeMs: null,
14
+ defaultInviteLifetimeMs: null,
15
+ updateCheck: true,
16
+ autoUpdate: false,
17
+ defaultTurnMode: 'sequential',
18
+ defaultInviteRole: 'member',
19
+ defaultGuestAccess: 'invite_only',
20
+ defaultPrivateOnline: false,
21
+ defaultAutoClose: 'off',
22
+ relayNetwork: 'off',
23
+ networkRooms: true,
24
+ };
25
+ export function dataDirectory() {
26
+ const override = process.env['PAIRLOBBY_DATA_DIR'];
27
+ if (override) {
28
+ return override;
29
+ }
30
+ const home = homedir();
31
+ switch (platform()) {
32
+ case 'darwin':
33
+ return join(home, 'Library', 'Application Support', 'PairLobby');
34
+ case 'win32':
35
+ return join(process.env['APPDATA'] ?? join(home, 'AppData', 'Roaming'), 'PairLobby');
36
+ default:
37
+ return join(process.env['XDG_DATA_HOME'] ?? join(home, '.local', 'share'), 'pairlobby');
38
+ }
39
+ }
40
+ export class LocalStore {
41
+ directory;
42
+ constructor(directory = dataDirectory()) {
43
+ this.directory = directory;
44
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
45
+ }
46
+ get roomsFile() {
47
+ return join(this.directory, 'rooms.json');
48
+ }
49
+ get credentialsFile() {
50
+ return join(this.directory, 'credentials.json');
51
+ }
52
+ get profileFile() {
53
+ return join(this.directory, 'profile.json');
54
+ }
55
+ profile() {
56
+ return readJson(this.profileFile, {});
57
+ }
58
+ setProfile(profile) {
59
+ const merged = { ...this.profile(), ...profile };
60
+ for (const key of Object.keys(merged)) {
61
+ if (merged[key] === undefined) {
62
+ delete merged[key];
63
+ }
64
+ }
65
+ writeJsonPrivate(this.profileFile, merged);
66
+ return merged;
67
+ }
68
+ clearProfile() {
69
+ writeJsonPrivate(this.profileFile, {});
70
+ }
71
+ get settingsFile() {
72
+ return join(this.directory, 'settings.json');
73
+ }
74
+ settings() {
75
+ return { ...DEFAULT_SETTINGS, ...readJson(this.settingsFile, {}) };
76
+ }
77
+ setSettings(update) {
78
+ const merged = { ...this.settings(), ...update };
79
+ writeJsonPrivate(this.settingsFile, merged);
80
+ return merged;
81
+ }
82
+ resetSettings() {
83
+ writeJsonPrivate(this.settingsFile, {});
84
+ return DEFAULT_SETTINGS;
85
+ }
86
+ rooms() {
87
+ return readJson(this.roomsFile, []);
88
+ }
89
+ room(roomId) {
90
+ return this.rooms().find((entry) => entry.roomId === roomId);
91
+ }
92
+ /** Resolves a room by id, exact name, or unique name prefix. Ambiguity is reported, never guessed. */
93
+ resolveRoom(reference) {
94
+ const rooms = this.rooms();
95
+ const exact = rooms.find((entry) => entry.roomId === reference || entry.name === reference);
96
+ if (exact) {
97
+ return { room: exact };
98
+ }
99
+ const matches = rooms.filter((entry) => entry.name.startsWith(reference) || entry.roomId.startsWith(reference));
100
+ if (matches.length === 1) {
101
+ return { room: matches[0] };
102
+ }
103
+ if (matches.length > 1) {
104
+ return { ambiguous: matches };
105
+ }
106
+ return { missing: true };
107
+ }
108
+ /** The only room when there is exactly one open room on this device. */
109
+ soleRoom(now = Date.now()) {
110
+ const open = this.rooms().filter((entry) => entry.expiresAt === null || entry.expiresAt > now);
111
+ return open.length === 1 ? open[0] : null;
112
+ }
113
+ upsertRoom(entry) {
114
+ const previous = this.rooms();
115
+ const rooms = previous.filter((candidate) => candidate.roomId !== entry.roomId);
116
+ rooms.push({ ...previous.find((candidate) => candidate.roomId === entry.roomId), ...entry });
117
+ writeJsonPrivate(this.roomsFile, rooms);
118
+ }
119
+ rememberHumanSession(roomId, sessionId) {
120
+ const room = this.room(roomId);
121
+ const session = room?.sessions.find((candidate) => candidate.sessionId === sessionId);
122
+ if (!room || session?.kind !== 'human' || !this.credential(roomId, sessionId)) {
123
+ throw new Error('a preferred human session must have a saved human membership and credential');
124
+ }
125
+ this.upsertRoom({ ...room, preferredHumanSessionId: sessionId });
126
+ }
127
+ updateSessionName(roomId, sessionId, displayName, nameSource) {
128
+ const room = this.room(roomId);
129
+ const session = room?.sessions.find((candidate) => candidate.sessionId === sessionId);
130
+ if (!room || !session) {
131
+ return;
132
+ }
133
+ session.displayName = displayName;
134
+ if (nameSource) {
135
+ session.nameSource = nameSource;
136
+ }
137
+ this.upsertRoom(room);
138
+ }
139
+ addSession(roomId, session) {
140
+ const room = this.room(roomId);
141
+ if (!room) {
142
+ throw new Error(`room ${roomId} is not in the local registry`);
143
+ }
144
+ room.sessions = room.sessions.filter((candidate) => candidate.sessionId !== session.sessionId);
145
+ room.sessions.push(session);
146
+ this.upsertRoom(room);
147
+ }
148
+ /** Attaches or corrects the runtime conversation id for a session already in the registry. */
149
+ setConversation(roomId, sessionId, conversationId) {
150
+ const room = this.room(roomId);
151
+ const session = room?.sessions.find((candidate) => candidate.sessionId === sessionId);
152
+ if (!room || !session) {
153
+ return false;
154
+ }
155
+ session.conversationId = conversationId;
156
+ this.upsertRoom(room);
157
+ return true;
158
+ }
159
+ updateCursor(roomId, sessionId, lastReadSeq) {
160
+ const room = this.room(roomId);
161
+ const session = room?.sessions.find((candidate) => candidate.sessionId === sessionId);
162
+ if (!room || !session) {
163
+ return;
164
+ }
165
+ session.lastReadSeq = lastReadSeq;
166
+ this.upsertRoom(room);
167
+ }
168
+ forgetRoom(roomId) {
169
+ writeJsonPrivate(this.roomsFile, this.rooms().filter((entry) => entry.roomId !== roomId));
170
+ const credentials = this.credentials();
171
+ for (const key of Object.keys(credentials)) {
172
+ if (key.startsWith(`${roomId}:`)) {
173
+ delete credentials[key];
174
+ }
175
+ }
176
+ writeJsonPrivate(this.credentialsFile, credentials);
177
+ }
178
+ putCredential(roomId, scope, credential) {
179
+ const credentials = this.credentials();
180
+ credentials[`${roomId}:${scope}`] = credential;
181
+ writeJsonPrivate(this.credentialsFile, credentials);
182
+ }
183
+ /** Admission codes are private local data, not public registry metadata. */
184
+ putSessionInvite(roomId, sessionId, code) {
185
+ this.putCredential(roomId, `invite:${sessionId}`, code);
186
+ }
187
+ sessionInvite(roomId, sessionId) {
188
+ return this.credential(roomId, `invite:${sessionId}`);
189
+ }
190
+ credential(roomId, scope) {
191
+ return this.credentials()[`${roomId}:${scope}`];
192
+ }
193
+ credentials() {
194
+ return readJson(this.credentialsFile, {});
195
+ }
196
+ }
197
+ function readJson(file, fallback) {
198
+ try {
199
+ return JSON.parse(readFileSync(file, 'utf8'));
200
+ }
201
+ catch {
202
+ return fallback;
203
+ }
204
+ }
205
+ /** Written through a temporary file so a crash mid-write cannot truncate the store. */
206
+ function writeJsonPrivate(file, value) {
207
+ const temporary = `${file}.${process.pid}.tmp`;
208
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
209
+ chmodSync(temporary, 0o600);
210
+ renameSync(temporary, file);
211
+ }
212
+ //# sourceMappingURL=local-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-store.js","sourceRoot":"","sources":["../src/local-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { RoomEvent } from '@pairlobby/protocol';
2
+ export interface OpenRequest {
3
+ eventId: string;
4
+ seq: number;
5
+ from: string;
6
+ to: string;
7
+ text: string;
8
+ at: number;
9
+ received: boolean;
10
+ waitingMs: number;
11
+ state?: string;
12
+ }
13
+ export declare function openRequests(events: RoomEvent[], now?: number): OpenRequest[];
14
+ export declare function owedByMe(events: RoomEvent[], participantId: string, now?: number): OpenRequest[];
15
+ export declare function unreceipted(events: RoomEvent[], participantId: string, allReaders?: boolean): RoomEvent[];
16
+ //# sourceMappingURL=open-requests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-requests.d.ts","sourceRoot":"","sources":["../src/open-requests.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,GAAG,SAAa,GAAG,WAAW,EAAE,CAuCjF;AACD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,WAAW,EAAE,CAEpG;AACD,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,UAAQ,GAAG,SAAS,EAAE,CAGvG"}
@@ -0,0 +1,48 @@
1
+ export function openRequests(events, now = Date.now()) {
2
+ const open = new Map();
3
+ const ordered = [...events].sort((a, b) => a.seq - b.seq);
4
+ for (const event of ordered) {
5
+ if (event.type === 'message.received') {
6
+ const request = open.get(event.payload.eventId);
7
+ if (request?.to === event.senderId) {
8
+ request.received = true;
9
+ if (['no_action', 'declined', 'done'].includes(event.payload.action ?? '')) {
10
+ open.delete(event.payload.eventId);
11
+ if (event.payload.action === 'done' && event.payload.responseEventId) {
12
+ open.delete(event.payload.responseEventId);
13
+ }
14
+ }
15
+ }
16
+ continue;
17
+ }
18
+ if (event.type !== 'message' || event.senderId === null || event.recipientId === null) {
19
+ continue;
20
+ }
21
+ if (event.replyTo !== null) {
22
+ const target = open.get(event.replyTo);
23
+ if (target && event.senderId === target.to && event.recipientId === target.from && event.payload.responseStage !== 'progress') {
24
+ open.delete(event.replyTo);
25
+ }
26
+ continue;
27
+ }
28
+ open.set(event.eventId, {
29
+ eventId: event.eventId,
30
+ seq: event.seq,
31
+ from: event.senderId,
32
+ to: event.recipientId,
33
+ text: event.payload.text,
34
+ at: event.at,
35
+ received: false,
36
+ waitingMs: Math.max(0, now - event.at)
37
+ });
38
+ }
39
+ return [...open.values()];
40
+ }
41
+ export function owedByMe(events, participantId, now = Date.now()) {
42
+ return openRequests(events, now).filter((request) => request.to === participantId);
43
+ }
44
+ export function unreceipted(events, participantId, allReaders = false) {
45
+ const confirmed = new Set(events.flatMap((event) => (event.type === 'message.received' && event.senderId === participantId ? [event.payload.eventId] : [])));
46
+ return events.filter((event) => event.type === 'message' && (allReaders || event.recipientId === participantId) && event.senderId !== participantId && !confirmed.has(event.eventId));
47
+ }
48
+ //# sourceMappingURL=open-requests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-requests.js","sourceRoot":"","sources":["../src/open-requests.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "@pairlobby/client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.12.0",
4
+ "description": "PairLobby client: 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/client"
24
+ },
25
+ "homepage": "https://pairlobby.com",
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "dependencies": {
33
+ "@pairlobby/protocol": "0.12.0"
34
+ }
35
+ }