@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,151 @@
1
+ import {
2
+ ChatService
3
+ } from "./chunk-U4Z5INIW.js";
4
+ import "./chunk-XQ6FLRX7.js";
5
+ import {
6
+ callLogText
7
+ } from "./chunk-KL4WRZKZ.js";
8
+ import "./chunk-XQFQN73U.js";
9
+ import {
10
+ ModerationCallPolicy,
11
+ ModerationChatPolicy
12
+ } from "./chunk-6I27CI32.js";
13
+ import "./chunk-SEDRF62Q.js";
14
+ import "./chunk-GR4PMGCS.js";
15
+ import "./chunk-BBFVG7QU.js";
16
+ import "./chunk-KLKRK6OG.js";
17
+ import {
18
+ logger
19
+ } from "./chunk-5BJ2SNM5.js";
20
+
21
+ // src/integration/chatCall.ts
22
+ var CALL_PLACEHOLDER = "[Call Initiated]";
23
+ var ChatCallBridge = class {
24
+ constructor(chat, call) {
25
+ this.chat = chat;
26
+ this.call = call;
27
+ }
28
+ chat;
29
+ call;
30
+ async resolveChatId(db, callerId, receiverId, _requestedChatId) {
31
+ return (await new ChatService(db, this.call.hub.dialect).getOrCreateChat(callerId, receiverId)).id;
32
+ }
33
+ async onCallCreated(db, info) {
34
+ if (info.chatId === null) return;
35
+ await new ChatService(db, this.call.hub.dialect).sendMessage(
36
+ info.chatId,
37
+ info.callerId,
38
+ CALL_PLACEHOLDER,
39
+ `${info.callType}_call`,
40
+ { callId: info.callId }
41
+ );
42
+ }
43
+ async onCallFinalizing(trx, info) {
44
+ const text = callLogText(info.status, info.isVideoCall, info.duration, info.endReason);
45
+ info.messageId = await new ChatService(trx, this.call.hub.dialect).rewriteCallMessage(info.callId, text);
46
+ info.logText = text;
47
+ }
48
+ /** `call.finalized` listener: both parties' chat entry now reads e.g. "Voice call · 05:42". */
49
+ async sendCallLog(info) {
50
+ if (!info.messageId) return;
51
+ const payload = {
52
+ type: "call_log",
53
+ call_id: info.callId,
54
+ chat_id: info.chatId,
55
+ message_id: info.messageId,
56
+ content: info.logText,
57
+ status: info.status,
58
+ duration_seconds: info.duration
59
+ };
60
+ const hub = this.call.hub;
61
+ await hub.send(info.callerId, payload);
62
+ await hub.send(info.receiverId, payload);
63
+ }
64
+ };
65
+ function linkChatAndCall(chat, call) {
66
+ if (chat.hub !== call.hub) throw new Error("chat and call must be installed on the same CommsHub");
67
+ if (call.chatBridge) throw new Error("call feature is already linked to a chat bridge");
68
+ const bridge = new ChatCallBridge(chat, call);
69
+ call.chatBridge = bridge;
70
+ call.hub.events.on("call.finalized", (info) => bridge.sendCallLog(info));
71
+ return bridge;
72
+ }
73
+
74
+ // src/integration/moderation.ts
75
+ var BLOCKED_END_REASON = "blocked";
76
+ var ModerationLink = class {
77
+ constructor(moderation, chat = null, call = null) {
78
+ this.moderation = moderation;
79
+ this.chat = chat;
80
+ this.call = call;
81
+ }
82
+ moderation;
83
+ chat;
84
+ call;
85
+ /** `moderation.blocked` listener. */
86
+ async endCallsOnBlock(blockerId, blockedId) {
87
+ if (this.call) await this.call.lifecycle.endCallsBetween(blockerId, blockedId, BLOCKED_END_REASON, blockerId);
88
+ }
89
+ /**
90
+ * `ModerationFeature.contextProvider`: MessageDtos (as the reporter sees them, newest first) around `messageId` in the
91
+ * pair's chat, or the latest ones. Hides / clears are ignored.
92
+ */
93
+ async reportMessages(reporterId, reportedId, messageId, before, after) {
94
+ if (!this.chat || !reporterId || !reportedId) return [];
95
+ const { ChatService: ChatService2, messageDto } = await import("./chat.js");
96
+ const hub = this.chat.hub;
97
+ const service = new ChatService2(hub.db, hub.dialect);
98
+ const chat = await service.getChatBetween(reporterId, reportedId);
99
+ if (!chat) return [];
100
+ const rows = await service.messagesAround(chat.id, messageId, before, after);
101
+ const previews = await service.getReplyPreviews(rows.map((m) => m.reply_to_id));
102
+ return rows.map((m) => messageDto(m, reporterId, m.reply_to_id ? previews.get(m.reply_to_id) ?? null : null));
103
+ }
104
+ };
105
+ function linkModeration(moderation, features = {}) {
106
+ const hub = moderation.hub;
107
+ const { chat = null, call = null } = features;
108
+ for (const f of [chat, call]) {
109
+ if (f && f.hub !== hub) throw new Error("moderation, chat and call must be installed on the same CommsHub");
110
+ }
111
+ if (moderation.link) throw new Error("moderation is already linked");
112
+ if (chat && !(chat.policy instanceof ModerationChatPolicy)) chat.policy = moderation.chatPolicy(chat.policy);
113
+ if (call && !(call.policy instanceof ModerationCallPolicy)) call.policy = moderation.callPolicy(call.policy);
114
+ const link = new ModerationLink(moderation, chat, call);
115
+ if (chat && !moderation.contextProvider) moderation.contextProvider = (a, b, m, bf, af) => link.reportMessages(a, b, m, bf, af);
116
+ hub.events.on("moderation.blocked", (blocker, blocked) => link.endCallsOnBlock(blocker, blocked));
117
+ moderation.link = link;
118
+ return link;
119
+ }
120
+
121
+ // src/integration/privacy.ts
122
+ var ORDER = ["chat", "call", "moderation", "devices"];
123
+ async function deleteUserEverywhere(hub, userId, opts = {}) {
124
+ const uid = String(userId);
125
+ const rank = (n) => ORDER.includes(n) ? ORDER.indexOf(n) : ORDER.length;
126
+ const names = Object.keys(hub.features).sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : a > b ? 1 : 0));
127
+ const results = {};
128
+ for (const name of names) {
129
+ const feature = hub.features[name];
130
+ if (typeof feature?.deleteUserData !== "function") continue;
131
+ try {
132
+ results[name] = await feature.deleteUserData(uid);
133
+ } catch (err) {
134
+ if (opts.raiseErrors) throw err;
135
+ logger.error(`deleteUserData failed in feature ${name}`, err);
136
+ results[name] = { error: String(err?.message ?? err) };
137
+ }
138
+ }
139
+ await hub.events.emit("user.deleted", uid);
140
+ return results;
141
+ }
142
+ export {
143
+ BLOCKED_END_REASON,
144
+ CALL_PLACEHOLDER,
145
+ ChatCallBridge,
146
+ ModerationLink,
147
+ deleteUserEverywhere,
148
+ linkChatAndCall,
149
+ linkModeration
150
+ };
151
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/integration/chatCall.ts","../src/integration/moderation.ts","../src/integration/privacy.ts"],"sourcesContent":["/** Chat + call wiring: calls live in the chat as call-log entries. The only file importing both features. */\nimport { type CallChatBridge, type CallFeature, type CallInfo, callLogText } from \"../call/index.js\";\nimport { type ChatFeature, ChatService } from \"../chat/index.js\";\nimport type { Db } from \"../core/db.js\";\n\nexport const CALL_PLACEHOLDER = \"[Call Initiated]\";\n\n/** Implements `CallChatBridge` on top of the chat tables. */\nexport class ChatCallBridge implements CallChatBridge {\n constructor(\n readonly chat: ChatFeature,\n readonly call: CallFeature,\n ) {}\n\n async resolveChatId(db: Db, callerId: string, receiverId: string, _requestedChatId: unknown): Promise<number | null> {\n // Always the pair's chat; a client-sent chat_id is never trusted.\n return (await new ChatService(db, this.call.hub.dialect).getOrCreateChat(callerId, receiverId)).id;\n }\n\n async onCallCreated(db: Db, info: CallInfo): Promise<void> {\n if (info.chatId === null) return;\n await new ChatService(db, this.call.hub.dialect).sendMessage(\n info.chatId,\n info.callerId,\n CALL_PLACEHOLDER,\n `${info.callType}_call`,\n { callId: info.callId },\n );\n }\n\n async onCallFinalizing(trx: Db, info: CallInfo): Promise<void> {\n const text = callLogText(info.status, info.isVideoCall, info.duration, info.endReason);\n info.messageId = await new ChatService(trx, this.call.hub.dialect).rewriteCallMessage(info.callId, text);\n info.logText = text;\n }\n\n /** `call.finalized` listener: both parties' chat entry now reads e.g. \"Voice call · 05:42\". */\n async sendCallLog(info: CallInfo): Promise<void> {\n if (!info.messageId) return;\n const payload = {\n type: \"call_log\",\n call_id: info.callId,\n chat_id: info.chatId,\n message_id: info.messageId,\n content: info.logText,\n status: info.status,\n duration_seconds: info.duration,\n };\n const hub = this.call.hub;\n await hub.send(info.callerId, payload);\n await hub.send(info.receiverId, payload);\n }\n}\n\n/** Both features must already be installed on the same hub. */\nexport function linkChatAndCall(chat: ChatFeature, call: CallFeature): ChatCallBridge {\n if (chat.hub !== call.hub) throw new Error(\"chat and call must be installed on the same CommsHub\");\n if (call.chatBridge) throw new Error(\"call feature is already linked to a chat bridge\");\n const bridge = new ChatCallBridge(chat, call);\n call.chatBridge = bridge;\n call.hub.events.on(\"call.finalized\", (info: CallInfo) => bridge.sendCallLog(info));\n return bridge;\n}\n","/**\n * Moderation wiring (port of commskit.integration.moderation): the block list in front of the chat / call policies, and\n * live calls between a pair end when one blocks the other.\n *\n * `src/moderation` never imports chat or call; it emits `moderation.blocked` on the hub's event bus and exposes policy\n * wrappers. This module connects the two sides. It only uses structural types for chat and call (so it compiles without the\n * call module), and loads the chat module lazily, only when a chat feature is linked.\n */\nimport type { CommsHub } from \"../core/hub.js\";\nimport {\n type CallPolicyLike, type ChatPolicyLike, type ModerationFeature, ModerationCallPolicy, ModerationChatPolicy,\n} from \"../moderation/index.js\";\n\nexport const BLOCKED_END_REASON = \"blocked\";\n\n/** The slice of `ChatFeature` the link needs. */\nexport interface LinkableChat {\n readonly hub: CommsHub;\n policy: ChatPolicyLike;\n}\n\n/** The slice of `CallFeature` the link needs (`CallFeature.lifecycle.endCallsBetween`). */\nexport interface CallLifecycleLike {\n endCallsBetween(userA: string, userB: string, reason: string, endedBy?: string | null): Promise<unknown>;\n}\nexport interface LinkableCall {\n readonly hub: CommsHub;\n policy: CallPolicyLike;\n readonly lifecycle: CallLifecycleLike;\n}\n\nexport class ModerationLink {\n constructor(\n readonly moderation: ModerationFeature,\n readonly chat: LinkableChat | null = null,\n readonly call: LinkableCall | null = null,\n ) {}\n\n /** `moderation.blocked` listener. */\n async endCallsOnBlock(blockerId: string, blockedId: string): Promise<void> {\n if (this.call) await this.call.lifecycle.endCallsBetween(blockerId, blockedId, BLOCKED_END_REASON, blockerId);\n }\n\n /**\n * `ModerationFeature.contextProvider`: MessageDtos (as the reporter sees them, newest first) around `messageId` in the\n * pair's chat, or the latest ones. Hides / clears are ignored.\n */\n async reportMessages(\n reporterId: string | null,\n reportedId: string | null,\n messageId: number | null,\n before: number,\n after: number,\n ): Promise<Array<Record<string, any>>> {\n if (!this.chat || !reporterId || !reportedId) return [];\n const { ChatService, messageDto } = await import(\"../chat/index.js\");\n const hub = this.chat.hub;\n const service = new ChatService(hub.db, hub.dialect);\n const chat = await service.getChatBetween(reporterId, reportedId);\n if (!chat) return [];\n const rows = await service.messagesAround(chat.id, messageId, before, after);\n const previews = await service.getReplyPreviews(rows.map((m) => m.reply_to_id));\n return rows.map((m) => messageDto(m, reporterId, m.reply_to_id ? (previews.get(m.reply_to_id) ?? null) : null));\n }\n}\n\n/**\n * Install-time wiring; all given features must be installed on the same hub.\n * - `chat.policy` becomes `moderation.chatPolicy(<previous policy>)`: a blocked pair cannot send_message / media_upload\n * (error) or typing (dropped), and `res_users` / `res_messages.info` carry `is_blocked` / `blocked_by_other`.\n * - `call.policy` becomes `moderation.callPolicy(<previous policy>)`: call_initiate to/from a blocked user is denied before\n * the host policy runs (no credits charged).\n * - on block, live calls between the pair end with `end_reason: \"blocked\"`.\n * - with chat: `moderation.contextProvider` (unless the host set one) reads the chat tables for the admin\n * `/moderation/admin/reports/:id/context` route.\n * Policies already wrapped (e.g. `new ChatFeature({ policy: moderation.chatPolicy(...) })`) are not wrapped twice.\n */\nexport function linkModeration(moderation: ModerationFeature, features: { chat?: LinkableChat | null; call?: LinkableCall | null } = {}): ModerationLink {\n const hub = moderation.hub;\n const { chat = null, call = null } = features;\n for (const f of [chat, call]) {\n if (f && f.hub !== hub) throw new Error(\"moderation, chat and call must be installed on the same CommsHub\");\n }\n if (moderation.link) throw new Error(\"moderation is already linked\");\n\n if (chat && !(chat.policy instanceof ModerationChatPolicy)) chat.policy = moderation.chatPolicy(chat.policy);\n if (call && !(call.policy instanceof ModerationCallPolicy)) call.policy = moderation.callPolicy(call.policy);\n\n const link = new ModerationLink(moderation, chat, call);\n if (chat && !moderation.contextProvider) moderation.contextProvider = (a, b, m, bf, af) => link.reportMessages(a, b, m, bf, af);\n hub.events.on(\"moderation.blocked\", (blocker: string, blocked: string) => link.endCallsOnBlock(blocker, blocked));\n moderation.link = link;\n return link;\n}\n","/**\n * Account deletion across every installed feature (port of commskit.integration.privacy). Works through `hub.features` (any\n * feature with an async `deleteUserData(userId)`), so it imports no feature package.\n */\nimport type { CommsHub } from \"../core/hub.js\";\nimport { logger } from \"../core/logger.js\";\n\n// Chat first: its messages may reference calls (call-log entries); devices last.\nconst ORDER = [\"chat\", \"call\", \"moderation\", \"devices\"];\n\n/**\n * Call `deleteUserData(userId)` on every installed feature (chat, call, moderation, devices, then any others), then emit\n * `user.deleted(userId)`. Returns `{featureName: result}`. A failing feature is logged and reported as `{error}` (or\n * re-thrown with `raiseErrors`); the others still run.\n */\nexport async function deleteUserEverywhere(hub: CommsHub, userId: string, opts: { raiseErrors?: boolean } = {}): Promise<Record<string, unknown>> {\n const uid = String(userId);\n const rank = (n: string): number => (ORDER.includes(n) ? ORDER.indexOf(n) : ORDER.length);\n const names = Object.keys(hub.features).sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : a > b ? 1 : 0));\n const results: Record<string, unknown> = {};\n for (const name of names) {\n const feature = hub.features[name] as { deleteUserData?: (id: string) => Promise<unknown> } | undefined;\n if (typeof feature?.deleteUserData !== \"function\") continue;\n try {\n results[name] = await feature.deleteUserData(uid);\n } catch (err) {\n if (opts.raiseErrors) throw err;\n logger.error(`deleteUserData failed in feature ${name}`, err);\n results[name] = { error: String((err as Error)?.message ?? err) };\n }\n }\n await hub.events.emit(\"user.deleted\", uid);\n return results;\n}\n"],"mappings":"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","names":["ChatService"]}
@@ -0,0 +1,31 @@
1
+ import { Generated } from 'kysely';
2
+ import { a as Dialect } from './hub-CPrWmIvB.js';
3
+
4
+ /**
5
+ * Moderation tables `comms_blocks` and `comms_reports` (same columns as the Python models; no FK to a users table).
6
+ * ModerationFeature / service / policy wrappers build on these.
7
+ */
8
+
9
+ type Ts = string | Date;
10
+ interface BlocksTable {
11
+ id: Generated<number>;
12
+ blocker_id: string;
13
+ blocked_id: string;
14
+ created_at: Ts;
15
+ }
16
+ interface ReportsTable {
17
+ id: Generated<number>;
18
+ reporter_id: string | null;
19
+ reported_id: string | null;
20
+ reason: string | null;
21
+ category: string | null;
22
+ message_id: number | null;
23
+ status: string;
24
+ created_at: Ts;
25
+ resolved_at: Ts | null;
26
+ resolved_by: string | null;
27
+ note: string | null;
28
+ }
29
+ declare function moderationDdl(dialect: Dialect): string[];
30
+
31
+ export { type BlocksTable as B, type ReportsTable as R, moderationDdl as m };
@@ -0,0 +1,34 @@
1
+ import { Generated } from 'kysely';
2
+ import { a as Dialect } from './hub-CPrWmIvB.js';
3
+
4
+ /**
5
+ * Call table (`calls`), same columns as the Python `commskit.call.models.Call`.
6
+ * `chat_id` is nullable so calls work without the chat feature.
7
+ * CallFeature / CallService / lifecycle build on this.
8
+ */
9
+
10
+ type Ts = string | Date;
11
+ interface CallsTable {
12
+ id: Generated<number>;
13
+ call_id: string;
14
+ chat_id: number | null;
15
+ caller_id: string;
16
+ receiver_id: string;
17
+ recording_url: string | null;
18
+ call_status: string;
19
+ is_video_call: boolean | number | null;
20
+ offer_sdp: string | null;
21
+ answer_sdp: string | null;
22
+ ice_candidates: string | null;
23
+ started_at: Ts;
24
+ answered_at: Ts | null;
25
+ ended_at: Ts | null;
26
+ created_at: Ts | null;
27
+ updated_at: Ts | null;
28
+ duration: number | null;
29
+ missed_at: Ts | null;
30
+ end_reason: string | null;
31
+ }
32
+ declare function callDdl(dialect: Dialect): string[];
33
+
34
+ export { type CallsTable as C, callDdl as c };
@@ -0,0 +1,9 @@
1
+ export { B as BlocksTable, R as ReportsTable, m as moderationDdl } from './models-D1lehXIu.js';
2
+ export { A as AdminDependency, B as BLOCKED_BY_TEXT, a as BLOCKED_TEXT, C as CALL_REFUSAL, b as CHAT_REFUSAL, c as CallPolicyLike, d as ChatPolicyLike, e as ContextProvider, M as ModerationActions, f as ModerationCallPolicy, g as ModerationChatPolicy, h as ModerationDecision, i as ModerationError, j as ModerationFeature, k as ModerationFeatureOptions, l as ModerationHandlers, m as ModerationService, R as REPORT_CATEGORIES, n as REPORT_STATUSES, o as ReportDict, p as ReportHook, q as ReportInfo, U as UNBLOCKED_BY_TEXT, r as UNBLOCKED_TEXT, s as moderationError, t as reportDict, u as targetId } from './feature-B2bEXLZG.js';
3
+ import 'kysely';
4
+ import './hub-CPrWmIvB.js';
5
+ import 'node:http';
6
+ import 'node:stream';
7
+ import 'ws';
8
+ import './adapters-8ztjEYtz.js';
9
+ import './connections-CGEPiZPF.js';
@@ -0,0 +1,48 @@
1
+ import {
2
+ BLOCKED_BY_TEXT,
3
+ BLOCKED_TEXT,
4
+ CALL_REFUSAL,
5
+ CHAT_REFUSAL,
6
+ ModerationActions,
7
+ ModerationCallPolicy,
8
+ ModerationChatPolicy,
9
+ ModerationError,
10
+ ModerationFeature,
11
+ ModerationHandlers,
12
+ ModerationService,
13
+ REPORT_CATEGORIES,
14
+ REPORT_STATUSES,
15
+ UNBLOCKED_BY_TEXT,
16
+ UNBLOCKED_TEXT,
17
+ moderationError,
18
+ reportDict,
19
+ targetId
20
+ } from "./chunk-6I27CI32.js";
21
+ import {
22
+ moderationDdl
23
+ } from "./chunk-SEDRF62Q.js";
24
+ import "./chunk-GR4PMGCS.js";
25
+ import "./chunk-KLKRK6OG.js";
26
+ import "./chunk-5BJ2SNM5.js";
27
+ export {
28
+ BLOCKED_BY_TEXT,
29
+ BLOCKED_TEXT,
30
+ CALL_REFUSAL,
31
+ CHAT_REFUSAL,
32
+ ModerationActions,
33
+ ModerationCallPolicy,
34
+ ModerationChatPolicy,
35
+ ModerationError,
36
+ ModerationFeature,
37
+ ModerationHandlers,
38
+ ModerationService,
39
+ REPORT_CATEGORIES,
40
+ REPORT_STATUSES,
41
+ UNBLOCKED_BY_TEXT,
42
+ UNBLOCKED_TEXT,
43
+ moderationDdl,
44
+ moderationError,
45
+ reportDict,
46
+ targetId
47
+ };
48
+ //# sourceMappingURL=moderation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/push.d.ts ADDED
@@ -0,0 +1,198 @@
1
+ import { a as DeviceStore } from './devices-oLIB_M19.js';
2
+ export { b as DeviceFields, c as DeviceInfo, e as DevicesFeature, D as DevicesTable, P as PLATFORMS, S as SqlDeviceRegistry, T as TokenKind, f as buildDevicesRoutes, d as devicesDdl } from './devices-oLIB_M19.js';
3
+ import { P as PushSender } from './adapters-8ztjEYtz.js';
4
+ import 'kysely';
5
+ import './hub-CPrWmIvB.js';
6
+ import 'node:http';
7
+ import 'node:stream';
8
+ import 'ws';
9
+ import './connections-CGEPiZPF.js';
10
+
11
+ declare const EXPO_PUSH_URL = "https://exp.host/--/api/v2/push/send";
12
+ declare const EXPO_RECEIPTS_URL = "https://exp.host/--/api/v2/push/getReceipts";
13
+ declare const APNS_PRODUCTION_HOST = "https://api.push.apple.com";
14
+ declare const APNS_SANDBOX_HOST = "https://api.sandbox.push.apple.com";
15
+ /** CallKit UUID for a call id: UUIDv5(NAMESPACE_URL, `commskit:call:<callId>`). Shared with the mobile kit. */
16
+ declare function callkitUuid(callId: string): string;
17
+ type FetchLike = (url: string, init: {
18
+ method: string;
19
+ headers: Record<string, string>;
20
+ body: string;
21
+ signal?: AbortSignal;
22
+ }) => Promise<{
23
+ status: number;
24
+ text(): Promise<string>;
25
+ }>;
26
+ interface ExpoTicket {
27
+ status?: string;
28
+ id?: string;
29
+ message?: string;
30
+ details?: {
31
+ error?: string;
32
+ expoPushToken?: string;
33
+ [k: string]: unknown;
34
+ };
35
+ [k: string]: unknown;
36
+ }
37
+ type ExpoMessage = Record<string, any>;
38
+ interface ExpoPushSenderOptions {
39
+ registry?: DeviceStore | null;
40
+ accessToken?: string | null;
41
+ /** Defaults to the global `fetch`. */
42
+ fetch?: FetchLike;
43
+ url?: string;
44
+ batchSize?: number;
45
+ /** Seconds. */
46
+ timeout?: number;
47
+ sound?: string | null;
48
+ receiptsUrl?: string;
49
+ receiptBatchSize?: number;
50
+ maxPendingTickets?: number;
51
+ /** Seconds. */
52
+ receiptTtl?: number;
53
+ }
54
+ interface BuildMessageOptions {
55
+ channel?: string | null;
56
+ ttl?: number | null;
57
+ priority?: "default" | "normal" | "high";
58
+ /** `false` (default) = the sender's default sound. */
59
+ sound?: string | null | false;
60
+ dataOnly?: boolean;
61
+ }
62
+ /**
63
+ * Expo push client; also a `PushSender` on its own (needs `registry`). `DeviceNotRegistered` tickets remove that
64
+ * token; accepted ticket ids are remembered (up to `maxPendingTickets`, `receiptTtl`) and `pollReceipts()`
65
+ * checks them later (`startReceiptPoller(900)` runs it in the background).
66
+ */
67
+ declare class ExpoPushSender implements PushSender {
68
+ registry: DeviceStore | null;
69
+ accessToken: string | null;
70
+ url: string;
71
+ batchSize: number;
72
+ timeout: number;
73
+ sound: string | null;
74
+ receiptsUrl: string;
75
+ receiptBatchSize: number;
76
+ maxPendingTickets: number;
77
+ receiptTtl: number;
78
+ private fetchImpl?;
79
+ private tickets;
80
+ private poller;
81
+ constructor(opts?: ExpoPushSenderOptions);
82
+ private headers;
83
+ private post;
84
+ /** Nothing to release by default (global fetch); stops the receipt poller. */
85
+ aclose(): Promise<void>;
86
+ /** One Expo message. `dataOnly` omits title/body/sound: Android hands it to the app instead of displaying it. */
87
+ buildMessage(token: string, title: string | null, body: string | null, data: Record<string, unknown>, opts?: BuildMessageOptions): ExpoMessage;
88
+ /** POST in batches of `batchSize`. One ticket per message, in order; a failed batch yields error tickets. */
89
+ sendMessages(messages: readonly ExpoMessage[]): Promise<ExpoTicket[]>;
90
+ private dropToken;
91
+ private handleTickets;
92
+ private rememberTickets;
93
+ /** `{ticketId: expoPushToken}` still waiting for a receipt. */
94
+ get pendingTickets(): Record<string, string>;
95
+ /**
96
+ * Fetch receipts for the remembered tickets (`getReceipts`, at most `receiptBatchSize` ids per request).
97
+ * `DeviceNotRegistered` removes the token. Tickets with a receipt are forgotten; others stay pending until
98
+ * `receiptTtl`. Returns `{checked, ok, errors, unregistered, pending}`.
99
+ */
100
+ pollReceipts(): Promise<{
101
+ checked: number;
102
+ ok: number;
103
+ errors: number;
104
+ unregistered: number;
105
+ pending: number;
106
+ }>;
107
+ /** Poll receipts every `intervalSeconds` in the background. `stopReceiptPoller()` / `aclose()` stop it. */
108
+ startReceiptPoller(intervalSeconds?: number): NodeJS.Timeout;
109
+ stopReceiptPoller(): void;
110
+ /** `PushSender`: visible notification to every Expo token of `userId`. */
111
+ send(userId: string, title: string, body: string, data: Record<string, unknown>, channel?: string, ttl?: number | null): Promise<void>;
112
+ }
113
+ interface ApnsResponse {
114
+ status: number;
115
+ body: string;
116
+ }
117
+ /** One HTTP/2 connection to APNs (injectable for tests). */
118
+ interface ApnsSession {
119
+ request(path: string, headers: Record<string, string>, body: string): Promise<ApnsResponse>;
120
+ close(): void | Promise<void>;
121
+ }
122
+ type ApnsSessionFactory = (origin: string) => ApnsSession;
123
+ /** Default: a lazily connected, reconnecting `node:http2` session. */
124
+ declare function defaultApnsSessionFactory(timeoutSeconds?: number): ApnsSessionFactory;
125
+ interface ApnsVoipSenderOptions {
126
+ /** PEM text of the .p8 key. */
127
+ key?: string;
128
+ keyPath?: string;
129
+ keyId: string;
130
+ teamId: string;
131
+ bundleId: string;
132
+ useSandbox?: boolean;
133
+ registry?: DeviceStore | null;
134
+ /** Seconds a push may wait at APNs (apns-expiration = now + this). */
135
+ expiration?: number;
136
+ /** Seconds before the provider JWT is re-signed (< 3600, as Apple requires). */
137
+ tokenRefresh?: number;
138
+ sessionFactory?: ApnsSessionFactory;
139
+ /** Seconds. */
140
+ timeout?: number;
141
+ }
142
+ /**
143
+ * iOS PushKit (VoIP) pushes over HTTP/2 with token-based (.p8) auth. Headers: `apns-push-type: voip`,
144
+ * `apns-topic: <bundle>.voip`, `apns-priority: 10`, `apns-expiration: now + expiration`. The provider JWT (ES256,
145
+ * kid = key id, iss = team id) is cached and refreshed every `tokenRefresh` seconds. 410 / BadDeviceToken /
146
+ * Unregistered remove the token; ExpiredProviderToken re-signs once and retries.
147
+ */
148
+ declare class ApnsVoipSender {
149
+ static readonly INVALID_TOKEN_REASONS: Set<string>;
150
+ readonly keyId: string;
151
+ readonly teamId: string;
152
+ readonly bundleId: string;
153
+ readonly topic: string;
154
+ readonly host: string;
155
+ registry: DeviceStore | null;
156
+ expiration: number;
157
+ tokenRefresh: number;
158
+ /** Epoch seconds the cached JWT was signed (tests age it). */
159
+ jwtIssuedAt: number;
160
+ private pem;
161
+ private jwt;
162
+ private session;
163
+ private sessionFactory;
164
+ constructor(opts: ApnsVoipSenderOptions);
165
+ /** From APNS_KEY / APNS_KEY_PATH, APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, APNS_USE_SANDBOX. Null when not configured. */
166
+ static fromEnv(registry?: DeviceStore | null, overrides?: Partial<ApnsVoipSenderOptions>, env?: NodeJS.ProcessEnv): ApnsVoipSender | null;
167
+ private getSession;
168
+ aclose(): Promise<void>;
169
+ /** Cached ES256 provider JWT; re-signed after `tokenRefresh` seconds. */
170
+ providerToken(): Promise<string>;
171
+ headers(): Promise<Record<string, string>>;
172
+ /** POST /3/device/<token>. True when APNs accepted it. */
173
+ sendVoip(token: string, payload: Record<string, unknown>): Promise<boolean>;
174
+ }
175
+ interface DevicePushRouterOptions {
176
+ registry: DeviceStore;
177
+ expo?: ExpoPushSender;
178
+ voip?: ApnsVoipSender | null;
179
+ androidCallStyle?: "notification" | "data";
180
+ }
181
+ /**
182
+ * `PushSender` that picks the transport per device (PROTOCOL.md section 8).
183
+ * - `data.type === "incoming_call"`: iOS device with a voip token and a `voip` sender -> APNs VoIP with
184
+ * `uuid = callkitUuid(call_id)` (if APNs refuses and the device has an Expo token: visible Expo instead);
185
+ * Android + `androidCallStyle "data"` -> Expo data-only; anything else with an Expo token -> visible Expo, priority high.
186
+ * - everything else (chat, missed call, host pushes) -> visible Expo notification.
187
+ */
188
+ declare class DevicePushRouter implements PushSender {
189
+ readonly registry: DeviceStore;
190
+ readonly expo: ExpoPushSender;
191
+ readonly voip: ApnsVoipSender | null;
192
+ readonly androidCallStyle: "notification" | "data";
193
+ constructor(opts: DevicePushRouterOptions);
194
+ aclose(): Promise<void>;
195
+ send(userId: string, title: string, body: string, data: Record<string, unknown>, channel?: string, ttl?: number | null): Promise<void>;
196
+ }
197
+
198
+ export { APNS_PRODUCTION_HOST, APNS_SANDBOX_HOST, type ApnsResponse, type ApnsSession, type ApnsSessionFactory, ApnsVoipSender, type ApnsVoipSenderOptions, type BuildMessageOptions, DevicePushRouter, type DevicePushRouterOptions, DeviceStore, EXPO_PUSH_URL, EXPO_RECEIPTS_URL, type ExpoMessage, ExpoPushSender, type ExpoPushSenderOptions, type ExpoTicket, type FetchLike, callkitUuid, defaultApnsSessionFactory };