Building a Web UI
You have Strata services running. You have an RPC Bridge in front of them. Now you want your web app to call services from the browser.
This guide walks through using @strata-js/rpcbridge-client — a lightweight WebSocket client that connects to the bridge and gives you type-safe access to your services.
Install
npm install @strata-js/rpcbridge-clientBasic Usage
import { WebSocketClient } from '@strata-js/rpcbridge-client';
const client = new WebSocketClient('http://localhost:3000/rpc');
// Type-safe request with generics
interface UserProfile
{
id : string;
name : string;
email : string;
}
const user = await client.request<UserProfile>(
'users', 'profile', 'get',
{ userId: 'abc123' }
);
console.log(user.name);The client connects via Socket.IO, emits an rpc event with the request, and resolves the promise with the service's response payload.
Connection Lifecycle
The client uses Socket.IO under the hood, which handles reconnection automatically. The defaults are sensible for most cases:
const client = new WebSocketClient('http://localhost:3000/rpc', {
transports: [ 'websocket' ],
reconnection: true,
emitTimeout: 20000,
});Monitoring Connection State
Access the underlying Socket.IO instance for connection events:
const socket = client.socket;
socket.on('connect', () =>
{
console.log('Connected to bridge');
});
socket.on('disconnect', (reason) =>
{
console.log('Disconnected:', reason);
});
socket.on('connect_error', (err) =>
{
console.error('Connection failed:', err.message);
});Handling Disconnects in the UI
In a real app, you want the UI to react to connection state. A common pattern:
import { ref } from 'vue';
const connected = ref(false);
client.socket.on('connect', () => { connected.value = true; });
client.socket.on('disconnect', () => { connected.value = false; });Then show a banner or disable actions when connected is false. Don't silently queue requests — users should know when the connection is down.
Building a Transport Layer
For anything beyond a prototype, wrap the WebSocketClient in an application-specific transport class. This gives you a single place to handle auth injection, metadata, and session management.
import { WebSocketClient, RemoteServiceError } from '@strata-js/rpcbridge-client';
class AppTransport
{
private client : WebSocketClient;
private getToken : () => string | undefined;
constructor(bridgeUrl : string, getToken : () => string | undefined)
{
this.client = new WebSocketClient(bridgeUrl);
this.getToken = getToken;
}
async request<T>(
service : string,
context : string,
operation : string,
payload : Record<string, unknown> = {}
) : Promise<T>
{
const token = this.getToken();
return this.client.request<T>(
service, context, operation,
payload,
undefined,
token
);
}
get socket() { return this.client.socket; }
}Usage:
const transport = new AppTransport(
'http://localhost:3000/rpc',
() => authStore.accessToken
);
const user = await transport.request<UserProfile>('users', 'profile', 'get', { userId });This keeps auth handling centralized. Every request automatically picks up the current token without callers needing to think about it.
Error Handling
The client throws two typed errors:
RemoteServiceError
The bridge received a response from the service, but the service returned a failure. Contains code, message, name, stack, and optional details from the remote service.
EmitTimeoutError
The Socket.IO emit timed out — the bridge didn't respond within the configured emitTimeout.
import { RemoteServiceError, EmitTimeoutError } from '@strata-js/rpcbridge-client';
try
{
await client.request('users', 'profile', 'get', { userId: 'bad' });
}
catch(err)
{
if(err instanceof RemoteServiceError)
{
// Service returned an error — show the user a meaningful message
showError(`${ err.message } (${ err.code })`);
}
else if(err instanceof EmitTimeoutError)
{
// Bridge didn't respond — likely a connection issue
showError('Request timed out. Check your connection.');
}
}In a transport layer, you can intercept specific error codes for cross-cutting concerns:
async request<T>(/* ... */) : Promise<T>
{
try
{
return await this.client.request<T>(/* ... */);
}
catch(err)
{
if(err instanceof RemoteServiceError && err.code === 'SESSION_EXPIRED')
{
// Trigger a re-auth flow
authStore.clearSession();
router.push('/login');
}
throw err;
}
}Auth Integration
Passing Tokens
The auth parameter on request() is passed through to the Strata service as-is. What you put there depends on your auth setup:
// Bearer token
await client.request('users', 'profile', 'get', payload, undefined, `Bearer ${ token }`);
// Or via the transport layer (recommended)
const transport = new AppTransport(bridgeUrl, () => `Bearer ${ authStore.token }`);Token Refresh
If your tokens expire, handle refresh in the transport layer:
async request<T>(/* ... */) : Promise<T>
{
let token = this.getToken();
// Check if token needs refresh before sending
if(this.isTokenExpired(token))
{
token = await this.refreshToken();
}
return this.client.request<T>(service, context, operation, payload, undefined, token);
}Session Expiry
When the remote service detects an invalid session, it will return a service error. Catch it in your transport layer and redirect to login:
if(err instanceof RemoteServiceError && err.code === 'UNAUTHORIZED')
{
authStore.clearSession();
router.push('/login');
}Production Considerations
CORS
If your web app and bridge are on different origins, configure CORS on the bridge server:
import cors from 'cors';
// When using BYOS (Bring Your Own Server)
app.use(cors({ origin: 'https://app.example.com', credentials: true }));For Socket.IO, pass CORS options to the server:
const io = new SIOServer(server, {
cors: {
origin: 'https://app.example.com',
credentials: true,
},
});Path Configuration
Match the bridge's path config to your deployment:
// Bridge config
server: { path: '/api/rpc', /* ... */ }
// Client connection
const client = new WebSocketClient('https://app.example.com/api/rpc');Enable Only What You Need
If your app only uses WebSocket, disable HTTP (and vice versa):
server: {
enableHTTP: false,
enableWS: true,
// ...
}Fewer endpoints = smaller attack surface.
Next Steps
- Exposing Services — bridge setup, access control, and middleware
- RPC Bridge reference — complete config types and interface details