@digo-labs/ws 0.1.3

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.
@@ -0,0 +1,27 @@
1
+ import { WebsocketConnectionConfig, WebsocketListener } from '@digo-labs/common';
2
+ export declare class WebsocketClient {
3
+ private url;
4
+ private id;
5
+ private reconnectEnabled;
6
+ private reconnectInterval;
7
+ private maxReconnectInterval;
8
+ private reconnectDelay;
9
+ private socket;
10
+ private alive;
11
+ private listeners;
12
+ private subscribedChannels;
13
+ private openHandlers;
14
+ private closeHandlers;
15
+ private errorHandlers;
16
+ constructor(properties: WebsocketConnectionConfig);
17
+ private open;
18
+ private sendToHub;
19
+ send(channel: string, data?: unknown): void;
20
+ setState(channel: string, data?: unknown): void;
21
+ subscribe(channel: string, listener: WebsocketListener): void;
22
+ unsubscribe(channel: string, listener?: WebsocketListener): void;
23
+ onOpen(handler: () => void): void;
24
+ onClose(handler: () => void): void;
25
+ onError(handler: (error: Event) => void): void;
26
+ close(): void;
27
+ }
package/dist/client.js ADDED
@@ -0,0 +1,76 @@
1
+ var c = Object.defineProperty;
2
+ var h = (i, e, s) => e in i ? c(i, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : i[e] = s;
3
+ var t = (i, e, s) => h(i, typeof e != "symbol" ? e + "" : e, s);
4
+ import { WebsocketClientEvent as n } from "@digo-labs/common";
5
+ class d {
6
+ constructor(e) {
7
+ t(this, "url");
8
+ t(this, "id");
9
+ t(this, "reconnectEnabled");
10
+ t(this, "reconnectInterval");
11
+ t(this, "maxReconnectInterval");
12
+ t(this, "reconnectDelay");
13
+ t(this, "socket", null);
14
+ t(this, "alive", !0);
15
+ t(this, "listeners", /* @__PURE__ */ new Map());
16
+ t(this, "subscribedChannels", /* @__PURE__ */ new Set());
17
+ t(this, "openHandlers", []);
18
+ t(this, "closeHandlers", []);
19
+ t(this, "errorHandlers", []);
20
+ this.url = `ws://${e.host}:${e.port}`, this.id = e.id, this.reconnectEnabled = e.reconnect ?? !0, this.reconnectInterval = e.reconnectInterval ?? 1e3, this.maxReconnectInterval = e.maxReconnectInterval ?? 1e4, this.reconnectDelay = this.reconnectInterval, this.open();
21
+ }
22
+ open() {
23
+ this.socket = new WebSocket(this.url), this.socket.onopen = () => {
24
+ this.reconnectDelay = this.reconnectInterval, this.sendToHub({ type: n.IDENTIFY, id: this.id }), this.subscribedChannels.size > 0 && this.sendToHub({ type: n.SUBSCRIBE, channels: [...this.subscribedChannels] }), this.openHandlers.forEach((e) => e());
25
+ }, this.socket.onmessage = (e) => {
26
+ let s;
27
+ try {
28
+ s = JSON.parse(e.data);
29
+ } catch {
30
+ return;
31
+ }
32
+ const r = this.listeners.get(s.channel);
33
+ r == null || r.forEach((o) => o(s.data));
34
+ }, this.socket.onclose = (e) => {
35
+ this.closeHandlers.forEach((s) => s()), e.code !== 4e3 && this.alive && this.reconnectEnabled && setTimeout(() => {
36
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectInterval), this.open();
37
+ }, this.reconnectDelay);
38
+ }, this.socket.onerror = (e) => {
39
+ this.errorHandlers.forEach((s) => s(e));
40
+ };
41
+ }
42
+ sendToHub(e) {
43
+ var s;
44
+ ((s = this.socket) == null ? void 0 : s.readyState) === WebSocket.OPEN && this.socket.send(JSON.stringify(e));
45
+ }
46
+ send(e, s) {
47
+ this.sendToHub({ type: n.MESSAGE, channel: e, data: s });
48
+ }
49
+ setState(e, s) {
50
+ this.sendToHub({ type: n.STATE, channel: e, data: s });
51
+ }
52
+ subscribe(e, s) {
53
+ this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(s), this.subscribedChannels.has(e) || (this.subscribedChannels.add(e), this.sendToHub({ type: n.SUBSCRIBE, channels: [e] }));
54
+ }
55
+ unsubscribe(e, s) {
56
+ var r, o;
57
+ s ? ((r = this.listeners.get(e)) == null || r.delete(s), ((o = this.listeners.get(e)) == null ? void 0 : o.size) === 0 && (this.listeners.delete(e), this.subscribedChannels.delete(e), this.sendToHub({ type: n.UNSUBSCRIBE, channels: [e] }))) : (this.listeners.delete(e), this.subscribedChannels.delete(e), this.sendToHub({ type: n.UNSUBSCRIBE, channels: [e] }));
58
+ }
59
+ onOpen(e) {
60
+ this.openHandlers.push(e);
61
+ }
62
+ onClose(e) {
63
+ this.closeHandlers.push(e);
64
+ }
65
+ onError(e) {
66
+ this.errorHandlers.push(e);
67
+ }
68
+ close() {
69
+ var e;
70
+ this.alive = !1, (e = this.socket) == null || e.close();
71
+ }
72
+ }
73
+ export {
74
+ d as WebsocketClient
75
+ };
76
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sources":["../src/client.ts"],"sourcesContent":["import { WebsocketConnectionConfig, WebsocketClientEvent, WebsocketClientMessage, WebsocketHubMessage, WebsocketListener } from '@digo-labs/common';\n\nexport class WebsocketClient {\n private url: string;\n private id: string;\n private reconnectEnabled: boolean;\n private reconnectInterval: number;\n private maxReconnectInterval: number;\n private reconnectDelay: number;\n\n private socket: WebSocket | null = null;\n private alive = true;\n private listeners = new Map<string, Set<WebsocketListener>>();\n private subscribedChannels = new Set<string>();\n private openHandlers: (() => void)[] = [];\n private closeHandlers: (() => void)[] = [];\n private errorHandlers: ((error: Event) => void)[] = [];\n\n constructor(properties: WebsocketConnectionConfig) {\n this.url = `ws://${properties.host}:${properties.port}`;\n this.id = properties.id;\n this.reconnectEnabled = properties.reconnect ?? true;\n this.reconnectInterval = properties.reconnectInterval ?? 1000;\n this.maxReconnectInterval = properties.maxReconnectInterval ?? 10000;\n this.reconnectDelay = this.reconnectInterval;\n\n this.open();\n }\n\n private open(): void {\n this.socket = new WebSocket(this.url);\n\n this.socket.onopen = () => {\n this.reconnectDelay = this.reconnectInterval;\n\n this.sendToHub({ type: WebsocketClientEvent.IDENTIFY, id: this.id });\n\n if (this.subscribedChannels.size > 0) {\n this.sendToHub({ type: WebsocketClientEvent.SUBSCRIBE, channels: [...this.subscribedChannels] });\n }\n\n this.openHandlers.forEach(h => h());\n };\n\n this.socket.onmessage = (event) => {\n let message: WebsocketHubMessage;\n\n try {\n message = JSON.parse(event.data as string);\n } catch { return; }\n\n const channelListeners = this.listeners.get(message.channel);\n\n channelListeners?.forEach(listener => listener(message.data));\n };\n\n this.socket.onclose = (event) => {\n this.closeHandlers.forEach(h => h());\n\n if (event.code === 4000) return;\n\n if (this.alive && this.reconnectEnabled) {\n setTimeout(() => {\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectInterval);\n this.open();\n }, this.reconnectDelay);\n }\n };\n\n this.socket.onerror = (error) => {\n this.errorHandlers.forEach(h => h(error));\n };\n }\n\n private sendToHub(message: WebsocketClientMessage): void {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(JSON.stringify(message));\n }\n }\n\n public send(channel: string, data?: unknown): void {\n this.sendToHub({ type: WebsocketClientEvent.MESSAGE, channel, data });\n }\n\n public setState(channel: string, data?: unknown): void {\n this.sendToHub({ type: WebsocketClientEvent.STATE, channel, data });\n }\n\n public subscribe(channel: string, listener: WebsocketListener): void {\n if (!this.listeners.has(channel)) this.listeners.set(channel, new Set());\n\n this.listeners.get(channel)!.add(listener);\n\n if (!this.subscribedChannels.has(channel)) {\n this.subscribedChannels.add(channel);\n this.sendToHub({ type: WebsocketClientEvent.SUBSCRIBE, channels: [channel] });\n }\n }\n\n public unsubscribe(channel: string, listener?: WebsocketListener): void {\n if (listener) {\n this.listeners.get(channel)?.delete(listener);\n\n if (this.listeners.get(channel)?.size === 0) {\n this.listeners.delete(channel);\n this.subscribedChannels.delete(channel);\n this.sendToHub({ type: WebsocketClientEvent.UNSUBSCRIBE, channels: [channel] });\n }\n } else {\n this.listeners.delete(channel);\n this.subscribedChannels.delete(channel);\n this.sendToHub({ type: WebsocketClientEvent.UNSUBSCRIBE, channels: [channel] });\n }\n }\n\n public onOpen(handler: () => void): void { this.openHandlers.push(handler); }\n\n public onClose(handler: () => void): void { this.closeHandlers.push(handler); }\n\n public onError(handler: (error: Event) => void): void { this.errorHandlers.push(handler); }\n\n public close(): void {\n this.alive = false;\n this.socket?.close();\n }\n}\n"],"names":["WebsocketClient","properties","__publicField","WebsocketClientEvent","h","event","message","channelListeners","listener","error","_a","channel","data","_b","handler"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export * from './client';
@@ -0,0 +1,19 @@
1
+ import { WebsocketsHubConfig } from '@digo-labs/common';
2
+ export declare class WebsocketHub {
3
+ private wss;
4
+ private clients;
5
+ private state;
6
+ private logs;
7
+ private messageHandlers;
8
+ private heartbeatInterval;
9
+ constructor(config: WebsocketsHubConfig);
10
+ private heartbeat;
11
+ private handleConnection;
12
+ private handleMessage;
13
+ private addLog;
14
+ private broadcast;
15
+ private broadcastStatus;
16
+ onMessage(handler: (channel: string, data: unknown) => void): void;
17
+ getState(channel: string): unknown;
18
+ close(): void;
19
+ }
package/dist/server.js ADDED
@@ -0,0 +1,131 @@
1
+ var S = Object.defineProperty;
2
+ var E = (i, t, e) => t in i ? S(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
3
+ var h = (i, t, e) => E(i, typeof t != "symbol" ? t + "" : t, e);
4
+ import { WebSocketServer as b, WebSocket as l } from "ws";
5
+ import { WebsocketLogEvent as o, WebsocketClientEvent as r, WebSocketHubEvent as d, WEBSOCKET_HUB_CHANNEL as c } from "@digo-labs/common";
6
+ class w {
7
+ constructor(t) {
8
+ h(this, "wss");
9
+ h(this, "clients", /* @__PURE__ */ new Set());
10
+ h(this, "state", /* @__PURE__ */ new Map());
11
+ h(this, "logs", []);
12
+ h(this, "messageHandlers", []);
13
+ h(this, "heartbeatInterval");
14
+ this.wss = new b({ port: t.port }), this.heartbeatInterval = setInterval(() => this.heartbeat(), 3e4), this.wss.on("close", () => clearInterval(this.heartbeatInterval)), this.wss.on("connection", (e) => this.handleConnection(e));
15
+ }
16
+ heartbeat() {
17
+ let t = !1;
18
+ this.clients.forEach((e) => {
19
+ if (!e.alive) {
20
+ e.ws.terminate(), this.clients.delete(e), t = !0;
21
+ return;
22
+ }
23
+ e.alive = !1, e.ws.ping();
24
+ }), t && this.broadcastStatus();
25
+ }
26
+ handleConnection(t) {
27
+ const e = {
28
+ id: "unknown",
29
+ ws: t,
30
+ channels: /* @__PURE__ */ new Set(),
31
+ alive: !0
32
+ };
33
+ this.clients.add(e), t.on("error", () => {
34
+ }), t.on("pong", () => {
35
+ e.alive = !0;
36
+ }), t.on("message", (a) => {
37
+ let s;
38
+ try {
39
+ s = JSON.parse(a.toString());
40
+ } catch {
41
+ return;
42
+ }
43
+ this.handleMessage(e, t, s);
44
+ }), t.on("close", () => {
45
+ this.clients.delete(e), this.addLog(e.id, o.DISCONNECT, ""), this.broadcastStatus();
46
+ });
47
+ }
48
+ handleMessage(t, e, a) {
49
+ switch (a.type) {
50
+ case r.IDENTIFY: {
51
+ const s = [...this.clients].find((n) => n.id === a.id && n !== t);
52
+ s && (s.ws.close(4e3), this.clients.delete(s)), t.id = a.id, this.addLog(a.id, o.CONNECT, ""), this.broadcastStatus();
53
+ break;
54
+ }
55
+ case r.SUBSCRIBE: {
56
+ a.channels.forEach((s) => {
57
+ if (t.channels.add(s), this.state.has(s)) {
58
+ const n = {
59
+ type: d.STATE,
60
+ channel: s,
61
+ data: this.state.get(s)
62
+ };
63
+ e.send(JSON.stringify(n));
64
+ }
65
+ }), this.addLog(t.id, o.SUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
66
+ break;
67
+ }
68
+ case r.UNSUBSCRIBE: {
69
+ a.channels.forEach((s) => t.channels.delete(s)), this.addLog(t.id, o.UNSUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
70
+ break;
71
+ }
72
+ case r.MESSAGE: {
73
+ const s = {
74
+ type: d.MESSAGE,
75
+ channel: a.channel,
76
+ data: a.data
77
+ };
78
+ this.broadcast(t, a.channel, s), this.messageHandlers.forEach((n) => n(a.channel, a.data)), this.addLog(t.id, o.MESSAGE, a.channel, a.data), this.broadcastStatus();
79
+ break;
80
+ }
81
+ case r.STATE: {
82
+ this.state.set(a.channel, a.data);
83
+ const s = {
84
+ type: d.STATE,
85
+ channel: a.channel,
86
+ data: a.data
87
+ };
88
+ this.broadcast(t, a.channel, s), this.messageHandlers.forEach((n) => n(a.channel, a.data)), this.addLog(t.id, o.STATE, a.channel, a.data), this.broadcastStatus();
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ addLog(t, e, a, s) {
94
+ this.logs.push({ timestamp: Date.now(), sender: t, type: e, channel: a, data: s }), this.logs.length > 808 && this.logs.shift();
95
+ }
96
+ broadcast(t, e, a) {
97
+ const s = JSON.stringify(a);
98
+ this.clients.forEach((n) => {
99
+ n !== t && n.channels.has(e) && n.ws.readyState === l.OPEN && n.ws.send(s);
100
+ });
101
+ }
102
+ broadcastStatus() {
103
+ const t = {
104
+ clients: [...this.clients].map((s) => ({ id: s.id, channels: [...s.channels] })),
105
+ state: Object.fromEntries([...this.state].filter(([s]) => s !== c)),
106
+ logs: this.logs
107
+ };
108
+ this.state.set(c, t);
109
+ const e = {
110
+ type: d.STATE,
111
+ channel: c,
112
+ data: t
113
+ }, a = JSON.stringify(e);
114
+ this.clients.forEach((s) => {
115
+ s.channels.has(c) && s.ws.readyState === l.OPEN && s.ws.send(a);
116
+ });
117
+ }
118
+ onMessage(t) {
119
+ this.messageHandlers.push(t);
120
+ }
121
+ getState(t) {
122
+ return this.state.get(t);
123
+ }
124
+ close() {
125
+ clearInterval(this.heartbeatInterval), this.wss.close();
126
+ }
127
+ }
128
+ export {
129
+ w as WebsocketHub
130
+ };
131
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sources":["../src/server.ts"],"sourcesContent":["import { WebSocketServer, WebSocket } from 'ws';\n\nimport { WebsocketClientMessage, WebsocketClientEvent, WebSocketHubEvent, WebsocketHubMessage, WebsocketHubLogEntry, WebsocketLogEvent, WebsocketHubStatus, WebsocketsHubConfig, WEBSOCKET_HUB_CHANNEL } from '@digo-labs/common';\n\ninterface ConnectedClient {\n id: string;\n ws: WebSocket;\n channels: Set<string>;\n alive: boolean;\n}\n\nexport class WebsocketHub {\n private wss: WebSocketServer;\n\n private clients = new Set<ConnectedClient>();\n private state = new Map<string, unknown>();\n\n private logs: WebsocketHubLogEntry[] = [];\n private messageHandlers: ((channel: string, data: unknown) => void)[] = [];\n private heartbeatInterval: ReturnType<typeof setInterval>;\n\n constructor(config: WebsocketsHubConfig) {\n this.wss = new WebSocketServer({ port: config.port });\n\n this.heartbeatInterval = setInterval(() => this.heartbeat(), 30000);\n\n this.wss.on('close', () => clearInterval(this.heartbeatInterval));\n this.wss.on('connection', socket => this.handleConnection(socket));\n }\n\n private heartbeat(): void {\n let terminated = false;\n\n this.clients.forEach((client) => {\n if (!client.alive) {\n client.ws.terminate();\n\n this.clients.delete(client);\n\n terminated = true;\n\n return;\n }\n\n client.alive = false;\n\n client.ws.ping();\n });\n\n if (terminated) this.broadcastStatus();\n }\n\n private handleConnection(socket: WebSocket): void {\n const client: ConnectedClient = {\n id: 'unknown',\n ws: socket,\n channels: new Set(),\n alive: true,\n };\n\n this.clients.add(client);\n\n socket.on('error', () => {});\n\n socket.on('pong', () => {\n client.alive = true;\n });\n\n socket.on('message', (raw) => {\n let message: WebsocketClientMessage;\n\n try {\n message = JSON.parse(raw.toString());\n } catch { return; }\n\n this.handleMessage(client, socket, message);\n });\n\n socket.on('close', () => {\n this.clients.delete(client);\n\n this.addLog(client.id, WebsocketLogEvent.DISCONNECT, '');\n\n this.broadcastStatus();\n });\n }\n\n private handleMessage(client: ConnectedClient, socket: WebSocket, message: WebsocketClientMessage): void {\n switch (message.type) {\n case WebsocketClientEvent.IDENTIFY: {\n const existing = [...this.clients].find(c => c.id === message.id && c !== client);\n\n if (existing) {\n existing.ws.close(4000);\n\n this.clients.delete(existing);\n }\n\n client.id = message.id;\n\n this.addLog(message.id, WebsocketLogEvent.CONNECT, '');\n\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.SUBSCRIBE: {\n message.channels.forEach((channel) => {\n client.channels.add(channel);\n\n if (this.state.has(channel)) {\n const stateMessage: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: channel,\n data: this.state.get(channel),\n };\n\n socket.send(JSON.stringify(stateMessage));\n }\n });\n\n this.addLog(client.id, WebsocketLogEvent.SUBSCRIBE, message.channels.join(', '));\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.UNSUBSCRIBE: {\n message.channels.forEach(channel => client.channels.delete(channel));\n\n this.addLog(client.id, WebsocketLogEvent.UNSUBSCRIBE, message.channels.join(', '));\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.MESSAGE: {\n const outgoing: WebsocketHubMessage = {\n type: WebSocketHubEvent.MESSAGE,\n channel: message.channel,\n data: message.data,\n };\n\n this.broadcast(client, message.channel, outgoing);\n\n this.messageHandlers.forEach(h => h(message.channel, message.data));\n\n this.addLog(client.id, WebsocketLogEvent.MESSAGE, message.channel, message.data);\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.STATE: {\n this.state.set(message.channel, message.data);\n\n const outgoing: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: message.channel,\n data: message.data,\n };\n\n this.broadcast(client, message.channel, outgoing);\n\n this.messageHandlers.forEach(h => h(message.channel, message.data));\n\n this.addLog(client.id, WebsocketLogEvent.STATE, message.channel, message.data);\n\n this.broadcastStatus();\n break;\n }\n }\n }\n\n private addLog(sender: string, type: WebsocketLogEvent, channel: string, data?: unknown): void {\n this.logs.push({ timestamp: Date.now(), sender, type, channel, data });\n\n if (this.logs.length > 808) this.logs.shift();\n }\n\n private broadcast(sender: ConnectedClient, channel: string, message: WebsocketHubMessage): void {\n const payload = JSON.stringify(message);\n\n this.clients.forEach((client) => {\n if (client !== sender && client.channels.has(channel) && client.ws.readyState === WebSocket.OPEN) {\n client.ws.send(payload);\n }\n });\n }\n\n private broadcastStatus(): void {\n const status: WebsocketHubStatus = {\n clients: [...this.clients].map(c => ({ id: c.id, channels: [...c.channels] })),\n state: Object.fromEntries([...this.state].filter(([k]) => k !== WEBSOCKET_HUB_CHANNEL)),\n logs: this.logs,\n };\n\n this.state.set(WEBSOCKET_HUB_CHANNEL, status);\n\n const message: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: WEBSOCKET_HUB_CHANNEL,\n data: status,\n };\n\n const payload = JSON.stringify(message);\n\n this.clients.forEach((client) => {\n if (client.channels.has(WEBSOCKET_HUB_CHANNEL) && client.ws.readyState === WebSocket.OPEN) {\n client.ws.send(payload);\n }\n });\n }\n\n public onMessage(handler: (channel: string, data: unknown) => void): void { this.messageHandlers.push(handler); }\n\n public getState(channel: string): unknown { return this.state.get(channel); }\n\n public close(): void {\n clearInterval(this.heartbeatInterval);\n this.wss.close();\n }\n}\n"],"names":["WebsocketHub","config","__publicField","WebSocketServer","socket","terminated","client","raw","message","WebsocketLogEvent","WebsocketClientEvent","existing","c","channel","stateMessage","WebSocketHubEvent","outgoing","h","sender","type","data","payload","WebSocket","status","k","WEBSOCKET_HUB_CHANNEL","handler"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@digo-labs/ws",
3
+ "version": "0.1.3",
4
+ "type": "module",
5
+ "main": "./dist/client.js",
6
+ "module": "./dist/client.js",
7
+ "types": "./dist/client.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "development": "./src/index.ts",
11
+ "types": "./dist/client.d.ts",
12
+ "import": "./dist/client.js"
13
+ },
14
+ "./server": {
15
+ "development": "./src/server.ts",
16
+ "types": "./dist/server.d.ts",
17
+ "import": "./dist/server.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "registry": "https://registry.npmjs.org/"
27
+ },
28
+ "scripts": {
29
+ "build": "vite build --config vite.lib.ts",
30
+ "hub": "npx tsx src/start-hub.ts",
31
+ "lint": "eslint ./src",
32
+ "lint:fix": "eslint ./src --fix"
33
+ },
34
+ "peerDependencies": {
35
+ "@digo-labs/common": "^0.1.5"
36
+ },
37
+ "dependencies": {
38
+ "ws": "^8.18.0"
39
+ },
40
+ "devDependencies": {
41
+ "@digo-labs/dev-dependencies": "*",
42
+ "@types/ws": "^8.5.13"
43
+ }
44
+ }