{"_id":"@basalam-saas/chat-sdk","_rev":"5-913a626cb8787e8293e478bd493bbdb0","name":"@basalam-saas/chat-sdk","dist-tags":{"latest":"0.5.0"},"versions":{"0.1.0":{"name":"@basalam-saas/chat-sdk","version":"0.1.0","keywords":["chat","realtime","websocket","messaging","sdk","typescript","chat-as-a-service"],"author":{"name":"Basalam"},"license":"MIT","_id":"@basalam-saas/chat-sdk@0.1.0","maintainers":[{"name":"cina_pm","email":"cinapm375@gmail.com"}],"homepage":"https://chat.titanapp.dev/docs","dist":{"shasum":"39ce143f4d1092b5abb4190180b92bec2a19fb8c","tarball":"https://registry.npmjs.org/@basalam-saas/chat-sdk/-/chat-sdk-0.1.0.tgz","fileCount":12,"integrity":"sha512-bi1ie+NB+pxFwetf0JUAYi2OtJFkaT5wR1765ZJLlroKf4BqHBIWVVUABiwQ8OTAUjw/FEsc1uC84WsmnGeJSA==","signatures":[{"sig":"MEQCICB89ocxhfxLGEtJPhNfAI9NT/ZlM6LuJn4U0dvPVfdzAiAhXZ3XjfGzApaK8omgts70ayPMMdnwU/EITJFOhb4tiA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":38574},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"c3fd3a7e4ee8cca53fe20f16ffb587a1a500767d","scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"cina_pm","email":"cinapm375@gmail.com"},"repository":{"url":"git+https://git.basalam.dev/generalization/chat-as-a-service.git","type":"git","directory":"sdk-js"},"_npmVersion":"11.12.1","description":"TypeScript client for chat-as-a-service — REST + WebSocket with reconnect, seq-dedup, and backfill built in.","directories":{},"sideEffects":false,"_nodeVersion":"26.0.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/chat-sdk_0.1.0_1786863625383_0.7428606526398309","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@basalam-saas/chat-sdk","version":"0.2.0","keywords":["chat","realtime","websocket","messaging","sdk","typescript","chat-as-a-service"],"author":{"name":"Basalam"},"license":"MIT","_id":"@basalam-saas/chat-sdk@0.2.0","maintainers":[{"name":"cina_pm","email":"cinapm375@gmail.com"}],"homepage":"https://chat.titanapp.dev/","dist":{"shasum":"aa693131d48eb28dec15fb1de37434ae5712e80b","tarball":"https://registry.npmjs.org/@basalam-saas/chat-sdk/-/chat-sdk-0.2.0.tgz","fileCount":12,"integrity":"sha512-DzesQWzrUgXbUC+Zw0gNjJuLZiNG8mbRKRMEjIls3mzQ9NgT0r/mxltfPmsxzH8gTcK3bu9kAGmQDrtNBQQ32w==","signatures":[{"sig":"MEUCIQDVsxUV35prNiBvNusZgdmxPKpxHFWd3E3jE2ck/9B2OwIgTGmZ9b/NSZJ2X59ASooJBK5urvAUhtWp1nVQfv4TaWQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":43610},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"5ef31c94275db69e26c4eb7a49ca848017050c06","scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"cina_pm","email":"cinapm375@gmail.com"},"repository":{"url":"git+https://git.basalam.dev/generalization/chat-as-a-service.git","type":"git","directory":"sdk-js"},"_npmVersion":"11.12.1","description":"TypeScript client for chat-as-a-service — REST + WebSocket with reconnect, seq-dedup, and backfill built in.","directories":{},"sideEffects":false,"_nodeVersion":"26.0.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/chat-sdk_0.2.0_1788422734666_0.4645037771852354","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@basalam-saas/chat-sdk","version":"0.3.0","keywords":["chat","realtime","websocket","messaging","sdk","typescript","chat-as-a-service"],"author":{"name":"Basalam"},"license":"MIT","_id":"@basalam-saas/chat-sdk@0.3.0","maintainers":[{"name":"cina_pm","email":"cinapm375@gmail.com"}],"homepage":"https://chat.titanapp.dev/","dist":{"shasum":"0e039dd850d82e6f6a9acad2d4652aa16f582cca","tarball":"https://registry.npmjs.org/@basalam-saas/chat-sdk/-/chat-sdk-0.3.0.tgz","fileCount":12,"integrity":"sha512-FA8vYXUwl2D/6G2NsPefpHbhCv2T2AFvtMd8AV7BDG8Sukis4uS1Vn54ZOUk3hXd8kaVa4VTun1/HkCxVkaltA==","signatures":[{"sig":"MEYCIQCq+nxcp1+lREH+u+db29dTEHkFdK3Mh1tAkQ3xHsyLQQIhAJk+4MyQ1RRkuiggCHHfxwfy7DLSc42GkRceTKQHEL60","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":44986},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"e317c70130da8bc09848be9305591d927436a429","scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"cina_pm","email":"cinapm375@gmail.com"},"repository":{"url":"git+https://git.basalam.dev/generalization/chat-as-a-service.git","type":"git","directory":"sdk-js"},"_npmVersion":"11.12.1","description":"TypeScript client for chat-as-a-service — REST + WebSocket with reconnect, seq-dedup, and backfill built in.","directories":{},"sideEffects":false,"_nodeVersion":"26.0.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/chat-sdk_0.3.0_1788423189303_0.7738356615587372","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@basalam-saas/chat-sdk","version":"0.4.0","keywords":["chat","realtime","websocket","messaging","sdk","typescript","chat-as-a-service"],"author":{"name":"Basalam"},"license":"MIT","_id":"@basalam-saas/chat-sdk@0.4.0","maintainers":[{"name":"cina_pm","email":"cinapm375@gmail.com"}],"homepage":"https://chat.titanapp.dev/","dist":{"shasum":"b0a2933eceadcc600a40b929fa399a1ab15bfdd7","tarball":"https://registry.npmjs.org/@basalam-saas/chat-sdk/-/chat-sdk-0.4.0.tgz","fileCount":12,"integrity":"sha512-/GbjKkGRpYV5WSUs0Jx9PrBLV75nk/7gRFTWjFD8q8ud2in1+o10zzhA3drw9VmooemgGKxQHeKoXUleZ0/+8g==","signatures":[{"sig":"MEQCIBQ9IIb0NX4Z3hSeYRApx5LNeBoMTdJInGC2M9AnR31nAiBxOiw8jocQS5mOQTN+NMBffmeh/i+QpTAISyiHNlY1Cw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53006},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"4ae071f54c8361524b88726978376a5c3939b9e5","scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"cina_pm","email":"cinapm375@gmail.com"},"repository":{"url":"git+https://git.basalam.dev/generalization/chat-as-a-service.git","type":"git","directory":"sdk-js"},"_npmVersion":"11.12.1","description":"TypeScript client for chat-as-a-service — REST + WebSocket with reconnect, seq-dedup, and backfill built in.","directories":{},"sideEffects":false,"_nodeVersion":"26.0.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/chat-sdk_0.4.0_1788683496948_0.8844335243794887","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@basalam-saas/chat-sdk","version":"0.5.0","description":"TypeScript client for chat-as-a-service — REST + WebSocket with reconnect, seq-dedup, and backfill built in.","type":"module","main":"dist/index.js","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"sideEffects":false,"keywords":["chat","realtime","websocket","messaging","sdk","typescript","chat-as-a-service"],"license":"MIT","author":{"name":"Basalam"},"homepage":"https://chat.titanapp.dev/","repository":{"type":"git","url":"git+https://git.basalam.dev/generalization/chat-as-a-service.git","directory":"sdk-js"},"engines":{"node":">=18"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build"},"devDependencies":{"typescript":"^5.6.0"},"gitHead":"84631b50f60cdd0a8ed1b507be89f01fb955673a","_id":"@basalam-saas/chat-sdk@0.5.0","_nodeVersion":"26.0.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-0uzh2TRmKFVJlKlRqhibbvGszEeB+wlAJt13erqLh8n5dk/MZeWAzx7FkKsl+gd7dJe2u2OpDc7BRIHPedY/dQ==","shasum":"e717692293a055a0a3fbf672f500d91c2eef0a29","tarball":"https://registry.npmjs.org/@basalam-saas/chat-sdk/-/chat-sdk-0.5.0.tgz","fileCount":12,"unpackedSize":57730,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEe/u+vfyOM/7xrMNaV3AirriiEGQlmEWTFV29pxFYP9AiEA9kP7fdf9mA97gGWPdIC92yapslXy0TdZU6m43culUtM="}]},"_npmUser":{"name":"cina_pm","email":"cinapm375@gmail.com"},"directories":{},"maintainers":[{"name":"cina_pm","email":"cinapm375@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/chat-sdk_0.5.0_1788799321276_0.18892682350184908"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-16T07:00:25.182Z","modified":"2026-09-07T16:42:01.590Z","0.1.0":"2026-08-16T07:00:25.576Z","0.2.0":"2026-09-03T08:05:34.787Z","0.3.0":"2026-09-03T08:13:09.430Z","0.4.0":"2026-09-06T08:31:37.067Z","0.5.0":"2026-09-07T16:42:01.414Z"},"author":{"name":"Basalam"},"license":"MIT","homepage":"https://chat.titanapp.dev/","keywords":["chat","realtime","websocket","messaging","sdk","typescript","chat-as-a-service"],"repository":{"type":"git","url":"git+https://git.basalam.dev/generalization/chat-as-a-service.git","directory":"sdk-js"},"description":"TypeScript client for chat-as-a-service — REST + WebSocket with reconnect, seq-dedup, and backfill built in.","maintainers":[{"name":"cina_pm","email":"cinapm375@gmail.com"}],"readme":"# @basalam-saas/chat-sdk\n\nTypeScript client for **chat-as-a-service** — REST + WebSocket, with reconnect and\nmessage **deduplication + backfill** built in so you don't have to get the hard\nparts right yourself.\n\n```bash\nnpm install @basalam-saas/chat-sdk   # (or build from source: npm install && npm run build)\n```\n\n## 1. Your backend mints a user token\n\nThe chat service never sees your users' passwords. When a user logs into *your*\napp, your backend mints a short-lived JWT signed with the **per-tenant signing\nsecret** the chat service gave you (HS256), and hands it to your frontend:\n\n```ts\nimport jwt from \"jsonwebtoken\";\n\nconst token = jwt.sign(\n  { iss: \"<your-tenant-slug>\", sub: user.id, name: user.displayName, avatar: user.avatarUrl },\n  process.env.CHAT_SIGNING_SECRET,         // issued by the chat service, kept server-side\n  { algorithm: \"HS256\", expiresIn: \"1h\" },\n);\n```\n\n`sub` is your own user id (any string). The chat service JIT-creates the user on\nfirst use. (Server-to-server calls — provisioning conversations, registering\nwebhooks — use your **API key** instead, not this user token.)\n\n## 2. Your frontend talks to chat\n\n```ts\nimport { ChatClient } from \"@basalam-saas/chat-sdk\";\n\nconst chat = new ChatClient({\n  baseUrl: \"https://chat.example.ir/api/v1\",\n  wsUrl: \"wss://chat.example.ir/ws\",\n  token,                                   // from step 1\n});\n\n// Receive in real time. The SDK dedups by (conversation, seq) and backfills\n// anything missed across a reconnect — you just render what arrives.\nchat.on(\"message\", (m) => appendToUI(m));\nchat.on(\"typing\", (t) => showTyping(t));\nchat.connect();\n\n// Start a 1:1 and send.\nconst convo = await chat.createConversation({ type: \"direct\", participantExternalIds: [\"seller-42\"] });\nawait chat.sendMessage(convo.id, { body: \"Hi!\", clientMsgId: crypto.randomUUID() });\n\n// Quote-reply: quote an earlier message in the SAME conversation. It comes back\n// on every read as m.reply_to = { id, seq, sender_user_id, type, body, deleted_at }.\nawait chat.sendMessage(convo.id, { body: \"on it\", replyToMessageId: lastMessage.id });\n\n// History (seq cursor — not offset).\nconst { items } = await chat.getMessages(convo.id, { limit: 50 });\n\n// Inbox: each row carries the newest message, ready to render as the preview line.\nconst { items: convos } = await chat.listConversations();\nconvos.forEach((c) => renderRow(c.title, c.last_message?.body, c.unread_count));\n\n// Seen: every message carries its own state (see below).\nconst ticks = m.seen_by_all ? \"✓✓\" : `✓ ${m.seen_by_count}`;\n\n// Read receipts, typing, attachments.\nawait chat.markRead(convo.id, convo.last_message_seq);\nchat.sendTyping(convo.id, true);\nconst attachmentId = await chat.upload(file);            // request → PUT to Ceph → confirm\nawait chat.sendMessage(convo.id, { attachmentId });\n```\n\n## Groups: members and muting\n\n```ts\n// Owner/admin only. Ids are YOUR external_user_ids; unknown users are created.\nawait chat.addMembers(convo.id, [\"seller-42\", \"support-7\"]);\n\n// Remove someone (owner/admin). The owner can't be removed.\nawait chat.removeMember(convo.id, \"seller-42\");   // → updated Conversation\n\n// Leave it yourself. Resolves to nothing — you're not a member any more,\n// so there's no conversation left to return. Drop it from your UI.\nawait chat.leave(convo.id, myExternalUserId);\n\n// Mute/unmute FOR YOURSELF; read it back from convo.my_muted.\nawait chat.setMuted(convo.id, true);\n```\n\nEveryone in the conversation gets `member.added` / `member.removed`, and a removed\nuser is notified on their own channel so their client can drop it.\n\n> **What muting does — and doesn't.** `setMuted` is a per-member flag, private to\n> you: nobody else can see it. It changes **nothing** server-side. Messages still\n> arrive over the socket, still count toward `unread_count`, and still trigger the\n> offline webhook. It exists so *you* can act on it: skip the sound or badge in\n> your UI, and skip the push in your webhook handler. If you mute a conversation\n> and keep sending pushes, the user still gets notified.\n\n## Broadcast channels\n\nA channel is an announcement room: your admins post, everyone else reads. It\nbehaves like any other conversation for reading — it appears in\n`listConversations()`, carries `unread_count` and history, and new posts arrive\non the `message` event — but subscribers can't post, and can't see each other.\n\n```ts\n// End users opt themselves in / out.\nawait chat.subscribe(channelId);\nawait chat.unsubscribe(channelId);\n\n// Reading is identical to any conversation.\nconst { items } = await chat.getMessages(channelId, { limit: 50 });\n```\n\nChannels are **created and populated by your backend**, server-to-server with\nyour API key — an end user can't spin one up and mass-subscribe people:\n\n```bash\nPOST /api/v1/channels                       { title, admin_external_ids }\nPOST /api/v1/channels/{id}/subscribers      { external_user_ids }   # bulk\nDELETE /api/v1/channels/{id}/subscribers/{external_user_id}\nGET  /api/v1/channels/{id}/subscribers?page=1                       # paginated\n```\n\nWhat differs from a group, and why:\n\n| | |\n|---|---|\n| `conversation.type` | `\"channel\"` |\n| `subscriber_count` | how many subscribe (groups: `null`) |\n| `members` | the **admins only** — subscribers are not enumerable from a conversation payload |\n| Posting | admins only; anyone else gets **403** |\n| `seen_by_count` / `seen_by_all` | always `0` / `false` — \"seen by 8,432\" isn't a thing a broadcast UI shows |\n| `typing` / `read` events | not emitted |\n| `unread_count` | **works normally** |\n\n## Reliability (handled for you)\n\n- **Send is idempotent** — pass a `clientMsgId`; a retry returns the original message.\n- **Dedup** — the live socket and a post-reconnect backfill overlap; the SDK drops\n  duplicates by `(conversation_id, seq)`.\n- **Reconnect + backfill** — on reconnect the SDK refetches `after_seq` per\n  conversation, so a dropped socket never loses messages.\n- **Token refresh** — call `chat.setToken(newJwt)` before expiry; it re-auths the\n  live socket in place (listen for `auth.expired` as a backstop).\n\n## Seen status (read it once)\n\nEvery `Message` carries `seen_by_count` (how many **other** members have read it — the sender is\nnever counted) and `seen_by_all`. They're derived from each member's read cursor rather than stored\nper message, which is what keeps read state O(members) instead of O(members × messages).\n\nTwo consequences worth knowing:\n\n- They're a **snapshot at fetch time**, kept live by the `read` event. That event carries an\n  absolute `last_read_seq`, so re-applying it is harmless. It is sent to **every** member including\n  the reader, so your own other tabs/devices stay in sync — and it's suppressed entirely when a\n  cursor doesn't actually move, so replying to it with `markRead()` can't loop.\n- They are **not** replayed by `changedSinceEvent`. After a long disconnect, refetch the recent page\n  (or the conversation) to resync counts.\n\nNeed to know **who** saw a message, not just how many? Each `conversation.members[]` entry carries\n`last_read_seq`, so you can derive it yourself — and unlike the `read` event (which only reports\ncursor *moves*), this gives you the state at load time:\n\n```ts\nconst readers = convo.members\n  .filter((mem) => mem.user_id !== m.sender_user_id && mem.last_read_seq >= m.seq)\n  .map((mem) => mem.external_user_id);          // → [\"ali\", \"sara\"]\n```\n\nKeep it current by applying `read` events to your local copy of `members`:\n\n```ts\nchat.on(\"read\", (e) => {\n  const mem = convo.members.find((x) => x.external_user_id === e.user_id);\n  if (mem) mem.last_read_seq = e.last_read_seq;   // absolute, so this is idempotent\n});\n```\n\n`read.user_id` is your **external** user id; `Message.sender_user_id` is the platform's internal\nUUID. `conversation.members[]` carries both, if you need to map between them.\n\n## API surface\n\n`me()` · `listConversations()` · `getConversation()` · `createConversation()` ·\n`getMessages(id, {afterSeq,beforeSeq,changedSinceEvent,limit})` ·\n`sendMessage(id, {body,attachmentId,clientMsgId,replyToMessageId})` · `editMessage()` ·\n`deleteMessage()` · `markRead()` · `addMembers(id, ids)` · `removeMember(id, ext)` ·\n`leave(id, myExt)` · `setMuted(id, bool)` · `subscribe(channelId)` ·\n`unsubscribe(channelId)` · `upload(file)` · `getDownloadUrl()` ·\n`getPresence(ids)` · `connect()` / `disconnect()` / `setToken()` / `sendTyping()`.\n\nEvents: `message`, `message.updated`, `message.deleted`, `typing`, `presence`,\n`read`, `open`, `reconnect`, `close`, `auth.expired`.\n\n## Offline push\n\nFor users who are offline (no live socket), the chat service calls **your**\nbackend's registered webhook (HMAC-signed) so you can send a push notification —\nthe chat service never touches APNs/FCM. See `backend/CLAUDE.md` → webhooks.\n\nThe full wire contract (envelope, cursors, event shapes) lives in\n`backend/CLAUDE.md` — this SDK is a faithful mirror of it.\n","readmeFilename":"README.md"}