@nuraly/lumenjs 0.1.2 → 0.1.4

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 (337) hide show
  1. package/README.md +76 -235
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +73 -0
  11. package/dist/auth/native-auth.js +293 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +98 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/utils.d.ts +7 -0
  31. package/dist/auth/routes/utils.js +35 -0
  32. package/dist/auth/routes/verify.d.ts +3 -0
  33. package/dist/auth/routes/verify.js +26 -0
  34. package/dist/auth/routes.d.ts +8 -0
  35. package/dist/auth/routes.js +110 -0
  36. package/dist/auth/session.d.ts +8 -0
  37. package/dist/auth/session.js +54 -0
  38. package/dist/auth/token.d.ts +33 -0
  39. package/dist/auth/token.js +90 -0
  40. package/dist/auth/types.d.ts +156 -0
  41. package/dist/auth/types.js +2 -0
  42. package/dist/build/build-client.d.ts +15 -0
  43. package/dist/build/build-client.js +45 -0
  44. package/dist/build/build-prerender.d.ts +11 -0
  45. package/dist/build/build-prerender.js +159 -0
  46. package/dist/build/build-server.d.ts +17 -0
  47. package/dist/build/build-server.js +98 -0
  48. package/dist/build/build.js +52 -120
  49. package/dist/build/scan.d.ts +19 -0
  50. package/dist/build/scan.js +77 -6
  51. package/dist/build/serve-api.js +8 -2
  52. package/dist/build/serve-loaders.d.ts +4 -2
  53. package/dist/build/serve-loaders.js +128 -10
  54. package/dist/build/serve-ssr.js +38 -11
  55. package/dist/build/serve-static.js +3 -3
  56. package/dist/build/serve.js +229 -14
  57. package/dist/cli.js +37 -6
  58. package/dist/communication/encryption.d.ts +35 -0
  59. package/dist/communication/encryption.js +90 -0
  60. package/dist/communication/handlers/context.d.ts +27 -0
  61. package/dist/communication/handlers/context.js +1 -0
  62. package/dist/communication/handlers/conversation.d.ts +24 -0
  63. package/dist/communication/handlers/conversation.js +113 -0
  64. package/dist/communication/handlers/file-upload.d.ts +17 -0
  65. package/dist/communication/handlers/file-upload.js +62 -0
  66. package/dist/communication/handlers/messaging.d.ts +30 -0
  67. package/dist/communication/handlers/messaging.js +237 -0
  68. package/dist/communication/handlers/presence.d.ts +15 -0
  69. package/dist/communication/handlers/presence.js +76 -0
  70. package/dist/communication/handlers.d.ts +5 -0
  71. package/dist/communication/handlers.js +5 -0
  72. package/dist/communication/index.d.ts +9 -0
  73. package/dist/communication/index.js +7 -0
  74. package/dist/communication/link-preview.d.ts +18 -0
  75. package/dist/communication/link-preview.js +115 -0
  76. package/dist/communication/schema.d.ts +10 -0
  77. package/dist/communication/schema.js +101 -0
  78. package/dist/communication/server.d.ts +86 -0
  79. package/dist/communication/server.js +212 -0
  80. package/dist/communication/signaling.d.ts +43 -0
  81. package/dist/communication/signaling.js +271 -0
  82. package/dist/communication/store.d.ts +71 -0
  83. package/dist/communication/store.js +289 -0
  84. package/dist/communication/types.d.ts +454 -0
  85. package/dist/communication/types.js +1 -0
  86. package/dist/create.d.ts +1 -0
  87. package/dist/create.js +55 -0
  88. package/dist/db/auto-migrate.d.ts +3 -0
  89. package/dist/db/auto-migrate.js +100 -0
  90. package/dist/db/client.d.ts +3 -0
  91. package/dist/db/client.js +18 -0
  92. package/dist/db/context.d.ts +2 -0
  93. package/dist/db/context.js +9 -0
  94. package/dist/db/index.d.ts +23 -0
  95. package/dist/db/index.js +258 -0
  96. package/dist/db/seed.d.ts +12 -0
  97. package/dist/db/seed.js +88 -0
  98. package/dist/db/table.d.ts +10 -0
  99. package/dist/db/table.js +12 -0
  100. package/dist/dev-server/config.d.ts +14 -0
  101. package/dist/dev-server/config.js +26 -9
  102. package/dist/dev-server/index-html.d.ts +3 -0
  103. package/dist/dev-server/index-html.js +18 -6
  104. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  105. package/dist/dev-server/nuralyui-aliases.js +115 -94
  106. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  107. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  108. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  109. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  110. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  111. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  113. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  114. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  115. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  116. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  117. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  118. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  119. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  120. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  121. package/dist/dev-server/plugins/vite-plugin-loaders.d.ts +0 -1
  122. package/dist/dev-server/plugins/vite-plugin-loaders.js +311 -42
  123. package/dist/dev-server/plugins/vite-plugin-routes.js +18 -6
  124. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  125. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  127. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  128. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  129. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  130. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +111 -2
  131. package/dist/dev-server/server.js +128 -12
  132. package/dist/dev-server/ssr-render.d.ts +2 -1
  133. package/dist/dev-server/ssr-render.js +107 -48
  134. package/dist/editor/ai/backend.d.ts +20 -0
  135. package/dist/editor/ai/backend.js +104 -0
  136. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  137. package/dist/editor/ai/claude-code-client.js +145 -0
  138. package/dist/editor/ai/opencode-client.d.ts +14 -0
  139. package/dist/editor/ai/opencode-client.js +125 -0
  140. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  141. package/dist/editor/ai/snapshot-store.js +35 -0
  142. package/dist/editor/ai/types.d.ts +30 -0
  143. package/dist/editor/ai/types.js +136 -0
  144. package/dist/editor/ai-chat-panel.d.ts +13 -0
  145. package/dist/editor/ai-chat-panel.js +587 -0
  146. package/dist/editor/ai-markdown.d.ts +10 -0
  147. package/dist/editor/ai-markdown.js +70 -0
  148. package/dist/editor/ai-project-panel.d.ts +11 -0
  149. package/dist/editor/ai-project-panel.js +332 -0
  150. package/dist/editor/ast-modification.d.ts +11 -0
  151. package/dist/editor/ast-modification.js +1 -0
  152. package/dist/editor/ast-service.d.ts +30 -0
  153. package/dist/editor/ast-service.js +180 -0
  154. package/dist/editor/css-rules.d.ts +54 -0
  155. package/dist/editor/css-rules.js +423 -0
  156. package/dist/editor/editor-api-client.d.ts +51 -0
  157. package/dist/editor/editor-api-client.js +162 -0
  158. package/dist/editor/editor-bridge.d.ts +1 -0
  159. package/dist/editor/editor-bridge.js +17 -8
  160. package/dist/editor/editor-toolbar.d.ts +14 -0
  161. package/dist/editor/editor-toolbar.js +115 -0
  162. package/dist/editor/file-editor.d.ts +9 -0
  163. package/dist/editor/file-editor.js +236 -0
  164. package/dist/editor/file-service.d.ts +16 -0
  165. package/dist/editor/file-service.js +52 -0
  166. package/dist/editor/i18n-key-gen.d.ts +1 -0
  167. package/dist/editor/i18n-key-gen.js +7 -0
  168. package/dist/editor/inline-text-edit.d.ts +5 -0
  169. package/dist/editor/inline-text-edit.js +173 -92
  170. package/dist/editor/overlay-events.d.ts +5 -0
  171. package/dist/editor/overlay-events.js +364 -0
  172. package/dist/editor/overlay-hmr.d.ts +2 -0
  173. package/dist/editor/overlay-hmr.js +75 -0
  174. package/dist/editor/overlay-selection.d.ts +29 -0
  175. package/dist/editor/overlay-selection.js +148 -0
  176. package/dist/editor/overlay-utils.d.ts +12 -0
  177. package/dist/editor/overlay-utils.js +59 -0
  178. package/dist/editor/properties-panel-persist.d.ts +14 -0
  179. package/dist/editor/properties-panel-persist.js +70 -0
  180. package/dist/editor/properties-panel-rows.d.ts +10 -0
  181. package/dist/editor/properties-panel-rows.js +349 -0
  182. package/dist/editor/properties-panel-styles.d.ts +4 -0
  183. package/dist/editor/properties-panel-styles.js +174 -0
  184. package/dist/editor/properties-panel.d.ts +4 -0
  185. package/dist/editor/properties-panel.js +148 -0
  186. package/dist/editor/property-registry.d.ts +16 -0
  187. package/dist/editor/property-registry.js +303 -0
  188. package/dist/editor/standalone-file-panel.d.ts +0 -0
  189. package/dist/editor/standalone-file-panel.js +1 -0
  190. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  191. package/dist/editor/standalone-overlay-dom.js +1 -0
  192. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  193. package/dist/editor/standalone-overlay-styles.js +1 -0
  194. package/dist/editor/standalone-overlay.d.ts +1 -0
  195. package/dist/editor/standalone-overlay.js +76 -0
  196. package/dist/editor/syntax-highlighter.d.ts +4 -0
  197. package/dist/editor/syntax-highlighter.js +81 -0
  198. package/dist/editor/text-toolbar.d.ts +11 -0
  199. package/dist/editor/text-toolbar.js +327 -0
  200. package/dist/editor/toolbar-styles.d.ts +4 -0
  201. package/dist/editor/toolbar-styles.js +198 -0
  202. package/dist/email/index.d.ts +32 -0
  203. package/dist/email/index.js +154 -0
  204. package/dist/email/providers/resend.d.ts +2 -0
  205. package/dist/email/providers/resend.js +24 -0
  206. package/dist/email/providers/sendgrid.d.ts +2 -0
  207. package/dist/email/providers/sendgrid.js +31 -0
  208. package/dist/email/providers/smtp.d.ts +13 -0
  209. package/dist/email/providers/smtp.js +125 -0
  210. package/dist/email/template-engine.d.ts +18 -0
  211. package/dist/email/template-engine.js +116 -0
  212. package/dist/email/templates/base.d.ts +9 -0
  213. package/dist/email/templates/base.js +65 -0
  214. package/dist/email/templates/password-reset.d.ts +5 -0
  215. package/dist/email/templates/password-reset.js +15 -0
  216. package/dist/email/templates/verify-email.d.ts +5 -0
  217. package/dist/email/templates/verify-email.js +15 -0
  218. package/dist/email/templates/welcome.d.ts +5 -0
  219. package/dist/email/templates/welcome.js +13 -0
  220. package/dist/email/types.d.ts +49 -0
  221. package/dist/email/types.js +1 -0
  222. package/dist/llms/generate.d.ts +46 -0
  223. package/dist/llms/generate.js +185 -0
  224. package/dist/permissions/guard.d.ts +28 -0
  225. package/dist/permissions/guard.js +30 -0
  226. package/dist/permissions/index.d.ts +6 -0
  227. package/dist/permissions/index.js +3 -0
  228. package/dist/permissions/service.d.ts +80 -0
  229. package/dist/permissions/service.js +210 -0
  230. package/dist/permissions/tables.d.ts +5 -0
  231. package/dist/permissions/tables.js +68 -0
  232. package/dist/permissions/types.d.ts +33 -0
  233. package/dist/permissions/types.js +1 -0
  234. package/dist/runtime/app-shell.js +163 -0
  235. package/dist/runtime/auth.d.ts +10 -0
  236. package/dist/runtime/auth.js +30 -0
  237. package/dist/runtime/communication.d.ts +137 -0
  238. package/dist/runtime/communication.js +228 -0
  239. package/dist/runtime/error-boundary.d.ts +23 -0
  240. package/dist/runtime/error-boundary.js +120 -0
  241. package/dist/runtime/i18n.d.ts +6 -1
  242. package/dist/runtime/i18n.js +42 -21
  243. package/dist/runtime/router-data.d.ts +5 -0
  244. package/dist/runtime/router-data.js +121 -16
  245. package/dist/runtime/router-hydration.js +25 -0
  246. package/dist/runtime/router.d.ts +21 -1
  247. package/dist/runtime/router.js +221 -39
  248. package/dist/runtime/socket-client.d.ts +2 -0
  249. package/dist/runtime/socket-client.js +30 -0
  250. package/dist/runtime/webrtc.d.ts +47 -0
  251. package/dist/runtime/webrtc.js +178 -0
  252. package/dist/shared/graceful-shutdown.d.ts +8 -0
  253. package/dist/shared/graceful-shutdown.js +36 -0
  254. package/dist/shared/health.d.ts +8 -0
  255. package/dist/shared/health.js +25 -0
  256. package/dist/shared/llms-txt.d.ts +31 -0
  257. package/dist/shared/llms-txt.js +85 -0
  258. package/dist/shared/logger.d.ts +32 -0
  259. package/dist/shared/logger.js +93 -0
  260. package/dist/shared/meta.d.ts +27 -0
  261. package/dist/shared/meta.js +71 -0
  262. package/dist/shared/middleware-runner.d.ts +9 -0
  263. package/dist/shared/middleware-runner.js +29 -0
  264. package/dist/shared/rate-limit.d.ts +18 -0
  265. package/dist/shared/rate-limit.js +71 -0
  266. package/dist/shared/request-id.d.ts +5 -0
  267. package/dist/shared/request-id.js +18 -0
  268. package/dist/shared/route-matching.js +16 -1
  269. package/dist/shared/security-headers.d.ts +18 -0
  270. package/dist/shared/security-headers.js +38 -0
  271. package/dist/shared/socket-io-setup.d.ts +11 -0
  272. package/dist/shared/socket-io-setup.js +51 -0
  273. package/dist/shared/types.d.ts +16 -0
  274. package/dist/shared/utils.d.ts +37 -7
  275. package/dist/shared/utils.js +175 -26
  276. package/dist/storage/adapters/local.d.ts +44 -0
  277. package/dist/storage/adapters/local.js +85 -0
  278. package/dist/storage/adapters/s3.d.ts +32 -0
  279. package/dist/storage/adapters/s3.js +116 -0
  280. package/dist/storage/adapters/types.d.ts +53 -0
  281. package/dist/storage/adapters/types.js +1 -0
  282. package/dist/storage/index.d.ts +76 -0
  283. package/dist/storage/index.js +83 -0
  284. package/package.json +20 -1
  285. package/templates/blog/api/posts.ts +6 -0
  286. package/templates/blog/data/migrations/001_init.sql +13 -0
  287. package/templates/blog/lumenjs.config.ts +3 -0
  288. package/templates/blog/package.json +14 -0
  289. package/templates/blog/pages/_layout.ts +25 -0
  290. package/templates/blog/pages/index.ts +65 -0
  291. package/templates/blog/pages/posts/[slug].ts +60 -0
  292. package/templates/blog/pages/tag/[tag].ts +44 -0
  293. package/templates/dashboard/api/stats.ts +10 -0
  294. package/templates/dashboard/data/migrations/001_init.sql +13 -0
  295. package/templates/dashboard/lumenjs.config.ts +3 -0
  296. package/templates/dashboard/package.json +14 -0
  297. package/templates/dashboard/pages/_layout.ts +25 -0
  298. package/templates/dashboard/pages/index.ts +72 -0
  299. package/templates/dashboard/pages/settings/index.ts +29 -0
  300. package/templates/default/lumenjs.config.ts +3 -0
  301. package/templates/default/package.json +14 -0
  302. package/templates/default/pages/index.ts +24 -0
  303. package/templates/social/api/posts/[id].ts +14 -0
  304. package/templates/social/api/posts.ts +11 -0
  305. package/templates/social/api/profile/[username].ts +10 -0
  306. package/templates/social/api/upload.ts +19 -0
  307. package/templates/social/data/migrations/001_init.sql +78 -0
  308. package/templates/social/data/migrations/002_add_image_url.sql +1 -0
  309. package/templates/social/data/migrations/003_auth.sql +7 -0
  310. package/templates/social/docs/architecture.md +76 -0
  311. package/templates/social/docs/components.md +100 -0
  312. package/templates/social/docs/data.md +89 -0
  313. package/templates/social/docs/pages.md +96 -0
  314. package/templates/social/docs/theming.md +52 -0
  315. package/templates/social/lib/media.ts +130 -0
  316. package/templates/social/lumenjs.auth.ts +21 -0
  317. package/templates/social/lumenjs.config.ts +3 -0
  318. package/templates/social/package.json +5 -0
  319. package/templates/social/pages/_layout.ts +239 -0
  320. package/templates/social/pages/apps/[id].ts +173 -0
  321. package/templates/social/pages/apps/index.ts +116 -0
  322. package/templates/social/pages/auth/login.ts +92 -0
  323. package/templates/social/pages/bookmarks.ts +57 -0
  324. package/templates/social/pages/explore.ts +73 -0
  325. package/templates/social/pages/index.ts +351 -0
  326. package/templates/social/pages/messages.ts +298 -0
  327. package/templates/social/pages/new.ts +77 -0
  328. package/templates/social/pages/notifications.ts +73 -0
  329. package/templates/social/pages/post/[id].ts +124 -0
  330. package/templates/social/pages/profile/[username].ts +100 -0
  331. package/templates/social/pages/settings/accessibility.ts +153 -0
  332. package/templates/social/pages/settings/account.ts +260 -0
  333. package/templates/social/pages/settings/help.ts +141 -0
  334. package/templates/social/pages/settings/language.ts +103 -0
  335. package/templates/social/pages/settings/privacy.ts +183 -0
  336. package/templates/social/pages/settings/security.ts +133 -0
  337. package/templates/social/pages/settings.ts +185 -0
@@ -0,0 +1,113 @@
1
+ // ── Conversation ────────────────────────────────────────────────
2
+ export async function handleConversationCreate(ctx, data) {
3
+ const now = new Date().toISOString();
4
+ let conversation;
5
+ if (ctx.db) {
6
+ const convId = crypto.randomUUID();
7
+ await ctx.db.run(`INSERT INTO conversations (id, type, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`, convId, data.type, data.name || null, now, now);
8
+ // Add the creator as a participant with 'owner' role
9
+ const allParticipantIds = [ctx.userId, ...data.participantIds.filter(id => id !== ctx.userId)];
10
+ for (const uid of allParticipantIds) {
11
+ const role = uid === ctx.userId ? 'owner' : 'member';
12
+ await ctx.db.run(`INSERT INTO conversation_participants (conversation_id, user_id, role, joined_at) VALUES (?, ?, ?, ?)`, convId, uid, role, now);
13
+ }
14
+ conversation = {
15
+ id: convId,
16
+ type: data.type,
17
+ name: data.name,
18
+ participants: allParticipantIds.map(uid => ({
19
+ userId: uid,
20
+ displayName: '',
21
+ role: uid === ctx.userId ? 'owner' : 'member',
22
+ joinedAt: now,
23
+ presence: ctx.store.getPresence(uid)?.status || 'offline',
24
+ })),
25
+ createdAt: now,
26
+ updatedAt: now,
27
+ unreadCount: 0,
28
+ };
29
+ }
30
+ else {
31
+ const allParticipantIds = [ctx.userId, ...data.participantIds.filter(id => id !== ctx.userId)];
32
+ conversation = {
33
+ id: crypto.randomUUID(),
34
+ type: data.type,
35
+ name: data.name,
36
+ participants: allParticipantIds.map(uid => ({
37
+ userId: uid,
38
+ displayName: '',
39
+ role: uid === ctx.userId ? 'owner' : 'member',
40
+ joinedAt: now,
41
+ presence: ctx.store.getPresence(uid)?.status || 'offline',
42
+ })),
43
+ createdAt: now,
44
+ updatedAt: now,
45
+ unreadCount: 0,
46
+ };
47
+ }
48
+ // Auto-join the creator to the conversation room
49
+ ctx.joinRoom(`conv:${conversation.id}`);
50
+ // Notify the creator
51
+ ctx.push({ event: 'conversation:new', data: conversation });
52
+ // Notify other participants (they haven't joined the room yet)
53
+ if (ctx.emitToUser) {
54
+ for (const uid of data.participantIds) {
55
+ if (uid !== ctx.userId) {
56
+ ctx.emitToUser(uid, { event: 'conversation:new', data: conversation });
57
+ }
58
+ }
59
+ }
60
+ }
61
+ export async function handleConversationJoin(ctx, data) {
62
+ // Verify the user is an actual participant of this conversation
63
+ if (ctx.db) {
64
+ const row = await ctx.db.get('SELECT 1 FROM conversation_participants WHERE conversation_id = ? AND user_id = ?', data.conversationId, ctx.userId);
65
+ if (!row) {
66
+ ctx.push({ event: 'error', data: { code: 'FORBIDDEN', message: 'Not a participant of this conversation' } });
67
+ return;
68
+ }
69
+ }
70
+ ctx.joinRoom(`conv:${data.conversationId}`);
71
+ ctx.store.addConversationMember(data.conversationId, ctx.userId);
72
+ ctx.store.joinConversation(ctx.userId, data.conversationId);
73
+ }
74
+ export function handleConversationLeave(ctx, data) {
75
+ ctx.leaveRoom(`conv:${data.conversationId}`);
76
+ ctx.store.removeConversationMember(data.conversationId, ctx.userId);
77
+ ctx.store.leaveConversation(ctx.userId, data.conversationId);
78
+ }
79
+ export async function handleConversationArchive(ctx, data) {
80
+ // Verify the user is a participant
81
+ if (ctx.db) {
82
+ const row = await ctx.db.get('SELECT 1 FROM conversation_participants WHERE conversation_id = ? AND user_id = ?', data.conversationId, ctx.userId);
83
+ if (!row) {
84
+ ctx.push({ event: 'error', data: { code: 'FORBIDDEN', message: 'Not a participant of this conversation' } });
85
+ return;
86
+ }
87
+ await ctx.db.run(`UPDATE conversations SET archived = ?, updated_at = ? WHERE id = ?`, data.archived ? 1 : 0, new Date().toISOString(), data.conversationId);
88
+ }
89
+ ctx.broadcastAll(`conv:${data.conversationId}`, {
90
+ event: 'conversation:updated',
91
+ data: { id: data.conversationId, archived: data.archived },
92
+ });
93
+ }
94
+ export async function handleConversationMute(ctx, data) {
95
+ if (ctx.db) {
96
+ await ctx.db.run(`UPDATE conversation_participants SET muted = ?, updated_at = ? WHERE conversation_id = ? AND user_id = ?`, data.muted ? 1 : 0, new Date().toISOString(), data.conversationId, ctx.userId);
97
+ }
98
+ // Only notify the requesting user — mute is per-user
99
+ ctx.push({
100
+ event: 'conversation:updated',
101
+ data: { id: data.conversationId, muted: data.muted },
102
+ });
103
+ }
104
+ export async function handleConversationPin(ctx, data) {
105
+ if (ctx.db) {
106
+ await ctx.db.run(`UPDATE conversation_participants SET pinned = ?, updated_at = ? WHERE conversation_id = ? AND user_id = ?`, data.pinned ? 1 : 0, new Date().toISOString(), data.conversationId, ctx.userId);
107
+ }
108
+ // Only notify the requesting user — pin is per-user
109
+ ctx.push({
110
+ event: 'conversation:updated',
111
+ data: { id: data.conversationId, pinned: data.pinned },
112
+ });
113
+ }
@@ -0,0 +1,17 @@
1
+ import type { HandlerContext } from './context.js';
2
+ /**
3
+ * Handle a client requesting a presigned upload URL for a chat file attachment.
4
+ *
5
+ * E2E security model:
6
+ * 1. Client encrypts the file locally (AES-256-GCM with a random key).
7
+ * 2. This handler returns a presigned PUT URL — the server never sees the plaintext.
8
+ * 3. Client PUTs the encrypted blob directly to storage (S3 / local).
9
+ * 4. The file encryption key is included inside the Signal message envelope,
10
+ * encrypted per-recipient. The server cannot decrypt either the file or the key.
11
+ */
12
+ export declare function handleFileUploadRequest(ctx: HandlerContext, data: {
13
+ conversationId: string;
14
+ mimeType: string;
15
+ size: number;
16
+ fileName: string;
17
+ }): Promise<void>;
@@ -0,0 +1,62 @@
1
+ // ── File Upload (Presigned URL) ──────────────────────────────────
2
+ /**
3
+ * Handle a client requesting a presigned upload URL for a chat file attachment.
4
+ *
5
+ * E2E security model:
6
+ * 1. Client encrypts the file locally (AES-256-GCM with a random key).
7
+ * 2. This handler returns a presigned PUT URL — the server never sees the plaintext.
8
+ * 3. Client PUTs the encrypted blob directly to storage (S3 / local).
9
+ * 4. The file encryption key is included inside the Signal message envelope,
10
+ * encrypted per-recipient. The server cannot decrypt either the file or the key.
11
+ */
12
+ export async function handleFileUploadRequest(ctx, data) {
13
+ if (!ctx.storage) {
14
+ ctx.push({
15
+ event: 'file:upload-error',
16
+ data: { code: 'STORAGE_NOT_CONFIGURED', message: 'Storage is not configured on this server.' },
17
+ });
18
+ return;
19
+ }
20
+ // Validate against file upload config
21
+ if (ctx.config.fileUpload) {
22
+ const { maxFileSize, allowedMimeTypes } = ctx.config.fileUpload;
23
+ if (data.size > maxFileSize) {
24
+ ctx.push({
25
+ event: 'file:upload-error',
26
+ data: {
27
+ code: 'FILE_TOO_LARGE',
28
+ message: `File size ${data.size} exceeds maximum allowed size of ${maxFileSize} bytes.`,
29
+ },
30
+ });
31
+ return;
32
+ }
33
+ if (allowedMimeTypes && allowedMimeTypes.length > 0 && !allowedMimeTypes.includes(data.mimeType)) {
34
+ ctx.push({
35
+ event: 'file:upload-error',
36
+ data: {
37
+ code: 'MIME_TYPE_NOT_ALLOWED',
38
+ message: `MIME type '${data.mimeType}' is not allowed.`,
39
+ },
40
+ });
41
+ return;
42
+ }
43
+ }
44
+ // Generate a unique file key scoped to the conversation
45
+ const fileId = crypto.randomUUID();
46
+ const ext = data.fileName.includes('.') ? `.${data.fileName.split('.').pop()}` : '';
47
+ const key = `conversations/${data.conversationId}/${fileId}${ext}`;
48
+ const presigned = await ctx.storage.presignPut(key, {
49
+ mimeType: data.mimeType,
50
+ expiresIn: 300, // 5 minutes to complete the upload
51
+ maxSize: ctx.config.fileUpload?.maxFileSize,
52
+ });
53
+ ctx.push({
54
+ event: 'file:upload-ready',
55
+ data: {
56
+ fileId,
57
+ uploadUrl: presigned.uploadUrl,
58
+ key: presigned.key,
59
+ expiresAt: presigned.expiresAt,
60
+ },
61
+ });
62
+ }
@@ -0,0 +1,30 @@
1
+ import type { Message, MessageAttachment, MessageForward, EncryptedEnvelope } from '../types.js';
2
+ import type { HandlerContext } from './context.js';
3
+ export declare function handleMessageSend(ctx: HandlerContext, data: {
4
+ conversationId: string;
5
+ content: string;
6
+ type: Message['type'];
7
+ replyTo?: string;
8
+ attachment?: MessageAttachment;
9
+ encrypted?: boolean;
10
+ envelope?: EncryptedEnvelope;
11
+ }): Promise<void>;
12
+ export declare function handleMessageRead(ctx: HandlerContext, data: {
13
+ conversationId: string;
14
+ messageId: string;
15
+ }): Promise<void>;
16
+ export declare function handleMessageReact(ctx: HandlerContext, data: {
17
+ messageId: string;
18
+ conversationId: string;
19
+ emoji: string;
20
+ }): Promise<void>;
21
+ export declare function handleMessageEdit(ctx: HandlerContext, data: {
22
+ messageId: string;
23
+ conversationId: string;
24
+ content: string;
25
+ }): Promise<void>;
26
+ export declare function handleMessageDelete(ctx: HandlerContext, data: {
27
+ messageId: string;
28
+ conversationId: string;
29
+ }): Promise<void>;
30
+ export declare function handleMessageForward(ctx: HandlerContext, data: MessageForward): Promise<void>;
@@ -0,0 +1,237 @@
1
+ // ── Messages ────────────────────────────────────────────────────
2
+ export async function handleMessageSend(ctx, data) {
3
+ // ── Membership check ──────────────────────────────────────────
4
+ if (ctx.db) {
5
+ const row = await ctx.db.get('SELECT 1 FROM conversation_participants WHERE conversation_id = ? AND user_id = ?', data.conversationId, ctx.userId);
6
+ if (!row) {
7
+ ctx.push({ event: 'message:error', data: { code: 'FORBIDDEN', message: 'Not a participant of this conversation' } });
8
+ return;
9
+ }
10
+ }
11
+ else {
12
+ const members = ctx.store.getConversationMembers(data.conversationId);
13
+ if (members.size > 0 && !members.has(ctx.userId)) {
14
+ ctx.push({ event: 'message:error', data: { code: 'FORBIDDEN', message: 'Not a participant of this conversation' } });
15
+ return;
16
+ }
17
+ }
18
+ // ── Message length check ──────────────────────────────────────
19
+ const maxLen = ctx.config.maxMessageLength ?? 10_000;
20
+ if (data.content && data.content.length > maxLen) {
21
+ ctx.push({ event: 'message:error', data: { code: 'MESSAGE_TOO_LONG', message: `Message exceeds maximum length of ${maxLen} characters.` } });
22
+ return;
23
+ }
24
+ // ── Rate limit check ──────────────────────────────────────────
25
+ if (ctx.config.rateLimit) {
26
+ const allowed = ctx.store.checkRateLimit(ctx.userId, ctx.config.rateLimit);
27
+ if (!allowed) {
28
+ ctx.push({ event: 'message:error', data: { code: 'RATE_LIMITED', message: 'Message rate limit exceeded. Please wait before sending more messages.' } });
29
+ return;
30
+ }
31
+ }
32
+ // ── File upload validation ────────────────────────────────────
33
+ if (data.attachment && ctx.config.fileUpload) {
34
+ const { maxFileSize, allowedMimeTypes } = ctx.config.fileUpload;
35
+ if (data.attachment.fileSize > maxFileSize) {
36
+ ctx.push({ event: 'message:error', data: { code: 'FILE_TOO_LARGE', message: `File size ${data.attachment.fileSize} exceeds maximum allowed size of ${maxFileSize} bytes.` } });
37
+ return;
38
+ }
39
+ if (allowedMimeTypes && allowedMimeTypes.length > 0 && !allowedMimeTypes.includes(data.attachment.mimeType)) {
40
+ ctx.push({ event: 'message:error', data: { code: 'MIME_TYPE_NOT_ALLOWED', message: `MIME type '${data.attachment.mimeType}' is not allowed.` } });
41
+ return;
42
+ }
43
+ }
44
+ const now = new Date().toISOString();
45
+ let message;
46
+ // For encrypted messages, content is ciphertext — server stores it as opaque blob
47
+ const contentToStore = data.encrypted && data.envelope
48
+ ? JSON.stringify(data.envelope)
49
+ : data.content;
50
+ if (ctx.db) {
51
+ const inserted = await ctx.db.get(`INSERT INTO messages (conversation_id, sender_id, content, type, reply_to, attachment, status, encrypted, created_at)
52
+ VALUES (?, ?, ?, ?, ?, ?, 'sent', ?, ?) RETURNING id`, data.conversationId, ctx.userId, contentToStore, data.type, data.replyTo || null, data.attachment ? JSON.stringify(data.attachment) : null, data.encrypted ? 1 : 0, now);
53
+ message = {
54
+ id: String(inserted?.id ?? 0),
55
+ conversationId: data.conversationId,
56
+ senderId: ctx.userId,
57
+ content: contentToStore,
58
+ type: data.type,
59
+ createdAt: now,
60
+ replyTo: data.replyTo,
61
+ attachment: data.attachment,
62
+ status: 'sent',
63
+ readBy: [],
64
+ encrypted: data.encrypted,
65
+ envelope: data.envelope,
66
+ };
67
+ // Update conversation's last activity
68
+ await ctx.db.run(`UPDATE conversations SET updated_at = ? WHERE id = ?`, now, data.conversationId);
69
+ }
70
+ else {
71
+ // Without DB, create an in-memory message
72
+ message = {
73
+ id: crypto.randomUUID(),
74
+ conversationId: data.conversationId,
75
+ senderId: ctx.userId,
76
+ content: contentToStore,
77
+ type: data.type,
78
+ createdAt: now,
79
+ replyTo: data.replyTo,
80
+ attachment: data.attachment,
81
+ status: 'sent',
82
+ readBy: [],
83
+ encrypted: data.encrypted,
84
+ envelope: data.envelope,
85
+ };
86
+ }
87
+ // Clear typing indicator since the user just sent a message
88
+ ctx.store.clearTyping(data.conversationId, ctx.userId);
89
+ // Notify sender that the message has been persisted
90
+ ctx.push({ event: 'message:status', data: { messageId: message.id, status: 'sent' } });
91
+ // Broadcast to all participants in the conversation
92
+ ctx.broadcastAll(`conv:${data.conversationId}`, { event: 'message:new', data: message });
93
+ // Check if any recipient in the conversation has a connected socket
94
+ const members = ctx.store.getConversationMembers(data.conversationId);
95
+ const hasOnlineRecipient = Array.from(members).some((uid) => uid !== ctx.userId && ctx.store.isUserOnline(uid));
96
+ if (hasOnlineRecipient) {
97
+ if (ctx.db) {
98
+ await ctx.db.run(`UPDATE messages SET status = 'delivered' WHERE id = ? AND status = 'sent'`, message.id);
99
+ }
100
+ message.status = 'delivered';
101
+ ctx.push({ event: 'message:status', data: { messageId: message.id, status: 'delivered' } });
102
+ }
103
+ }
104
+ export async function handleMessageRead(ctx, data) {
105
+ const now = new Date().toISOString();
106
+ const receipt = { userId: ctx.userId, readAt: now };
107
+ if (ctx.db) {
108
+ // Insert read receipt (ignore duplicates)
109
+ await ctx.db.run(`INSERT OR IGNORE INTO read_receipts (message_id, user_id, read_at) VALUES (?, ?, ?)`, data.messageId, ctx.userId, now);
110
+ // Update message status to 'read' if all participants have read it
111
+ await ctx.db.run(`UPDATE messages SET status = 'read' WHERE id = ? AND status != 'read'`, data.messageId);
112
+ }
113
+ ctx.broadcastAll(`conv:${data.conversationId}`, {
114
+ event: 'read-receipt:update',
115
+ data: { conversationId: data.conversationId, messageId: data.messageId, readBy: receipt },
116
+ });
117
+ // Emit message:status so clients can update delivery indicators
118
+ ctx.broadcastAll(`conv:${data.conversationId}`, {
119
+ event: 'message:status',
120
+ data: { messageId: data.messageId, status: 'read' },
121
+ });
122
+ }
123
+ // ── Message Reactions ────────────────────────────────────────────
124
+ export async function handleMessageReact(ctx, data) {
125
+ if (ctx.db) {
126
+ // Toggle: if already reacted with this emoji, remove it
127
+ const existing = await ctx.db.get('SELECT 1 FROM message_reactions WHERE message_id = ? AND user_id = ? AND emoji = ?', data.messageId, ctx.userId, data.emoji);
128
+ if (existing) {
129
+ await ctx.db.run('DELETE FROM message_reactions WHERE message_id = ? AND user_id = ? AND emoji = ?', data.messageId, ctx.userId, data.emoji);
130
+ }
131
+ else {
132
+ await ctx.db.run('INSERT INTO message_reactions (message_id, user_id, emoji) VALUES (?, ?, ?)', data.messageId, ctx.userId, data.emoji);
133
+ }
134
+ // Fetch all reactions for this message
135
+ const reactions = await ctx.db.all('SELECT emoji, COUNT(*) as count, GROUP_CONCAT(user_id) as users FROM message_reactions WHERE message_id = ? GROUP BY emoji', data.messageId);
136
+ ctx.broadcastAll(`conv:${data.conversationId}`, {
137
+ event: 'message:reaction-update',
138
+ data: { messageId: data.messageId, reactions: reactions.map((r) => ({ emoji: r.emoji, count: r.count, users: r.users.split(',') })) },
139
+ });
140
+ }
141
+ else {
142
+ // Without DB, just broadcast the reaction event
143
+ ctx.broadcastAll(`conv:${data.conversationId}`, {
144
+ event: 'message:reaction-update',
145
+ data: { messageId: data.messageId, reactions: [{ emoji: data.emoji, count: 1, users: [ctx.userId] }] },
146
+ });
147
+ }
148
+ }
149
+ // ── Message Edit ────────────────────────────────────────────────
150
+ export async function handleMessageEdit(ctx, data) {
151
+ const now = new Date().toISOString();
152
+ if (ctx.db) {
153
+ // Only allow sender to edit — return early if not the owner
154
+ const msg = await ctx.db.get('SELECT sender_id FROM messages WHERE id = ?', data.messageId);
155
+ if (!msg || msg.sender_id !== ctx.userId)
156
+ return;
157
+ await ctx.db.run('UPDATE messages SET content = ?, updated_at = ? WHERE id = ? AND sender_id = ?', data.content, now, data.messageId, ctx.userId);
158
+ }
159
+ ctx.broadcastAll(`conv:${data.conversationId}`, {
160
+ event: 'message:updated',
161
+ data: { messageId: data.messageId, content: data.content, updatedAt: now, editedBy: ctx.userId },
162
+ });
163
+ }
164
+ // ── Message Delete ──────────────────────────────────────────────
165
+ export async function handleMessageDelete(ctx, data) {
166
+ if (ctx.db) {
167
+ // Only allow sender to delete — return early if not the owner
168
+ const msg = await ctx.db.get('SELECT sender_id FROM messages WHERE id = ?', data.messageId);
169
+ if (!msg || msg.sender_id !== ctx.userId)
170
+ return;
171
+ await ctx.db.run("UPDATE messages SET content = '', type = 'system', updated_at = ? WHERE id = ? AND sender_id = ?", new Date().toISOString(), data.messageId, ctx.userId);
172
+ }
173
+ ctx.broadcastAll(`conv:${data.conversationId}`, {
174
+ event: 'message:deleted',
175
+ data: { messageId: data.messageId, conversationId: data.conversationId, deletedBy: ctx.userId },
176
+ });
177
+ }
178
+ // ── Message Forward ─────────────────────────────────────────────
179
+ export async function handleMessageForward(ctx, data) {
180
+ // Verify user is a participant of both source and target conversations
181
+ if (ctx.db) {
182
+ const inSource = await ctx.db.get('SELECT 1 FROM conversation_participants WHERE conversation_id = ? AND user_id = ?', data.fromConversationId, ctx.userId);
183
+ if (!inSource) {
184
+ ctx.push({ event: 'message:error', data: { code: 'FORBIDDEN', message: 'Not a participant of the source conversation' } });
185
+ return;
186
+ }
187
+ const inTarget = await ctx.db.get('SELECT 1 FROM conversation_participants WHERE conversation_id = ? AND user_id = ?', data.toConversationId, ctx.userId);
188
+ if (!inTarget) {
189
+ ctx.push({ event: 'message:error', data: { code: 'FORBIDDEN', message: 'Not a participant of the target conversation' } });
190
+ return;
191
+ }
192
+ }
193
+ const now = new Date().toISOString();
194
+ if (ctx.db) {
195
+ // Fetch the original message
196
+ const original = await ctx.db.get(`SELECT * FROM messages WHERE id = ?`, data.messageId);
197
+ if (!original)
198
+ return;
199
+ // Insert a forwarded copy into the target conversation
200
+ const fwdInserted = await ctx.db.get(`INSERT INTO messages (conversation_id, sender_id, content, type, status, encrypted, created_at, forwarded_from_conversation_id, forwarded_from_message_id)
201
+ VALUES (?, ?, ?, ?, 'sent', ?, ?, ?, ?) RETURNING id`, data.toConversationId, ctx.userId, original.content, original.type, original.encrypted || 0, now, data.fromConversationId, data.messageId);
202
+ const forwarded = {
203
+ id: String(fwdInserted?.id ?? 0),
204
+ conversationId: data.toConversationId,
205
+ senderId: ctx.userId,
206
+ content: original.content,
207
+ type: original.type,
208
+ createdAt: now,
209
+ status: 'sent',
210
+ readBy: [],
211
+ encrypted: !!original.encrypted,
212
+ forwardedFrom: {
213
+ conversationId: data.fromConversationId,
214
+ messageId: data.messageId,
215
+ },
216
+ };
217
+ await ctx.db.run(`UPDATE conversations SET updated_at = ? WHERE id = ?`, now, data.toConversationId);
218
+ ctx.broadcastAll(`conv:${data.toConversationId}`, { event: 'message:forwarded', data: forwarded });
219
+ }
220
+ else {
221
+ const forwarded = {
222
+ id: crypto.randomUUID(),
223
+ conversationId: data.toConversationId,
224
+ senderId: ctx.userId,
225
+ content: '',
226
+ type: 'text',
227
+ createdAt: now,
228
+ status: 'sent',
229
+ readBy: [],
230
+ forwardedFrom: {
231
+ conversationId: data.fromConversationId,
232
+ messageId: data.messageId,
233
+ },
234
+ };
235
+ ctx.broadcastAll(`conv:${data.toConversationId}`, { event: 'message:forwarded', data: forwarded });
236
+ }
237
+ }
@@ -0,0 +1,15 @@
1
+ import type { PresenceStatus } from '../types.js';
2
+ import type { HandlerContext } from './context.js';
3
+ export declare function handleTypingStart(ctx: HandlerContext, data: {
4
+ conversationId: string;
5
+ }): void;
6
+ export declare function handleTypingStop(ctx: HandlerContext, data: {
7
+ conversationId: string;
8
+ }): void;
9
+ /** Broadcast online status to all conversation rooms when a user connects */
10
+ export declare function handleConnect(ctx: HandlerContext): void;
11
+ export declare function handlePresenceUpdate(ctx: HandlerContext, data: {
12
+ status: PresenceStatus;
13
+ }): void;
14
+ /** Called on disconnect to clean up user state */
15
+ export declare function handleDisconnect(ctx: HandlerContext, socketId: string): void;
@@ -0,0 +1,76 @@
1
+ // ── Typing ──────────────────────────────────────────────────────
2
+ export function handleTypingStart(ctx, data) {
3
+ ctx.store.setTyping(data.conversationId, ctx.userId, () => {
4
+ // Auto-expire: broadcast typing stopped
5
+ ctx.broadcastAll(`conv:${data.conversationId}`, {
6
+ event: 'typing:update',
7
+ data: { conversationId: data.conversationId, userId: ctx.userId, isTyping: false },
8
+ });
9
+ });
10
+ ctx.broadcast(`conv:${data.conversationId}`, {
11
+ event: 'typing:update',
12
+ data: { conversationId: data.conversationId, userId: ctx.userId, isTyping: true },
13
+ });
14
+ }
15
+ export function handleTypingStop(ctx, data) {
16
+ ctx.store.clearTyping(data.conversationId, ctx.userId);
17
+ ctx.broadcast(`conv:${data.conversationId}`, {
18
+ event: 'typing:update',
19
+ data: { conversationId: data.conversationId, userId: ctx.userId, isTyping: false },
20
+ });
21
+ }
22
+ // ── Presence ────────────────────────────────────────────────────
23
+ /** Broadcast online status to all conversation rooms when a user connects */
24
+ export function handleConnect(ctx) {
25
+ const conversations = ctx.store.getUserConversations(ctx.userId);
26
+ if (conversations.size === 0)
27
+ return;
28
+ const entry = ctx.store.getPresence(ctx.userId);
29
+ if (!entry)
30
+ return;
31
+ const payload = {
32
+ event: 'presence:changed',
33
+ data: { userId: ctx.userId, status: entry.status, lastSeen: entry.lastSeen },
34
+ };
35
+ for (const convId of conversations) {
36
+ ctx.broadcastAll(`conv:${convId}`, payload);
37
+ }
38
+ }
39
+ export function handlePresenceUpdate(ctx, data) {
40
+ const entry = ctx.store.setPresence(ctx.userId, data.status);
41
+ const payload = {
42
+ event: 'presence:changed',
43
+ data: { userId: ctx.userId, status: entry.status, lastSeen: entry.lastSeen },
44
+ };
45
+ // Broadcast to all conversation rooms the user has joined
46
+ for (const convId of ctx.store.getUserConversations(ctx.userId)) {
47
+ ctx.broadcastAll(`conv:${convId}`, payload);
48
+ }
49
+ }
50
+ /** Called on disconnect to clean up user state */
51
+ export function handleDisconnect(ctx, socketId) {
52
+ const userId = ctx.store.unmapUserSocket(socketId);
53
+ if (!userId)
54
+ return;
55
+ // Clear all typing indicators for this user
56
+ const clearedConversations = ctx.store.clearAllTypingForUser(userId);
57
+ for (const convId of clearedConversations) {
58
+ ctx.broadcastAll(`conv:${convId}`, {
59
+ event: 'typing:update',
60
+ data: { conversationId: convId, userId, isTyping: false },
61
+ });
62
+ }
63
+ // If user has no more connected sockets, set presence to offline and broadcast
64
+ if (!ctx.store.isUserOnline(userId)) {
65
+ const entry = ctx.store.setPresence(userId, 'offline');
66
+ const payload = {
67
+ event: 'presence:changed',
68
+ data: { userId, status: 'offline', lastSeen: entry.lastSeen },
69
+ };
70
+ // Broadcast offline to all conversation rooms before cleaning up membership
71
+ for (const convId of ctx.store.getUserConversations(userId)) {
72
+ ctx.broadcastAll(`conv:${convId}`, payload);
73
+ }
74
+ ctx.store.removeUserFromAllConversations(userId);
75
+ }
76
+ }
@@ -0,0 +1,5 @@
1
+ export type { HandlerContext } from './handlers/context.js';
2
+ export { handleConversationCreate, handleConversationJoin, handleConversationLeave, handleConversationArchive, handleConversationMute, handleConversationPin, } from './handlers/conversation.js';
3
+ export { handleMessageSend, handleMessageRead, handleMessageReact, handleMessageEdit, handleMessageDelete, handleMessageForward, } from './handlers/messaging.js';
4
+ export { handleTypingStart, handleTypingStop, handleConnect, handlePresenceUpdate, handleDisconnect, } from './handlers/presence.js';
5
+ export { handleFileUploadRequest } from './handlers/file-upload.js';
@@ -0,0 +1,5 @@
1
+ // Barrel re-export — keeps the public API identical for all existing consumers.
2
+ export { handleConversationCreate, handleConversationJoin, handleConversationLeave, handleConversationArchive, handleConversationMute, handleConversationPin, } from './handlers/conversation.js';
3
+ export { handleMessageSend, handleMessageRead, handleMessageReact, handleMessageEdit, handleMessageDelete, handleMessageForward, } from './handlers/messaging.js';
4
+ export { handleTypingStart, handleTypingStop, handleConnect, handlePresenceUpdate, handleDisconnect, } from './handlers/presence.js';
5
+ export { handleFileUploadRequest } from './handlers/file-upload.js';
@@ -0,0 +1,9 @@
1
+ export type { CommunicationConfig, RTCIceServerConfig, MediaConstraintDefaults, MediaTrackConstraintSet, Conversation, Participant, PresenceStatus, PresenceUpdate, Message, MessageStatus, MessageAttachment, ReadReceipt, TypingIndicator, MessageForward, CallType, CallState, CallEndReason, Call, CallParticipant, SignalOffer, SignalIceCandidate, SignalIceRestart, ConnectionQualityReport, CallInitiate, CallResponse, CallHangup, CallMediaToggle, CommunicationClientEvents, CommunicationServerEvents, RateLimitConfig, FileUploadConfig, ReconnectionConfig, EncryptionConfig, PreKey, KeyBundle, EncryptedEnvelope, KeyExchangeRequest, KeyExchangeResponse, NkCommunication, } from './types.js';
2
+ export { CommunicationStore, useCommunicationStore } from './store.js';
3
+ export type { PresenceEntry } from './store.js';
4
+ export type { HandlerContext } from './handlers.js';
5
+ export type { SignalingContext } from './signaling.js';
6
+ export type { EncryptionContext } from './encryption.js';
7
+ export { createCommunicationHandler, createCommunicationApiHandlers } from './server.js';
8
+ export type { CommunicationHandlerOptions } from './server.js';
9
+ export { ensureCommunicationTables } from './schema.js';
@@ -0,0 +1,7 @@
1
+ // ── Types ─────────────────────────────────────────────────────────
2
+ // ── Store ─────────────────────────────────────────────────────────
3
+ export { CommunicationStore, useCommunicationStore } from './store.js';
4
+ // ── Server (main entry points) ────────────────────────────────────
5
+ export { createCommunicationHandler, createCommunicationApiHandlers } from './server.js';
6
+ // ── Schema ────────────────────────────────────────────────────────
7
+ export { ensureCommunicationTables } from './schema.js';
@@ -0,0 +1,18 @@
1
+ interface LinkPreview {
2
+ url: string;
3
+ title: string | null;
4
+ description: string | null;
5
+ image: string | null;
6
+ domain: string;
7
+ }
8
+ interface Db {
9
+ get<T = any>(sql: string, ...params: any[]): Promise<T | undefined>;
10
+ run(sql: string, ...params: any[]): Promise<any>;
11
+ }
12
+ /**
13
+ * Fetch Open Graph metadata from a URL and cache it.
14
+ */
15
+ export declare function fetchLinkPreview(url: string, db?: Db): Promise<LinkPreview | null>;
16
+ /** Extract URLs from message text */
17
+ export declare function extractUrls(text: string): string[];
18
+ export {};