@snakechat/commskit-node 0.0.0-stage → 0.1.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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/adapters-8ztjEYtz.d.ts +83 -0
  4. package/dist/call.d.ts +34 -0
  5. package/dist/call.js +51 -0
  6. package/dist/call.js.map +1 -0
  7. package/dist/chat.d.ts +64 -0
  8. package/dist/chat.js +30 -0
  9. package/dist/chat.js.map +1 -0
  10. package/dist/chunk-5BJ2SNM5.js +26 -0
  11. package/dist/chunk-5BJ2SNM5.js.map +1 -0
  12. package/dist/chunk-6I27CI32.js +586 -0
  13. package/dist/chunk-6I27CI32.js.map +1 -0
  14. package/dist/chunk-BBFVG7QU.js +52 -0
  15. package/dist/chunk-BBFVG7QU.js.map +1 -0
  16. package/dist/chunk-GR4PMGCS.js +82 -0
  17. package/dist/chunk-GR4PMGCS.js.map +1 -0
  18. package/dist/chunk-KL4WRZKZ.js +799 -0
  19. package/dist/chunk-KL4WRZKZ.js.map +1 -0
  20. package/dist/chunk-KLKRK6OG.js +107 -0
  21. package/dist/chunk-KLKRK6OG.js.map +1 -0
  22. package/dist/chunk-LY2GJWN7.js +708 -0
  23. package/dist/chunk-LY2GJWN7.js.map +1 -0
  24. package/dist/chunk-SEDRF62Q.js +39 -0
  25. package/dist/chunk-SEDRF62Q.js.map +1 -0
  26. package/dist/chunk-U4Z5INIW.js +1192 -0
  27. package/dist/chunk-U4Z5INIW.js.map +1 -0
  28. package/dist/chunk-XOXZZYSW.js +247 -0
  29. package/dist/chunk-XOXZZYSW.js.map +1 -0
  30. package/dist/chunk-XQ6FLRX7.js +179 -0
  31. package/dist/chunk-XQ6FLRX7.js.map +1 -0
  32. package/dist/chunk-XQFQN73U.js +38 -0
  33. package/dist/chunk-XQFQN73U.js.map +1 -0
  34. package/dist/connections-CGEPiZPF.d.ts +128 -0
  35. package/dist/core.d.ts +26 -0
  36. package/dist/core.js +89 -0
  37. package/dist/core.js.map +1 -0
  38. package/dist/devices-oLIB_M19.d.ts +97 -0
  39. package/dist/express.d.ts +19 -0
  40. package/dist/express.js +34 -0
  41. package/dist/express.js.map +1 -0
  42. package/dist/fastify.d.ts +22 -0
  43. package/dist/fastify.js +42 -0
  44. package/dist/fastify.js.map +1 -0
  45. package/dist/feature-B2bEXLZG.d.ts +233 -0
  46. package/dist/feature-DQUbnYE-.d.ts +284 -0
  47. package/dist/feature-DwKkuOBh.d.ts +195 -0
  48. package/dist/hub-CPrWmIvB.d.ts +174 -0
  49. package/dist/index.d.ts +33 -0
  50. package/dist/index.js +138 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/integration.d.ts +97 -0
  53. package/dist/integration.js +151 -0
  54. package/dist/integration.js.map +1 -0
  55. package/dist/models-D1lehXIu.d.ts +31 -0
  56. package/dist/models-DTXEX1Q8.d.ts +34 -0
  57. package/dist/moderation.d.ts +9 -0
  58. package/dist/moderation.js +48 -0
  59. package/dist/moderation.js.map +1 -0
  60. package/dist/push.d.ts +198 -0
  61. package/dist/push.js +475 -0
  62. package/dist/push.js.map +1 -0
  63. package/dist/redis.d.ts +103 -0
  64. package/dist/redis.js +296 -0
  65. package/dist/redis.js.map +1 -0
  66. package/dist/s3.d.ts +75 -0
  67. package/dist/s3.js +315 -0
  68. package/dist/s3.js.map +1 -0
  69. package/dist/util-B32rIBVA.d.ts +22 -0
  70. package/package.json +146 -4
@@ -0,0 +1,22 @@
1
+ import { FastifyPluginAsync } from 'fastify';
2
+ import { C as CommsHub } from './hub-CPrWmIvB.js';
3
+ import 'node:http';
4
+ import 'node:stream';
5
+ import 'ws';
6
+ import './adapters-8ztjEYtz.js';
7
+ import './connections-CGEPiZPF.js';
8
+ import 'kysely';
9
+
10
+ /**
11
+ * Fastify adapter: `app.register(commskitPlugin, { hub })` registers the hub's REST routes and the
12
+ * `/ws/:token` upgrade on Fastify's http server. `fastify` is an optional peer dependency.
13
+ */
14
+
15
+ interface CommskitFastifyOptions {
16
+ hub: CommsHub;
17
+ /** Close sockets when Fastify closes (default true). */
18
+ shutdownOnClose?: boolean;
19
+ }
20
+ declare const commskitPlugin: FastifyPluginAsync<CommskitFastifyOptions>;
21
+
22
+ export { type CommskitFastifyOptions, commskitPlugin, commskitPlugin as default };
@@ -0,0 +1,42 @@
1
+ // src/fastify/index.ts
2
+ var commskitPlugin = async (app, opts) => {
3
+ const { hub } = opts;
4
+ if (app.hasContentTypeParser("application/json")) app.removeContentTypeParser("application/json");
5
+ app.addContentTypeParser("application/json", { parseAs: "string", bodyLimit: 20 * 1024 * 1024 }, (_req, body, done) => {
6
+ const text = typeof body === "string" ? body : body.toString("utf8");
7
+ if (!text) return done(null, void 0);
8
+ try {
9
+ done(null, JSON.parse(text));
10
+ } catch (err) {
11
+ err.statusCode = 400;
12
+ done(err, void 0);
13
+ }
14
+ });
15
+ for (const route of hub.restRoutes()) {
16
+ app.route({
17
+ method: route.method,
18
+ url: route.path,
19
+ async handler(request, reply) {
20
+ const result = await route.handler({
21
+ method: request.method,
22
+ path: route.path,
23
+ params: request.params ?? {},
24
+ query: request.query ?? {},
25
+ headers: request.headers,
26
+ body: request.body ?? void 0,
27
+ userId: null
28
+ });
29
+ for (const [k, v] of Object.entries(result.headers ?? {})) void reply.header(k, v);
30
+ return reply.status(result.status).send(result.body);
31
+ }
32
+ });
33
+ }
34
+ hub.attach(app.server);
35
+ if (opts.shutdownOnClose !== false) app.addHook("onClose", async () => hub.shutdown());
36
+ };
37
+ var fastify_default = commskitPlugin;
38
+ export {
39
+ commskitPlugin,
40
+ fastify_default as default
41
+ };
42
+ //# sourceMappingURL=fastify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fastify/index.ts"],"sourcesContent":["/**\r\n * Fastify adapter: `app.register(commskitPlugin, { hub })` registers the hub's REST routes and the\r\n * `/ws/:token` upgrade on Fastify's http server. `fastify` is an optional peer dependency.\r\n */\r\nimport type { FastifyInstance, FastifyPluginAsync } from \"fastify\";\r\nimport type { CommsHub } from \"../core/hub.js\";\r\n\r\nexport interface CommskitFastifyOptions {\r\n hub: CommsHub;\r\n /** Close sockets when Fastify closes (default true). */\r\n shutdownOnClose?: boolean;\r\n}\r\n\r\nexport const commskitPlugin: FastifyPluginAsync<CommskitFastifyOptions> = async (app: FastifyInstance, opts) => {\r\n const { hub } = opts;\r\n // Python/FastAPI accepts a JSON content-type with an empty body (e.g. POST /chats/start); so do we.\r\n if (app.hasContentTypeParser(\"application/json\")) app.removeContentTypeParser(\"application/json\");\r\n app.addContentTypeParser(\"application/json\", { parseAs: \"string\", bodyLimit: 20 * 1024 * 1024 }, (_req, body, done) => {\r\n const text = typeof body === \"string\" ? body : body.toString(\"utf8\");\r\n if (!text) return done(null, undefined);\r\n try {\r\n done(null, JSON.parse(text));\r\n } catch (err) {\r\n (err as { statusCode?: number }).statusCode = 400;\r\n done(err as Error, undefined);\r\n }\r\n });\r\n for (const route of hub.restRoutes()) {\r\n app.route({\r\n method: route.method,\r\n url: route.path,\r\n async handler(request, reply) {\r\n const result = await route.handler({\r\n method: request.method,\r\n path: route.path,\r\n params: (request.params ?? {}) as Record<string, string>,\r\n query: (request.query ?? {}) as Record<string, string | string[] | undefined>,\r\n headers: request.headers,\r\n body: request.body ?? undefined,\r\n userId: null,\r\n });\r\n for (const [k, v] of Object.entries(result.headers ?? {})) void reply.header(k, v);\r\n return reply.status(result.status).send(result.body);\r\n },\r\n });\r\n }\r\n hub.attach(app.server);\r\n if (opts.shutdownOnClose !== false) app.addHook(\"onClose\", async () => hub.shutdown());\r\n};\r\n\r\nexport default commskitPlugin;\r\n"],"mappings":";AAaO,IAAM,iBAA6D,OAAO,KAAsB,SAAS;AAC9G,QAAM,EAAE,IAAI,IAAI;AAEhB,MAAI,IAAI,qBAAqB,kBAAkB,EAAG,KAAI,wBAAwB,kBAAkB;AAChG,MAAI,qBAAqB,oBAAoB,EAAE,SAAS,UAAU,WAAW,KAAK,OAAO,KAAK,GAAG,CAAC,MAAM,MAAM,SAAS;AACrH,UAAM,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,SAAS,MAAM;AACnE,QAAI,CAAC,KAAM,QAAO,KAAK,MAAM,MAAS;AACtC,QAAI;AACF,WAAK,MAAM,KAAK,MAAM,IAAI,CAAC;AAAA,IAC7B,SAAS,KAAK;AACZ,MAAC,IAAgC,aAAa;AAC9C,WAAK,KAAc,MAAS;AAAA,IAC9B;AAAA,EACF,CAAC;AACD,aAAW,SAAS,IAAI,WAAW,GAAG;AACpC,QAAI,MAAM;AAAA,MACR,QAAQ,MAAM;AAAA,MACd,KAAK,MAAM;AAAA,MACX,MAAM,QAAQ,SAAS,OAAO;AAC5B,cAAM,SAAS,MAAM,MAAM,QAAQ;AAAA,UACjC,QAAQ,QAAQ;AAAA,UAChB,MAAM,MAAM;AAAA,UACZ,QAAS,QAAQ,UAAU,CAAC;AAAA,UAC5B,OAAQ,QAAQ,SAAS,CAAC;AAAA,UAC1B,SAAS,QAAQ;AAAA,UACjB,MAAM,QAAQ,QAAQ;AAAA,UACtB,QAAQ;AAAA,QACV,CAAC;AACD,mBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,EAAG,MAAK,MAAM,OAAO,GAAG,CAAC;AACjF,eAAO,MAAM,OAAO,OAAO,MAAM,EAAE,KAAK,OAAO,IAAI;AAAA,MACrD;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,OAAO,IAAI,MAAM;AACrB,MAAI,KAAK,oBAAoB,MAAO,KAAI,QAAQ,WAAW,YAAY,IAAI,SAAS,CAAC;AACvF;AAEA,IAAO,kBAAQ;","names":[]}
@@ -0,0 +1,233 @@
1
+ import { a as Dialect, D as Db, F as Feature, R as RestContext, C as CommsHub } from './hub-CPrWmIvB.js';
2
+
3
+ /** ModerationService: block list and reports (port of commskit.moderation.service). Never runs a query inside another's transaction. */
4
+
5
+ declare const REPORT_CATEGORIES: readonly ["spam", "harassment", "inappropriate", "fake_profile", "scam", "underage", "other"];
6
+ declare const REPORT_STATUSES: readonly ["open", "resolved", "dismissed"];
7
+ /** What `moderation.reported` listeners and `onReport` receive. */
8
+ interface ReportInfo {
9
+ id: number;
10
+ reporter_id: string;
11
+ reported_id: string;
12
+ reason: string | null;
13
+ category: string | null;
14
+ message_id: number | null;
15
+ status: string;
16
+ created_at: Date;
17
+ /** The reporter also blocked the user. */
18
+ blocked: boolean;
19
+ }
20
+ /** Admin wire shape (PROTOCOL.md section 9). */
21
+ interface ReportDict {
22
+ report_id: number;
23
+ reporter_id: string | null;
24
+ reported_id: string | null;
25
+ reason: string | null;
26
+ category: string | null;
27
+ message_id: number | null;
28
+ status: string;
29
+ created_at: string | null;
30
+ resolved_at: string | null;
31
+ resolved_by: string | null;
32
+ note: string | null;
33
+ }
34
+ declare function reportDict(r: any): ReportDict;
35
+ declare class ModerationService {
36
+ private db;
37
+ readonly dialect: Dialect;
38
+ constructor(db: Db, dialect?: Dialect);
39
+ /** True if a new block was stored (blocking twice is a no-op). */
40
+ block(blockerId: string, blockedId: string): Promise<boolean>;
41
+ unblock(blockerId: string, blockedId: string): Promise<boolean>;
42
+ hasBlocked(blockerId: string, blockedId: string): Promise<boolean>;
43
+ /** Either direction. */
44
+ isBlockedBetween(userA: string, userB: string): Promise<boolean>;
45
+ /** `{otherId: [isBlocked, blockedByOther]}` for ids with any block. */
46
+ blockStatus(viewerId: string, otherIds: readonly string[]): Promise<Record<string, [boolean, boolean]>>;
47
+ /** `[blockedId, blockedAt]`, newest first. */
48
+ listBlocked(blockerId: string): Promise<Array<[string, unknown]>>;
49
+ createReport(reporterId: string, reportedId: string, opts?: {
50
+ reason?: string | null;
51
+ category?: string | null;
52
+ messageId?: number | null;
53
+ }): Promise<ReportInfo>;
54
+ getReport(reportId: number): Promise<ReportDict | null>;
55
+ /** Newest first; `status` filters (open | resolved | dismissed). */
56
+ listReports(status?: string | null, limit?: number, offset?: number): Promise<ReportDict[]>;
57
+ /** Set a report to resolved / dismissed (or back to open). Null if it doesn't exist; throws Error on a bad status. */
58
+ resolveReport(reportId: number, status: string, resolvedBy?: string | null, note?: string | null): Promise<ReportDict | null>;
59
+ /**
60
+ * Account deletion: drop every block involving `userId`; keep reports (moderation history) but replace the user's id
61
+ * with NULL as reporter and as reported user. Use a transaction handle (`trx`) for `db` to make it atomic.
62
+ */
63
+ deleteUserData(userId: string): Promise<{
64
+ blocks: number;
65
+ reports_anonymized: number;
66
+ }>;
67
+ }
68
+
69
+ /** Moderation WebSocket handlers and the shared block/report actions (also used by REST). Port of commskit.moderation.handlers. */
70
+
71
+ declare const BLOCKED_TEXT = "User blocked successfully";
72
+ declare const BLOCKED_BY_TEXT = "You have been blocked by this user";
73
+ declare const UNBLOCKED_TEXT = "User unblocked successfully";
74
+ declare const UNBLOCKED_BY_TEXT = "You have been unblocked by this user";
75
+ declare const moderationError: (error: string, extra?: Record<string, unknown>) => Record<string, unknown>;
76
+ /** Bad request (missing / self target ...). */
77
+ declare class ModerationError extends Error {
78
+ }
79
+ declare function targetId(userId: string, value: unknown): string;
80
+ /** Block / unblock / report + their frames and events. */
81
+ declare class ModerationActions {
82
+ private feature;
83
+ constructor(feature: ModerationFeature);
84
+ private get hub();
85
+ block(db: Db, userId: string, target: string): Promise<boolean>;
86
+ unblock(db: Db, userId: string, target: string): Promise<boolean>;
87
+ report(db: Db, userId: string, target: string, opts?: {
88
+ reason?: unknown;
89
+ category?: unknown;
90
+ messageId?: unknown;
91
+ block?: boolean;
92
+ }): Promise<ReportInfo>;
93
+ }
94
+ declare class ModerationHandlers {
95
+ private feature;
96
+ constructor(feature: ModerationFeature);
97
+ private get hub();
98
+ register(): void;
99
+ private guard;
100
+ reportAndBlock(userId: string, message: Record<string, any>, db: Db): Promise<void>;
101
+ block(userId: string, message: Record<string, any>, db: Db): Promise<void>;
102
+ unblock(userId: string, message: Record<string, any>, db: Db): Promise<void>;
103
+ }
104
+
105
+ /**
106
+ * Chat / call policy wrappers that put the block list in front of the host's own policy (port of commskit.moderation.policy).
107
+ *
108
+ * They match the chat `ChatPolicy` and the call `CallPolicy` structurally, so this package never imports chat or call:
109
+ *
110
+ * new ChatFeature({ policy: moderation.chatPolicy(myChatPolicy) })
111
+ * new CallFeature({ policy: moderation.callPolicy(myCreditPolicy) })
112
+ *
113
+ * or let `linkModeration` (src/integration) wrap the installed features' policies. A blocked pair (either direction) is refused
114
+ * BEFORE the inner policy runs, so e.g. a credit-charging call policy never charges for a call that would be refused anyway.
115
+ */
116
+
117
+ declare const CHAT_REFUSAL = "Cannot send message. User is blocked.";
118
+ declare const CALL_REFUSAL = "Cannot call. User is blocked.";
119
+ /** Same shape as the call feature's decision (`allowed`, `error`). */
120
+ interface ModerationDecision {
121
+ allowed: boolean;
122
+ error?: Record<string, unknown> | null;
123
+ }
124
+ /** Structural view of a host chat policy. */
125
+ interface ChatPolicyLike {
126
+ refusalMessage?: string;
127
+ canMessage(db: Db, senderId: string, recipientId: string): Promise<boolean>;
128
+ blockStatus(db: Db, viewerId: string, otherIds: readonly string[]): Promise<Record<string, [boolean, boolean]>>;
129
+ extraConversationInfo(db: Db, viewerId: string, otherId: string): Promise<Record<string, unknown>>;
130
+ }
131
+ /** Structural view of a host call policy. */
132
+ interface CallPolicyLike {
133
+ authorize(db: Db, callerId: string, receiverId: string, message: Record<string, any>): Promise<any> | any;
134
+ }
135
+ declare class ModerationChatPolicy implements ChatPolicyLike {
136
+ inner: ChatPolicyLike | null;
137
+ blockedMessage: string;
138
+ /**
139
+ * The chat feature reads `refusalMessage` right after `canMessage` returned false; `lastRefusal` remembers whose refusal
140
+ * it was (the block list or the inner policy). Both messages are constant per policy, so concurrent frames cannot mix
141
+ * up different texts except when the host policy's message differs from the block text.
142
+ */
143
+ private lastRefusal;
144
+ constructor(inner?: ChatPolicyLike | null, blockedMessage?: string);
145
+ get refusalMessage(): string;
146
+ canMessage(db: Db, senderId: string, recipientId: string): Promise<boolean>;
147
+ /** Kit block list OR the inner policy's (e.g. a legacy host block table). */
148
+ blockStatus(db: Db, viewerId: string, otherIds: readonly string[]): Promise<Record<string, [boolean, boolean]>>;
149
+ extraConversationInfo(db: Db, viewerId: string, otherId: string): Promise<Record<string, unknown>>;
150
+ }
151
+ declare class ModerationCallPolicy implements CallPolicyLike {
152
+ inner: CallPolicyLike | null;
153
+ blockedMessage: string;
154
+ constructor(inner?: CallPolicyLike | null, blockedMessage?: string);
155
+ authorize(db: Db, callerId: string, receiverId: string, message: Record<string, any>): Promise<ModerationDecision>;
156
+ }
157
+
158
+ /** `hub.install(new ModerationFeature({...}))` (port of commskit.moderation.feature). Imports only core. */
159
+
160
+ type ReportHook = (report: ReportInfo) => Promise<void> | void;
161
+ /** `contextProvider(reporterId, reportedId, messageId, before, after)` -> MessageDto list (newest first). Set by linkModeration. */
162
+ type ContextProvider = (reporterId: string | null, reportedId: string | null, messageId: number | null, before: number, after: number) => Promise<Array<Record<string, any>>>;
163
+ /** Authenticates a moderator for the admin routes: return their id, or throw an `HttpError` (401 / 403). */
164
+ type AdminDependency = (ctx: RestContext) => Promise<string | number | null | undefined> | string | number | null | undefined;
165
+ interface ModerationFeatureOptions {
166
+ onReport?: ReportHook;
167
+ /** Send `blocked_by_user` / `unblocked_by_user` to the other user (default true, for compatibility). */
168
+ notifyBlockedUser?: boolean;
169
+ /** When set, mounts `/moderation/admin/reports...`. */
170
+ adminDependency?: AdminDependency;
171
+ contextProvider?: ContextProvider;
172
+ }
173
+ /**
174
+ * Block list + reports. Installs WS handlers `report_and_block` / `block` / `unblock` and `/moderation/...` REST routes.
175
+ * It does not touch chat or call by itself: wrap your policies (`chatPolicy(inner)` / `callPolicy(inner)`) or use
176
+ * `linkModeration(moderation, { chat, call })`, which also ends a live call between the pair on block.
177
+ *
178
+ * Events on `hub.events`: `moderation.blocked(blockerId, blockedId)`, `moderation.unblocked(blockerId, blockedId)`,
179
+ * `moderation.reported(ReportInfo)`, `moderation.report_resolved(report)`.
180
+ */
181
+ declare class ModerationFeature implements Feature {
182
+ readonly name = "moderation";
183
+ onReport?: ReportHook;
184
+ notifyBlockedUser: boolean;
185
+ adminDependency?: AdminDependency;
186
+ contextProvider?: ContextProvider;
187
+ actions: ModerationActions;
188
+ handlers: ModerationHandlers;
189
+ /** Set by linkModeration. */
190
+ link: unknown;
191
+ private _hub;
192
+ constructor(opts?: ModerationFeatureOptions);
193
+ get hub(): CommsHub;
194
+ install(hub: CommsHub): this;
195
+ private service;
196
+ /** ChatPolicy: refuses a blocked pair, otherwise asks `inner` (allow-all if null). */
197
+ chatPolicy(inner?: ChatPolicyLike | null): ModerationChatPolicy;
198
+ /** CallPolicy: denies a blocked pair (`reason: "blocked"`), otherwise `inner`. */
199
+ callPolicy(inner?: CallPolicyLike | null): ModerationCallPolicy;
200
+ /** `{otherId: [isBlocked, blockedByOther]}` for `res_users` / `res_messages.info`. */
201
+ blockStatus(viewerId: string, otherIds: readonly string[], db?: Db): Promise<Record<string, [boolean, boolean]>>;
202
+ isBlockedBetween(userA: string, userB: string, db?: Db): Promise<boolean>;
203
+ listReports(status?: string | null, opts?: {
204
+ limit?: number;
205
+ offset?: number;
206
+ }): Promise<ReportDict[]>;
207
+ /** `status`: resolved | dismissed (| open). Null if the report doesn't exist. */
208
+ resolveReport(reportId: number, status: string, opts?: {
209
+ resolvedBy?: string | null;
210
+ note?: string | null;
211
+ }): Promise<ReportDict | null>;
212
+ /**
213
+ * `{report, messages}`: messages around the reported message (or the latest between the pair), newest first, from
214
+ * `contextProvider` (empty without one, e.g. call-only apps).
215
+ */
216
+ reportContext(reportId: number, opts?: {
217
+ before?: number;
218
+ after?: number;
219
+ }): Promise<{
220
+ report: ReportDict;
221
+ messages: Array<Record<string, any>>;
222
+ } | null>;
223
+ /**
224
+ * Account deletion: blocks involving the user are deleted; reports are kept for the moderation history with the user's
225
+ * id set to NULL (as reporter and as reported user).
226
+ */
227
+ deleteUserData(userId: string): Promise<{
228
+ blocks: number;
229
+ reports_anonymized: number;
230
+ }>;
231
+ }
232
+
233
+ export { type AdminDependency as A, BLOCKED_BY_TEXT as B, CALL_REFUSAL as C, ModerationActions as M, REPORT_CATEGORIES as R, UNBLOCKED_BY_TEXT as U, BLOCKED_TEXT as a, CHAT_REFUSAL as b, type CallPolicyLike as c, type ChatPolicyLike as d, type ContextProvider as e, ModerationCallPolicy as f, ModerationChatPolicy as g, type ModerationDecision as h, ModerationError as i, ModerationFeature as j, type ModerationFeatureOptions as k, ModerationHandlers as l, ModerationService as m, REPORT_STATUSES as n, type ReportDict as o, type ReportHook as p, type ReportInfo as q, UNBLOCKED_TEXT as r, moderationError as s, reportDict as t, targetId as u };
@@ -0,0 +1,284 @@
1
+ import { M as MediaInfo, b as MediaStorage, D as DeviceRegistry } from './adapters-8ztjEYtz.js';
2
+ import { D as Db, a as Dialect, F as Feature, C as CommsHub } from './hub-CPrWmIvB.js';
3
+ import { S as SocketLike, R as RateLimit } from './connections-CGEPiZPF.js';
4
+
5
+ /** Plain copy of a `messages` row with normalised types (safe to use anywhere). */
6
+ interface MessageRow {
7
+ id: number;
8
+ chat_id: number;
9
+ sender_id: string;
10
+ content: string | null;
11
+ message_type: string | null;
12
+ file_url: string | null;
13
+ file_name: string | null;
14
+ file_size: number | null;
15
+ file_type: string | null;
16
+ thumbnail_url: string | null;
17
+ duration: number | null;
18
+ is_read: boolean;
19
+ is_delivered: boolean;
20
+ call_id: string | null;
21
+ reply_to_id: number | null;
22
+ created_at: unknown;
23
+ }
24
+ interface ReplyPreview {
25
+ id: number;
26
+ sender_id: string;
27
+ content: string | null;
28
+ message_type: string | null;
29
+ file_name: string | null;
30
+ }
31
+ type MessageDto = Record<string, any>;
32
+ /**
33
+ * `sender_type` is relative to `viewerId`: "user" = me, "other" = them; "system" for
34
+ * `message_type === "system"` (PROTOCOL.md section 9).
35
+ */
36
+ declare function messageDto(m: MessageRow, viewerId: string, replyTo?: ReplyPreview | null | undefined, overrides?: Record<string, unknown>): MessageDto;
37
+
38
+ /** ChatPolicy: host rules for who may message whom (blocking, consent...). */
39
+
40
+ interface ChatPolicy {
41
+ /** Sent as `{"type":"error","error": refusalMessage}` when `canMessage` is false. */
42
+ refusalMessage: string;
43
+ /** False refuses send_message / media_upload and silently drops typing. */
44
+ canMessage(db: Db, senderId: string, recipientId: string): Promise<boolean>;
45
+ /** `{otherId: [isBlocked, blockedByOther]}`; missing ids = [false, false]. */
46
+ blockStatus(db: Db, viewerId: string, otherIds: readonly string[]): Promise<Record<string, [boolean, boolean]>>;
47
+ /** Merged into `res_messages.info` (e.g. `chat_consent_given`). */
48
+ extraConversationInfo(db: Db, viewerId: string, otherId: string): Promise<Record<string, unknown>>;
49
+ }
50
+ /** Default: everyone may message everyone, nobody is blocked. */
51
+ declare class AllowAllChatPolicy implements ChatPolicy {
52
+ refusalMessage: string;
53
+ canMessage(): Promise<boolean>;
54
+ blockStatus(): Promise<Record<string, [boolean, boolean]>>;
55
+ extraConversationInfo(): Promise<Record<string, unknown>>;
56
+ }
57
+
58
+ /**
59
+ * ChatService: persistence for 1:1 chats (port of commskit.chat.service).
60
+ * Portable queries (PostgreSQL + SQLite); methods return plain rows (`ChatRow` / `MessageRow`).
61
+ */
62
+
63
+ declare const DELETED_TYPE = "deleted";
64
+ declare const SYSTEM_TYPE = "system";
65
+ /** Message types only the server may create (a client sending one gets "text"). */
66
+ declare const RESERVED_TYPES: ReadonlySet<string>;
67
+ interface ChatRow {
68
+ id: number;
69
+ user1_id: string;
70
+ user2_id: string;
71
+ created_at: unknown;
72
+ updated_at: unknown;
73
+ }
74
+ interface LastMessageSummary {
75
+ content: string | null;
76
+ sender_id: string;
77
+ timestamp: string | null;
78
+ message_type: string | null;
79
+ file_url: string | null;
80
+ file_size: number | null;
81
+ file_type: string | null;
82
+ file_name: string | null;
83
+ is_read: boolean;
84
+ is_delivered: boolean;
85
+ }
86
+ interface Conversation {
87
+ chat_id: number;
88
+ other_user_id: string;
89
+ updated_at: string | null;
90
+ last_message: LastMessageSummary | null;
91
+ unread_count: number;
92
+ }
93
+ /**
94
+ * Refused chat action (PROTOCOL.md section 9). `code`: not_found | not_allowed | window_expired |
95
+ * rate_limited | too_long (`extra` carries `retry_after` / `max`), or undefined for policy refusals.
96
+ */
97
+ declare class ChatActionError extends Error {
98
+ error: string;
99
+ code?: string | undefined;
100
+ status: number;
101
+ extra: Record<string, unknown>;
102
+ constructor(error: string, code?: string | undefined, status?: number, extra?: Record<string, unknown>);
103
+ frame(): Record<string, unknown>;
104
+ }
105
+ declare class ChatService {
106
+ private db;
107
+ readonly dialect: Dialect;
108
+ constructor(db: Db, dialect?: Dialect);
109
+ private ts;
110
+ getChatBetween(userA: string, userB: string): Promise<ChatRow | null>;
111
+ /** Idempotent per pair; survives a concurrent create (unique constraint). */
112
+ getOrCreateChat(userA: string, userB: string): Promise<ChatRow>;
113
+ /** Chat only if `userId` is a participant. */
114
+ getChatForUser(chatId: number, userId: string): Promise<ChatRow | null>;
115
+ partnerIds(userId: string): Promise<string[]>;
116
+ /** Chat summaries, newest first. "Last message per chat" via MAX(id) (ids grow with time). */
117
+ getConversations(userId: string): Promise<Conversation[]>;
118
+ /** Stores a message and bumps the chat's `updated_at` (one transaction). */
119
+ sendMessage(chatId: number, senderId: string, content: string | null, messageType?: string, opts?: {
120
+ media?: MediaInfo | null;
121
+ callId?: string | null;
122
+ replyToId?: unknown;
123
+ }): Promise<MessageRow>;
124
+ /** Only messages from the same chat can be quoted. */
125
+ private validReplyTarget;
126
+ getReplyPreviews(messageIds: Array<number | null | undefined>): Promise<Map<number, ReplyPreview>>;
127
+ /** Newest first. With `viewerId`, that user's hidden / cleared messages are left out. */
128
+ getMessagesPage(chatId: number, limit?: number, offset?: number, viewerId?: string | null): Promise<MessageRow[]>;
129
+ getLastMessage(chatId: number, viewerId?: string | null): Promise<MessageRow | null>;
130
+ getMessage(messageId: number): Promise<MessageRow | null>;
131
+ /** Read receipts are per chat: everything the other person sent becomes read + delivered. */
132
+ markMessagesAsRead(chatId: number, userId: string): Promise<void>;
133
+ markDelivered(messageIds: number[]): Promise<void>;
134
+ /** Mark everything sent to `userId` delivered; returns [chat_id, sender_id, message_id] (system messages excluded). */
135
+ markDeliveredForRecipient(userId: string): Promise<Array<[number, string, number]>>;
136
+ getUnreadCount(userId: string): Promise<number>;
137
+ /** Delete for me (idempotent). Does not check access; callers do. */
138
+ hideMessage(userId: string, messageId: number): Promise<void>;
139
+ /**
140
+ * Rewrite the row as `deleted` for everyone; returns the new row and the media URLs to delete.
141
+ * `windowSeconds` > 0: throws `window_expired` for older messages.
142
+ */
143
+ unsendMessage(messageId: number, windowSeconds: number): Promise<[MessageRow, string[]]>;
144
+ /** Hide every message of the chat, up to now, for `userId` only. Returns `cleared_at`. */
145
+ clearChat(userId: string, chatId: number): Promise<Date>;
146
+ /**
147
+ * Newest first: up to `before` older messages, the message itself, up to `after` newer ones.
148
+ * Without a `messageId` from this chat: the latest `before + after + 1` messages. Ignores hides / clears.
149
+ */
150
+ messagesAround(chatId: number, messageId: number | null, before?: number, after?: number): Promise<MessageRow[]>;
151
+ /**
152
+ * Delete every chat `userId` is in, with all its messages (a 1:1 chat has one shared copy, so the
153
+ * partner loses that conversation too), hides and clears. Returns counts and the media URLs to delete.
154
+ */
155
+ deleteUserData(userId: string): Promise<[{
156
+ chats: number;
157
+ messages: number;
158
+ }, string[]]>;
159
+ /** Set a call entry's text; returns its message id (null when none). Used by the call/chat link. */
160
+ rewriteCallMessage(callId: string, content: string): Promise<number | null>;
161
+ }
162
+
163
+ declare class ChatHandlers {
164
+ private feature;
165
+ private hub;
166
+ private pairChatIds;
167
+ constructor(feature: ChatFeature);
168
+ get config(): ChatConfig;
169
+ get policy(): ChatPolicy;
170
+ service(db: Db): ChatService;
171
+ register(): void;
172
+ forgetUser(userId: string): void;
173
+ private reply;
174
+ private failed;
175
+ /** Seconds to wait (0 = allowed). Takes a token when allowed. */
176
+ private rateLimited;
177
+ reqUsers(userId: string, message: Record<string, any>, db: Db): Promise<void>;
178
+ getMessages(userId: string, message: Record<string, any>, db: Db): Promise<void>;
179
+ /**
180
+ * Store + deliver a message (WS `send_message`, `POST /chats/send`, `sendMessageAs`).
181
+ * Returns the MessageDto as the sender sees it. Throws `ChatActionError` when refused.
182
+ */
183
+ sendText(db: Db, sender: string, recipient: string, content: unknown, opts?: {
184
+ messageType?: string;
185
+ replyToId?: unknown;
186
+ checkPolicy?: boolean;
187
+ rateLimit?: boolean;
188
+ }): Promise<MessageDto>;
189
+ sendMessage(userId: string, message: Record<string, any>, db: Db): Promise<void>;
190
+ /** new_message -> recipient, mark delivered, offline push, message_sent -> sender. */
191
+ private deliver;
192
+ /** Push when the recipient has no foreground socket anywhere. */
193
+ notifyOffline(recipient: string, sender: string, chatId: number, preview: string, title?: string | null, extra?: Record<string, unknown>): Promise<void>;
194
+ mediaUpload(userId: string, message: Record<string, any>, db: Db): Promise<void>;
195
+ /** `chat_id` wins; otherwise the chat with `candidate_user_id`. Null if not a participant. */
196
+ resolveChat(service: ChatService, userId: string, chatId: unknown, candidate: unknown): Promise<ChatRow | null>;
197
+ markRead(userId: string, message: Record<string, any>, db: Db): Promise<void>;
198
+ notifyRead(chatId: number, reader: string, other: string): Promise<void>;
199
+ typing(userId: string, message: Record<string, any>, db: Db): Promise<void>;
200
+ deleteMessage(userId: string, message: Record<string, any>, db: Db): Promise<void>;
201
+ /** Delete for me / unsend for everyone. Sends `message_deleted`; throws `ChatActionError`. */
202
+ deleteFor(db: Db, userId: string, messageId: number, forEveryone: boolean): Promise<Record<string, unknown>>;
203
+ clearChat(userId: string, message: Record<string, any>, db: Db): Promise<void>;
204
+ clearFor(db: Db, userId: string, chatId: unknown, candidate: unknown): Promise<Record<string, unknown>>;
205
+ /** Flush delivery receipts for messages that arrived while the user was offline. */
206
+ onConnect(userId: string, _ws: SocketLike): Promise<void>;
207
+ }
208
+
209
+ /** `hub.install(new ChatFeature({...}))` (port of commskit.chat.feature). */
210
+
211
+ interface ChatConfig {
212
+ maxUploadBytes: number;
213
+ defaultPageSize: number;
214
+ maxPageSize: number;
215
+ pushPreviewChars: number;
216
+ /** Android channel created natively by the app. */
217
+ pushChannel: string;
218
+ /** PROTOCOL.md section 9 "Limits". null disables a limit; maxTextLength 0 = unlimited. */
219
+ maxTextLength: number;
220
+ messageRate: RateLimit | null;
221
+ uploadRate: RateLimit | null;
222
+ typingRate: RateLimit | null;
223
+ /** Unsend ("delete for everyone"): seconds after sending; 0 = no limit, -1 = disabled. */
224
+ unsendWindowSeconds: number;
225
+ /** System messages without aboutUserId go into a chat with this id (null: aboutUserId required). */
226
+ systemSenderId: string | null;
227
+ /** Push title for system messages (null: the about-user's name, else "Notification"). */
228
+ systemPushTitle: string | null;
229
+ }
230
+ declare const defaultChatConfig: () => ChatConfig;
231
+ interface ChatFeatureOptions {
232
+ policy?: ChatPolicy;
233
+ storage?: MediaStorage;
234
+ /** Optional: enables the legacy push-token REST routes. */
235
+ devices?: DeviceRegistry;
236
+ config?: Partial<ChatConfig>;
237
+ }
238
+ /** 1:1 chat. */
239
+ declare class ChatFeature implements Feature {
240
+ readonly name = "chat";
241
+ policy: ChatPolicy;
242
+ storage: MediaStorage;
243
+ devices?: DeviceRegistry;
244
+ config: ChatConfig;
245
+ handlers: ChatHandlers;
246
+ private _hub;
247
+ constructor(opts?: ChatFeatureOptions);
248
+ get hub(): CommsHub;
249
+ install(hub: CommsHub): this;
250
+ /** Conversation partners (receivers of this user's `user_status`). */
251
+ partnerIds(userId: string): Promise<string[]>;
252
+ /**
253
+ * Send a text message as `senderId` from trusted server code (bots, admin tools, tests). Same storage,
254
+ * events and push as WS `send_message`; no rate limit. The policy is only checked with `enforcePolicy`.
255
+ * Returns the MessageDto as the sender sees it; throws `ChatActionError` (self-message, too long, policy).
256
+ */
257
+ sendMessageAs(senderId: string, toUserId: string, content: string, opts?: {
258
+ replyToId?: number | null;
259
+ enforcePolicy?: boolean;
260
+ }): Promise<MessageDto>;
261
+ /**
262
+ * A `message_type: "system"` message shown to `toUserId` only (e.g. "You matched with Asha"). It goes into
263
+ * the chat between `toUserId` and `aboutUserId` (created if needed), stored with `sender_id = aboutUserId`
264
+ * and hidden for `aboutUserId`. Without `aboutUserId`, `config.systemSenderId` is the chat partner
265
+ * (throws if neither is set). `data` is added to the live frame (`message.data`) and to the push data; it is
266
+ * not stored. Returns the MessageDto as `toUserId` sees it.
267
+ */
268
+ sendSystemMessage(toUserId: string, content: string, opts?: {
269
+ aboutUserId?: string | null;
270
+ data?: Record<string, unknown> | null;
271
+ push?: boolean;
272
+ }): Promise<MessageDto>;
273
+ /**
274
+ * Account deletion: every chat `userId` is in, all its messages (the partner's view goes too: a 1:1 chat has
275
+ * one copy), hides / clears, and the stored media (through `MediaStorage.delete`). Returns counts.
276
+ */
277
+ deleteUserData(userId: string): Promise<{
278
+ chats: number;
279
+ messages: number;
280
+ media: number;
281
+ }>;
282
+ }
283
+
284
+ export { AllowAllChatPolicy as A, ChatActionError as C, DELETED_TYPE as D, type MessageDto as M, RESERVED_TYPES as R, SYSTEM_TYPE as S, type ChatConfig as a, ChatFeature as b, type ChatFeatureOptions as c, type ChatPolicy as d, type ChatRow as e, ChatService as f, type MessageRow as g, type ReplyPreview as h, defaultChatConfig as i, messageDto as m };