inbox.bh
v0.3.0
Published
Provider-first frontend SDK for inbox.bh REST, Socket.IO, and IndexedDB cache.
Maintainers
Readme
inbox.bh
Framework-agnostic provider SDK for modern frontend apps. inbox.bh works with Vue, React, Nuxt, Vite, and plain browser applications that need provider REST APIs, signed media URLs, Socket.IO realtime events, and optional IndexedDB cache.
Install
npm install inbox.bhGitHub Packages mirror:
npm install @muhasip/inbox-bh --registry=https://npm.pkg.github.comGitHub Packages installs require npm auth for npm.pkg.github.com. When using this mirror, import from @muhasip/inbox-bh and @muhasip/inbox-bh/cache.
Quick Start
import {createProvider} from "inbox.bh"
const provider = createProvider({
auth: {
key: "PROVIDER_KEY",
secret: "PROVIDER_SECRET"
}
})
const sessions = await provider.sessions.list()
const chats = await provider.chats.list("store-wa-1", {limit: 50})
await provider.messages.send({
sessionId: "store-wa-1",
type: "text",
to: "[email protected]",
text: "Merhaba"
})REST methods keep the API envelope intact:
const response = await provider.sessions.list()
if (response.status) {
console.log(response.data.rows)
}Auth
Provider auth uses a key and secret pair. The SDK manages the service endpoint internally, so applications only pass credentials and optional runtime adapters such as fetch or Socket.IO options.
createProvider({
auth: {
key: "PROVIDER_KEY",
secret: "PROVIDER_SECRET"
}
})Provider REST
await provider.health()
await provider.sessions.list()
await provider.sessions.create({sessionId: "store-wa-1", phone: "+90 555 111 22 33"})
await provider.sessions.get("store-wa-1")
await provider.sessions.getQr("store-wa-1")
await provider.sessions.logout("store-wa-1")
await provider.sessions.delete("store-wa-1")
await provider.contacts.search({phone: "905551112233"})
await provider.contacts.list("store-wa-1", {q: "Ali", type: "contact"})
await provider.contacts.get("store-wa-1", "[email protected]")
await provider.chats.list("store-wa-1", {limit: 50, hasLastMessage: true})
await provider.chats.get("store-wa-1", "[email protected]")
await provider.chats.messages("store-wa-1", "[email protected]", {limit: 50})
await provider.messages.list("store-wa-1", {direction: "incoming"})
await provider.messages.get("store-wa-1", "MESSAGE_ID")
await provider.messages.send({sessionId: "store-wa-1", type: "read", targets: []})
await provider.groups.list("store-wa-1")
await provider.groups.get("store-wa-1", "[email protected]")
await provider.profilePhoto.get("store-wa-1", {jid: "[email protected]"})
await provider.media.list("store-wa-1")
const mediaResponse = await provider.media.fetch("store-wa-1", "cdn/path/file.jpg")Signed Media
const url = provider.media.signedUrl("SIGNED_TOKEN")
const response = await provider.media.getSigned("SIGNED_TOKEN")Signed media URLs do not require auth headers. When a token expires, read the related REST record again to receive a fresh URL.
Realtime
Socket.IO path: /ws
provider.realtime.on("message.received", (event) => {
console.log(event.data.message)
})
provider.realtime.onAny((event) => {
console.log(event.event, event.data)
})
provider.realtime.connect()Cache and Offline Sync
inbox.bh/cache adds Dexie-powered IndexedDB cache and a small offline outbox for selected message actions.
import {createProvider} from "inbox.bh"
import {createIndexedDbCache, createProviderSync} from "inbox.bh/cache"
const provider = createProvider({
auth: {
key: "PROVIDER_KEY",
secret: "PROVIDER_SECRET"
}
})
const cache = createIndexedDbCache({key: "PROVIDER_KEY"})
const sync = createProviderSync({provider, cache})
const chats = await sync.chats.list("store-wa-1", {limit: 50})
sync.realtime.bind()
provider.realtime.connect()
const result = await sync.messages.send(
{
sessionId: "store-wa-1",
type: "text",
to: "[email protected]",
text: "Queued when offline"
},
{offline: "queue"}
)
if (result.queued) {
console.log(result.item.localId)
}
await sync.outbox.flush()The cache stores provider data by key and sessionId. Secrets are never written to IndexedDB. Data retention is application-controlled through cache.clear(), cache.clearSession(sessionId), and cache.compact().
Vue 3
import {createProvider} from "inbox.bh"
import {createIndexedDbCache, createProviderSync} from "inbox.bh/cache"
const key = localStorage.getItem("inbox_bh_key") || ""
const secret = localStorage.getItem("inbox_bh_secret") || ""
export const provider = createProvider({
auth: {key, secret}
})
export const sync = createProviderSync({
provider,
cache: createIndexedDbCache({key})
})React
import {useEffect, useState} from "react"
import {createProvider} from "inbox.bh"
import {createIndexedDbCache, createProviderSync} from "inbox.bh/cache"
const key = localStorage.getItem("inbox_bh_key") || ""
const secret = localStorage.getItem("inbox_bh_secret") || ""
const provider = createProvider({auth: {key, secret}})
const sync = createProviderSync({provider, cache: createIndexedDbCache({key})})
export function InboxView() {
const [chats, setChats] = useState([])
useEffect(() => {
sync.chats.list("store-wa-1").then((result) => setChats(result.data.rows))
sync.realtime.bind()
provider.realtime.connect()
}, [])
return <pre>{JSON.stringify(chats, null, 2)}</pre>
}Nuxt
// plugins/inbox.client.ts
import {createProvider} from "inbox.bh"
import {createIndexedDbCache, createProviderSync} from "inbox.bh/cache"
export default defineNuxtPlugin(() => {
const key = localStorage.getItem("inbox_bh_key") || ""
const secret = localStorage.getItem("inbox_bh_secret") || ""
const provider = createProvider({auth: {key, secret}})
return {
provide: {
provider,
inboxSync: createProviderSync({provider, cache: createIndexedDbCache({key})})
}
}
})Error Handling
HTTP error envelopes are thrown as InboxBhApiError. Cache and offline queue errors are thrown as InboxBhCacheError.
import {InboxBhApiError} from "inbox.bh"
import {InboxBhCacheError} from "inbox.bh/cache"
try {
await provider.sessions.get("missing-session")
} catch (error) {
if (error instanceof InboxBhApiError) {
console.log(error.statusCode, error.code, error.details)
}
if (error instanceof InboxBhCacheError) {
console.log(error.code, error.details)
}
}