Skip to main content

Realtime

frappe-js-client/realtime is an optional entry. Core frappe-js-client stays zero-dependency. Install the peer only if you use this module:

pnpm add socket.io-client
import { createFrappeClient, tokenAuth } from 'frappe-js-client'
import { createRealtime } from 'frappe-js-client/realtime'

const frappe = createFrappeClient({
url: 'https://frappe.example.com',
auth: tokenAuth({ apiKey: '...', apiSecret: '...' }),
})

const realtime = createRealtime(frappe)
const stop = realtime.subscribeDoc('ToDo', 'TD-0001', (event) => {
console.log(event.doctype, event.name)
})

realtime.subscribeDocType('ToDo', (event) => {
console.log('list', event.doctype)
})

const stopViewers = realtime.subscribeDocViewers('ToDo', 'TD-0001', (event) => {
console.log(event.viewers)
})

const stopDisconnect = realtime.on('disconnect', (reason) => {
console.log(reason)
})

stop()
stopViewers()
stopDisconnect()
realtime.close()

Options

createRealtime(client, options?):

OptionDefaultMeaning
socketUrlclient.config.baseUrlSocket.IO server URL
allowCrossOriginCredentialsfalsePermit client cookies/authorization on a socket origin different from the HTTP site.
path/socket.ioSocket.IO path
reconnectiontrue
reconnectionAttemptsInfinityMatches Frappe's socketio_client.js
reconnectionDelay1000ms
reconnectionDelayMax5000ms
transports['websocket', 'polling']Tried in order
autoConnecttrueConnect on create. Set false to call .connect() yourself.
authderivedExtra socket.handshake.auth payload. Object, or (ctx) => … / async. Merged over { cookie?, authorization? } from the client's AuthStrategy.

Client credentials are resolved again for every Socket.IO handshake, so rotating bearer/OAuth tokens are not captured permanently at construction. On a different socketUrl origin, client cookies and authorization are omitted unless allowCrossOriginCredentials: true is explicit. Custom auth values are still sent to the socket origin you selected.

RealtimeAuthContext is { cookie?, authorization? } — values taken from client.config.auth.apply(...).

Methods

MethodMeaning
connectedWhether the socket is currently connected
connect()Connects if needed. Concurrent callers share one in-flight connect. Resolves on connect, rejects on connect_error.
close()Disconnects and drops all subscriptions. Safe to call more than once. After close, create a new instance — the old one cannot reconnect.
subscribeDoc(doctype, name, handler)doc_update for one document
subscribeDocType(doctype, handler)list_update for a DocType
subscribeDocViewers(doctype, name, handler)doc_viewers presence
on(event, handler)Connection lifecycle: connect, disconnect, reconnect, reconnect_attempt, reconnect_error, reconnect_failed, connect_error

Every subscribe / on returns Unsubscribe (() => void). Unsubscribe functions are idempotent. Multiple subscribers to the same document share one Socket.IO doc_subscribe until the last unsubscribe. Active document and DocType subscriptions are restored after Socket.IO reconnects without duplicating handlers. Calling a subscribe method after close() throws ConfigurationError.

socket.io-client is loaded lazily on connect(). Importing frappe-js-client/realtime without the peer installed is fine; connect() throws ConfigurationError if it is missing. Using a closed instance also throws ConfigurationError.

With autoConnect: true (default), a failed first connect is swallowed; later .connect() or connect_error listeners surface it.

Event payloads

TypeFields
DocUpdateEventdoctype, name, plus any extra keys the server sent
ListUpdateEventdoctype, plus extra keys
DocViewersEventdoctype, name, viewers: Array<{ user, ... }>
EvictionEvent{ userId?, ... } — exported payload shape for Frappe's eviction message; there is no dedicated subscribe helper

Connection on() payloads:

EventPayload
connectvoid
disconnectstring (reason)
reconnectnumber
reconnect_attemptnumber
reconnect_errorError
reconnect_failedvoid
connect_errorError