@pylonsync/sync 0.22.39 → 0.22.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/room-subscriptions.d.ts +12 -1
- package/package.json +1 -1
- package/src/index.ts +18 -1
- package/src/room-subscriptions.test.ts +12 -0
- package/src/room-subscriptions.ts +31 -6
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { LocalStore } from "./local-store";
|
|
|
2
2
|
import { MutationQueue } from "./mutation-queue";
|
|
3
3
|
import { type RoomError, type RoomMember, type RoomSubscriber } from "./room-subscriptions";
|
|
4
4
|
import { SessionResolver } from "./session-resolver";
|
|
5
|
-
export { RoomSubscriptions, type RoomError, type RoomErrorCode, type RoomMember, type RoomMessage, type RoomMessageSubscriber, type RoomSubscriber, } from "./room-subscriptions";
|
|
5
|
+
export { RoomSubscriptions, rowRoom, type RoomError, type RoomErrorCode, type RoomMember, type RoomMessage, type RoomMessageSubscriber, type RoomSubscriber, } from "./room-subscriptions";
|
|
6
6
|
export { IndexedDBPersistence, persistChange } from "./persistence";
|
|
7
7
|
export type { ReplicaPersistence } from "./persistence";
|
|
8
8
|
export { BEARER_REJECTED_HEADER, bearerRejected, buildRequest, clearRejectedToken, pylonFetch, pylonFetchRaw, PylonHttpError, resolveBaseUrl, } from "./transport";
|
|
@@ -4,7 +4,18 @@ export interface RoomMember {
|
|
|
4
4
|
data?: Record<string, unknown>;
|
|
5
5
|
}
|
|
6
6
|
/** Reason codes the SDK exposes on a room subscription error. */
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* - `NOT_IN_ROOM`: subscribed before joining (rejoin and resubscribe).
|
|
9
|
+
* - `ROOM_FORBIDDEN`: a room tied to a row the caller may not read.
|
|
10
|
+
* - `ROOM_REVOKED`: the caller lost access to that row while in the room.
|
|
11
|
+
*/
|
|
12
|
+
export type RoomErrorCode = "NOT_IN_ROOM" | "ROOM_FORBIDDEN" | "ROOM_REVOKED" | "UNKNOWN";
|
|
13
|
+
/**
|
|
14
|
+
* The room tied to a row: its members are the people who may read the row,
|
|
15
|
+
* and the server checks that on every room operation. `@` starts only these
|
|
16
|
+
* names.
|
|
17
|
+
*/
|
|
18
|
+
export declare function rowRoom(entity: string, id: string): string;
|
|
8
19
|
export interface RoomError {
|
|
9
20
|
code: RoomErrorCode;
|
|
10
21
|
message?: string;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { OpQueue } from "./op-queue";
|
|
|
22
22
|
import {
|
|
23
23
|
RoomSubscriptions,
|
|
24
24
|
type RoomError,
|
|
25
|
+
type RoomErrorCode,
|
|
25
26
|
type RoomMember,
|
|
26
27
|
type RoomSubscriber,
|
|
27
28
|
} from "./room-subscriptions";
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
import { generateClientId, generateId } from "./ids";
|
|
38
39
|
export {
|
|
39
40
|
RoomSubscriptions,
|
|
41
|
+
rowRoom,
|
|
40
42
|
type RoomError,
|
|
41
43
|
type RoomErrorCode,
|
|
42
44
|
type RoomMember,
|
|
@@ -4152,6 +4154,20 @@ export class SyncEngine {
|
|
|
4152
4154
|
}
|
|
4153
4155
|
return;
|
|
4154
4156
|
}
|
|
4157
|
+
// The server removed us from a room tied to a row we may no longer
|
|
4158
|
+
// read. It stops sending the room's events; clear the members and tell
|
|
4159
|
+
// the hook, which stops rejoining.
|
|
4160
|
+
if (msg.type === "room-revoked" && typeof msg.room === "string") {
|
|
4161
|
+
const room = msg.room;
|
|
4162
|
+
const error: RoomError = { code: "ROOM_REVOKED" };
|
|
4163
|
+
this.rooms.applySnapshot(room, []);
|
|
4164
|
+
this.rooms.applyError(room, error);
|
|
4165
|
+
if ((this.roomForwarders.get(room)?.size ?? 0) > 0) {
|
|
4166
|
+
this.broadcastToTabs({ type: "room-fanout-snapshot", room, members: [] });
|
|
4167
|
+
this.broadcastToTabs({ type: "room-fanout-error", room, error });
|
|
4168
|
+
}
|
|
4169
|
+
return;
|
|
4170
|
+
}
|
|
4155
4171
|
if (
|
|
4156
4172
|
msg.type === "error" &&
|
|
4157
4173
|
typeof msg.room === "string" &&
|
|
@@ -4163,7 +4179,8 @@ export class SyncEngine {
|
|
|
4163
4179
|
// path right now, but the dispatch is keyed by `code` so future
|
|
4164
4180
|
// error codes (rate-limited, room-full, etc.) route here too.
|
|
4165
4181
|
const room = msg.room;
|
|
4166
|
-
const code =
|
|
4182
|
+
const code: RoomErrorCode =
|
|
4183
|
+
msg.code === "NOT_IN_ROOM" || msg.code === "ROOM_FORBIDDEN" ? msg.code : "UNKNOWN";
|
|
4167
4184
|
const error: RoomError = {
|
|
4168
4185
|
code,
|
|
4169
4186
|
message: typeof msg.message === "string" ? msg.message : undefined,
|
|
@@ -299,3 +299,15 @@ describe("RoomSubscriptions: resubscribe after a rejoin", () => {
|
|
|
299
299
|
expect(sent).toEqual([{ type: "room-sub-resubscribe", room: "channel:a" }]);
|
|
300
300
|
});
|
|
301
301
|
});
|
|
302
|
+
|
|
303
|
+
test("presence from someone not in the list adds them back", () => {
|
|
304
|
+
const { rooms } = makeHarness();
|
|
305
|
+
rooms.register("doc", () => {});
|
|
306
|
+
rooms.applySnapshot("doc", [{ user_id: "alice", joined_at: "t", data: {} }]);
|
|
307
|
+
// A leave relayed from another machine, though Alice is still connected
|
|
308
|
+
// through this one.
|
|
309
|
+
rooms.applyUpdate("doc", "leave", { user_id: "alice", joined_at: "", data: {} }, undefined);
|
|
310
|
+
expect(rooms.members("doc")).toEqual([]);
|
|
311
|
+
rooms.applyUpdate("doc", "presence", { user_id: "alice", joined_at: "", data: {} }, { cursor: 4 });
|
|
312
|
+
expect(rooms.members("doc")).toEqual([{ user_id: "alice", joined_at: "", data: { cursor: 4 } }]);
|
|
313
|
+
});
|
|
@@ -51,7 +51,21 @@ export interface RoomMember {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/** Reason codes the SDK exposes on a room subscription error. */
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* - `NOT_IN_ROOM`: subscribed before joining (rejoin and resubscribe).
|
|
56
|
+
* - `ROOM_FORBIDDEN`: a room tied to a row the caller may not read.
|
|
57
|
+
* - `ROOM_REVOKED`: the caller lost access to that row while in the room.
|
|
58
|
+
*/
|
|
59
|
+
export type RoomErrorCode = "NOT_IN_ROOM" | "ROOM_FORBIDDEN" | "ROOM_REVOKED" | "UNKNOWN";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The room tied to a row: its members are the people who may read the row,
|
|
63
|
+
* and the server checks that on every room operation. `@` starts only these
|
|
64
|
+
* names.
|
|
65
|
+
*/
|
|
66
|
+
export function rowRoom(entity: string, id: string): string {
|
|
67
|
+
return `@${entity}/${id}`;
|
|
68
|
+
}
|
|
55
69
|
|
|
56
70
|
export interface RoomError {
|
|
57
71
|
code: RoomErrorCode;
|
|
@@ -272,11 +286,22 @@ export class RoomSubscriptions {
|
|
|
272
286
|
_data && typeof _data === "object"
|
|
273
287
|
? (_data as Record<string, unknown>)
|
|
274
288
|
: undefined;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
289
|
+
if (entry.members.some((m) => m.user_id === member.user_id)) {
|
|
290
|
+
entry.members = entry.members.map((m) =>
|
|
291
|
+
m.user_id === member.user_id
|
|
292
|
+
? { ...m, ...member, ...(presence ? { data: presence } : {}) }
|
|
293
|
+
: m,
|
|
294
|
+
);
|
|
295
|
+
} else {
|
|
296
|
+
// Presence from someone not in the list is in the room: on a
|
|
297
|
+
// cluster, a leave relayed from one machine can drop a person
|
|
298
|
+
// still connected through another. Their next presence puts them
|
|
299
|
+
// back.
|
|
300
|
+
entry.members = [
|
|
301
|
+
...entry.members,
|
|
302
|
+
{ ...member, joined_at: member.joined_at ?? "", ...(presence ? { data: presence } : {}) },
|
|
303
|
+
];
|
|
304
|
+
}
|
|
280
305
|
break;
|
|
281
306
|
}
|
|
282
307
|
case "broadcast": {
|