@zap-socket/server 0.0.16 → 0.0.17
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/dist/server.d.ts +20 -6
- package/dist/server.js +74 -10
- package/dist/utils.js +2 -0
- package/package.json +2 -2
package/dist/server.d.ts
CHANGED
@@ -1,9 +1,21 @@
|
|
1
1
|
import { WebSocketServer, WebSocket } from "ws";
|
2
2
|
import type { EventMap, ZapEvent, ZapServerEvent } from "@zap-socket/types";
|
3
|
-
|
3
|
+
import { ZodType, z } from "zod";
|
4
|
+
type CorsOptions = {
|
5
|
+
origin: string[];
|
6
|
+
methods: string[];
|
7
|
+
headers: string[];
|
8
|
+
credentials: boolean;
|
9
|
+
};
|
10
|
+
type ZapServerConstructorT = {
|
4
11
|
port: number;
|
5
12
|
events?: EventMap;
|
6
|
-
|
13
|
+
cors?: CorsOptions;
|
14
|
+
options?: {
|
15
|
+
heartbeatPingFrequency: number;
|
16
|
+
};
|
17
|
+
};
|
18
|
+
type ExtractSendData<T, K extends keyof T> = T[K] extends ZapServerEvent<any> ? T[K]['data'] : T[K] extends ZapEvent<any, any> ? T[K]['emitType'] extends ZodType<any, any, any> ? z.infer<T[K]['emitType']> : ReturnType<T[K]['process']> extends undefined ? undefined : ReturnType<T[K]['process']> : never;
|
7
19
|
export declare class ZapServer<T extends EventMap> {
|
8
20
|
wss: WebSocketServer;
|
9
21
|
onconnect: (handler: (ctx: {
|
@@ -14,19 +26,21 @@ export declare class ZapServer<T extends EventMap> {
|
|
14
26
|
private wsToId;
|
15
27
|
private idToWs;
|
16
28
|
private _events;
|
17
|
-
|
29
|
+
private heartbeatMiss;
|
30
|
+
constructor({ port, events, options }: ZapServerConstructorT, callback?: () => void);
|
18
31
|
private removeClient;
|
32
|
+
private heartbeat;
|
19
33
|
sendMessageRaw(clientId: string, data: any): void;
|
20
34
|
sendMessage(event: keyof T, clientId: string, data: any): void;
|
21
35
|
broadcastRaw(data: any): void;
|
22
36
|
broadcast(event: keyof T, data: any): void;
|
23
37
|
selectiveBroascast(event: string, data: any, connections: string[]): void;
|
24
38
|
get events(): { [K in keyof T as T[K] extends ZapServerEvent<any> | ZapEvent<any, any> ? K : never]: {
|
25
|
-
send: (clientId: string, data?:
|
26
|
-
broadcast: (data?:
|
39
|
+
send: (clientId: string, data?: ExtractSendData<T, K>) => void;
|
40
|
+
broadcast: (data?: ExtractSendData<T, K>) => void;
|
27
41
|
}; };
|
28
42
|
get clients(): string[];
|
29
43
|
get socketMap(): Map<string, WebSocket>;
|
30
44
|
}
|
31
|
-
export declare const createZapServer: <T extends EventMap>({ port, events }: ZapServerConstructorT, callback?: () => void) => ZapServer<T>;
|
45
|
+
export declare const createZapServer: <T extends EventMap>({ port, events, cors, options }: ZapServerConstructorT, callback?: () => void) => ZapServer<T>;
|
32
46
|
export {};
|
package/dist/server.js
CHANGED
@@ -4,13 +4,44 @@ const isClientEvent = (event) => {
|
|
4
4
|
return "process" in event; // both zapEvent and zapStream have process in them.
|
5
5
|
};
|
6
6
|
export class ZapServer {
|
7
|
+
// public server: http.Server;
|
7
8
|
wss;
|
8
9
|
onconnect;
|
9
10
|
onconnectHandler;
|
10
11
|
wsToId;
|
11
12
|
idToWs;
|
12
13
|
_events = {};
|
13
|
-
|
14
|
+
heartbeatMiss = new Map();
|
15
|
+
constructor({ port, events = {}, options }, callback) {
|
16
|
+
// this.server = http.createServer((req, res) => {
|
17
|
+
//
|
18
|
+
// if (cors) {
|
19
|
+
// const origin = req.headers.origin;
|
20
|
+
//
|
21
|
+
// if (origin && cors.origin && (cors.origin.includes(origin) || cors.origin.includes("*"))) {
|
22
|
+
// res.setHeader('Access-Control-Allow-Origin', origin);
|
23
|
+
// }
|
24
|
+
// if (cors.methods) {
|
25
|
+
// res.setHeader("Access-Control-Allow-Methods", cors.methods.join(", "));
|
26
|
+
// }
|
27
|
+
// if (cors.headers) {
|
28
|
+
// res.setHeader("Access-Control-Allow-Headers", cors.headers.join(", "));
|
29
|
+
// }
|
30
|
+
// if (cors.credentials !== undefined) {
|
31
|
+
// res.setHeader("Access-Control-Allow-Credentials", cors.credentials ? "true" : "false");
|
32
|
+
// }
|
33
|
+
// }
|
34
|
+
//
|
35
|
+
// // pre-flight response
|
36
|
+
// if (req.method === "OPTIONS") {
|
37
|
+
// res.writeHead(204);
|
38
|
+
// res.end();
|
39
|
+
// return;
|
40
|
+
// }
|
41
|
+
//
|
42
|
+
// res.writeHead(404);
|
43
|
+
// res.end();
|
44
|
+
// });
|
14
45
|
this.wss = new WebSocketServer({ port });
|
15
46
|
this.wsToId = new Map();
|
16
47
|
this.idToWs = new Map();
|
@@ -19,13 +50,18 @@ export class ZapServer {
|
|
19
50
|
this.onconnect = (handler) => {
|
20
51
|
this.onconnectHandler = handler;
|
21
52
|
};
|
53
|
+
const seconds = options?.heartbeatPingFrequency ?? 5;
|
54
|
+
const frequency = (Number.isFinite(seconds) && seconds > 0 ? seconds : 5) * 1000;
|
55
|
+
setInterval(() => this.heartbeat(), frequency);
|
22
56
|
this.wss.on("listening", () => {
|
23
57
|
if (callback)
|
24
58
|
callback();
|
25
59
|
});
|
26
60
|
this.wss.on("connection", (ws, req) => {
|
27
|
-
ws.on("message", (message) => {
|
28
|
-
|
61
|
+
ws.on("message", async (message) => {
|
62
|
+
const id = this.wsToId.get(ws);
|
63
|
+
// setting up socket id
|
64
|
+
if (!id && message.toString() === "OPEN") {
|
29
65
|
const id = generateId();
|
30
66
|
this.wsToId.set(ws, id);
|
31
67
|
this.idToWs.set(id, ws);
|
@@ -36,6 +72,9 @@ export class ZapServer {
|
|
36
72
|
});
|
37
73
|
return;
|
38
74
|
}
|
75
|
+
else if (id && message.toString() === "heartbeat") {
|
76
|
+
this.heartbeatMiss.set(id, 0);
|
77
|
+
}
|
39
78
|
const clientId = this.wsToId.get(ws);
|
40
79
|
const parsedMessage = deserialize(message.toString());
|
41
80
|
if (!parsedMessage)
|
@@ -45,6 +84,15 @@ export class ZapServer {
|
|
45
84
|
const eventObj = this._events[key];
|
46
85
|
if (!eventObj || !isClientEvent(eventObj))
|
47
86
|
return;
|
87
|
+
// Type validation.
|
88
|
+
const inputType = eventObj.input;
|
89
|
+
const { success, error } = inputType.safeParse(data);
|
90
|
+
if (!success && error) {
|
91
|
+
// check if the message is of req-res
|
92
|
+
if (requestId) {
|
93
|
+
}
|
94
|
+
return;
|
95
|
+
}
|
48
96
|
const { process, middleware } = eventObj;
|
49
97
|
// Setup middleware context
|
50
98
|
const ctx = {};
|
@@ -61,7 +109,9 @@ export class ZapServer {
|
|
61
109
|
data: parsedMessage,
|
62
110
|
metadata,
|
63
111
|
};
|
64
|
-
|
112
|
+
let shouldPass = m(ctx, msg);
|
113
|
+
shouldPass = shouldPass instanceof Promise ? await shouldPass : shouldPass;
|
114
|
+
if (!shouldPass)
|
65
115
|
return;
|
66
116
|
}
|
67
117
|
}
|
@@ -70,14 +120,13 @@ export class ZapServer {
|
|
70
120
|
if (requestId) { // req-res premitive
|
71
121
|
let result;
|
72
122
|
if (batch) {
|
73
|
-
if (!data) {
|
74
|
-
ws.send("ACK " + requestId);
|
75
|
-
return;
|
76
|
-
}
|
77
123
|
result = data.map((part) => process(part, context));
|
78
124
|
}
|
79
125
|
else {
|
80
126
|
result = process(data, context);
|
127
|
+
if (result instanceof Promise) {
|
128
|
+
result = await result;
|
129
|
+
}
|
81
130
|
}
|
82
131
|
if (result === undefined) { // just ACK the request process returns nothing
|
83
132
|
ws.send("ACK " + requestId);
|
@@ -114,6 +163,21 @@ export class ZapServer {
|
|
114
163
|
this.idToWs.delete(clientId);
|
115
164
|
}
|
116
165
|
}
|
166
|
+
heartbeat() {
|
167
|
+
this.idToWs.forEach((ws, id) => {
|
168
|
+
const misses = this.heartbeatMiss.get(id) ?? 0;
|
169
|
+
if (misses > 2) {
|
170
|
+
if (ws)
|
171
|
+
ws.close();
|
172
|
+
this.idToWs.delete(id);
|
173
|
+
this.heartbeatMiss.delete(id);
|
174
|
+
}
|
175
|
+
else {
|
176
|
+
this.heartbeatMiss.set(id, misses + 1);
|
177
|
+
}
|
178
|
+
});
|
179
|
+
this.broadcastRaw("heartbeat");
|
180
|
+
}
|
117
181
|
sendMessageRaw(clientId, data) {
|
118
182
|
const ws = this.idToWs.get(clientId);
|
119
183
|
// TODO: throw a nice error
|
@@ -209,7 +273,7 @@ export class ZapServer {
|
|
209
273
|
return this.idToWs;
|
210
274
|
}
|
211
275
|
}
|
212
|
-
export const createZapServer = ({ port, events }, callback) => {
|
213
|
-
const server = new ZapServer({ port, events }, callback);
|
276
|
+
export const createZapServer = ({ port, events, cors, options }, callback) => {
|
277
|
+
const server = new ZapServer({ port, events, cors, options }, callback);
|
214
278
|
return server;
|
215
279
|
};
|
package/dist/utils.js
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@zap-socket/server",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.17",
|
4
4
|
"description": "A fully typesafe tRPC-inspired WebSocket library with Zod validation, req-res model, and native subscriptions.",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"types": "dist/index.d.ts",
|
@@ -23,7 +23,7 @@
|
|
23
23
|
"vitest": "^3.0.9"
|
24
24
|
},
|
25
25
|
"dependencies": {
|
26
|
-
"@zap-socket/types": "^0.0.
|
26
|
+
"@zap-socket/types": "^0.0.9",
|
27
27
|
"zod": "^3.24.2"
|
28
28
|
}
|
29
29
|
}
|