backendium 0.0.0

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/src/router.ts ADDED
@@ -0,0 +1,228 @@
1
+ import backendiumHandler, {BackendiumHandlerType, RawHandlerType} from "./handler.js";
2
+ import {AuthCheckerType, AuthFailedType, BackendiumRequestOptionsType} from "./request.js";
3
+ import {NextFunction, Request, RequestHandler} from "express";
4
+ import Backendium from "./index.js";
5
+ import {WebSocketRouteConstructor} from "./ws.js";
6
+ import {WSRequestHandler, WSResponse} from "websocket-express";
7
+
8
+ export type MethodType = "use" | "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "checkout"
9
+ | "connect" | "copy" | "lock" | "merge" | "mkactivity" | "mkcol" | "move" | "m-search" | "notify" | "propfind"
10
+ | "proppatch" | "purge" | "report" | "search" | "subscribe" | "unsubscribe" | "trace" | "unlock" | "link" | "unlink"
11
+ | "useHTTP";
12
+
13
+ export type BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType> =
14
+ Array<BackendiumHandlerType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>>
15
+ | [string, ...Array<BackendiumHandlerType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>>]
16
+ | [...Array<BackendiumHandlerType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>>,
17
+ BackendiumRequestOptionsType<BodyType, ParamsType, QueryType, AuthType, HeadersType>]
18
+ | [string, ...Array<BackendiumHandlerType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>>,
19
+ BackendiumRequestOptionsType<BodyType, ParamsType, QueryType, AuthType, HeadersType>];
20
+
21
+ export class BackendiumRouter<GlobalAuthType = undefined> {
22
+ protected _handlers: Array<[MethodType, string | undefined, Array<ReturnType<RawHandlerType<GlobalAuthType>>>] | ["ws", string, Array<(app: Backendium) => WSRequestHandler>]> = [];
23
+ public authChecker: AuthCheckerType<GlobalAuthType> | undefined;
24
+ public authFailed: AuthFailedType | undefined;
25
+
26
+ constructor() {}
27
+
28
+ get handlers() {return this._handlers}
29
+
30
+ protected parseArgs<BodyType, ParamsType, QueryType, AuthType, HeadersType>(args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>): [string | undefined, Array<RawHandlerType<GlobalAuthType>>] {
31
+ let route: string | undefined = undefined, options: BackendiumRequestOptionsType<BodyType, ParamsType, QueryType, AuthType, HeadersType> | undefined = undefined;
32
+ let handlers: Array<BackendiumHandlerType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>> = args.map(elem => {
33
+ if (typeof elem === "string") route = elem;
34
+ if (typeof elem === "function") return elem;
35
+ // @ts-ignore
36
+ else options = elem;
37
+ }).filter(elem => typeof elem === "function");
38
+ return [route, handlers.map(handler => backendiumHandler(handler, options ?? {auth: false}))];
39
+ }
40
+
41
+ public addHandler<BodyType, ParamsType, QueryType, AuthType, HeadersType>(method: MethodType,
42
+ ...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
43
+ ): void {
44
+ let [route, handlers] = this.parseArgs(args);
45
+ this._handlers.push([method, route, handlers.map(handler => handler(this))]);
46
+ }
47
+
48
+ use<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
49
+ ): void {
50
+ this.addHandler("use", ...args);
51
+ }
52
+
53
+ useHTTP<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
54
+ ): void {
55
+ this.addHandler("useHTTP", ...args);
56
+ }
57
+
58
+ all<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
59
+ ): void {
60
+ this.addHandler("all", ...args);
61
+ }
62
+
63
+ get<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
64
+ ): void {
65
+ this.addHandler("get", ...args);
66
+ }
67
+
68
+ post<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
69
+ ): void {
70
+ this.addHandler("post", ...args);
71
+ }
72
+
73
+ put<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
74
+ ): void {
75
+ this.addHandler("put", ...args);
76
+ }
77
+
78
+ delete<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
79
+ ): void {
80
+ this.addHandler("delete", ...args);
81
+ }
82
+
83
+ patch<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
84
+ ): void {
85
+ this.addHandler("patch", ...args);
86
+ }
87
+
88
+ options<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
89
+ ): void {
90
+ this.addHandler("options", ...args);
91
+ }
92
+
93
+ head<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
94
+ ): void {
95
+ this.addHandler("head", ...args);
96
+ }
97
+
98
+ checkout<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
99
+ ): void {
100
+ this.addHandler("checkout", ...args);
101
+ }
102
+
103
+ connect<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
104
+ ): void {
105
+ this.addHandler("connect", ...args);
106
+ }
107
+
108
+ copy<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
109
+ ): void {
110
+ this.addHandler("copy", ...args);
111
+ }
112
+
113
+ lock<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
114
+ ): void {
115
+ this.addHandler("lock", ...args);
116
+ }
117
+
118
+ merge<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
119
+ ): void {
120
+ this.addHandler("merge", ...args);
121
+ }
122
+
123
+ mkactivity<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
124
+ ): void {
125
+ this.addHandler("mkactivity", ...args);
126
+ }
127
+
128
+ mkcol<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
129
+ ): void {
130
+ this.addHandler("mkcol", ...args);
131
+ }
132
+
133
+ move<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
134
+ ): void {
135
+ this.addHandler("move", ...args);
136
+ }
137
+
138
+ "m-search"<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
139
+ ): void {
140
+ this.addHandler("m-search", ...args);
141
+ }
142
+
143
+ notify<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
144
+ ): void {
145
+ this.addHandler("notify", ...args);
146
+ }
147
+
148
+ propfind<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
149
+ ): void {
150
+ this.addHandler("propfind", ...args);
151
+ }
152
+
153
+ proppatch<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
154
+ ): void {
155
+ this.addHandler("proppatch", ...args);
156
+ }
157
+
158
+ purge<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
159
+ ): void {
160
+ this.addHandler("purge", ...args);
161
+ }
162
+
163
+ report<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
164
+ ): void {
165
+ this.addHandler("report", ...args);
166
+ }
167
+
168
+ search<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
169
+ ): void {
170
+ this.addHandler("search", ...args);
171
+ }
172
+
173
+ subscribe<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
174
+ ): void {
175
+ this.addHandler("subscribe", ...args);
176
+ }
177
+
178
+ unsubscribe<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
179
+ ): void {
180
+ this.addHandler("unsubscribe", ...args);
181
+ }
182
+
183
+ trace<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
184
+ ): void {
185
+ this.addHandler("trace", ...args);
186
+ }
187
+
188
+ unlock<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
189
+ ): void {
190
+ this.addHandler("unlock", ...args);
191
+ }
192
+
193
+ link<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
194
+ ): void {
195
+ this.addHandler("link", ...args);
196
+ }
197
+
198
+ unlink<BodyType = Buffer, ParamsType = {}, QueryType = {}, AuthType = GlobalAuthType, HeadersType = {}>(...args: BackendiumMethodArgsType<BodyType, ParamsType, QueryType, AuthType, HeadersType, GlobalAuthType>
199
+ ): void {
200
+ this.addHandler("unlink", ...args);
201
+ }
202
+
203
+ ws<InitDataType>(route: string): WebSocketRouteConstructor<InitDataType> {
204
+ const constructor = new WebSocketRouteConstructor<InitDataType>();
205
+ this._handlers.push(["ws", route, [(app: Backendium) => (request: Request, response: WSResponse, next: NextFunction) => {
206
+ constructor._handle(request, response, next, app);
207
+ }]]);
208
+ return constructor;
209
+ }
210
+
211
+ protected static addPrefix([method, route, handler]: [MethodType, string | undefined, Array<(app: Backendium) => RequestHandler>], prefix: string): [MethodType, string, Array<(app: Backendium) => RequestHandler>];
212
+ protected static addPrefix([method, route, handler]: ["ws", string, Array<(app: Backendium) => WSRequestHandler>], prefix: string): ["ws", string, Array<(app: Backendium) => WSRequestHandler>];
213
+ protected static addPrefix([method, route, handler]: [MethodType, string | undefined, Array<(app: Backendium) => RequestHandler>] | ["ws", string, Array<(app: Backendium) => WSRequestHandler>], prefix: string) {
214
+ return [method, route || !route?.startsWith("/") ? prefix + (route ?? "") : prefix + (route ?? ""), handler];
215
+ }
216
+
217
+ router<AuthType>(router: BackendiumRouter<AuthType>, routePrefix = "") {
218
+ this._handlers.push(...router._handlers.map((handler) => {
219
+ // @ts-ignore
220
+ return BackendiumRouter.addPrefix(handler, routePrefix);
221
+ }));
222
+ }
223
+
224
+ setAuth(checker?: AuthCheckerType<GlobalAuthType>, failHandler?: AuthFailedType) {
225
+ this.authChecker = checker ?? this.authChecker;
226
+ this.authFailed = failHandler ?? this.authFailed;
227
+ }
228
+ }
package/src/ws.ts ADDED
@@ -0,0 +1,371 @@
1
+ import {NextFunction, Request} from "express";
2
+ import {WSResponse} from "websocket-express";
3
+ import Backendium from "./index.js";
4
+ import {EventEmitter, EventKey} from "event-emitter-typescript";
5
+ import {ValidationError, Validator} from "checkeasy";
6
+ import * as WebSocket from "ws";
7
+ import {ClientRequest, IncomingMessage} from "node:http";
8
+
9
+ interface NextMessageOptions {
10
+ timeout?: number | undefined;
11
+ }
12
+
13
+ interface WebSocketMessage {
14
+ data: Buffer;
15
+ isBinary: boolean;
16
+ }
17
+
18
+ interface WebSocketExtension {
19
+ nextMessage(options?: NextMessageOptions): Promise<WebSocketMessage>;
20
+ }
21
+
22
+ export type WebSocketHeadEventType = {event: string};
23
+ export type WebSocketHeadType = WebSocketHeadEventType | {operation: string, operationConfig: string};
24
+
25
+ export type BackendiumWebSocketEvents<InitDataType> = {
26
+ notEventMessage: [Buffer, BackendiumWebSocket<InitDataType>, Backendium, boolean],
27
+ unknownEvent: [Buffer, BackendiumWebSocket<InitDataType>, Backendium, WebSocketHeadEventType],
28
+ parsingFailed: [Buffer, BackendiumWebSocket<InitDataType>, Backendium, Validator<any> | undefined],
29
+ initParsingFailed: [Buffer, WebSocket & WebSocketExtension, Backendium, Validator<any> | undefined],
30
+ initFailed: [Buffer, WebSocket & WebSocketExtension, Backendium],
31
+ accept: [BackendiumWebSocket<InitDataType>, WebSocketRouteConstructor<InitDataType>, Backendium],
32
+ reject: [Request, WSResponse, Backendium, number | undefined, string | undefined],
33
+ message: [Buffer, BackendiumWebSocket<InitDataType>, Backendium, boolean],
34
+ messageBeforeEvents: [Buffer, BackendiumWebSocket<InitDataType>, Backendium, boolean],
35
+ close: [BackendiumWebSocket<InitDataType>, number, Buffer, Backendium],
36
+ error: [BackendiumWebSocket<InitDataType>, Error, Backendium],
37
+ init: [WebSocket & WebSocketExtension, Buffer, Backendium, string],
38
+ upgrade: [BackendiumWebSocket<InitDataType>, IncomingMessage, Backendium],
39
+ open: [BackendiumWebSocket<InitDataType>, Backendium],
40
+ ping: [BackendiumWebSocket<InitDataType>, Buffer, Backendium],
41
+ pong: [BackendiumWebSocket<InitDataType>, Buffer, Backendium],
42
+ unexpectedResponse: [BackendiumWebSocket<InitDataType>, ClientRequest, IncomingMessage, Backendium]
43
+ }
44
+
45
+ export class BackendiumWebSocket<InitDataType> {
46
+ protected eventEmitter = new EventEmitter<BackendiumWebSocketEvents<InitDataType>>;
47
+ protected wsEventEmitter = new EventEmitter<WebSocketEvents<InitDataType>>;
48
+ protected events = new Set<string>;
49
+ protected operations = new EventEmitter<WebSocketOperations<InitDataType>>;
50
+ protected useEvents = false;
51
+
52
+ public static rawDataParse(data: WebSocket.RawData): Buffer {
53
+ return data instanceof Buffer ? data : data instanceof ArrayBuffer ? Buffer.from(data) : data.reduce((prev, cur) => Buffer.concat([prev, cur]), Buffer.alloc(0));
54
+ }
55
+
56
+ protected parseEventHead(head: string, message: Buffer, socket: BackendiumWebSocket<InitDataType>, app: Backendium): WebSocketHeadType | undefined {
57
+ if (head.length < 1 || !head.startsWith("$")) {
58
+ this.eventEmitter.emit("notEventMessage", [message, socket, app, false]);
59
+ this.wsConstructor.eventEmitter.emit("notEventMessage", [message, socket, app, false]);
60
+ return;
61
+ }
62
+ let [, name, ...other] = head.split('$');
63
+ if (name.length) return {event: name.trim()};
64
+ let [operation, ...operationConfig] = other;
65
+ return {operation: operation.trim(), operationConfig: operationConfig.join('$').trim()};
66
+ }
67
+
68
+ protected emitIncomingEvent(event: string, payload: Buffer, socket: BackendiumWebSocket<InitDataType>, app: Backendium, head: WebSocketHeadEventType) {
69
+ if (this.events.has(event)) this.wsEventEmitter.emit(event, [payload, socket, app]);
70
+ else {
71
+ this.eventEmitter.emit("unknownEvent", [payload, socket, app, head]);
72
+ this.wsConstructor.eventEmitter.emit("unknownEvent", [payload, socket, app, head]);
73
+ }
74
+ }
75
+
76
+ protected emitIncomingOperation(operation: string, payload: Buffer, operationConfig: string, socket: BackendiumWebSocket<InitDataType>, app: Backendium) {
77
+ this.operations.emit(operation, [payload, operationConfig, socket, app]);
78
+ }
79
+
80
+ protected parseEventMessage(message: Buffer, socket: BackendiumWebSocket<InitDataType>, app: Backendium, isBinary: boolean): void {
81
+ if (isBinary) {
82
+ this.eventEmitter.emit("notEventMessage", [message, socket, app, isBinary]);
83
+ this.wsConstructor.eventEmitter.emit("notEventMessage", [message, socket, app, isBinary]);
84
+ if (app.config.logging?.fullWs) app.logger.wsInputFull(this.url, message);
85
+ else app.logger.wsInput(this.url);
86
+ return;
87
+ }
88
+ try {
89
+ let [head_, ...data] = message.toString().split("\n");
90
+ let payload = Buffer.from(data.join("\n")), head = this.parseEventHead(head_, message, socket, app);
91
+ if (!head) {
92
+ if (app.config.logging?.fullWs) app.logger.wsInputFull(this.url, message);
93
+ else app.logger.wsInput(this.url);
94
+ return;
95
+ }
96
+ if ("event" in head) {
97
+ this.emitIncomingEvent(head.event, payload, socket, app, head);
98
+ if (app.config.logging?.fullWs) app.logger.wsIncomingEventFull(this.url, head.event, payload.length ? payload : null);
99
+ else app.logger.wsIncomingEvent(this.url, head.event);
100
+ }
101
+ else this.emitIncomingOperation(head.operation, payload, head.operationConfig, socket, app);
102
+ } catch (error) {
103
+ this.eventEmitter.emit("notEventMessage", [message, socket, app, isBinary]);
104
+ this.wsConstructor.eventEmitter.emit("notEventMessage", [message, socket, app, isBinary]);
105
+ if (app.config.logging?.fullWs) app.logger.wsInputFull(this.url, message);
106
+ else app.logger.wsInput(this.url);
107
+ return;
108
+ }
109
+ }
110
+
111
+ constructor(public socket: WebSocket & WebSocketExtension, public wsConstructor: WebSocketRouteConstructor<InitDataType>, public app: Backendium, public initData: InitDataType, public url: string) {
112
+ this.eventEmitter.emit("accept", [this, this.wsConstructor, app]);
113
+ this.wsConstructor.eventEmitter.emit("accept", [this, this.wsConstructor, app]);
114
+ socket.on("message", (data, isBinary) => {
115
+ let buffer = BackendiumWebSocket.rawDataParse(data);
116
+ if (this.useEvents) {
117
+ this.eventEmitter.emit("messageBeforeEvents", [buffer, this, app, isBinary]);
118
+ this.wsConstructor.eventEmitter.emit("messageBeforeEvents", [buffer, this, app, isBinary]);
119
+ this.parseEventMessage(buffer, this, app, isBinary);
120
+ }
121
+ else {
122
+ this.eventEmitter.emit("notEventMessage", [buffer, this, app, isBinary]);
123
+ this.wsConstructor.eventEmitter.emit("notEventMessage", [buffer, this, app, isBinary]);
124
+ if (app.config.logging?.fullWs) app.logger.wsInputFull(url, data);
125
+ else app.logger.wsInput(url);
126
+ }
127
+ this.eventEmitter.emit("message", [buffer, this, app, isBinary]);
128
+ this.wsConstructor.eventEmitter.emit("message", [buffer, this, app, isBinary]);
129
+ });
130
+ socket.on("close", (code, reason) => {
131
+ this.eventEmitter.emit("close", [this, code, reason, app]);
132
+ this.wsConstructor.eventEmitter.emit("close", [this, code, reason, app]);
133
+ app.logger.wsClose(url);
134
+ });
135
+ socket.on("upgrade", (request) => {
136
+ this.eventEmitter.emit("upgrade", [this, request, app]);
137
+ this.wsConstructor.eventEmitter.emit("upgrade", [this, request, app]);
138
+ });
139
+ socket.on("open", () => {
140
+ this.eventEmitter.emit("open", [this, app]);
141
+ this.wsConstructor.eventEmitter.emit("open", [this, app]);
142
+ });
143
+ socket.on("ping", (buffer) => {
144
+ this.eventEmitter.emit("ping", [this, buffer, app]);
145
+ this.wsConstructor.eventEmitter.emit("ping", [this, buffer, app]);
146
+ });
147
+ socket.on("pong", (buffer) => {
148
+ this.eventEmitter.emit("pong", [this, buffer, app]);
149
+ this.wsConstructor.eventEmitter.emit("pong", [this, buffer, app]);
150
+ });
151
+ socket.on("unexpected-response", (clientResponse, request) => {
152
+ this.eventEmitter.emit("unexpectedResponse", [this, clientResponse, request, app]);
153
+ this.wsConstructor.eventEmitter.emit("unexpectedResponse", [this, clientResponse, request, app]);
154
+ });
155
+ }
156
+
157
+ public static eventNameCheck(str: string) {
158
+ if (str.includes("$")) throw new Error("event name cannot contain '$'");
159
+ }
160
+
161
+ public event<Type extends Buffer>(event: string, callback: (data: Type, socket: BackendiumWebSocket<InitDataType>, app: Backendium) => void): void;
162
+ public event<Type>(event: string, callback: (data: Type, socket: BackendiumWebSocket<InitDataType>, app: Backendium, validator: Validator<Type>) => void, validator: Validator<Type>): void;
163
+
164
+ public event<Type>(event: string, callback: (data: Type, socket: BackendiumWebSocket<InitDataType>, app: Backendium, validator: Validator<Type>) => void, validator?: Validator<Type>): void {
165
+ this.useEvents = true;
166
+ event = event.trim();
167
+ BackendiumWebSocket.eventNameCheck(event);
168
+ this.events.add(event);
169
+ this.wsEventEmitter.on(event, ([data, socket, app]) => {
170
+ let [mainData, parsed] = validator ? parse(data, validator) : [data, true];
171
+ if (!parsed || !mainData) {
172
+ this.eventEmitter.emit("parsingFailed", [data, socket, app, validator]);
173
+ this.wsConstructor.eventEmitter.emit("parsingFailed", [data, socket, app, validator]);
174
+ return;
175
+ }
176
+ // @ts-ignore
177
+ callback(mainData, socket, app, validator ?? bufferValidator);
178
+ });
179
+ }
180
+
181
+ public operation<E extends EventKey<WebSocketOperations<InitDataType>>>(event: E, subscriber: (...args: WebSocketOperations<InitDataType>[E]) => void): void {
182
+ BackendiumWebSocket.eventNameCheck(event);
183
+ this.operations.on(event, (args) => subscriber(...args));
184
+ };
185
+
186
+ public on<E extends EventKey<BackendiumWebSocketEvents<InitDataType>>>(event: E, subscriber: (...args: BackendiumWebSocketEvents<InitDataType>[E]) => void): () => void {
187
+ return this.eventEmitter.on(event, (args) => subscriber(...args));
188
+ };
189
+
190
+ public once<E extends EventKey<BackendiumWebSocketEvents<InitDataType>>>(event: E, subscriber: (...args: BackendiumWebSocketEvents<InitDataType>[E]) => void): () => void {
191
+ return this.eventEmitter.once(event, (args) => subscriber(...args));
192
+ };
193
+
194
+ public off<E extends EventKey<BackendiumWebSocketEvents<InitDataType>>>(event: E, subscriber: (...args: BackendiumWebSocketEvents<InitDataType>[E]) => void): void {
195
+ this.eventEmitter.off(event, (args) => subscriber(...args));
196
+ };
197
+
198
+ protected _send(data: any) {
199
+ if (!(data instanceof Buffer) && typeof data === "object" || typeof data === "boolean") data = JSON.stringify(data);
200
+ this.socket.send(data);
201
+ }
202
+
203
+ send(data: any) {
204
+ this._send(data);
205
+ if (this.app.config.logging?.fullWs) this.app.logger.wsOutputFull(this.url, data);
206
+ else this.app.logger.wsOutput(this.url);
207
+ }
208
+
209
+ protected static AnyToString(data: any): string {
210
+ return typeof data === "string" ? data : data instanceof Buffer ? data.toString() : data === undefined ? "undefined" : (typeof data === "number" && isNaN(data)) ? "NaN" : JSON.stringify(data);
211
+ }
212
+
213
+ emit(event: string, payload?: any) {
214
+ BackendiumWebSocket.eventNameCheck(event);
215
+ this._send(`$${event}\n${BackendiumWebSocket.AnyToString(payload)}`);
216
+ if (this.app.config.logging?.fullWs) this.app.logger.wsOutgoingEventFull(this.url, event, payload);
217
+ else this.app.logger.wsOutgoingEvent(this.url, event);
218
+ }
219
+
220
+ emitOperation(event: string, operationConfig: any, payload: any) {
221
+ BackendiumWebSocket.eventNameCheck(event);
222
+ this._send(`$$${event}$${BackendiumWebSocket.AnyToString(operationConfig)}\n${BackendiumWebSocket.AnyToString(payload)}`);
223
+ }
224
+ }
225
+
226
+ export type WebSocketEvents<InitDataType> = {
227
+ [key: string]: [Buffer, BackendiumWebSocket<InitDataType>, Backendium];
228
+ };
229
+
230
+ export type WebSocketOperations<InitDataType> = {
231
+ [key: string]: [Buffer, string, BackendiumWebSocket<InitDataType>, Backendium];
232
+ };
233
+
234
+ const bufferValidator: Validator<Buffer> = (value: any, path: string) => {
235
+ if (value instanceof Buffer) return value;
236
+ throw new ValidationError(`[${path}] is not buffer`);
237
+ };
238
+
239
+ function parse<Type>(data: Buffer, validator: Validator<Type>): [Type, true] | [null, false] {
240
+ try {
241
+ return [validator(data, ""), true];
242
+ }
243
+ catch (error) {
244
+ try {
245
+ return [validator(data.toString(), ""), true];
246
+ }
247
+ catch (error) {
248
+ try {
249
+ return [validator(JSON.parse(data.toString()), ""), true];
250
+ }
251
+ catch (error) {
252
+ return [null, false];
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ export type AcceptResponseCallbackReturnType = boolean | [number | undefined] | [number | undefined, string | undefined];
259
+
260
+ export class WebSocketRouteConstructor<InitDataType> {
261
+ protected sockets: Array<BackendiumWebSocket<InitDataType>> = []
262
+ public eventEmitter = new EventEmitter<BackendiumWebSocketEvents<InitDataType>>;
263
+ protected acceptRejectFn: ((request: Request, response: WSResponse, app: Backendium) => Promise<[boolean, number | undefined, string | undefined]>) | undefined;
264
+ protected eventHandlers: Array<[string, (data: any, socket: BackendiumWebSocket<InitDataType>, app: Backendium, validator: Validator<any>) => void, Validator<any> | undefined]> = []
265
+ protected operations = new EventEmitter<WebSocketOperations<InitDataType>>;
266
+ protected initRequired = false;
267
+
268
+ protected _backendiumWebsocket(socket: WebSocket & WebSocketExtension, app: Backendium, initData: InitDataType, url: string) {
269
+ let backendiumSocket = new BackendiumWebSocket<InitDataType>(socket, this, app, initData, url);
270
+ // @ts-ignore
271
+ this.eventHandlers.forEach(([event, socket, validator]) => backendiumSocket.event(event, socket, validator));
272
+ }
273
+
274
+ public async _handle(request: Request, response: WSResponse, next: NextFunction, app: Backendium): Promise<void> {
275
+ if (this.acceptRejectFn) {
276
+ let [flag, code, message] = await this.acceptRejectFn(request, response, app);
277
+ if (!flag) {
278
+ response.reject(code, message);
279
+ app.logger.wsRejected(request.originalUrl);
280
+ this.eventEmitter.emit("reject", [request, response, app, code, message]);
281
+ return;
282
+ }
283
+ }
284
+ let socket = await response.accept();
285
+ app.logger.wsConnected(request.originalUrl);
286
+ if (this.initRequired) {
287
+ socket.once("message", (data) => {
288
+ this.eventEmitter.emit("init", [socket, BackendiumWebSocket.rawDataParse(data), app, request.originalUrl]);
289
+ });
290
+ }
291
+ // @ts-ignore
292
+ else this._backendiumWebsocket(socket, app, undefined, request.originalUrl);
293
+ }
294
+
295
+ public acceptReject(callback: (request: Request, response: WSResponse, app: Backendium) => AcceptResponseCallbackReturnType | Promise<AcceptResponseCallbackReturnType>): WebSocketRouteConstructor<InitDataType> {
296
+ this.acceptRejectFn = async (request: Request, response: WSResponse, app: Backendium) => {
297
+ let ans = callback(request, response, app), data: AcceptResponseCallbackReturnType;
298
+ if (ans instanceof Promise) data = await ans;
299
+ else data = ans;
300
+ return typeof data === "boolean" ? [data, undefined, undefined] : [false, data[0], data[1]];
301
+ };
302
+ return this;
303
+ }
304
+
305
+ public event<Type extends Buffer>(event: string, callback: (data: Type, socket: BackendiumWebSocket<InitDataType>, app: Backendium) => void): WebSocketRouteConstructor<InitDataType>;
306
+ public event<Type>(event: string, callback: (data: Type, socket: BackendiumWebSocket<InitDataType>, app: Backendium, validator: Validator<Type>) => void, validator: Validator<Type>): WebSocketRouteConstructor<InitDataType>;
307
+
308
+ public event<Type>(event: string, callback: (data: Type, socket: BackendiumWebSocket<InitDataType>, app: Backendium, validator: Validator<Type>) => void, validator?: Validator<Type>): WebSocketRouteConstructor<InitDataType> {
309
+ BackendiumWebSocket.eventNameCheck(event);
310
+ // @ts-ignore
311
+ this.sockets.forEach(socket => socket.event(event, callback, validator));
312
+ this.eventHandlers.push([event, callback, validator]);
313
+ return this;
314
+ }
315
+
316
+ public operation<E extends EventKey<WebSocketOperations<InitDataType>>>(event: E, subscriber: (...args: WebSocketOperations<InitDataType>[E]) => void): void {
317
+ BackendiumWebSocket.eventNameCheck(event);
318
+ this.operations.on(event, (args) => subscriber(...args));
319
+ }
320
+
321
+ public on_<E extends EventKey<BackendiumWebSocketEvents<InitDataType>>>(event: E, subscriber: (...args: BackendiumWebSocketEvents<InitDataType>[E]) => void): () => void {
322
+ return this.eventEmitter.on(event, (args) => subscriber(...args));
323
+ }
324
+
325
+ public once_<E extends EventKey<BackendiumWebSocketEvents<InitDataType>>>(event: E, subscriber: (...args: BackendiumWebSocketEvents<InitDataType>[E]) => void): () => void {
326
+ return this.eventEmitter.once(event, (args) => subscriber(...args));
327
+ }
328
+
329
+ public on<E extends EventKey<BackendiumWebSocketEvents<InitDataType>>>(event: E, subscriber: (...args: BackendiumWebSocketEvents<InitDataType>[E]) => void): WebSocketRouteConstructor<InitDataType> {
330
+ this.eventEmitter.on(event, (args) => subscriber(...args));
331
+ return this;
332
+ }
333
+
334
+ public once<E extends EventKey<BackendiumWebSocketEvents<InitDataType>>>(event: E, subscriber: (...args: BackendiumWebSocketEvents<InitDataType>[E]) => void): WebSocketRouteConstructor<InitDataType> {
335
+ this.eventEmitter.once(event, (args) => subscriber(...args));
336
+ return this;
337
+ }
338
+
339
+ public off<E extends EventKey<BackendiumWebSocketEvents<InitDataType>>>(event: E, subscriber: (...args: BackendiumWebSocketEvents<InitDataType>[E]) => void): WebSocketRouteConstructor<InitDataType> {
340
+ this.eventEmitter.off(event, (args) => subscriber(...args));
341
+ return this;
342
+ }
343
+
344
+ public requireInit<Type>(callback: (connection: WebSocket & WebSocketExtension, data: Type, app: Backendium) => null | InitDataType | Promise<null | InitDataType>, validator: Validator<Type>) {
345
+ this.initRequired = true;
346
+ this.on("init", async (socket, data, app, url) => {
347
+ let [mainData, parsed] = validator ? parse(data, validator) : [data, true];
348
+ if (!parsed || !mainData) {
349
+ this.eventEmitter.emit("initParsingFailed", [data, socket, app, validator]);
350
+ if (app.config.logging?.fullWs) app.logger.wsInitFailedFull(url, data);
351
+ else app.logger.wsInitFailed(url);
352
+ return;
353
+ }
354
+ // @ts-ignore
355
+ let ret = callback(socket, mainData, app);
356
+ if (ret instanceof Promise) ret = await ret;
357
+ if (ret !== null) {
358
+ if (app.config.logging?.fullWs) app.logger.wsInitFull(url, mainData);
359
+ else app.logger.wsInit(url);
360
+ this._backendiumWebsocket(socket, app, ret, url);
361
+ }
362
+ else {
363
+ this.eventEmitter.emit("initFailed", [data, socket, app]);
364
+ if (app.config.logging?.fullWs) app.logger.wsInitFailedFull(url, mainData);
365
+ else app.logger.wsInitFailed(url);
366
+ }
367
+ });
368
+ }
369
+ }
370
+
371
+ // @TODO error handling +termination logging
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Visit https://aka.ms/tsconfig to read more about this file */
4
+ "target": "es2016",
5
+ "module": "ES2022",
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "moduleResolution": "node",
11
+ "outDir": "./dist",
12
+ "declaration": true,
13
+ "lib": ["ES2021.String"]
14
+ },
15
+ "include": ["./src"]
16
+ }