@forinda/kickjs-ws 7.1.1 → 7.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -75
- package/dist/centrifugo.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{interfaces-DsBi7cdB.mjs → interfaces-DCcsT2d4.mjs} +2 -2
- package/dist/{interfaces-DsBi7cdB.mjs.map → interfaces-DCcsT2d4.mjs.map} +1 -1
- package/dist/redis.mjs +1 -1
- package/dist/socket-io.mjs +2 -2
- package/dist/{ws-context-B9Wb2ZWj.mjs → ws-context-C5N1jlAf.mjs} +2 -2
- package/dist/{ws-context-B9Wb2ZWj.mjs.map → ws-context-C5N1jlAf.mjs.map} +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
# @forinda/kickjs-ws
|
|
2
2
|
|
|
3
|
-
Realtime for KickJS
|
|
4
|
-
|
|
5
|
-
| Import | What it gives you | Needs |
|
|
6
|
-
| ------------------------------- | ------------------------------------------------------------- | ----------------------------- |
|
|
7
|
-
| `@forinda/kickjs-ws` | `WsAdapter` — raw WebSockets on `ws` | `ws` |
|
|
8
|
-
| `@forinda/kickjs-ws/redis` | `redisBroker` — relay `WsAdapter` broadcasts across instances | a Redis client (e.g. ioredis) |
|
|
9
|
-
| `@forinda/kickjs-ws/socket.io` | `SocketIoAdapter` — the same controllers over Socket.IO | `socket.io` |
|
|
10
|
-
| `@forinda/kickjs-ws/centrifugo` | `CentrifugoAdapter` — publish through a Centrifugo server | a Centrifugo server |
|
|
3
|
+
Realtime for KickJS: decorator-driven handlers (`@WsController`, `@OnConnect`, `@OnMessage`, …), namespaces, rooms, heartbeat and an auth hook. Runs on raw WebSockets (`ws`), Socket.IO or Centrifugo, with a Redis broker for several instances.
|
|
11
4
|
|
|
12
5
|
## Install
|
|
13
6
|
|
|
@@ -15,11 +8,11 @@ Realtime for KickJS — decorator-driven handlers (`@WsController`, `@OnConnect`
|
|
|
15
8
|
kick add ws
|
|
16
9
|
```
|
|
17
10
|
|
|
18
|
-
## Quick
|
|
11
|
+
## Quick example
|
|
19
12
|
|
|
20
13
|
```ts
|
|
21
|
-
|
|
22
|
-
import {
|
|
14
|
+
import { bootstrap } from '@forinda/kickjs'
|
|
15
|
+
import { OnConnect, OnMessage, WsAdapter, WsController, type WsContext } from '@forinda/kickjs-ws'
|
|
23
16
|
|
|
24
17
|
@WsController('/chat')
|
|
25
18
|
export class ChatController {
|
|
@@ -33,77 +26,17 @@ export class ChatController {
|
|
|
33
26
|
ctx.broadcast('say', ctx.data)
|
|
34
27
|
}
|
|
35
28
|
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
// src/index.ts
|
|
40
|
-
import { bootstrap } from '@forinda/kickjs'
|
|
41
|
-
import { WsAdapter } from '@forinda/kickjs-ws'
|
|
42
|
-
import { modules } from './modules'
|
|
43
29
|
|
|
44
|
-
export const app = await bootstrap({
|
|
45
|
-
modules,
|
|
46
|
-
adapters: [WsAdapter({ path: '/ws' })],
|
|
47
|
-
})
|
|
30
|
+
export const app = await bootstrap({ modules, adapters: [WsAdapter({ path: '/ws' })] })
|
|
48
31
|
```
|
|
49
32
|
|
|
50
33
|
Clients connect to `ws://localhost:3000/ws/chat`.
|
|
51
34
|
|
|
52
|
-
## More than one instance
|
|
53
|
-
|
|
54
|
-
Rooms and broadcasts reach sockets in one process unless you pass a `broker`:
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
import Redis from 'ioredis'
|
|
58
|
-
import { getEnv } from '@forinda/kickjs'
|
|
59
|
-
import { WsAdapter } from '@forinda/kickjs-ws'
|
|
60
|
-
import { redisBroker } from '@forinda/kickjs-ws/redis'
|
|
61
|
-
|
|
62
|
-
const redis = new Redis(getEnv('REDIS_URL'))
|
|
63
|
-
|
|
64
|
-
WsAdapter({ broker: redisBroker({ publisher: redis, subscriber: redis.duplicate() }) })
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Socket.IO
|
|
68
|
-
|
|
69
|
-
The same controllers run on Socket.IO:
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import { SocketIoAdapter } from '@forinda/kickjs-ws/socket.io'
|
|
73
|
-
|
|
74
|
-
bootstrap({ modules, adapters: [SocketIoAdapter({ cors: { origin: 'https://app.example.com' } })] })
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Rooms are per namespace, `WS_ROOM_MANAGER` is replaced by the `SOCKET_IO` token, and it scales with Socket.IO's own adapters (`@socket.io/redis-adapter`).
|
|
78
|
-
|
|
79
|
-
## Centrifugo
|
|
80
|
-
|
|
81
|
-
Centrifugo holds the connections; KickJS signs connection tokens, answers its connect proxy, and publishes:
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
import { bootstrap, getEnv } from '@forinda/kickjs'
|
|
85
|
-
import { CentrifugoAdapter } from '@forinda/kickjs-ws/centrifugo'
|
|
86
|
-
|
|
87
|
-
bootstrap({
|
|
88
|
-
modules,
|
|
89
|
-
adapters: [
|
|
90
|
-
CentrifugoAdapter({ url: getEnv('CENTRIFUGO_URL'), apiKey: getEnv('CENTRIFUGO_API_KEY') }),
|
|
91
|
-
],
|
|
92
|
-
})
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Services inject `CENTRIFUGO` to publish. `@WsController` classes do not apply — the sockets are not in Node.
|
|
96
|
-
|
|
97
|
-
## Common to all
|
|
98
|
-
|
|
99
|
-
- `WS_USER_BROADCASTER` is registered by every adapter, so `broadcastToUser(userId, event, data)` works unchanged when you switch transports.
|
|
100
|
-
- Adapters run under Node `bootstrap()`, not the `@forinda/kickjs/web` edge entry (Workers, Bun, Deno).
|
|
101
|
-
|
|
102
35
|
## Documentation
|
|
103
36
|
|
|
104
|
-
- [WebSockets](https://kickjs.app/guide/websockets)
|
|
105
|
-
- [Socket.IO](https://kickjs.app/guide/socketio)
|
|
106
|
-
- [Centrifugo](https://kickjs.app/guide/centrifugo)
|
|
37
|
+
- [WebSockets](https://kickjs.app/guide/websockets): rooms, auth, scaling with `@forinda/kickjs-ws/redis`
|
|
38
|
+
- [Socket.IO](https://kickjs.app/guide/socketio) (`@forinda/kickjs-ws/socket.io`)
|
|
39
|
+
- [Centrifugo](https://kickjs.app/guide/centrifugo) (`@forinda/kickjs-ws/centrifugo`)
|
|
107
40
|
|
|
108
41
|
## License
|
|
109
42
|
|
package/dist/centrifugo.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-ws v7.1.
|
|
2
|
+
* @forinda/kickjs-ws v7.1.2
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{i as WS_USER_BROADCASTER}from"./interfaces-
|
|
11
|
+
import{i as WS_USER_BROADCASTER}from"./interfaces-DCcsT2d4.mjs";import{createHmac}from"node:crypto";import{createLogger,createToken,defineAdapter}from"@forinda/kickjs";const log=createLogger(`CentrifugoAdapter`);var CentrifugoApiError=class extends Error{method;code;constructor(method,code,message){super(`Centrifugo ${method} failed (${code}): ${message}`),this.method=method,this.code=code,this.name=`CentrifugoApiError`}};function centrifugoClient({url,apiKey,fetch:fetchImpl=globalThis.fetch,timeoutMs=1e4}){let base=url.replace(/\/+$/,``),call=async(method,body)=>{let res=await fetchImpl(`${base}/api/${method}`,{method:`POST`,headers:{"Content-Type":`application/json`,"X-API-Key":apiKey},body:JSON.stringify(body),signal:AbortSignal.timeout(timeoutMs)});if(!res.ok)throw new CentrifugoApiError(method,res.status,res.statusText||`HTTP error`);let reply=await res.json().catch(()=>({}));if(reply.error)throw new CentrifugoApiError(method,reply.error.code,reply.error.message)};return{publish:(channel,data)=>call(`publish`,{channel,data}),broadcast:(channels,data)=>call(`broadcast`,{channels,data}),subscribe:(user,channel)=>call(`subscribe`,{user,channel}),disconnect:user=>call(`disconnect`,{user})}}const base64url=input=>Buffer.from(input).toString(`base64url`);function connectionToken({secret,sub,expiresInSeconds,info,channels}){let claims={sub};expiresInSeconds!==void 0&&(claims.exp=Math.floor(Date.now()/1e3)+expiresInSeconds),info!==void 0&&(claims.info=info),channels!==void 0&&(claims.channels=channels);let unsigned=`${base64url(JSON.stringify({alg:`HS256`,typ:`JWT`}))}.${base64url(JSON.stringify(claims))}`;return`${unsigned}.${createHmac(`sha256`,secret).update(unsigned).digest(`base64url`)}`}async function centrifugoConnect(request,resolveUser){try{let user=await resolveUser(request);return!user||!user.id?{disconnect:{code:4401,reason:`unauthorized`}}:{result:{user:String(user.id)}}}catch(err){return log.error({err},`Centrifugo connect proxy: resolveUser threw`),{error:{code:100,message:`internal server error`}}}}const CENTRIFUGO=createToken(`kick/ws/Centrifugo`),CentrifugoAdapter=defineAdapter({name:`CentrifugoAdapter`,build:options=>{let client=centrifugoClient(options),roomFor=userId=>options.personalChannelNamespace?`${options.personalChannelNamespace}:#${userId}`:`#${userId}`,broadcastToUser=(userId,event,data)=>{client.publish(roomFor(userId),{event,data}).catch(err=>log.error({err},`Centrifugo publish failed`))},broadcaster={roomFor,broadcastToUser,toUser:id=>({send:(event,data)=>broadcastToUser(id,event,data)})};return{beforeStart({container}){container.registerInstance(CENTRIFUGO,client),container.registerInstance(WS_USER_BROADCASTER,broadcaster)}}}});export{CENTRIFUGO,CentrifugoAdapter,CentrifugoApiError,centrifugoClient,centrifugoConnect,connectionToken};
|
|
12
12
|
//# sourceMappingURL=centrifugo.mjs.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-ws v7.1.
|
|
2
|
+
* @forinda/kickjs-ws v7.1.2
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{a as wsControllerRegistry,i as WS_USER_BROADCASTER,n as WS_METADATA,r as WS_ROOM_MANAGER,t as WS_ADAPTER}from"./interfaces-
|
|
11
|
+
import{a as wsControllerRegistry,i as WS_USER_BROADCASTER,n as WS_METADATA,r as WS_ROOM_MANAGER,t as WS_ADAPTER}from"./interfaces-DCcsT2d4.mjs";import{t as WsContext}from"./ws-context-C5N1jlAf.mjs";import{randomUUID}from"node:crypto";import{WebSocketServer}from"ws";import{Service,createLogger,defineAdapter,getClassMeta,getClassMetaOrUndefined,pushClassMeta,ref,setClassMeta}from"@forinda/kickjs";var RoomManager=class{onSend;onBroadcast;constructor(onSend,onBroadcast){this.onSend=onSend,this.onBroadcast=onBroadcast}socketRooms=new Map;roomSockets=new Map;join(socketId,socket,room){this.socketRooms.has(socketId)||this.socketRooms.set(socketId,new Set),this.socketRooms.get(socketId).add(room),this.roomSockets.has(room)||this.roomSockets.set(room,new Map),this.roomSockets.get(room).set(socketId,socket)}leave(socketId,room){this.socketRooms.get(socketId)?.delete(room),this.roomSockets.get(room)?.delete(socketId),this.roomSockets.get(room)?.size===0&&this.roomSockets.delete(room)}leaveAll(socketId){let rooms=this.socketRooms.get(socketId);if(rooms)for(let room of rooms)this.roomSockets.get(room)?.delete(socketId),this.roomSockets.get(room)?.size===0&&this.roomSockets.delete(room);this.socketRooms.delete(socketId)}getRooms(socketId){return Array.from(this.socketRooms.get(socketId)??[])}getSockets(room){return this.roomSockets.get(room)??new Map}getAllRooms(){let result={};for(let[room,sockets]of this.roomSockets)result[room]=sockets.size;return result}broadcast(room,event,data,excludeId){this.deliver(room,event,data,excludeId),this.onBroadcast?.(room,event,data,excludeId)}deliver(room,event,data,excludeId){let sockets=this.roomSockets.get(room);if(!sockets)return;let message=JSON.stringify({event,data}),sent=0;for(let[id,socket]of sockets)id!==excludeId&&socket.readyState===socket.OPEN&&(socket.send(message),sent++);sent&&this.onSend?.(sent)}};const log=createLogger(`WsAdapter`),WsAdapter=defineAdapter({name:`WsAdapter`,defaults:{path:`/ws`,heartbeatInterval:3e4},build:options=>{let basePath=options.path,heartbeatInterval=options.heartbeatInterval,maxPayload=options.maxPayload,auth=options.auth,userRoomPrefix=options.auth?.userRoomPrefix??`user:`,wss=null,container=null,namespaces=new Map,heartbeatTimer=null,detachUpgrade=null,totalConnections=ref(0),activeConnections=ref(0),messagesReceived=ref(0),messagesSent=ref(0),wsErrors=ref(0),countSent=n=>{messagesSent.value+=n},broker=options.broker,origin=randomUUID(),relay=message=>{try{Promise.resolve(broker.publish(message)).catch(err=>log.error({err},`WS broker publish failed`))}catch(err){log.error({err},`WS broker publish failed`)}},relayRoom=broker?(room,event,data,exclude)=>relay({origin,room,event,data,exclude}):void 0,relayNamespace=broker?(namespace,event,data,exclude)=>relay({origin,namespace,event,data,exclude}):void 0,roomManager=new RoomManager(countSent,relayRoom),isBrokerMessage=m=>typeof m==`object`&&!!m&&typeof m.origin==`string`&&typeof m.event==`string`&&(m.exclude===void 0||typeof m.exclude==`string`)&&typeof m.room==`string`!=(typeof m.namespace==`string`)&&(m.room===void 0||typeof m.room==`string`)&&(m.namespace===void 0||typeof m.namespace==`string`),onRelayed=message=>{if(!isBrokerMessage(message)||message.origin===origin)return;let{room,namespace,event,data,exclude}=message;if(room!==void 0)return roomManager.deliver(room,event,data,exclude);let frame=JSON.stringify({event,data}),sent=0;for(let entry of namespaces.values())if(entry.namespace===namespace)for(let[id,socket]of entry.sockets)id!==exclude&&socket.readyState===socket.OPEN&&(socket.send(frame),sent++);sent&&countSent(sent)},userRoom=userId=>userRoomPrefix+userId,broadcastToUser=(userId,event,data)=>{roomManager.broadcast(userRoom(userId),event,data)},buildUserBroadcaster=()=>({roomFor:id=>userRoom(id),broadcastToUser:(id,event,data)=>broadcastToUser(id,event,data),toUser:id=>({send:(event,data)=>broadcastToUser(id,event,data)})}),getStats=()=>{let namespaceStats={};for(let[path,entry]of namespaces)namespaceStats[path]={connections:entry.sockets.size,handlers:entry.handlers.length};return{totalConnections:totalConnections.value,activeConnections:activeConnections.value,messagesReceived:messagesReceived.value,messagesSent:messagesSent.value,errors:wsErrors.value,namespaces:namespaceStats,rooms:roomManager.getAllRooms()}},safeInvoke=async(controller,method,ctx)=>{try{await controller[method](ctx)}catch(err){log.error({err},`WS handler error in ${method}`)}},invokeHandlers=async(controller,handlers,type,ctx)=>{await Promise.all(handlers.filter(handler=>handler.type===type).map(handler=>safeInvoke(controller,handler.handlerName,ctx)))},authenticate=async ctx=>{if(!auth)return!0;try{let user=await auth.resolveUser(ctx.request);return ctx.socket.readyState===ctx.socket.OPEN?!user||!user.id?(ctx.socket.close(4401,`Unauthorized`),!1):(ctx.set(`user`,user),ctx.set(`userId`,user.id),auth.autoJoinUserRoom!==!1&&ctx.join(userRoom(user.id)),!0):!1}catch(err){return log.warn(`WS auth failed`,err),ctx.socket.close(4401,`Unauthorized`),!1}},handleConnection=(ws,entry,request)=>{let socketId=randomUUID();ws.__alive=!0,entry.sockets.set(socketId,ws),totalConnections.value++,activeConnections.value++;let ctx=new WsContext(ws,wss,roomManager,entry.sockets,socketId,entry.namespace,request,countSent,relayNamespace);entry.contexts.set(socketId,ctx);let controller=container.resolve(entry.controllerClass);ws.on(`pong`,()=>{ws.__alive=!0});let handleMessage=raw=>{messagesReceived.value++;try{let parsed=JSON.parse(raw.toString()),event=parsed.event,data=parsed.data;if(!event||typeof event!=`string`){ctx.send(`error`,{message:`Invalid message format: missing "event" field`});return}let messageCtx=Object.create(ctx);messageCtx.event=event,messageCtx.data=data;let handler=entry.handlers.find(h=>h.type===`message`&&h.event===event);if(handler)safeInvoke(controller,handler.handlerName,messageCtx);else{let catchAll=entry.handlers.find(h=>h.type===`message`&&h.event===`*`);catchAll&&safeInvoke(controller,catchAll.handlerName,messageCtx)}}catch{let errorCtx=Object.create(ctx);errorCtx.data={message:`Invalid JSON`},invokeHandlers(controller,entry.handlers,`error`,errorCtx)}},ready=!1,pending=[],pendingBytes=0,dropPending=()=>{pending.length=0,pendingBytes=0};ws.on(`message`,raw=>{if(ready)return handleMessage(raw);let size=typeof raw==`string`?Buffer.byteLength(raw):raw.length;if(pending.length>=64||pendingBytes+size>1048576){dropPending(),ws.close(1008,`Too many messages before connect`);return}pending.push(raw),pendingBytes+=size}),(auth?authenticate(ctx):Promise.resolve(!0)).then(async ok=>{if(!ok||ws.readyState!==ws.OPEN||(await invokeHandlers(controller,entry.handlers,`connect`,ctx),ws.readyState!==ws.OPEN))return dropPending();ready=!0;for(let raw of pending.splice(0))handleMessage(raw);pendingBytes=0}),ws.on(`close`,()=>{dropPending(),activeConnections.value--,invokeHandlers(controller,entry.handlers,`disconnect`,ctx),roomManager.leaveAll(socketId),entry.sockets.delete(socketId),entry.contexts.delete(socketId)}),ws.on(`error`,err=>{wsErrors.value++;let errorCtx=Object.create(ctx);errorCtx.data={message:err.message,name:err.name},invokeHandlers(controller,entry.handlers,`error`,errorCtx)})};return{getStats,userRoom,broadcastToUser,totalConnections,activeConnections,messagesReceived,messagesSent,wsErrors,async beforeStart({container:containerArg}){container=containerArg,container.registerInstance(WS_ADAPTER,{getStats,userRoom,broadcastToUser,totalConnections,activeConnections,messagesReceived,messagesSent,wsErrors}),container.registerInstance(WS_ROOM_MANAGER,roomManager),container.registerInstance(WS_USER_BROADCASTER,buildUserBroadcaster());for(let controllerClass of wsControllerRegistry){let namespace=getClassMetaOrUndefined(WS_METADATA.WS_CONTROLLER,controllerClass);if(namespace===void 0)continue;let handlers=getClassMeta(WS_METADATA.WS_HANDLERS,controllerClass,[]),fullPath=basePath+(namespace===`/`?``:namespace);namespaces.set(fullPath,{namespace,controllerClass,handlers,sockets:new Map,contexts:new Map}),log.info(`Registered WS namespace: ${fullPath} (${controllerClass.name})`)}broker&&await broker.subscribe(onRelayed)},afterStart({server}){if(!server)return;wss=new WebSocketServer({noServer:!0,maxPayload});let onUpgrade=(request,socket,head)=>{let pathname=(request.url||`/`).split(`?`)[0],entry=namespaces.get(pathname);if(!entry){if(server.listenerCount(`upgrade`)>1)return;socket.write(`HTTP/1.1 404 Not Found\r
|
|
12
12
|
\r
|
|
13
13
|
`),socket.destroy();return}wss.handleUpgrade(request,socket,head,ws=>{handleConnection(ws,entry,request)})};server.on(`upgrade`,onUpgrade),detachUpgrade=()=>server.off(`upgrade`,onUpgrade),heartbeatInterval>0&&(heartbeatTimer=setInterval(()=>{for(let[,entry]of namespaces)for(let[,socket]of entry.sockets){if(socket.__alive===!1){socket.terminate();continue}socket.__alive=!1,socket.ping()}},heartbeatInterval));let totalHandlers=Array.from(namespaces.values()).reduce((sum,e)=>sum+e.handlers.length,0);log.info(`WebSocket ready — ${namespaces.size} namespace(s), ${totalHandlers} handler(s)`)},async shutdown(){heartbeatTimer&&clearInterval(heartbeatTimer);for(let[,entry]of namespaces){for(let[,socket]of entry.sockets)socket.close(1001,`Server shutting down`);entry.sockets.clear(),entry.contexts.clear()}detachUpgrade?.(),detachUpgrade=null,wss?.close(),await broker?.close?.()}}}});function WsController(namespace){return target=>{Service()(target),setClassMeta(WS_METADATA.WS_CONTROLLER,namespace||`/`,target),wsControllerRegistry.add(target)}}function createWsHandlerDecorator(type,event){return()=>(target,propertyKey)=>{pushClassMeta(WS_METADATA.WS_HANDLERS,target.constructor,{type,event,handlerName:propertyKey})}}const OnConnect=createWsHandlerDecorator(`connect`),OnDisconnect=createWsHandlerDecorator(`disconnect`),OnError=createWsHandlerDecorator(`error`);function OnMessage(event){return(target,propertyKey)=>{pushClassMeta(WS_METADATA.WS_HANDLERS,target.constructor,{type:`message`,event,handlerName:propertyKey})}}export{OnConnect,OnDisconnect,OnError,OnMessage,RoomManager,WS_ADAPTER,WS_METADATA,WS_ROOM_MANAGER,WS_USER_BROADCASTER,WsAdapter,WsContext,WsController};
|
|
14
14
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-ws v7.1.
|
|
2
|
+
* @forinda/kickjs-ws v7.1.2
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
11
|
import{createToken}from"@forinda/kickjs";const WS_METADATA={WS_CONTROLLER:`kick/ws/controller`,WS_HANDLERS:`kick/ws/handlers`},WS_ADAPTER=createToken(`kick/ws/Adapter`),WS_ROOM_MANAGER=createToken(`kick/ws/RoomManager`),WS_USER_BROADCASTER=createToken(`kick/ws/UserBroadcaster`),wsControllerRegistry=new Set;export{wsControllerRegistry as a,WS_USER_BROADCASTER as i,WS_METADATA as n,WS_ROOM_MANAGER as r,WS_ADAPTER as t};
|
|
12
|
-
//# sourceMappingURL=interfaces-
|
|
12
|
+
//# sourceMappingURL=interfaces-DCcsT2d4.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interfaces-
|
|
1
|
+
{"version":3,"file":"interfaces-DCcsT2d4.mjs","names":[],"sources":["../src/interfaces.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport { createToken } from '@forinda/kickjs'\nimport type { RoomManager } from './room-manager'\n\ntype Constructor = new (...args: any[]) => any\n\n// String metadata keys (post-Symbol migration). Slash-delimited under\n// `kick/ws/` for consistency with other framework decorators and to keep\n// Reflect.metadata storage collision-safe.\nexport const WS_METADATA = {\n WS_CONTROLLER: 'kick/ws/controller',\n WS_HANDLERS: 'kick/ws/handlers',\n} as const\n\nexport type WsHandlerType = 'connect' | 'disconnect' | 'message' | 'error'\n\nexport interface WsHandlerDefinition {\n type: WsHandlerType\n /** Event name — only for 'message' type */\n event?: string\n /** Method name on the controller class */\n handlerName: string\n}\n\n/**\n * Resolved principal returned from {@link WsAuthConfig.resolveUser}. Only `id`\n * is required. The whole object is stored on the `WsContext` as `user`, and\n * `id` alone as `userId` — read them with `ctx.get('user')` / `ctx.get('userId')`.\n */\nexport interface WsAuthenticatedUser {\n id: string\n [key: string]: unknown\n}\n\nexport interface WsAuthConfig {\n /**\n * Resolve a user from the upgrade request. Called once per socket, before\n * any `@OnConnect` handler fires. Return `null` or throw to reject.\n *\n * A rejected socket is accepted and then closed with code `4401` — a\n * WebSocket close code, not an HTTP status. Browsers do not expose a failed\n * handshake's status to script, so a close code is what a client can act on.\n *\n * Messages the client sends before this settles are held and delivered once\n * `@OnConnect` has settled, up to 64 messages or 1 MiB; beyond either the socket is\n * closed with `1008`. A socket that closes while this runs never reaches\n * `@OnConnect` or its user room.\n *\n * `handshakeAuth` is Socket.IO only: the payload of the client's `auth`\n * option (`io(url, { auth: { token } })`) — the usual place for a token,\n * since a browser cannot set headers on a WebSocket. `WsAdapter` omits it.\n */\n resolveUser: (\n request: IncomingMessage,\n handshakeAuth?: Record<string, unknown>,\n ) => Promise<WsAuthenticatedUser | null> | WsAuthenticatedUser | null\n /**\n * Join each authenticated socket to `user:<id>` as soon as `resolveUser`\n * resolves (default: `true` — pass `false` to opt out). Pairs with\n * {@link WsUserBroadcaster}.\n */\n autoJoinUserRoom?: boolean\n /**\n * Room name prefix for per-user broadcasting (default: `'user:'`).\n * Must match what `@forinda/kickjs-ws`'s `WsUserBroadcaster` targets.\n */\n userRoomPrefix?: string\n}\n\nexport interface WsAdapterOptions {\n /** Base path for WebSocket upgrade (default: '/ws') */\n path?: string\n /** Heartbeat ping interval in ms (default: 30000). Set to 0 to disable. */\n heartbeatInterval?: number\n /** Maximum message payload size in bytes */\n maxPayload?: number\n /** Optional authenticated-handshake configuration. */\n auth?: WsAuthConfig\n /**\n * Relays broadcasts between instances. Without one, rooms, namespace\n * broadcasts and per-user sends reach sockets in this process only.\n * Use `redisBroker` from `@forinda/kickjs-ws/redis`, or implement\n * {@link WsBroker} over any pub/sub.\n */\n broker?: WsBroker\n}\n\n/** A broadcast relayed between instances. Exactly one of `room` / `namespace` is set. */\nexport type WsBrokerMessage = {\n /** Id of the publishing instance — each instance skips its own messages. */\n origin: string\n event: string\n data: unknown\n /** Socket id that must not receive it (the sender of `ctx.broadcast`). */\n exclude?: string\n} & (\n | {\n /** Room broadcast: `ctx.to(room)`, `WS_ROOM_MANAGER`, per-user sends. */\n room: string\n namespace?: never\n }\n | {\n /** Namespace broadcast: `ctx.broadcast()` / `ctx.broadcastAll()`. */\n namespace: string\n room?: never\n }\n)\n\n/**\n * Cross-instance pub/sub for {@link WsAdapter}. The adapter delivers every\n * broadcast to its own sockets immediately, then publishes it; other\n * instances deliver it to theirs. `data` must be JSON-serialisable — it\n * already is, since it is sent to clients as JSON.\n */\nexport interface WsBroker {\n /**\n * Called synchronously from the broadcasting handler. Serialise `message`\n * before the first `await`: the handler may mutate `data` afterwards, and\n * local sockets already received the value as it was.\n */\n publish(message: WsBrokerMessage): void | Promise<void>\n /** Called once from the adapter's `beforeStart`; startup waits for it. */\n subscribe(onMessage: (message: WsBrokerMessage) => void): void | Promise<void>\n /** Called from the adapter's `shutdown`. Connections the caller passed in stay theirs to close. */\n close?(): void | Promise<void>\n}\n\n/**\n * Per-user broadcasting across all WS namespaces. Always registered on the DI\n * container. With {@link WsAdapterOptions.auth} sockets join `user:<id>`\n * automatically; without it a controller can join the room manually and the\n * helper works the same.\n *\n * Reaches sockets in this process only, unless the adapter has a\n * {@link WsAdapterOptions.broker}.\n */\nexport interface WsUserBroadcaster {\n /** Send a single event to every socket bound to this user. */\n toUser(userId: string): { send(event: string, data: unknown): void }\n /** Convenience — `toUser(id).send(event, data)` in one call. */\n broadcastToUser(userId: string, event: string, data: unknown): void\n /** Room name for a given user (respects `userRoomPrefix`). */\n roomFor(userId: string): string\n}\n\n/** DI token for the live {@link WsAdapter} instance. */\nexport const WS_ADAPTER = createToken<unknown>('kick/ws/Adapter')\n/** DI token for the shared {@link RoomManager}. */\nexport const WS_ROOM_MANAGER = createToken<RoomManager>('kick/ws/RoomManager')\n/** DI token for the per-user broadcaster helper. */\nexport const WS_USER_BROADCASTER = createToken<WsUserBroadcaster>('kick/ws/UserBroadcaster')\n\n/** Registry of all @WsController classes — populated at decorator time */\nexport const wsControllerRegistry = new Set<Constructor>()\n"],"mappings":";;;;;;;;;;yCASA,MAAa,YAAc,CACzB,cAAe,qBACf,YAAa,kBACf,EAsIa,WAAa,YAAqB,iBAAiB,EAEnD,gBAAkB,YAAyB,qBAAqB,EAEhE,oBAAsB,YAA+B,yBAAyB,EAG9E,qBAAuB,IAAI"}
|
package/dist/redis.mjs
CHANGED
package/dist/socket-io.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-ws v7.1.
|
|
2
|
+
* @forinda/kickjs-ws v7.1.2
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{a as wsControllerRegistry,i as WS_USER_BROADCASTER,n as WS_METADATA}from"./interfaces-
|
|
11
|
+
import{a as wsControllerRegistry,i as WS_USER_BROADCASTER,n as WS_METADATA}from"./interfaces-DCcsT2d4.mjs";import{n as parseCookies}from"./ws-context-C5N1jlAf.mjs";import{createLogger,createToken,defineAdapter,getClassMeta,getClassMetaOrUndefined}from"@forinda/kickjs";import{Server}from"socket.io";const log=createLogger(`SocketIoAdapter`),SOCKET_IO=createToken(`kick/ws/SocketIo`);var SocketIoContext=class{socket;server;namespace;data=null;event=``;metadata=new Map;constructor(socket,server,namespace){this.socket=socket,this.server=server,this.namespace=namespace}get id(){return this.socket.id}get request(){return this.socket.request}get cookies(){return parseCookies(this.request.headers.cookie)}get(key){return this.metadata.get(key)}set(key,value){this.metadata.set(key,value)}send(event,data){this.socket.emit(event,data)}broadcast(event,data){this.socket.broadcast.emit(event,data)}broadcastAll(event,data){this.socket.nsp.emit(event,data)}join(room){return this.socket.join(room)}leave(room){return this.socket.leave(room)}rooms(){return[...this.socket.rooms].filter(room=>room!==this.socket.id)}to(room){return{send:(event,data)=>{this.socket.nsp.to(room).emit(event,data)}}}};const SocketIoAdapter=defineAdapter({name:`SocketIoAdapter`,build:({auth,...serverOptions})=>{let io=new Server(serverOptions),detachFromServer=null,namespaces=[],userRoomPrefix=auth?.userRoomPrefix??`user:`,userRoom=userId=>userRoomPrefix+userId,invoke=async(controller,method,ctx)=>{try{await controller[method](ctx)}catch(err){log.error({err},`Socket.IO handler error in ${method}`)}},invokeAll=(controller,handlers,type,ctx)=>Promise.all(handlers.filter(h=>h.type===type).map(h=>invoke(controller,h.handlerName,ctx))),userBroadcaster={roomFor:userRoom,broadcastToUser:(userId,event,data)=>{for(let nsp of namespaces)nsp.to(userRoom(userId)).emit(event,data)},toUser:userId=>({send:(event,data)=>userBroadcaster.broadcastToUser(userId,event,data)})},handleConnection=(socket,namespace,handlers,controller)=>{let ctx=new SocketIoContext(socket,io,namespace),user=socket.data.user,userRoomJoined;user&&(ctx.set(`user`,user),ctx.set(`userId`,user.id),auth?.autoJoinUserRoom!==!1&&(userRoomJoined=ctx.join(userRoom(user.id))));let forEvent=(event,data)=>{let eventCtx=Object.create(ctx);return eventCtx.event=event,eventCtx.data=data,eventCtx},dispatch=(event,data)=>{let handler=handlers.find(h=>h.type===`message`&&h.event===event)??handlers.find(h=>h.type===`message`&&h.event===`*`);handler&&invoke(controller,handler.handlerName,forEvent(event,data))},ready=!1,pending=[],pendingBytes=0;socket.onAny((event,data)=>{if(ready)return dispatch(event,data);let size=Buffer.byteLength(JSON.stringify([event,data]));if(pending.length>=64||pendingBytes+size>1048576){pending.length=0,pendingBytes=0,socket.disconnect(!0);return}pending.push([event,data]),pendingBytes+=size}),socket.on(`disconnect`,()=>{pending.length=0,pendingBytes=0,invokeAll(controller,handlers,`disconnect`,ctx)}),socket.on(`error`,err=>{let errorCtx=forEvent(ctx.event,{message:err.message,name:err.name});invokeAll(controller,handlers,`error`,errorCtx)}),Promise.resolve(userRoomJoined).then(()=>invokeAll(controller,handlers,`connect`,ctx)).then(()=>{if(socket.connected){ready=!0,pendingBytes=0;for(let[event,data]of pending.splice(0))dispatch(event,data)}}).catch(err=>{log.error({err},`Socket.IO user-room join failed; disconnecting`),pending.length=0,pendingBytes=0,socket.disconnect(!0)})};return{beforeStart({container}){container.registerInstance(SOCKET_IO,io),container.registerInstance(WS_USER_BROADCASTER,userBroadcaster);for(let controllerClass of wsControllerRegistry){let namespace=getClassMetaOrUndefined(WS_METADATA.WS_CONTROLLER,controllerClass);if(namespace===void 0)continue;let handlers=getClassMeta(WS_METADATA.WS_HANDLERS,controllerClass,[]),nsp=io.of(namespace);namespaces.push(nsp),auth&&nsp.use(async(socket,next)=>{try{let resolved=await auth.resolveUser(socket.request,socket.handshake.auth);if(!resolved?.id)return next(Error(`Unauthorized`));socket.data.user=resolved,next()}catch{next(Error(`Unauthorized`))}}),nsp.on(`connection`,socket=>handleConnection(socket,namespace,handlers,container.resolve(controllerClass))),log.info(`Registered Socket.IO namespace: ${namespace} (${controllerClass.name})`)}},afterStart({server}){if(!server)return;let events=[`request`,`upgrade`,`close`,`listening`],before=new Map(events.map(event=>[event,server.listeners(event)]));io.attach(server);let changes=events.map(event=>{let prior=before.get(event),after=server.listeners(event);return{event,added:after.filter(listener=>!prior.includes(listener)),displaced:prior.filter(listener=>!after.includes(listener))}});detachFromServer=()=>{for(let{event,added,displaced}of changes){for(let listener of added)server.off(event,listener);for(let listener of displaced)server.listeners(event).includes(listener)||server.on(event,listener)}}},async shutdown(){await Promise.allSettled(namespaces.map(async nsp=>{nsp.disconnectSockets(!0),await nsp.adapter.close()})),io.engine?.close(),detachFromServer?.(),detachFromServer=null}}}});export{SOCKET_IO,SocketIoAdapter,SocketIoContext};
|
|
12
12
|
//# sourceMappingURL=socket-io.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-ws v7.1.
|
|
2
|
+
* @forinda/kickjs-ws v7.1.2
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
11
|
function parseCookies(header){if(!header)return{};let out={};for(let part of header.split(`;`)){let idx=part.indexOf(`=`);if(idx===-1)continue;let k=part.slice(0,idx).trim(),v=part.slice(idx+1).trim();if(k)try{out[k]=decodeURIComponent(v)}catch{out[k]=v}}return out}var WsContext=class{socket;server;roomManager;namespaceSockets;onSend;onBroadcast;id;data;event;namespace;request;metadata=new Map;constructor(socket,server,roomManager,namespaceSockets,id,namespace,request,onSend,onBroadcast){this.socket=socket,this.server=server,this.roomManager=roomManager,this.namespaceSockets=namespaceSockets,this.onSend=onSend,this.onBroadcast=onBroadcast,this.id=id,this.namespace=namespace,this.request=request,this.data=null,this.event=``}get cookies(){return parseCookies(this.request.headers.cookie)}get(key){return this.metadata.get(key)}set(key,value){this.metadata.set(key,value)}send(event,data){this.socket.readyState===this.socket.OPEN&&(this.socket.send(JSON.stringify({event,data})),this.onSend?.(1))}broadcast(event,data){let message=JSON.stringify({event,data}),sent=0;for(let[id,socket]of this.namespaceSockets)id!==this.id&&socket.readyState===socket.OPEN&&(socket.send(message),sent++);sent&&this.onSend?.(sent),this.onBroadcast?.(this.namespace,event,data,this.id)}broadcastAll(event,data){let message=JSON.stringify({event,data}),sent=0;for(let[,socket]of this.namespaceSockets)socket.readyState===socket.OPEN&&(socket.send(message),sent++);sent&&this.onSend?.(sent),this.onBroadcast?.(this.namespace,event,data)}join(room){this.roomManager.join(this.id,this.socket,room)}leave(room){this.roomManager.leave(this.id,room)}rooms(){return this.roomManager.getRooms(this.id)}to(room){return{send:(event,data)=>{this.roomManager.broadcast(room,event,data)}}}};export{parseCookies as n,WsContext as t};
|
|
12
|
-
//# sourceMappingURL=ws-context-
|
|
12
|
+
//# sourceMappingURL=ws-context-C5N1jlAf.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-context-
|
|
1
|
+
{"version":3,"file":"ws-context-C5N1jlAf.mjs","names":[],"sources":["../src/ws-context.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport type { WebSocket, WebSocketServer } from 'ws'\nimport type { RoomManager } from './room-manager'\n\n/** Raw `Cookie` header parse, shared by the ws and Socket.IO contexts. */\nexport function parseCookies(header: string | undefined): Record<string, string> {\n if (!header) return {}\n const out: Record<string, string> = {}\n for (const part of header.split(';')) {\n const idx = part.indexOf('=')\n if (idx === -1) continue\n const k = part.slice(0, idx).trim()\n const v = part.slice(idx + 1).trim()\n if (!k) continue\n try {\n out[k] = decodeURIComponent(v)\n } catch {\n out[k] = v // malformed %-encoding: keep it raw rather than fail the handler\n }\n }\n return out\n}\n\n/**\n * Context object passed to WebSocket handler methods.\n * Analogous to RequestContext for HTTP controllers.\n *\n * @example\n * ```ts\n * @OnMessage('chat:send')\n * handleSend(ctx: WsContext) {\n * console.log(ctx.data) // parsed message payload\n * ctx.send('chat:ack', { ok: true })\n * ctx.broadcast('chat:receive', ctx.data)\n * ctx.join('room-1')\n * ctx.to('room-1').send('chat:receive', ctx.data)\n * }\n * ```\n */\nexport class WsContext {\n /** Unique connection ID */\n readonly id: string\n /** Parsed message payload (set for @OnMessage handlers) */\n data: any\n /** Event name from the message envelope (set for @OnMessage handlers) */\n event: string\n /** The namespace this connection belongs to */\n readonly namespace: string\n /** The HTTP upgrade request — available from @OnConnect onward. Use to read\n * cookies, headers, query string, and client IP for authenticated handshakes. */\n readonly request: IncomingMessage\n\n private metadata = new Map<string, any>()\n\n constructor(\n readonly socket: WebSocket,\n readonly server: WebSocketServer,\n private readonly roomManager: RoomManager,\n private readonly namespaceSockets: Map<string, WebSocket>,\n id: string,\n namespace: string,\n request: IncomingMessage,\n /** Called with the number of frames each send wrote — feeds `messagesSent`. */\n private readonly onSend?: (count: number) => void,\n /** Relays namespace broadcasts to other instances when the adapter has a broker. */\n private readonly onBroadcast?: (\n namespace: string,\n event: string,\n data: any,\n excludeId?: string,\n ) => void,\n ) {\n this.id = id\n this.namespace = namespace\n this.request = request\n this.data = null\n this.event = ''\n }\n\n /** Parsed cookies from the upgrade request (raw `Cookie` header parse). */\n get cookies(): Record<string, string> {\n return parseCookies(this.request.headers.cookie)\n }\n\n /** Get a metadata value */\n get<T = any>(key: string): T | undefined {\n return this.metadata.get(key)\n }\n\n /** Set a metadata value (persists for the lifetime of the connection) */\n set(key: string, value: any): void {\n this.metadata.set(key, value)\n }\n\n /** Send a message to this socket */\n send(event: string, data: any): void {\n if (this.socket.readyState === this.socket.OPEN) {\n this.socket.send(JSON.stringify({ event, data }))\n this.onSend?.(1)\n }\n }\n\n /** Send to all sockets in the same namespace except this one */\n broadcast(event: string, data: any): void {\n const message = JSON.stringify({ event, data })\n let sent = 0\n for (const [id, socket] of this.namespaceSockets) {\n if (id !== this.id && socket.readyState === socket.OPEN) {\n socket.send(message)\n sent++\n }\n }\n if (sent) this.onSend?.(sent)\n this.onBroadcast?.(this.namespace, event, data, this.id)\n }\n\n /** Send to all sockets in the same namespace including this one */\n broadcastAll(event: string, data: any): void {\n const message = JSON.stringify({ event, data })\n let sent = 0\n for (const [, socket] of this.namespaceSockets) {\n if (socket.readyState === socket.OPEN) {\n socket.send(message)\n sent++\n }\n }\n if (sent) this.onSend?.(sent)\n this.onBroadcast?.(this.namespace, event, data)\n }\n\n /** Join a room. Names are global across namespaces — see {@link RoomManager}. */\n join(room: string): void {\n this.roomManager.join(this.id, this.socket, room)\n }\n\n /** Leave a room */\n leave(room: string): void {\n this.roomManager.leave(this.id, room)\n }\n\n /** Get all rooms this socket is in */\n rooms(): string[] {\n return this.roomManager.getRooms(this.id)\n }\n\n /** Send to all sockets in a room */\n to(room: string): { send(event: string, data: any): void } {\n return {\n send: (event: string, data: any) => {\n this.roomManager.broadcast(room, event, data)\n },\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAKA,SAAgB,aAAa,OAAoD,CAC/E,GAAI,CAAC,OAAQ,MAAO,CAAC,EACrB,IAAM,IAA8B,CAAC,EACrC,IAAK,IAAM,QAAQ,OAAO,MAAM,GAAG,EAAG,CACpC,IAAM,IAAM,KAAK,QAAQ,GAAG,EAC5B,GAAI,MAAQ,GAAI,SAChB,IAAM,EAAI,KAAK,MAAM,EAAG,GAAG,CAAC,CAAC,KAAK,EAC5B,EAAI,KAAK,MAAM,IAAM,CAAC,CAAC,CAAC,KAAK,EAC9B,KACL,GAAI,CACF,IAAI,GAAK,mBAAmB,CAAC,CAC/B,MAAQ,CACN,IAAI,GAAK,CACX,CACF,CACA,OAAO,GACT,CAkBA,IAAa,UAAb,KAAuB,CAgBV,OACA,OACQ,YACA,iBAKA,OAEA,YAxBnB,GAEA,KAEA,MAEA,UAGA,QAEA,SAAmB,IAAI,IAEvB,YACE,OACA,OACA,YACA,iBACA,GACA,UACA,QAEA,OAEA,YAMA,CAhBS,KAAA,OAAA,OACA,KAAA,OAAA,OACQ,KAAA,YAAA,YACA,KAAA,iBAAA,iBAKA,KAAA,OAAA,OAEA,KAAA,YAAA,YAOjB,KAAK,GAAK,GACV,KAAK,UAAY,UACjB,KAAK,QAAU,QACf,KAAK,KAAO,KACZ,KAAK,MAAQ,EACf,CAGA,IAAI,SAAkC,CACpC,OAAO,aAAa,KAAK,QAAQ,QAAQ,MAAM,CACjD,CAGA,IAAa,IAA4B,CACvC,OAAO,KAAK,SAAS,IAAI,GAAG,CAC9B,CAGA,IAAI,IAAa,MAAkB,CACjC,KAAK,SAAS,IAAI,IAAK,KAAK,CAC9B,CAGA,KAAK,MAAe,KAAiB,CAC/B,KAAK,OAAO,aAAe,KAAK,OAAO,OACzC,KAAK,OAAO,KAAK,KAAK,UAAU,CAAE,MAAO,IAAK,CAAC,CAAC,EAChD,KAAK,SAAS,CAAC,EAEnB,CAGA,UAAU,MAAe,KAAiB,CACxC,IAAM,QAAU,KAAK,UAAU,CAAE,MAAO,IAAK,CAAC,EAC1C,KAAO,EACX,IAAK,GAAM,CAAC,GAAI,UAAW,KAAK,iBAC1B,KAAO,KAAK,IAAM,OAAO,aAAe,OAAO,OACjD,OAAO,KAAK,OAAO,EACnB,QAGA,MAAM,KAAK,SAAS,IAAI,EAC5B,KAAK,cAAc,KAAK,UAAW,MAAO,KAAM,KAAK,EAAE,CACzD,CAGA,aAAa,MAAe,KAAiB,CAC3C,IAAM,QAAU,KAAK,UAAU,CAAE,MAAO,IAAK,CAAC,EAC1C,KAAO,EACX,IAAK,GAAM,EAAG,UAAW,KAAK,iBACxB,OAAO,aAAe,OAAO,OAC/B,OAAO,KAAK,OAAO,EACnB,QAGA,MAAM,KAAK,SAAS,IAAI,EAC5B,KAAK,cAAc,KAAK,UAAW,MAAO,IAAI,CAChD,CAGA,KAAK,KAAoB,CACvB,KAAK,YAAY,KAAK,KAAK,GAAI,KAAK,OAAQ,IAAI,CAClD,CAGA,MAAM,KAAoB,CACxB,KAAK,YAAY,MAAM,KAAK,GAAI,IAAI,CACtC,CAGA,OAAkB,CAChB,OAAO,KAAK,YAAY,SAAS,KAAK,EAAE,CAC1C,CAGA,GAAG,KAAwD,CACzD,MAAO,CACL,MAAO,MAAe,OAAc,CAClC,KAAK,YAAY,UAAU,KAAM,MAAO,IAAI,CAC9C,CACF,CACF,CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forinda/kickjs-ws",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.2",
|
|
4
4
|
"description": "Realtime for KickJS: decorator-driven WebSocket controllers on ws or Socket.IO, a Redis broker for multiple instances, and a Centrifugo integration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kickjs",
|
|
@@ -48,16 +48,16 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
51
|
-
"@swc/core": "^1.16.
|
|
52
|
-
"@types/node": "^26.
|
|
51
|
+
"@swc/core": "^1.16.2",
|
|
52
|
+
"@types/node": "^26.6.2",
|
|
53
53
|
"@types/ws": "^8.18.0",
|
|
54
|
-
"ioredis": "^
|
|
54
|
+
"ioredis": "^6.0.0",
|
|
55
55
|
"socket.io": "^4.8.3",
|
|
56
56
|
"socket.io-client": "^4.8.3",
|
|
57
57
|
"typescript": "^7.0.2",
|
|
58
|
-
"vitest": "^5.0.
|
|
58
|
+
"vitest": "^5.0.1",
|
|
59
59
|
"ws": "^8.21.3",
|
|
60
|
-
"@forinda/kickjs": "
|
|
60
|
+
"@forinda/kickjs": "9.0.1"
|
|
61
61
|
},
|
|
62
62
|
"publishConfig": {
|
|
63
63
|
"access": "public"
|