edge-utils
Version:
Platform-agnostic utilities for edge computing and serverless environments
3 lines (2 loc) • 4.84 kB
JavaScript
"use strict";const e={CONNECTING:0,OPEN:1,CLOSING:2,CLOSED:3},t={TEXT:"text",BINARY:"binary",PING:"ping",PONG:"pong",CLOSE:"close"};class n{constructor(e={}){this.options={maxReconnectAttempts:3,reconnectDelay:1e3,pingInterval:3e4,pongTimeout:5e3,...e},this.connections=new Map,this.reconnectAttempts=new Map,this.pingTimers=new Map,this.pongTimeouts=new Map}connect(e,t,n=null){if(this.connections.has(e))throw new Error(`Connection with id '${e}' already exists`);try{const s=new WebSocket(t,n);return this.connections.set(e,s),this.reconnectAttempts.set(e,0),s.addEventListener("open",t=>this._handleOpen(e,t)),s.addEventListener("message",t=>this._handleMessage(e,t)),s.addEventListener("error",t=>this._handleError(e,t)),s.addEventListener("close",t=>this._handleClose(e,t)),s}catch(e){throw new Error(`Failed to create WebSocket connection: ${e.message}`)}}send(t,n,s={}){const o=this.connections.get(t);if(!o)throw new Error(`Connection with id '${t}' not found`);if(o.readyState!==e.OPEN)throw new Error(`Connection '${t}' is not open (state: ${o.readyState})`);try{s.binary?o.send(n):o.send("string"==typeof n?n:JSON.stringify(n))}catch(e){throw new Error(`Failed to send message on connection '${t}': ${e.message}`)}}broadcast(e,t,n={}){const s=[];if(e.forEach(e=>{try{this.send(e,t,n)}catch(t){s.push({id:e,error:t.message})}}),s.length>0)throw new Error(`Broadcast failed for connections: ${s.map(e=>`${e.id} (${e.error})`).join(", ")}`)}close(e,t=1e3,n=""){const s=this.connections.get(e);s&&s.close(t,n)}getState(t){const n=this.connections.get(t);return n?n.readyState:e.CLOSED}getActiveConnections(){return Array.from(this.connections.keys()).filter(t=>this.connections.get(t).readyState===e.OPEN)}_handleOpen(e,t){this.reconnectAttempts.set(e,0),this._startPing(e),this.options.onOpen&&this.options.onOpen(e,t)}_handleMessage(e,t){this._isPongMessage(t.data)?this._handlePong(e):this.options.onMessage&&this.options.onMessage(e,t)}_handleError(e,t){this.options.onError&&this.options.onError(e,t)}_handleClose(e,t){this._cleanup(e),1e3!==t.code&&1001!==t.code&&this._attemptReconnect(e),this.options.onClose&&this.options.onClose(e,t)}_startPing(t){const n=setInterval(()=>{const n=this.connections.get(t);if(n&&n.readyState===e.OPEN)try{n.send("ping"),this._startPongTimeout(t)}catch(e){this.close(t)}else this._cleanup(t)},this.options.pingInterval);this.pingTimers.set(t,n)}_startPongTimeout(e){const t=setTimeout(()=>{this.close(e)},this.options.pongTimeout);this.pongTimeouts.set(e,t)}_handlePong(e){const t=this.pongTimeouts.get(e);t&&(clearTimeout(t),this.pongTimeouts.delete(e))}_isPongMessage(e){return"pong"===e||"PONG"===e}_attemptReconnect(e){const t=this.reconnectAttempts.get(e)||0;t>=this.options.maxReconnectAttempts||(this.reconnectAttempts.set(e,t+1),setTimeout(()=>{this.connections.delete(e),this.reconnectAttempts.delete(e)},this.options.reconnectDelay*t))}_cleanup(e){const t=this.pingTimers.get(e);t&&(clearInterval(t),this.pingTimers.delete(e));const n=this.pongTimeouts.get(e);n&&(clearTimeout(n),this.pongTimeouts.delete(e)),this.connections.delete(e)}}module.exports={WebSocketManager:n,WebSocketUtils:class{static parseMessage(e){const{data:n,type:s}=e;if("string"!=typeof n)return{type:t.BINARY,data:n,raw:n,isJson:!1};try{const e=JSON.parse(n);return{type:t.TEXT,data:e,raw:n,isJson:!0}}catch{return{type:t.TEXT,data:n,raw:n,isJson:!1}}}static createMessage(e,t,n={}){return JSON.stringify({type:e,payload:t,timestamp:Date.now(),...n})}static isValidWebSocketUrl(e){try{const t=new URL(e);return"ws:"===t.protocol||"wss:"===t.protocol}catch{return!1}}static getCloseCodeDescription(e){return{1e3:"Normal Closure",1001:"Going Away",1002:"Protocol Error",1003:"Unsupported Data",1004:"Reserved",1005:"No Status Received",1006:"Abnormal Closure",1007:"Invalid Frame Payload Data",1008:"Policy Violation",1009:"Message Too Big",1010:"Mandatory Extension",1011:"Internal Server Error",1012:"Service Restart",1013:"Try Again Later",1014:"Bad Gateway",1015:"TLS Handshake"}[e]||"Unknown Close Code"}},WebSocketState:e,MessageType:t,createWebSocketMiddleware:function(e={}){const t=new n(e);return{manager:t,async handleUpgrade(e,n,s){try{if("websocket"!==e.headers.get("Upgrade"))return new Response("Expected Upgrade: websocket",{status:426});const n=new WebSocketPair,s=n[0],o=n[1],r=`ws_${Date.now()}_${Math.random().toString(36).substr(2,9)}`;return t.connections.set(r,o),o.addEventListener("message",e=>{t.options.onMessage&&t.options.onMessage(r,e)}),o.addEventListener("close",e=>{t.connections.delete(r),t.options.onClose&&t.options.onClose(r,e)}),o.addEventListener("error",e=>{t.options.onError&&t.options.onError(r,e)}),new Response(null,{status:101,webSocket:s})}catch(e){return new Response(`WebSocket upgrade failed: ${e.message}`,{status:500})}}}}};
//# sourceMappingURL=websocket.js.map