Skip to content
On this page

Client guide

Installation

bash
npm install @jcbuisson/express-x socket.io-client

Example

js
import { io } from 'socket.io-client'
import { createClient } from '@jcbuisson/express-x/client'

const socket = io('http://localhost:8000', { transports: ['websocket'] })

const app = createClient(socket)

const user = await app.service('user').findUnique({ where: { id: 22 } })

app.service('user').on('create', (user) => {
   console.log('CREATE USER event', user)
})
...

Configuration

createClient(socket, options)

Options passed to createClient:

OptionTypeDefaultDescription
debugbooleanfalseLog socket events and service requests to the console

Socket.io connection options (path, reconnection delays, extra headers, etc.) are passed directly to io():

js
const socket = io('http://localhost:8000', {
   path: '/myapp-socket.io/',
   transports: ['websocket'],
   reconnectionDelay: 1000,
   reconnectionDelayMax: 10000,
   extraHeaders: {
      'bearer-token': 'mytoken'
   },
})

Service options

Options can be passed per-service to app.service(name, options):

timeout (integer)

An exception is thrown if a service request doesn't get an answer after timeout ms. Default: 20000

js
app.service('mail', { timeout: 10000 }).send(...)

volatile (boolean)

When volatile is set, a service request is not buffered if the connection is down — it will simply not be emitted. Default: false

Connection lifecycle

js
// called when socket connects or reconnects
app.addConnectListener((socket) => { ... })
app.removeConnectListener(func)

// called when socket disconnects
app.addDisconnectListener((socket) => { ... })
app.removeDisconnectListener(func)

// called on connection error
app.addErrorListener((socket) => { ... })
app.removeErrorListener(func)

Handling exceptions

When an exception occurs during a service method execution on the server, the error is serialized and sent back to the client so that it can be caught:

js
try {
   const { userId } = await app.service('auth').login(email, password)
   ...
} catch(err) {
   if (err.code === 'wrong-credentials') {
      ...
   }
}

Use EXError on the server to attach a structured code to errors.

Publish-subscribe

Service events callbacks

When a service event is sent by the server, the client can set a callback. In the following example, the callback is executed when the client receives a 'create' event from the 'user' service. Its only argument is the data returned by the method called — here the created user.

js
app.service('user').on('create', (createdUser) => {
   ...
})

Application event callbacks

When an application event is sent by the server, the client can set a callback with app.on(). In the following example, the callback is executed whenever the application-wide event 'expireAt' is sent to the client:

js
app.on('expireAt', (date) => {
   ...
})

Reload plugin

reloadPlugin allows page reloads while preserving the information attached to the connection (room memberships, socket.data).

The server-side reloadPlugin must be configured.

js
import { createClient } from '@jcbuisson/express-x/client'
import { reloadPlugin } from '@jcbuisson/express-x-plugins/reload-client'

const socket = io('http://localhost:8000', { transports: ['websocket'] })
const app = createClient(socket)
await reloadPlugin(app)

It uses a short-lived, one-time transfer token to preserve approved socket rooms and transferable socket.data across page reloads. On reconnect, it emits a cnx-transfer message to the server to copy the previous socket's state onto the new connection.

reloadPlugin uses sessionStorage internally to track the previous socket ID and transfer token. Token listeners and acknowledgement listeners are bounded and cleaned up across reconnects.

Room restoration is denied by default on the server. See the server-side reloadPlugin for authorization configuration.

Electric client plugin

The Electric client plugin adds reactive models backed by Electric Shapes. Writes use the matching ExpressX service; Electric streams committed PostgreSQL changes to subscribed clients.

See the offline guide for full setup and usage.

js
import { createClient } from '@jcbuisson/express-x/client'
import { electricClientPlugin } from '@jcbuisson/express-x-plugins/electric-client'

const socket = io('http://localhost:8000', { transports: ['websocket'] })
const app = createClient(socket)
app.configure(electricClientPlugin, { shapePath: '/electric/v1/shape' })
const post = app.createElectricModel('post')

// create a record through ExpressX; Electric then streams the committed row
await post.create({ title: 'Hello', content: 'World' })

// reactive observable — emits when the Shape changes
post.getObservable({ userId: 42 }).subscribe(posts => {
   console.log('posts', posts)
})

Filters accept exact values, null, and gt/gte/lt/lte range objects. Unsubscribe from the returned RxJS subscription when the view is disposed.

app.service(name).call(action, ...args) is available for remote method names that collide with local proxy methods, such as toString or constructor.