{"_id":"@codasignal/routo-messaging","_rev":"4-d62488c8941da133c01971afeb05afbe","name":"@codasignal/routo-messaging","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.1":{"name":"@codasignal/routo-messaging","version":"0.1.1","keywords":["chat","expo","messaging","react-native","realtime","routo","websocket"],"license":"Apache-2.0","_id":"@codasignal/routo-messaging@0.1.1","maintainers":[{"name":"falconiererb","email":"hello@codasignal.com"}],"homepage":"https://routo.io","bugs":{"url":"https://github.com/CodaSignal/routo.io/issues"},"dist":{"shasum":"987baf9c7916b20cd5f485be4ab381f2dfcdb053","tarball":"https://registry.npmjs.org/@codasignal/routo-messaging/-/routo-messaging-0.1.1.tgz","fileCount":28,"integrity":"sha512-5mScs/D1qEOh20ftpZK6ifw73PPuuzEsj6tNTec5NfuX1JG9olQsun3asrXXQ9dsiuS8qEY4FTMVX/TBLZpsIg==","signatures":[{"sig":"MEYCIQDyaJTCLLbKv9deCmfieHzcVAF47FGPzw8MSc2Ssa0dCAIhAI8+VoCio5EfVqiTFJP6VPh+jXxCwfwjbUy6uz2nmKLx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":238159},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./expo":{"types":"./dist/expo/index.d.ts","import":"./dist/expo/index.mjs","require":"./dist/expo/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.mjs","require":"./dist/react/index.cjs"},"./package.json":"./package.json","./storage/indexeddb":{"types":"./dist/store/indexeddb.d.ts","import":"./dist/store/indexeddb.mjs","require":"./dist/store/indexeddb.cjs"}},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","check-types":"tsc --noEmit && tsc --noEmit -p tsconfig.lib.json"},"_npmUser":{"name":"falconiererb","email":"hello@codasignal.com"},"repository":{"url":"git+https://github.com/CodaSignal/routo.io.git","type":"git","directory":"packages/messaging-sdk"},"_npmVersion":"10.8.2","description":"Realtime messaging client for Routo — web, iOS and Android (Expo), with offline persistence","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","dependencies":{"zod":"^4.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^3.1.1","typescript":"^5.9.2","@types/react":"^19.1.10","@cloudflare/workers-types":"^4.20251011.0","@routo/messaging-contracts":"workspace:*","@cloudflare/vitest-pool-workers":"^0.8.62"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","expo-sqlite":"*","expo-secure-store":"*","expo-notifications":"*"},"peerDependenciesMeta":{"react":{"optional":true},"expo-sqlite":{"optional":true},"expo-secure-store":{"optional":true},"expo-notifications":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/routo-messaging_0.1.1_1785292650744_0.3213901138731923","host":"s3://npm-registry-packages-npm-production"},"deprecated":"This package is no longer maintained."},"0.1.2":{"name":"@codasignal/routo-messaging","version":"0.1.2","keywords":["chat","expo","messaging","react-native","realtime","routo","websocket"],"license":"Apache-2.0","_id":"@codasignal/routo-messaging@0.1.2","maintainers":[{"name":"falconiererb","email":"hello@codasignal.com"}],"homepage":"https://routo.io","bugs":{"url":"https://github.com/CodaSignal/routo.io/issues"},"dist":{"shasum":"9daf1583242a6962abe50df206eda0934059de77","tarball":"https://registry.npmjs.org/@codasignal/routo-messaging/-/routo-messaging-0.1.2.tgz","fileCount":28,"integrity":"sha512-z3+gmTIsABW1C62D5DUd+g30PEfXJKi2JaIThmsf+VE1ifWSuf3CBwuA+PFADN8CWBH37PDKq87zudeUq5tQ1A==","signatures":[{"sig":"MEYCIQDJfXe5jo2WZwM6eECqsuX+EuVYltmBsKN6tWnqDdeNKwIhAJQvffZMggaqGM6+UebncSOHLElqu7TKI2BaWOIKOIMP","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":239913},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./expo":{"types":"./dist/expo/index.d.ts","import":"./dist/expo/index.mjs","require":"./dist/expo/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.mjs","require":"./dist/react/index.cjs"},"./package.json":"./package.json","./storage/indexeddb":{"types":"./dist/store/indexeddb.d.ts","import":"./dist/store/indexeddb.mjs","require":"./dist/store/indexeddb.cjs"}},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","check-types":"tsc --noEmit && tsc --noEmit -p tsconfig.lib.json"},"_npmUser":{"name":"falconiererb","email":"hello@codasignal.com"},"repository":{"url":"git+https://github.com/CodaSignal/routo.io.git","type":"git","directory":"packages/messaging-sdk"},"_npmVersion":"10.8.2","description":"Realtime messaging client for Routo — web, iOS and Android (Expo), with offline persistence","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","dependencies":{"zod":"4.1.12"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^3.1.1","typescript":"^5.9.2","@types/react":"^19.1.10","@cloudflare/workers-types":"4.20251014.0","@routo/messaging-contracts":"workspace:*","@cloudflare/vitest-pool-workers":"^0.8.62"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","expo-sqlite":"*","expo-secure-store":"*","expo-notifications":"*"},"peerDependenciesMeta":{"react":{"optional":true},"expo-sqlite":{"optional":true},"expo-secure-store":{"optional":true},"expo-notifications":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/routo-messaging_0.1.2_1786511880142_0.4746835822327711","host":"s3://npm-registry-packages-npm-production"},"deprecated":"This package is no longer maintained."},"0.1.3":{"name":"@codasignal/routo-messaging","version":"0.1.3","keywords":["chat","expo","messaging","react-native","realtime","routo","websocket"],"license":"Apache-2.0","_id":"@codasignal/routo-messaging@0.1.3","maintainers":[{"name":"falconiererb","email":"hello@codasignal.com"}],"homepage":"https://routo.io","bugs":{"url":"https://github.com/CodaSignal/routo.io/issues"},"dist":{"shasum":"986cb9a2cc2580dbfe4c1c0bb289785763b29500","tarball":"https://registry.npmjs.org/@codasignal/routo-messaging/-/routo-messaging-0.1.3.tgz","fileCount":28,"integrity":"sha512-p6Eq8Ui/hr7UDZXgKxj9GhhAtEEmql+8Gekf8frEmKtklB2ccKuUkg7Qmk+pwiU3Jr+kBSvfaEQFM4Te1YvWtw==","signatures":[{"sig":"MEUCIQC7/apA/7VAAGAUnYIlj+FdQQcH5Em0ig2AEa3mtt8nkgIgNAntun1BW11nfqYnJVV9FuOfWpSglpPrZPYXwojij74=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":240543},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./expo":{"types":"./dist/expo/index.d.ts","import":"./dist/expo/index.mjs","require":"./dist/expo/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.mjs","require":"./dist/react/index.cjs"},"./package.json":"./package.json","./storage/indexeddb":{"types":"./dist/store/indexeddb.d.ts","import":"./dist/store/indexeddb.mjs","require":"./dist/store/indexeddb.cjs"}},"gitHead":"7d9b5fbafbddd04a9863beac622016bd6c9fb66a","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","check-types":"tsc --noEmit && tsc --noEmit -p tsconfig.lib.json"},"_npmUser":{"name":"falconiererb","email":"hello@codasignal.com"},"repository":{"url":"git+https://github.com/CodaSignal/routo.io.git","type":"git","directory":"packages/messaging-sdk"},"_npmVersion":"12.0.2","description":"Realtime messaging client for Routo — web, iOS and Android (Expo), with offline persistence","directories":{},"sideEffects":false,"_nodeVersion":"22.23.2","dependencies":{"zod":"4.1.12"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^3.1.1","typescript":"^5.9.2","@types/react":"^19.1.10","@cloudflare/workers-types":"4.20251014.0","@routo/messaging-contracts":"workspace:*","@cloudflare/vitest-pool-workers":"^0.8.62"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","expo-sqlite":"*","expo-secure-store":"*","expo-notifications":"*"},"peerDependenciesMeta":{"react":{"optional":true},"expo-sqlite":{"optional":true},"expo-secure-store":{"optional":true},"expo-notifications":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/routo-messaging_0.1.3_1787038192031_0.2640898605577511","host":"s3://npm-registry-packages-npm-production"},"deprecated":"This package is no longer maintained."}},"time":{"created":"2026-07-29T02:37:30.639Z","modified":"2026-09-03T16:33:28.458Z","0.1.1":"2026-07-29T02:37:30.920Z","0.1.2":"2026-08-12T05:18:00.303Z","0.1.3":"2026-08-18T07:29:52.174Z"},"bugs":{"url":"https://github.com/CodaSignal/routo.io/issues"},"license":"Apache-2.0","homepage":"https://routo.io","keywords":["chat","expo","messaging","react-native","realtime","routo","websocket"],"repository":{"url":"git+https://github.com/CodaSignal/routo.io.git","type":"git","directory":"packages/messaging-sdk"},"description":"Realtime messaging client for Routo — web, iOS and Android (Expo), with offline persistence","maintainers":[{"name":"falconiererb","email":"hello@codasignal.com"}],"readme":"# @codasignal/routo-messaging\n\nRealtime messaging client for [Routo](https://routo.io) — one package for web, iOS and Android (Expo).\n\nIt owns the parts that are tedious to get right: WebSocket lifecycle and backoff, JWT refresh,\n`seq` ordering, catch-up and gap recovery after a reconnect, optimistic sends, and an offline queue.\n\n```bash\nbun add @codasignal/routo-messaging\n```\n\n## Quick start\n\n```ts\nimport { createMessagingClient } from \"@codasignal/routo-messaging\";\n\nconst messaging = createMessagingClient({\n  baseUrl: \"https://messaging.routo.io\",\n  // YOUR backend mints this with your Routo API key — never ship the key to a client.\n  tokenProvider: () => fetch(\"/api/routo-token\").then((response) => response.text()),\n});\n\nconst channel = messaging.channel(\"room-1\");\nchannel.on(\"message\", (message) => console.info(message.text));\nawait channel.connect();\nawait channel.send({ text: \"hello\" });\n```\n\n### React / React Native\n\n```tsx\nimport { MessagingProvider, useChannel } from \"@codasignal/routo-messaging/react\";\n\nfunction Room() {\n  const { messages, send, onlineUserIds, status } = useChannel(\"room-1\");\n  return (\n    <>\n      {messages.map((message) => (\n        <Text key={message.id} dimmed={message.delivery === \"pending\"}>\n          {message.text}\n        </Text>\n      ))}\n      <Button onPress={() => send({ text: \"hi\" })} disabled={status === \"failed\"} />\n    </>\n  );\n}\n```\n\n`useChannel` connects on mount and disconnects on unmount. Handles are refcounted, so several\ncomponents can use the same channel over one socket.\n\n### Persistence\n\nMessages live in memory unless you pass a store:\n\n```ts\n// Web\nimport { IndexedDbMessageStore } from \"@codasignal/routo-messaging/storage/indexeddb\";\n// React Native (needs expo-sqlite)\nimport { ExpoSqliteMessageStore } from \"@codasignal/routo-messaging/expo\";\n\ncreateMessagingClient({ baseUrl, tokenProvider, store: new ExpoSqliteMessageStore() });\n```\n\nRows are namespaced by `(appId, userId)`, so a shared device never shows one account's history to\nanother. A store you pass in is yours: `client.close()` leaves it open.\n\n### Expo extras\n\n```ts\nimport {\n  bindAppState,\n  createSecureStoreTokenCache,\n  registerForPush,\n} from \"@codasignal/routo-messaging/expo\";\n\ncreateMessagingClient({ baseUrl, tokenProvider, tokenCache: createSecureStoreTokenCache() });\nconst unbind = await bindAppState(messaging); // disconnect in background, catch up on return\n```\n\nOptional peers, installed only if you use them: `expo-sqlite`, `expo-secure-store`,\n`expo-notifications`, `expo-crypto` (UUIDs on Hermes — otherwise pass `idFactory`).\n\n> **Push notifications are not delivered by Routo.** `registerForPush()` asks for permission and\n> hands you the Expo push token; storing it and sending notifications is your backend's job. Routo\n> has no device registry and no offline fan-out yet.\n\n## How it behaves\n\n| Situation | What the client does |\n|---|---|\n| Reconnect | Fetches `?after=<lastSeq>`, paging until caught up, before applying live frames |\n| A frame skips ahead | Fetches the missing range, then applies everything in `seq` order |\n| You send while offline | Queues it (`delivery: \"pending\"`), flushes on reconnect; the server de-duplicates |\n| `send()` resolves | As soon as the message is durably queued — **not** on the ack, which may never come offline |\n| Ack arrives | The same row becomes `delivery: \"sent\"` with the server `id`/`seq`, via `message:updated` |\n| Token expires | Re-mints through `tokenProvider` and reconnects; a rejected fresh token fails instead of looping |\n| Not a channel member | Terminal `failed` status with a `MessagingAuthError` — no retry storm |\n| Storage fails | Degrades to memory, emits a `store.degraded` diagnostic, keeps messaging |\n\nTune `historyPageSize` (default 100, the server maximum) to trade round trips for\nsmaller responses on constrained clients.\n\n## API\n\n- `createMessagingClient(options)` → `MessagingClient`\n- `client.channel(id)`, `client.listMyChannels()`, `client.activeChannels()`, `client.close()`\n- `channel.connect() / disconnect() / send() / loadOlder() / members() / messages() / onlineUserIds() / on(event, listener)`\n- Events: `message`, `message:updated`, `message:deleted`, `presence`, `members`, `status`, `error`\n- Errors: `MessagingAuthError`, `MessagingConnectionError`, `MessagingRateLimitError`,\n  `MessagingFrameError`, `MessagingTokenError` — each with `code` and `retryable`\n\nServer limits are enforced before a frame leaves: 4000-character text, 8 KB `data`, 10 messages/second.\n\n## Requirements\n\n`zod` is the only runtime dependency. The runtime needs `fetch`, `WebSocket`, `TextEncoder`/\n`TextDecoder`, and a crypto source. Pass `socketFactory` or `idFactory` if yours lacks the last two.\nReact Native has shipped the text codecs since 0.74 (Expo SDK 51); on anything older, add a\npolyfill such as `text-encoding` before creating the client.\n\n## License\n\nApache-2.0\n","readmeFilename":"README.md"}