WebSocket Authentication Guide
The stream socket is public until you authenticate it. Authenticating joins the connection to its user
room and (for overseers) the overseer room, which is what delivers BALANCE_UPDATED, API_KEY_*,
USER_PROFILE_UPDATED, DOWNLOAD_READY, OVERSEER_* and the moderation events, and what lets the
connection send chat messages, reactions and typing events and join the overseers' events firehose.
There is no token in the handshake: a static handshake token would be presented, expired, on every
reconnect. The service sends an authenticate message instead, with the token current at that moment.
With a LaunchpadSDK
// `wallet` as here, or an app `session` (see below)
const sdk = createLaunchpadSDK({ env: 'stage', wallet: process.env.PRIVATE_KEY! });
const freshJwt = process.env.APP_JWT ?? '';
await sdk.authenticateStreamWebSocket(); // the session's current token
await sdk.authenticateStreamWebSocket(freshJwt); // right after the app refreshed its JWT
sdk.deauthenticateStreamWebSocket(); // when the wallet disconnects
- With an app
sessionthe token issession.accessToken(); otherwise it is the SDK's own session token (ensureValidToken(), refreshed when near expiry). authenticateStreamWebSocket()opens the connection if it is not open and resolves a boolean:trueonce the server confirms;falsewhen the server rejects the token (exceptionorstream_chat_auth_error), does not answer within 10 seconds, the connection drops meanwhile, there is no token, or a newerauthenticateStreamWebSocket/deauthenticateStreamWebSocketcall replaced it.- The same token on the same connection is not sent twice. A different token replaces a pending one
(that call resolves
false) and is sent after the server answered the one in flight, so the server's final identity is always the newest token. - Changing the SDK's wallet (
setWallet,setWalletProvider) deauthenticates the socket.
Reconnects
socket.io reconnects by itself. After every reconnect of a socket that was authenticated, the service
asks for the token current at that moment (the session, never a remembered one), authenticates
first, then re-joins every room that still has a subscriber, then calls the onReconnect listeners
(refetch there what you missed). A socket that was never authenticated, or was deauthenticated, is
not re-authenticated.
const stop = sdk.realtime.onReconnect(() => {
console.log('Reconnected: refetch what the screen shows');
});
deauthenticate() closes and reopens the connection
The server has no message that drops a connection's identity (its user room and the overseer room stay
joined until the connection closes). So deauthenticate() on an authenticated socket closes it and opens
it again: subscriptions stay registered, the public rooms are joined again on the new connection, and
onReconnect listeners fire.
With a bare StreamWebSocketService
import { StreamWebSocketService, getEnvironmentConfig } from '@gala-chain/launchpad-sdk';
const service = new StreamWebSocketService({
url: getEnvironmentConfig('prod').launchpadBaseUrl,
tokenProvider: async () => process.env.APP_JWT ?? null, // asked at every authentication and every reconnect
});
const ok = await service.authenticate(); // the provider's token
const ok2 = await service.authenticate(process.env.APP_JWT); // an explicit token
The overseers' firehose needs an authenticated overseer socket
subscribeEventsFirehose(listener, filters?) registers the subscriber at once, but the server only admits
an authenticated overseer to the room: the service joins it after authenticate() resolves true (and
again after every re-authentication), once for all subscribers. The server applies no filters, so each
subscriber's filters (eventTypes, tokenName, userAddress) are applied client-side.
Security
- Keep tokens out of logs and out of persistent browser storage where you can; prefer an httpOnly
cookie session (
sessioninLaunchpadSDKConfig) so the SDK never holds the token. - Use
wss://. - Call
deauthenticateStreamWebSocket()on logout and on wallet disconnect.