This specification defines a station client for coordinating same-origin execution contexts around a single logical connection to a Sovereignbase base station.
A station client provides a local-first coordination layer for applications that run in multiple same-origin contexts while sharing one opportunistic connection to a Sovereignbase base station.
This specification is maintained alongside a reference implementation.
To use the reference implementation see:
To test how the tabs sync locally see:
The abstract model in this specification is intentionally language-agnostic. It defines relaying, request-response transactions, leadership, and shutdown behavior without requiring a particular local coordination mechanism or transport stack.
The JavaScript reference binding defined below maps that abstract model to a concrete package surface and to a concrete base station wire convention.
This document references Infra [[INFRA]]. Infra-defined terminology, including tuple, is to be interpreted as defined there.
A station client operates over a coordination group. Each group MAY be configured with a base station endpoint identifier such as a WebSocket URL. When no endpoint identifier is configured, the group operates in local-only mode.
At most one leader context MUST own the active base station transport for a coordination group at a time. Other contexts in the same group MUST behave as follower contexts.
To relay an application message is to publish that application message to other same-origin contexts in the same coordination group.
A relay operation MUST NOT require a base station transport in order to complete locally.
A leader context SHOULD opportunistically forward a relayed application message to the base station transport, where it gets relayed to peers on a given topic according to logic outside the scope of this specification.
A relay operation MUST NOT synthesize a loopback local delivery back to the calling context unless an implementation explicitly specifies otherwise.
To transact is to submit an application message as a request to the base station and await a correlated response.
A transact operation MUST resolve with the corresponding application message response when the base station returns one.
A transact operation MAY instead resolve with a transport-unavailable result when the request cannot be issued, for example because no base station endpoint is configured, because the environment is offline, or because no active base station transport is currently available.
A transact operation MAY be aborted by the caller. A caller-supplied abort signal is distinct from any implementation-defined cleanup deadline used to discard stale routing state.
When a station client receives an application message from the base station that is not a transact response, it MUST dispatch that application message locally and SHOULD relay it to other same-origin contexts in the same coordination group.
When a station client is closed, it MUST release local coordination resources, cease further transport activity, and reject any pending transact operations that are still owned by that instance.
This section defines the JavaScript package surface exposed by the current reference implementation.
typedef object StationClientMessage;
typedef (StationClientMessage or boolean) StationClientTransactResult;
dictionary StationClientTransactOptions {
AbortSignal signal;
[EnforceRange] unsigned long ttlMs = 30000;
};
[Exposed=(Window,Worker)]
interface StationClient {
constructor(optional USVString webSocketUrl = "");
undefined relay(StationClientMessage message);
Promise<StationClientTransactResult> transact(
StationClientMessage message,
optional StationClientTransactOptions options = {}
);
undefined close();
undefined addEventListener(
DOMString type,
EventListener? callback,
optional (AddEventListenerOptions or boolean) options = {}
);
undefined removeEventListener(
DOMString type,
EventListener? callback,
optional (EventListenerOptions or boolean) options = {}
);
};
Constructing a {{StationClient}} with an empty
webSocketUrl
places the instance in local-only mode.
Instances constructed with the same origin and the same
webSocketUrl participate in the same coordination group.
relay() MethodThe {{StationClient/relay(message)}} method performs a relay operation by publishing |message| to other same-origin contexts in the coordination group.
If the calling instance currently owns the active base station transport, it attempts to forward |message| to the base station.
In the current binding, ordinary relayed upstream messages MAY be buffered while the host reports online status but no open upstream WebSocket is yet available.
Calling {{StationClient/relay(message)}} does not by itself dispatch a
local loopback message event to the same instance.
transact() MethodThe {{StationClient/transact(message, options)}} method performs a transact operation by submitting |message| as a request to the base station and returning a promise.
That promise resolves with an application message response when a correlated response is received from the base station.
That promise resolves with false when the binding can
determine that the request cannot presently be issued.
In the current binding, {{StationClient/transact(message, options)}}
resolves with false without issuing an upstream request
when the instance is closed, when no webSocketUrl is
configured, when the host reports offline status, or when the calling
instance is the leader but no open upstream WebSocket is available.
If options.signal is aborted, the promise rejects with an
abort error.
options.ttlMs sets the leader-side stale-routing lifetime
for a pending follower request. It is not a general request timeout.
This binding does not guarantee bounded completion time for follower requests that are waiting for leader-side handling. Without a correlated response or caller-supplied abort, such a promise MAY remain pending.
close() MethodThe {{StationClient/close()}} method closes the instance, releases local coordination resources, closes the active base station transport if owned by the instance, and rejects any still-pending transact operations owned by the instance.
After {{StationClient/close()}}, later
{{StationClient/relay(message)}} calls have no effect and later
{{StationClient/transact(message, options)}} calls resolve with
false.
A {{StationClient}} dispatches a message event when it
receives an application message from another same-origin context or
from the base station.
The JavaScript reference binding exposes {{StationClient/addEventListener()}} and {{StationClient/removeEventListener()}} for observing those events.
The event object MUST be a {{CustomEvent}}, and its {{CustomEvent/detail}} attribute MUST contain the application message.
Transact responses are delivered through the promise returned by
{{StationClient/transact(message, options)}} rather than through the
message event.
The JavaScript reference binding requires host support for
BroadcastChannel, EventTarget,
CustomEvent, MessageEvent,
AbortSignal, and crypto.randomUUID().
Upstream transport additionally depends on WebSocket.
Leadership and reconnection behavior additionally depend on
navigator.locks and navigator.onLine.
In the current binding, upstream connection attempts are only started
when a webSocketUrl is configured and the host reports
online status. Reconnection attempts cease while the host reports
offline status and resume only after online status returns.
The JavaScript reference binding uses a WebSocket-based base station transport and serializes transport payloads with MessagePack [[MSGPACK]].
A relayed or inbound application message is transported as the application message value itself.
A transact request is transported as the
tuple
["station-client-request", id, message], where
id is a binding-generated request identifier.
A transact response is transported as the
tuple
["station-client-response", id, message], where
id identifies the pending transact operation being
fulfilled.