@api.global/typedsocket
Version:
A library for creating typed WebSocket connections, supporting bi-directional communication with type safety.
458 lines • 34.7 kB
JavaScript
import * as plugins from './typedsocket.plugins.js';
const TAG_PREFIX = '__typedsocket_tag__';
/**
* Creates a wrapper around IWebSocketPeer for tag compatibility
*/
function wrapSmartServePeer(peer) {
return {
peer,
async getTagById(tagId) {
if (!peer.tags.has(tagId)) {
return undefined;
}
const payload = peer.data.get(`${TAG_PREFIX}${tagId}`);
return { id: tagId, payload };
},
};
}
export class TypedSocket {
// ============================================================================
// STATIC METHODS
// ============================================================================
/**
* Creates a TypedSocket client using native WebSocket.
* Works in both browser and Node.js environments.
*
* @param typedrouterArg - TypedRouter for handling server-initiated requests
* @param serverUrlArg - Server URL (e.g., 'http://localhost:3000' or 'wss://example.com')
* @param options - Connection options
*
* @example
* ```typescript
* const typedRouter = new TypedRouter();
* const client = await TypedSocket.createClient(
* typedRouter,
* 'http://localhost:3000',
* { autoReconnect: true }
* );
* ```
*/
static async createClient(typedrouterArg, serverUrlArg, options = {}) {
const defaultOptions = {
autoReconnect: true,
maxRetries: 100,
initialBackoffMs: 1000,
maxBackoffMs: 60000,
};
const opts = { ...defaultOptions, ...options };
const typedSocket = new TypedSocket('client', typedrouterArg);
typedSocket.clientOptions = opts;
typedSocket.serverUrl = serverUrlArg;
typedSocket.currentBackoff = opts.initialBackoffMs;
await typedSocket.connect();
return typedSocket;
}
/**
* Returns the current window location origin URL.
* Useful in browser environments for connecting to the same origin.
*/
static { this.useWindowLocationOriginUrl = () => {
return plugins.smarturl.Smarturl.createFromUrl(globalThis.location.origin).toString();
}; }
/**
* Creates a TypedSocket server from an existing SmartServe instance.
* This is the only way to create a server-side TypedSocket.
*
* @param smartServeArg - SmartServe instance with typedRouter configured in websocket options
* @param typedRouterArg - TypedRouter for handling requests (must match SmartServe's typedRouter)
*
* @example
* ```typescript
* const typedRouter = new TypedRouter();
* const smartServe = new SmartServe({
* port: 3000,
* websocket: {
* typedRouter,
* onConnectionOpen: (peer) => peer.tags.add('client')
* }
* });
* await smartServe.start();
* const typedSocket = TypedSocket.fromSmartServe(smartServe, typedRouter);
* ```
*/
static fromSmartServe(smartServeArg, typedRouterArg) {
const connectionWrappers = new Map();
// Register built-in tag handlers
TypedSocket.registerTagHandlers(typedRouterArg);
const typedSocket = new TypedSocket('server', typedRouterArg);
typedSocket.smartServeRef = smartServeArg;
typedSocket.smartServeConnectionWrappers = connectionWrappers;
return typedSocket;
}
/**
* Registers built-in TypedHandlers for tag management
*/
static registerTagHandlers(typedRouter) {
// Set tag handler
typedRouter.addTypedHandler(new plugins.typedrequest.TypedHandler('__typedsocket_setTag', async (data, meta) => {
const peer = meta?.localData?.peer;
if (!peer) {
console.warn('setTag: No peer found in request context');
return { success: false };
}
peer.tags.add(data.name);
peer.data.set(`${TAG_PREFIX}${data.name}`, data.payload);
return { success: true };
}));
// Remove tag handler
typedRouter.addTypedHandler(new plugins.typedrequest.TypedHandler('__typedsocket_removeTag', async (data, meta) => {
const peer = meta?.localData?.peer;
if (!peer) {
console.warn('removeTag: No peer found in request context');
return { success: false };
}
peer.tags.delete(data.name);
peer.data.delete(`${TAG_PREFIX}${data.name}`);
return { success: true };
}));
}
// ============================================================================
// CONSTRUCTOR
// ============================================================================
constructor(sideArg, typedrouterArg) {
// Connection status observable
this.statusSubject = new plugins.smartrx.rxjs.Subject();
this.connectionStatus = 'new';
// Client-specific properties
this.websocket = null;
this.clientOptions = null;
this.serverUrl = '';
this.retryCount = 0;
this.currentBackoff = 1000;
this.pendingRequests = new Map();
// Server-specific properties (SmartServe mode)
this.smartServeRef = null;
this.smartServeConnectionWrappers = new Map();
this.side = sideArg;
this.typedrouter = typedrouterArg;
}
// ============================================================================
// CLIENT METHODS
// ============================================================================
/**
* Connects the client to the server using native WebSocket
*/
async connect() {
const done = plugins.smartpromise.defer();
this.updateStatus('connecting');
// Convert HTTP URL to WebSocket URL
const wsUrl = this.toWebSocketUrl(this.serverUrl);
console.log(`TypedSocket connecting to ${wsUrl}...`);
this.websocket = new WebSocket(wsUrl);
const connectionTimeout = setTimeout(() => {
if (this.connectionStatus !== 'connected') {
console.warn('TypedSocket connection timeout');
this.websocket?.close();
done.reject(new Error('Connection timeout'));
}
}, 10000);
this.websocket.onopen = () => {
clearTimeout(connectionTimeout);
console.log('TypedSocket connected!');
this.updateStatus('connected');
this.retryCount = 0;
this.currentBackoff = this.clientOptions?.initialBackoffMs ?? 1000;
done.resolve();
};
this.websocket.onmessage = async (event) => {
await this.handleMessage(event.data);
};
this.websocket.onclose = () => {
clearTimeout(connectionTimeout);
this.handleDisconnect();
};
this.websocket.onerror = (error) => {
console.error('TypedSocket WebSocket error:', error);
};
try {
await done.promise;
}
catch (err) {
clearTimeout(connectionTimeout);
if (this.clientOptions?.autoReconnect) {
await this.scheduleReconnect();
}
else {
throw err;
}
}
}
/**
* Converts an HTTP(S) URL to a WebSocket URL
*/
toWebSocketUrl(url) {
const parsed = new URL(url);
const wsProtocol = parsed.protocol === 'https:' ? 'wss:' : 'ws:';
return `${wsProtocol}//${parsed.host}${parsed.pathname}`;
}
/**
* Handles incoming WebSocket messages
*/
async handleMessage(data) {
try {
const messageText = typeof data === 'string' ? data : new TextDecoder().decode(data);
const message = plugins.smartjson.parse(messageText);
// Check if this is a response to a pending request
if (message.correlation?.id && this.pendingRequests.has(message.correlation.id)) {
const pending = this.pendingRequests.get(message.correlation.id);
this.pendingRequests.delete(message.correlation.id);
pending.resolve(message);
return;
}
// Server-initiated request - route through TypedRouter
const response = await this.typedrouter.routeAndAddResponse(message);
if (response && this.websocket?.readyState === WebSocket.OPEN) {
this.websocket.send(plugins.smartjson.stringify(response));
}
}
catch (err) {
console.error('TypedSocket failed to process message:', err);
}
}
/**
* Handles WebSocket disconnection
*/
handleDisconnect() {
if (this.connectionStatus === 'disconnected') {
return; // Already handled
}
this.updateStatus('disconnected');
if (this.clientOptions?.autoReconnect && this.retryCount < this.clientOptions.maxRetries) {
this.scheduleReconnect();
}
}
/**
* Schedules a reconnection attempt with exponential backoff
*/
async scheduleReconnect() {
if (!this.clientOptions)
return;
this.updateStatus('reconnecting');
this.retryCount++;
// Exponential backoff with jitter
const jitter = this.currentBackoff * 0.2 * (Math.random() * 2 - 1);
const delay = Math.min(this.currentBackoff + jitter, this.clientOptions.maxBackoffMs);
console.log(`TypedSocket reconnecting in ${Math.round(delay)}ms (attempt ${this.retryCount}/${this.clientOptions.maxRetries})`);
await plugins.smartdelay.delayFor(delay);
// Increase backoff for next time
this.currentBackoff = Math.min(this.currentBackoff * 2, this.clientOptions.maxBackoffMs);
try {
await this.connect();
}
catch (err) {
console.error('TypedSocket reconnection failed:', err);
}
}
/**
* Updates connection status and notifies subscribers
*/
updateStatus(status) {
if (this.connectionStatus !== status) {
this.connectionStatus = status;
this.statusSubject.next(status);
}
}
/**
* Sends a request to the server and waits for response (client-side)
*/
async sendRequest(request) {
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
throw new Error('WebSocket not connected');
}
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pendingRequests.delete(request.correlation.id);
reject(new Error('Request timeout'));
}, 30000);
this.pendingRequests.set(request.correlation.id, {
resolve: (response) => {
clearTimeout(timeout);
resolve(response);
},
reject: (error) => {
clearTimeout(timeout);
reject(error);
},
});
this.websocket.send(plugins.smartjson.stringify(request));
});
}
// ============================================================================
// PUBLIC API - SHARED
// ============================================================================
/**
* Creates a TypedRequest for the specified method.
* On clients, sends to the server.
* On servers, sends to the specified target connection.
*/
createTypedRequest(methodName, targetConnection) {
const postMethod = async (requestDataArg) => {
if (this.side === 'client') {
return this.sendRequest(requestDataArg);
}
// Server-side: send to target connection
if (!this.smartServeRef) {
throw new Error('Server not initialized');
}
let target = targetConnection;
if (!target) {
const allConnections = this.smartServeRef.getWebSocketConnections();
if (allConnections.length === 1) {
const peer = allConnections[0];
target = this.getOrCreateWrapper(peer);
}
else if (allConnections.length === 0) {
throw new Error('No WebSocket connections available');
}
else {
throw new Error('Multiple connections available - specify targetConnection');
}
}
// Register interest for response
const interest = await this.typedrouter.fireEventInterestMap.addInterest(requestDataArg.correlation.id, requestDataArg);
// Send request
target.peer.send(plugins.smartjson.stringify(requestDataArg));
// Wait for response
return await interest.interestFullfilled;
};
return new plugins.typedrequest.TypedRequest(new plugins.typedrequest.TypedTarget({ postMethod }), methodName);
}
/**
* Gets the current connection status
*/
getStatus() {
return this.connectionStatus;
}
/**
* Stops the TypedSocket client or cleans up server state
*/
async stop() {
if (this.side === 'client') {
if (this.clientOptions) {
this.clientOptions.autoReconnect = false;
}
if (this.websocket) {
this.websocket.close();
this.websocket = null;
}
this.pendingRequests.clear();
}
else {
// Server mode - just clear wrappers (SmartServe manages its own lifecycle)
this.smartServeConnectionWrappers.clear();
}
}
// ============================================================================
// CLIENT-ONLY METHODS
// ============================================================================
/**
* Sets a tag on this client connection.
* Tags are stored on the server and can be used for filtering.
* @client-only
*/
async setTag(name, payload) {
if (this.side !== 'client') {
throw new Error('setTag is only available on clients');
}
const request = this.createTypedRequest('__typedsocket_setTag');
const response = await request.fire({ name, payload });
if (!response.success) {
throw new Error('Failed to set tag on server');
}
}
/**
* Removes a tag from this client connection.
* @client-only
*/
async removeTag(name) {
if (this.side !== 'client') {
throw new Error('removeTag is only available on clients');
}
const request = this.createTypedRequest('__typedsocket_removeTag');
const response = await request.fire({ name });
if (!response.success) {
throw new Error('Failed to remove tag on server');
}
}
// ============================================================================
// SERVER-ONLY METHODS
// ============================================================================
/**
* Gets or creates a connection wrapper for a peer
*/
getOrCreateWrapper(peer) {
let wrapper = this.smartServeConnectionWrappers.get(peer.id);
if (!wrapper) {
wrapper = wrapSmartServePeer(peer);
this.smartServeConnectionWrappers.set(peer.id, wrapper);
}
return wrapper;
}
/**
* Finds all connections matching the filter function.
* @server-only
*/
async findAllTargetConnections(asyncFindFuncArg) {
if (this.side !== 'server' || !this.smartServeRef) {
throw new Error('findAllTargetConnections is only available on servers');
}
const matchingConnections = [];
for (const peer of this.smartServeRef.getWebSocketConnections()) {
const wrapper = this.getOrCreateWrapper(peer);
if (await asyncFindFuncArg(wrapper)) {
matchingConnections.push(wrapper);
}
}
return matchingConnections;
}
/**
* Finds the first connection matching the filter function.
* @server-only
*/
async findTargetConnection(asyncFindFuncArg) {
const allMatching = await this.findAllTargetConnections(asyncFindFuncArg);
return allMatching[0];
}
/**
* Finds all connections with the specified tag.
* @server-only
*/
async findAllTargetConnectionsByTag(keyArg, payloadArg) {
if (this.side !== 'server' || !this.smartServeRef) {
throw new Error('findAllTargetConnectionsByTag is only available on servers');
}
const peers = this.smartServeRef.getWebSocketConnectionsByTag(keyArg);
const results = [];
for (const peer of peers) {
const wrapper = this.getOrCreateWrapper(peer);
// If payload specified, also filter by payload
if (payloadArg !== undefined) {
const tag = await wrapper.getTagById(keyArg);
if (plugins.smartjson.stringify(tag?.payload) !== plugins.smartjson.stringify(payloadArg)) {
continue;
}
}
results.push(wrapper);
}
return results;
}
/**
* Finds the first connection with the specified tag.
* @server-only
*/
async findTargetConnectionByTag(keyArg, payloadArg) {
const allResults = await this.findAllTargetConnectionsByTag(keyArg, payloadArg);
return allResults[0];
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZWRzb2NrZXQuY2xhc3Nlcy50eXBlZHNvY2tldC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3R5cGVkc29ja2V0LmNsYXNzZXMudHlwZWRzb2NrZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSwwQkFBMEIsQ0FBQztBQUtwRCxNQUFNLFVBQVUsR0FBRyxxQkFBcUIsQ0FBQztBQW1DekM7O0dBRUc7QUFDSCxTQUFTLGtCQUFrQixDQUFDLElBQTRCO0lBQ3RELE9BQU87UUFDTCxJQUFJO1FBQ0osS0FBSyxDQUFDLFVBQVUsQ0FBQyxLQUFhO1lBQzVCLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO2dCQUMxQixPQUFPLFNBQVMsQ0FBQztZQUNuQixDQUFDO1lBQ0QsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxVQUFVLEdBQUcsS0FBSyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsQ0FBQztRQUNoQyxDQUFDO0tBQ0YsQ0FBQztBQUNKLENBQUM7QUFFRCxNQUFNLE9BQU8sV0FBVztJQUN0QiwrRUFBK0U7SUFDL0UsaUJBQWlCO0lBQ2pCLCtFQUErRTtJQUUvRTs7Ozs7Ozs7Ozs7Ozs7Ozs7T0FpQkc7SUFDSSxNQUFNLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FDOUIsY0FBZ0QsRUFDaEQsWUFBb0IsRUFDcEIsVUFBcUMsRUFBRTtRQUV2QyxNQUFNLGNBQWMsR0FBd0M7WUFDMUQsYUFBYSxFQUFFLElBQUk7WUFDbkIsVUFBVSxFQUFFLEdBQUc7WUFDZixnQkFBZ0IsRUFBRSxJQUFJO1lBQ3RCLFlBQVksRUFBRSxLQUFLO1NBQ3BCLENBQUM7UUFDRixNQUFNLElBQUksR0FBRyxFQUFFLEdBQUcsY0FBYyxFQUFFLEdBQUcs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