@foony/chat 0.0.1
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 +215 -0
- package/README.md +67 -0
- package/lib/chatClient.d.ts +22 -0
- package/lib/chatClient.d.ts.map +1 -0
- package/lib/chatClient.js +28 -0
- package/lib/chatClient.js.map +1 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +18 -0
- package/lib/index.js.map +1 -0
- package/lib/messages.d.ts +45 -0
- package/lib/messages.d.ts.map +1 -0
- package/lib/messages.js +146 -0
- package/lib/messages.js.map +1 -0
- package/lib/occupancy.d.ts +30 -0
- package/lib/occupancy.d.ts.map +1 -0
- package/lib/occupancy.js +65 -0
- package/lib/occupancy.js.map +1 -0
- package/lib/presence.d.ts +0 -0
- package/lib/presence.d.ts.map +1 -0
- package/lib/presence.js +0 -0
- package/lib/presence.js.map +1 -0
- package/lib/protocol.d.ts +52 -0
- package/lib/protocol.d.ts.map +1 -0
- package/lib/protocol.js +72 -0
- package/lib/protocol.js.map +1 -0
- package/lib/reactions.d.ts +26 -0
- package/lib/reactions.d.ts.map +1 -0
- package/lib/reactions.js +56 -0
- package/lib/reactions.js.map +1 -0
- package/lib/reconciler.d.ts +29 -0
- package/lib/reconciler.d.ts.map +1 -0
- package/lib/reconciler.js +149 -0
- package/lib/reconciler.js.map +1 -0
- package/lib/room.d.ts +53 -0
- package/lib/room.d.ts.map +1 -0
- package/lib/room.js +81 -0
- package/lib/room.js.map +1 -0
- package/lib/rooms.d.ts +20 -0
- package/lib/rooms.d.ts.map +1 -0
- package/lib/rooms.js +38 -0
- package/lib/rooms.js.map +1 -0
- package/lib/types.d.ts +139 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +9 -0
- package/lib/types.js.map +1 -0
- package/lib/typing.d.ts +43 -0
- package/lib/typing.d.ts.map +1 -0
- package/lib/typing.js +108 -0
- package/lib/typing.js.map +1 -0
- package/lib/util.d.ts +9 -0
- package/lib/util.d.ts.map +1 -0
- package/lib/util.js +12 -0
- package/lib/util.js.map +1 -0
- package/lib-cjs/chatClient.js +32 -0
- package/lib-cjs/chatClient.js.map +1 -0
- package/lib-cjs/index.js +30 -0
- package/lib-cjs/index.js.map +1 -0
- package/lib-cjs/messages.js +150 -0
- package/lib-cjs/messages.js.map +1 -0
- package/lib-cjs/occupancy.js +69 -0
- package/lib-cjs/occupancy.js.map +1 -0
- package/lib-cjs/package.json +3 -0
- package/lib-cjs/presence.js +0 -0
- package/lib-cjs/presence.js.map +1 -0
- package/lib-cjs/protocol.js +79 -0
- package/lib-cjs/protocol.js.map +1 -0
- package/lib-cjs/reactions.js +60 -0
- package/lib-cjs/reactions.js.map +1 -0
- package/lib-cjs/reconciler.js +153 -0
- package/lib-cjs/reconciler.js.map +1 -0
- package/lib-cjs/room.js +85 -0
- package/lib-cjs/room.js.map +1 -0
- package/lib-cjs/rooms.js +42 -0
- package/lib-cjs/rooms.js.map +1 -0
- package/lib-cjs/types.js +10 -0
- package/lib-cjs/types.js.map +1 -0
- package/lib-cjs/typing.js +112 -0
- package/lib-cjs/typing.js.map +1 -0
- package/lib-cjs/util.js +15 -0
- package/lib-cjs/util.js.map +1 -0
- package/package.json +56 -0
- package/src/chatClient.ts +31 -0
- package/src/index.ts +32 -0
- package/src/messages.ts +159 -0
- package/src/occupancy.ts +78 -0
- package/src/presence.ts +0 -0
- package/src/protocol.ts +90 -0
- package/src/reactions.ts +64 -0
- package/src/reconciler.ts +169 -0
- package/src/room.ts +97 -0
- package/src/rooms.ts +42 -0
- package/src/types.ts +141 -0
- package/src/typing.ts +123 -0
- package/src/util.ts +12 -0
package/lib/occupancy.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-room occupancy, derived entirely from the presence set — no edge call.
|
|
3
|
+
*
|
|
4
|
+
* `connections` counts distinct (clientId, connectionId) pairs present (a user
|
|
5
|
+
* on two devices = two connections); `presenceMembers` counts distinct client
|
|
6
|
+
* ids. This reuses presence's TTL-based ghost-leave detection, so counts
|
|
7
|
+
* self-heal after a crash. Limitation: it only counts members who entered
|
|
8
|
+
* presence — always the case in a chat room, where joining enters presence.
|
|
9
|
+
*/
|
|
10
|
+
/** Default debounce for occupancy change events. */
|
|
11
|
+
const DEFAULT_DEBOUNCE_MS = 1_000;
|
|
12
|
+
/** The occupancy feature of a {@link Room}. */
|
|
13
|
+
export class Occupancy {
|
|
14
|
+
presence;
|
|
15
|
+
listeners = new Set();
|
|
16
|
+
debounceMs;
|
|
17
|
+
debounceTimer = null;
|
|
18
|
+
lastEmitted = null;
|
|
19
|
+
membersChangedUnsubscribe = null;
|
|
20
|
+
constructor(presence, debounceMs) {
|
|
21
|
+
this.presence = presence;
|
|
22
|
+
this.debounceMs = debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
23
|
+
}
|
|
24
|
+
/** Current occupancy snapshot, computed from the local presence member set. */
|
|
25
|
+
get() {
|
|
26
|
+
const members = this.presence.get();
|
|
27
|
+
const clientIds = new Set(members.map((member) => member.clientId));
|
|
28
|
+
return { connections: members.length, presenceMembers: clientIds.size };
|
|
29
|
+
}
|
|
30
|
+
/** Subscribe to occupancy changes (debounced, deduplicated). */
|
|
31
|
+
subscribe(listener) {
|
|
32
|
+
this.listeners.add(listener);
|
|
33
|
+
if (!this.membersChangedUnsubscribe) {
|
|
34
|
+
this.membersChangedUnsubscribe = this.presence.onMembersChanged(() => this.scheduleEmit());
|
|
35
|
+
}
|
|
36
|
+
return () => {
|
|
37
|
+
this.listeners.delete(listener);
|
|
38
|
+
if (this.listeners.size === 0 && this.membersChangedUnsubscribe) {
|
|
39
|
+
this.membersChangedUnsubscribe();
|
|
40
|
+
this.membersChangedUnsubscribe = null;
|
|
41
|
+
if (this.debounceTimer) {
|
|
42
|
+
clearTimeout(this.debounceTimer);
|
|
43
|
+
this.debounceTimer = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
scheduleEmit() {
|
|
49
|
+
if (this.debounceTimer) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.debounceTimer = setTimeout(() => {
|
|
53
|
+
this.debounceTimer = null;
|
|
54
|
+
const current = this.get();
|
|
55
|
+
if (this.lastEmitted && this.lastEmitted.connections === current.connections && this.lastEmitted.presenceMembers === current.presenceMembers) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.lastEmitted = current;
|
|
59
|
+
for (const listener of [...this.listeners]) {
|
|
60
|
+
listener(current);
|
|
61
|
+
}
|
|
62
|
+
}, this.debounceMs);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=occupancy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"occupancy.js","sourceRoot":"","sources":["../src/occupancy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,oDAAoD;AACpD,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAElC,+CAA+C;AAC/C,MAAM,OAAO,SAAS;IAQD;IAPF,SAAS,GAAG,IAAI,GAAG,EAAqB,CAAC;IACzC,UAAU,CAAS;IAC5B,aAAa,GAAyC,IAAI,CAAC;IAC3D,WAAW,GAAyB,IAAI,CAAC;IACzC,yBAAyB,GAAyB,IAAI,CAAC;IAE/D,YACmB,QAAkB,EACnC,UAAmB;QADF,aAAQ,GAAR,QAAQ,CAAU;QAGnC,IAAI,CAAC,UAAU,GAAG,UAAU,IAAI,mBAAmB,CAAC;IACtD,CAAC;IAED,+EAA+E;IAC/E,GAAG;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpE,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC;IAC1E,CAAC;IAED,gEAAgE;IAChE,SAAS,CAAC,QAA2B;QACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACpC,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;QAC7F,CAAC;QACD,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,yBAAyB,EAAE,CAAC;gBAChE,IAAI,CAAC,yBAAyB,EAAE,CAAC;gBACjC,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC;gBACtC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;oBACvB,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;oBACjC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC5B,CAAC;YACH,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAEO,YAAY;QAClB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;YACnC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,eAAe,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC;gBAC7I,OAAO;YACT,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;YAC3B,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3C,QAAQ,CAAC,OAAO,CAAC,CAAC;YACpB,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;CACF"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../src/presence.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAsB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEhE,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE9D,kFAAkF;AAClF,KAAK,sBAAsB,GAAG,MAAM,IAAI,CAAC;AAEzC,8CAA8C;AAC9C,qBAAa,QAAQ;IAOP,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqC;IAC7E,OAAO,CAAC,kBAAkB,CAA8B;gBAE3B,OAAO,EAAE,OAAO;IAE7C,mEAAmE;IAC7D,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,kDAAkD;IAC5C,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,6CAA6C;IACvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,kFAAkF;IAClF,GAAG,IAAI,cAAc,EAAE;IAIvB,oEAAoE;IACpE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IASxC,iFAAiF;IACjF,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,aAAa;IAQpD,sFAAsF;IACtF,gBAAgB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,aAAa;IAQjE,OAAO,CAAC,yBAAyB;IAOjC,OAAO,CAAC,eAAe;CAqBxB"}
|
package/lib/presence.js
ADDED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../src/presence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal wire conventions for the chat layer: the reserved `name` values
|
|
3
|
+
* used on the underlying realtime channel and the JSON payload shapes carried
|
|
4
|
+
* in each. Not part of the public API — kept in one place so producers and the
|
|
5
|
+
* reconciler agree.
|
|
6
|
+
*/
|
|
7
|
+
/** Channel name for a room. The edge grammar forbids `$`, so we namespace with `:`. */
|
|
8
|
+
export declare function roomChannelName(roomName: string): string;
|
|
9
|
+
/** Event name carrying message create/update/delete payloads. */
|
|
10
|
+
export declare const MESSAGE_EVENT = "chat.message";
|
|
11
|
+
/** Event name carrying ephemeral room-level reactions. */
|
|
12
|
+
export declare const REACTION_EVENT = "chat.reaction";
|
|
13
|
+
/** Event name carrying ephemeral typing heartbeats and stops. */
|
|
14
|
+
export declare const TYPING_EVENT = "chat.typing";
|
|
15
|
+
/** Payload schema version, bumped if the on-channel shape ever changes. */
|
|
16
|
+
export declare const PAYLOAD_VERSION = 1;
|
|
17
|
+
/** Payload published under {@link MESSAGE_EVENT}. `id` is the message id in all actions. */
|
|
18
|
+
export type MessagePayload = {
|
|
19
|
+
readonly v: number;
|
|
20
|
+
readonly action: 'create';
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly text: string;
|
|
23
|
+
readonly metadata?: Record<string, unknown>;
|
|
24
|
+
readonly headers?: Record<string, unknown>;
|
|
25
|
+
} | {
|
|
26
|
+
readonly v: number;
|
|
27
|
+
readonly action: 'update';
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly text: string;
|
|
30
|
+
readonly metadata?: Record<string, unknown>;
|
|
31
|
+
readonly headers?: Record<string, unknown>;
|
|
32
|
+
} | {
|
|
33
|
+
readonly v: number;
|
|
34
|
+
readonly action: 'delete';
|
|
35
|
+
readonly id: string;
|
|
36
|
+
};
|
|
37
|
+
/** Payload published under {@link TYPING_EVENT}. */
|
|
38
|
+
export type TypingPayload = {
|
|
39
|
+
readonly state: 'started' | 'stopped';
|
|
40
|
+
};
|
|
41
|
+
/** Payload published under {@link REACTION_EVENT}. */
|
|
42
|
+
export type ReactionPayload = {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly metadata?: unknown;
|
|
45
|
+
};
|
|
46
|
+
/** Parse an unknown message payload, returning null if it is malformed or a foreign frame. */
|
|
47
|
+
export declare function parseMessagePayload(data: unknown): MessagePayload | null;
|
|
48
|
+
/** Parse an unknown typing payload, returning null if malformed. */
|
|
49
|
+
export declare function parseTypingPayload(data: unknown): TypingPayload | null;
|
|
50
|
+
/** Parse an unknown reaction payload, returning null if malformed. */
|
|
51
|
+
export declare function parseReactionPayload(data: unknown): ReactionPayload | null;
|
|
52
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,iEAAiE;AACjE,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAC5C,0DAA0D;AAC1D,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAC9C,iEAAiE;AACjE,eAAO,MAAM,YAAY,gBAAgB,CAAC;AAE1C,2EAA2E;AAC3E,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,4FAA4F;AAC5F,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACtL;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACtL;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,oDAAoD;AACpD,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,CAAC;AAEtE,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAErF,8FAA8F;AAC9F,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CA2BxE;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAMtE;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAU1E"}
|
package/lib/protocol.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal wire conventions for the chat layer: the reserved `name` values
|
|
3
|
+
* used on the underlying realtime channel and the JSON payload shapes carried
|
|
4
|
+
* in each. Not part of the public API — kept in one place so producers and the
|
|
5
|
+
* reconciler agree.
|
|
6
|
+
*/
|
|
7
|
+
/** Channel name for a room. The edge grammar forbids `$`, so we namespace with `:`. */
|
|
8
|
+
export function roomChannelName(roomName) {
|
|
9
|
+
return `chat:${roomName}`;
|
|
10
|
+
}
|
|
11
|
+
/** Event name carrying message create/update/delete payloads. */
|
|
12
|
+
export const MESSAGE_EVENT = 'chat.message';
|
|
13
|
+
/** Event name carrying ephemeral room-level reactions. */
|
|
14
|
+
export const REACTION_EVENT = 'chat.reaction';
|
|
15
|
+
/** Event name carrying ephemeral typing heartbeats and stops. */
|
|
16
|
+
export const TYPING_EVENT = 'chat.typing';
|
|
17
|
+
/** Payload schema version, bumped if the on-channel shape ever changes. */
|
|
18
|
+
export const PAYLOAD_VERSION = 1;
|
|
19
|
+
/** Parse an unknown message payload, returning null if it is malformed or a foreign frame. */
|
|
20
|
+
export function parseMessagePayload(data) {
|
|
21
|
+
if (typeof data !== 'object' || data === null) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const payload = data;
|
|
25
|
+
const action = payload['action'];
|
|
26
|
+
const id = payload['id'];
|
|
27
|
+
if (typeof id !== 'string' || id === '') {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (action === 'delete') {
|
|
31
|
+
return { v: PAYLOAD_VERSION, action, id };
|
|
32
|
+
}
|
|
33
|
+
if ((action === 'create' || action === 'update') && typeof payload['text'] === 'string') {
|
|
34
|
+
const text = payload['text'];
|
|
35
|
+
const metadata = isRecord(payload['metadata']) ? payload['metadata'] : undefined;
|
|
36
|
+
const headers = isRecord(payload['headers']) ? payload['headers'] : undefined;
|
|
37
|
+
return {
|
|
38
|
+
v: PAYLOAD_VERSION,
|
|
39
|
+
action,
|
|
40
|
+
id,
|
|
41
|
+
text,
|
|
42
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
43
|
+
...(headers === undefined ? {} : { headers }),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/** Parse an unknown typing payload, returning null if malformed. */
|
|
49
|
+
export function parseTypingPayload(data) {
|
|
50
|
+
if (typeof data !== 'object' || data === null) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const state = data['state'];
|
|
54
|
+
return state === 'started' || state === 'stopped' ? { state } : null;
|
|
55
|
+
}
|
|
56
|
+
/** Parse an unknown reaction payload, returning null if malformed. */
|
|
57
|
+
export function parseReactionPayload(data) {
|
|
58
|
+
if (typeof data !== 'object' || data === null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const name = data['name'];
|
|
62
|
+
if (typeof name !== 'string' || name === '') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const metadata = data['metadata'];
|
|
66
|
+
return metadata === undefined ? { name } : { name, metadata };
|
|
67
|
+
}
|
|
68
|
+
/** True when `value` is a non-null, non-array object. */
|
|
69
|
+
function isRecord(value) {
|
|
70
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-room reactions — ephemeral, fire-and-forget broadcasts (e.g. a floating
|
|
3
|
+
* emoji). Not stored or aggregated; distinct from per-message reactions (which
|
|
4
|
+
* are out of scope for v1). Pure pub/sub over the channel.
|
|
5
|
+
*/
|
|
6
|
+
import type { Channel, UnsubscribeFn } from '@foony/realtime';
|
|
7
|
+
import type { RoomReaction } from './types.js';
|
|
8
|
+
/** Listener invoked for every room reaction. */
|
|
9
|
+
export type ReactionListener = (reaction: RoomReaction) => void;
|
|
10
|
+
/** The room-reaction feature of a {@link Room}. */
|
|
11
|
+
export declare class Reactions {
|
|
12
|
+
private readonly channel;
|
|
13
|
+
private readonly getClientId;
|
|
14
|
+
private readonly listeners;
|
|
15
|
+
private channelUnsubscribe;
|
|
16
|
+
constructor(channel: Channel, getClientId: () => string | null);
|
|
17
|
+
/** Broadcast a room reaction. */
|
|
18
|
+
send(params: {
|
|
19
|
+
name: string;
|
|
20
|
+
metadata?: unknown;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
/** Subscribe to room reactions. Attaches the channel on first listener. */
|
|
23
|
+
subscribe(listener: ReactionListener): UnsubscribeFn;
|
|
24
|
+
private ensureChannelSubscription;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=reactions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactions.d.ts","sourceRoot":"","sources":["../src/reactions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;AAEhE,mDAAmD;AACnD,qBAAa,SAAS;IAKlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAL9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,OAAO,CAAC,kBAAkB,CAA8B;gBAGrC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,MAAM,MAAM,GAAG,IAAI;IAGnD,iCAAiC;IAC3B,IAAI,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAQvE,2EAA2E;IAC3E,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,aAAa;IAQpD,OAAO,CAAC,yBAAyB;CAsBlC"}
|
package/lib/reactions.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-room reactions — ephemeral, fire-and-forget broadcasts (e.g. a floating
|
|
3
|
+
* emoji). Not stored or aggregated; distinct from per-message reactions (which
|
|
4
|
+
* are out of scope for v1). Pure pub/sub over the channel.
|
|
5
|
+
*/
|
|
6
|
+
import { parseReactionPayload, REACTION_EVENT } from './protocol.js';
|
|
7
|
+
/** The room-reaction feature of a {@link Room}. */
|
|
8
|
+
export class Reactions {
|
|
9
|
+
channel;
|
|
10
|
+
getClientId;
|
|
11
|
+
listeners = new Set();
|
|
12
|
+
channelUnsubscribe = null;
|
|
13
|
+
constructor(channel, getClientId) {
|
|
14
|
+
this.channel = channel;
|
|
15
|
+
this.getClientId = getClientId;
|
|
16
|
+
}
|
|
17
|
+
/** Broadcast a room reaction. */
|
|
18
|
+
async send(params) {
|
|
19
|
+
const payload = {
|
|
20
|
+
name: params.name,
|
|
21
|
+
...(params.metadata === undefined ? {} : { metadata: params.metadata }),
|
|
22
|
+
};
|
|
23
|
+
await this.channel.publish(REACTION_EVENT, payload);
|
|
24
|
+
}
|
|
25
|
+
/** Subscribe to room reactions. Attaches the channel on first listener. */
|
|
26
|
+
subscribe(listener) {
|
|
27
|
+
this.listeners.add(listener);
|
|
28
|
+
this.ensureChannelSubscription();
|
|
29
|
+
return () => {
|
|
30
|
+
this.listeners.delete(listener);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
ensureChannelSubscription() {
|
|
34
|
+
if (this.channelUnsubscribe) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.channelUnsubscribe = this.channel.subscribe(REACTION_EVENT, (frame) => {
|
|
38
|
+
const payload = parseReactionPayload(frame.data);
|
|
39
|
+
if (payload === null) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const clientId = frame.clientId ?? '';
|
|
43
|
+
const reaction = {
|
|
44
|
+
name: payload.name,
|
|
45
|
+
clientId,
|
|
46
|
+
...(payload.metadata === undefined ? {} : { metadata: payload.metadata }),
|
|
47
|
+
createdAt: new Date(frame.timestamp),
|
|
48
|
+
isSelf: clientId !== '' && clientId === this.getClientId(),
|
|
49
|
+
};
|
|
50
|
+
for (const subscriber of [...this.listeners]) {
|
|
51
|
+
subscriber(reaction);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=reactions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactions.js","sourceRoot":"","sources":["../src/reactions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAwB,MAAM,eAAe,CAAC;AAM3F,mDAAmD;AACnD,MAAM,OAAO,SAAS;IAKD;IACA;IALF,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IACjD,kBAAkB,GAAyB,IAAI,CAAC;IAExD,YACmB,OAAgB,EAChB,WAAgC;QADhC,YAAO,GAAP,OAAO,CAAS;QAChB,gBAAW,GAAX,WAAW,CAAqB;IAChD,CAAC;IAEJ,iCAAiC;IACjC,KAAK,CAAC,IAAI,CAAC,MAA4C;QACrD,MAAM,OAAO,GAAoB;YAC/B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;SACxE,CAAC;QACF,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,2EAA2E;IAC3E,SAAS,CAAC,QAA0B;QAClC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,IAAI,CAAC,yBAAyB,EAAE,CAAC;QACjC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAEO,yBAAyB;QAC/B,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE;YACzE,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;YACtC,MAAM,QAAQ,GAAiB;gBAC7B,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,QAAQ;gBACR,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACzE,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;gBACpC,MAAM,EAAE,QAAQ,KAAK,EAAE,IAAI,QAAQ,KAAK,IAAI,CAAC,WAAW,EAAE;aAC3D,CAAC;YACF,KAAK,MAAM,UAAU,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7C,UAAU,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message reconciler: folds a stream of create/update/delete frames into
|
|
3
|
+
* materialized {@link Message} state, keyed by message id. The same reconciler
|
|
4
|
+
* runs over the live subscription and over replayed history, so both paths
|
|
5
|
+
* converge on identical state regardless of delivery order or duplicates.
|
|
6
|
+
*
|
|
7
|
+
* Version ordering uses the transport `(timestamp, messageId)` pair: a later
|
|
8
|
+
* mutation wins, and a re-delivered frame (same messageId) is ignored.
|
|
9
|
+
*/
|
|
10
|
+
import type { MessageFrame } from '@foony/realtime';
|
|
11
|
+
import type { ChatMessageEvent, Message } from './types.js';
|
|
12
|
+
/** Stateful reconciler for a single room's messages. */
|
|
13
|
+
export declare class MessageReconciler {
|
|
14
|
+
private readonly roomName;
|
|
15
|
+
private readonly byId;
|
|
16
|
+
constructor(roomName: string);
|
|
17
|
+
/**
|
|
18
|
+
* Fold one channel frame into state. Returns the event a subscriber should
|
|
19
|
+
* see, or null when the frame is malformed, a duplicate, or stale.
|
|
20
|
+
*/
|
|
21
|
+
apply(frame: MessageFrame): ChatMessageEvent | null;
|
|
22
|
+
/** The current materialized state of one message, if known. */
|
|
23
|
+
get(id: string): Message | undefined;
|
|
24
|
+
/** Current materialized messages, oldest-first by creation then id. */
|
|
25
|
+
snapshot(): Message[];
|
|
26
|
+
private applyCreate;
|
|
27
|
+
private applyMutation;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=reconciler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciler.d.ts","sourceRoot":"","sources":["../src/reconciler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAe5D,wDAAwD;AACxD,qBAAa,iBAAiB;IAGhB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;gBAEpB,QAAQ,EAAE,MAAM;IAE7C;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,gBAAgB,GAAG,IAAI;IAanD,+DAA+D;IAC/D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIpC,uEAAuE;IACvE,QAAQ,IAAI,OAAO,EAAE;IAMrB,OAAO,CAAC,WAAW;IAmDnB,OAAO,CAAC,aAAa;CA+CtB"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message reconciler: folds a stream of create/update/delete frames into
|
|
3
|
+
* materialized {@link Message} state, keyed by message id. The same reconciler
|
|
4
|
+
* runs over the live subscription and over replayed history, so both paths
|
|
5
|
+
* converge on identical state regardless of delivery order or duplicates.
|
|
6
|
+
*
|
|
7
|
+
* Version ordering uses the transport `(timestamp, messageId)` pair: a later
|
|
8
|
+
* mutation wins, and a re-delivered frame (same messageId) is ignored.
|
|
9
|
+
*/
|
|
10
|
+
import { parseMessagePayload } from './protocol.js';
|
|
11
|
+
/** Stateful reconciler for a single room's messages. */
|
|
12
|
+
export class MessageReconciler {
|
|
13
|
+
roomName;
|
|
14
|
+
byId = new Map();
|
|
15
|
+
constructor(roomName) {
|
|
16
|
+
this.roomName = roomName;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fold one channel frame into state. Returns the event a subscriber should
|
|
20
|
+
* see, or null when the frame is malformed, a duplicate, or stale.
|
|
21
|
+
*/
|
|
22
|
+
apply(frame) {
|
|
23
|
+
const payload = parseMessagePayload(frame.data);
|
|
24
|
+
if (payload === null) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const timestamp = frame.timestamp;
|
|
28
|
+
const versionId = frame.messageId;
|
|
29
|
+
if (payload.action === 'create') {
|
|
30
|
+
return this.applyCreate(payload, frame.clientId ?? '', timestamp, versionId);
|
|
31
|
+
}
|
|
32
|
+
return this.applyMutation(payload, frame.clientId ?? '', timestamp, versionId);
|
|
33
|
+
}
|
|
34
|
+
/** The current materialized state of one message, if known. */
|
|
35
|
+
get(id) {
|
|
36
|
+
return this.byId.get(id)?.message;
|
|
37
|
+
}
|
|
38
|
+
/** Current materialized messages, oldest-first by creation then id. */
|
|
39
|
+
snapshot() {
|
|
40
|
+
return [...this.byId.values()]
|
|
41
|
+
.map((entry) => entry.message)
|
|
42
|
+
.sort((left, right) => left.createdAt.getTime() - right.createdAt.getTime() || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0));
|
|
43
|
+
}
|
|
44
|
+
applyCreate(payload, clientId, timestamp, versionId) {
|
|
45
|
+
const existing = this.byId.get(payload.id);
|
|
46
|
+
if (existing) {
|
|
47
|
+
if (existing.applied.has(versionId)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
existing.applied.add(versionId);
|
|
51
|
+
// A mutation arrived before its create: backfill author/createdAt, and
|
|
52
|
+
// adopt the create's body only if it is newer than the winning mutation
|
|
53
|
+
// (i.e. no later edit/delete has superseded it).
|
|
54
|
+
const createdAt = new Date(timestamp);
|
|
55
|
+
const adoptBody = isNewer(existing.versionTs, existing.versionId, timestamp, versionId);
|
|
56
|
+
existing.createSeen = true;
|
|
57
|
+
existing.message = {
|
|
58
|
+
...existing.message,
|
|
59
|
+
clientId,
|
|
60
|
+
createdAt,
|
|
61
|
+
...(adoptBody
|
|
62
|
+
? {
|
|
63
|
+
text: payload.text,
|
|
64
|
+
metadata: payload.metadata ?? {},
|
|
65
|
+
headers: payload.headers ?? {},
|
|
66
|
+
updatedAt: createdAt,
|
|
67
|
+
action: 'create',
|
|
68
|
+
deleted: false,
|
|
69
|
+
}
|
|
70
|
+
: {}),
|
|
71
|
+
};
|
|
72
|
+
if (adoptBody) {
|
|
73
|
+
existing.versionTs = timestamp;
|
|
74
|
+
existing.versionId = versionId;
|
|
75
|
+
}
|
|
76
|
+
return { type: 'created', message: existing.message };
|
|
77
|
+
}
|
|
78
|
+
const createdAt = new Date(timestamp);
|
|
79
|
+
const message = {
|
|
80
|
+
id: payload.id,
|
|
81
|
+
clientId,
|
|
82
|
+
roomName: this.roomName,
|
|
83
|
+
text: payload.text,
|
|
84
|
+
metadata: payload.metadata ?? {},
|
|
85
|
+
headers: payload.headers ?? {},
|
|
86
|
+
createdAt,
|
|
87
|
+
updatedAt: createdAt,
|
|
88
|
+
action: 'create',
|
|
89
|
+
deleted: false,
|
|
90
|
+
};
|
|
91
|
+
this.byId.set(payload.id, { message, createSeen: true, versionTs: timestamp, versionId, applied: new Set([versionId]) });
|
|
92
|
+
return { type: 'created', message };
|
|
93
|
+
}
|
|
94
|
+
applyMutation(payload, clientId, timestamp, versionId) {
|
|
95
|
+
let entry = this.byId.get(payload.id);
|
|
96
|
+
if (!entry) {
|
|
97
|
+
// Mutation before create: stand up a placeholder the create can complete.
|
|
98
|
+
const createdAt = new Date(timestamp);
|
|
99
|
+
const placeholder = {
|
|
100
|
+
id: payload.id,
|
|
101
|
+
clientId,
|
|
102
|
+
roomName: this.roomName,
|
|
103
|
+
text: '',
|
|
104
|
+
metadata: {},
|
|
105
|
+
headers: {},
|
|
106
|
+
createdAt,
|
|
107
|
+
updatedAt: createdAt,
|
|
108
|
+
action: 'create',
|
|
109
|
+
deleted: false,
|
|
110
|
+
};
|
|
111
|
+
entry = { message: placeholder, createSeen: false, versionTs: -1, versionId: '', applied: new Set() };
|
|
112
|
+
this.byId.set(payload.id, entry);
|
|
113
|
+
}
|
|
114
|
+
if (entry.applied.has(versionId)) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
entry.applied.add(versionId);
|
|
118
|
+
if (!isNewer(entry.versionTs, entry.versionId, timestamp, versionId)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const updatedAt = new Date(timestamp);
|
|
122
|
+
if (payload.action === 'delete') {
|
|
123
|
+
entry.message = { ...entry.message, text: '', metadata: {}, headers: {}, updatedAt, action: 'delete', deleted: true };
|
|
124
|
+
entry.versionTs = timestamp;
|
|
125
|
+
entry.versionId = versionId;
|
|
126
|
+
return { type: 'deleted', message: entry.message };
|
|
127
|
+
}
|
|
128
|
+
entry.message = {
|
|
129
|
+
...entry.message,
|
|
130
|
+
text: payload.text,
|
|
131
|
+
metadata: payload.metadata ?? {},
|
|
132
|
+
headers: payload.headers ?? {},
|
|
133
|
+
updatedAt,
|
|
134
|
+
action: 'update',
|
|
135
|
+
deleted: false,
|
|
136
|
+
};
|
|
137
|
+
entry.versionTs = timestamp;
|
|
138
|
+
entry.versionId = versionId;
|
|
139
|
+
return { type: 'updated', message: entry.message };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** True when `(ts, id)` is strictly newer than `(priorTs, priorId)`. */
|
|
143
|
+
function isNewer(priorTs, priorId, ts, id) {
|
|
144
|
+
if (ts !== priorTs) {
|
|
145
|
+
return ts > priorTs;
|
|
146
|
+
}
|
|
147
|
+
return id > priorId;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=reconciler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciler.js","sourceRoot":"","sources":["../src/reconciler.ts"],"names":[],"mappings":"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"}
|
package/lib/room.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A chat room: one realtime channel (`chat:<name>`) carrying messages,
|
|
3
|
+
* presence, typing, reactions and occupancy. Lifecycle (`attach`/`detach`/
|
|
4
|
+
* status) delegates to the underlying channel rather than duplicating a state
|
|
5
|
+
* machine.
|
|
6
|
+
*/
|
|
7
|
+
import type { Channel, ChannelStateChange, UnsubscribeFn } from '@foony/realtime';
|
|
8
|
+
import { Messages } from './messages.js';
|
|
9
|
+
import { Occupancy } from './occupancy.js';
|
|
10
|
+
import { Presence } from './presence.js';
|
|
11
|
+
import { Reactions } from './reactions.js';
|
|
12
|
+
import { Typing } from './typing.js';
|
|
13
|
+
import type { RoomOptions } from './types.js';
|
|
14
|
+
/** Room lifecycle status — the underlying channel's state. */
|
|
15
|
+
export type RoomStatus = Channel['state'];
|
|
16
|
+
/** Listener invoked when a continuity gap is detected on (re)attach. */
|
|
17
|
+
export type DiscontinuityListener = (reason?: Error) => void;
|
|
18
|
+
/** A chat room and its features. Obtain via `chatClient.rooms.get(name)`. */
|
|
19
|
+
export declare class Room {
|
|
20
|
+
/** Room name (without the `chat:` channel prefix). */
|
|
21
|
+
readonly name: string;
|
|
22
|
+
private readonly channel;
|
|
23
|
+
/** Messages: send, edit, delete, subscribe, history. */
|
|
24
|
+
readonly messages: Messages;
|
|
25
|
+
/** Presence: enter/update/leave plus a local member snapshot. */
|
|
26
|
+
readonly presence: Presence;
|
|
27
|
+
/** Typing indicators. */
|
|
28
|
+
readonly typing: Typing;
|
|
29
|
+
/** Ephemeral room-level reactions. */
|
|
30
|
+
readonly reactions: Reactions;
|
|
31
|
+
/** Occupancy counts derived from presence. */
|
|
32
|
+
readonly occupancy: Occupancy;
|
|
33
|
+
private readonly discontinuityListeners;
|
|
34
|
+
private hadBeenAttached;
|
|
35
|
+
constructor(
|
|
36
|
+
/** Room name (without the `chat:` channel prefix). */
|
|
37
|
+
name: string, channel: Channel, getClientId: () => string | null, options?: RoomOptions);
|
|
38
|
+
/** Current room status. */
|
|
39
|
+
get status(): RoomStatus;
|
|
40
|
+
/** Ensure the room is attached so messages and presence flow. */
|
|
41
|
+
attach(): Promise<void>;
|
|
42
|
+
/** Detach from the room (stop receiving). Listeners are preserved. */
|
|
43
|
+
detach(): Promise<void>;
|
|
44
|
+
/** Subscribe to room status changes. Returns an unsubscribe function. */
|
|
45
|
+
onStatusChange(listener: (change: ChannelStateChange) => void): UnsubscribeFn;
|
|
46
|
+
/**
|
|
47
|
+
* Subscribe to continuity gaps — best-effort: fires when the room re-attaches
|
|
48
|
+
* without a resume, so the app can backfill via `messages.history()`.
|
|
49
|
+
*/
|
|
50
|
+
onDiscontinuity(listener: DiscontinuityListener): UnsubscribeFn;
|
|
51
|
+
private onChannelState;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=room.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"room.d.ts","sourceRoot":"","sources":["../src/room.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE1C,wEAAwE;AACxE,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;AAE7D,6EAA6E;AAC7E,qBAAa,IAAI;IAgBb,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjB1B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sCAAsC;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAoC;IAC3E,OAAO,CAAC,eAAe,CAAS;;IAG9B,sDAAsD;IAC7C,IAAI,EAAE,MAAM,EACJ,OAAO,EAAE,OAAO,EACjC,WAAW,EAAE,MAAM,MAAM,GAAG,IAAI,EAChC,OAAO,CAAC,EAAE,WAAW;IAUvB,2BAA2B;IAC3B,IAAI,MAAM,IAAI,UAAU,CAEvB;IAED,iEAAiE;IAC3D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,sEAAsE;IAChE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,yEAAyE;IACzE,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,GAAG,aAAa;IAI7E;;;OAGG;IACH,eAAe,CAAC,QAAQ,EAAE,qBAAqB,GAAG,aAAa;IAO/D,OAAO,CAAC,cAAc;CAavB"}
|