sleepy-serv 0.8.0 → 0.10.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/README.md +1 -1
- package/dist/errors.d.ts +1 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/socket.d.ts +17 -22
- package/dist/socket.d.ts.map +1 -1
- package/dist/utils.d.ts +43 -4
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +2 -5
- package/src/index.ts +50 -23
- package/src/middleware.ts +22 -26
- package/src/socket.ts +194 -113
- package/src/utils.ts +69 -18
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A directory-driven web server designed for REST-ful applications
|
|
|
5
5
|
## Important Notes
|
|
6
6
|
|
|
7
7
|
- This package requires [`bun.sh`](https://bun.sh) instead of NodeJS to run.
|
|
8
|
-
- This project requires bun v1.
|
|
8
|
+
- This project requires bun v1.4 or higher
|
|
9
9
|
|
|
10
10
|
## Getting Started
|
|
11
11
|
|
package/dist/errors.d.ts
CHANGED
|
@@ -127,8 +127,7 @@ export declare class UnavailableForLegalReasonsError extends RequestError {
|
|
|
127
127
|
}
|
|
128
128
|
export declare class InternalServerError extends RequestError {
|
|
129
129
|
static get status(): StatusCode;
|
|
130
|
-
|
|
131
|
-
constructor(message: string, ctx?: unknown);
|
|
130
|
+
constructor();
|
|
132
131
|
}
|
|
133
132
|
export declare class NotImplementedError extends RequestError {
|
|
134
133
|
static get status(): StatusCode;
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { AppOptions, Server } from './utils';
|
|
2
2
|
import type { SocketCommands } from './socket';
|
|
3
3
|
export * from './errors';
|
|
4
|
+
export { StatusCode, CloseCode, CloseReason } from './utils';
|
|
4
5
|
export { parseJsonBody, setValidationFormats, validateSchemas, } from './middleware';
|
|
5
|
-
export { HttpMethod, StatusCode } from './utils';
|
|
6
|
-
export type { AppOptions, EndpointRequest, FormattedError, Middleware, NextFn, Request, Server, SocketOptions, WebSocketRequest, } from './utils';
|
|
7
|
-
export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
|
|
8
6
|
export type { SocketCommands } from './socket';
|
|
7
|
+
export type { HttpMethod, ActiveSessions } from './utils';
|
|
8
|
+
export type { AppOptions, AsyncHandlerResult, BaseRequest, EndpointRequest, FormattedError, Handler, Middleware, MiddlewareChain, NextFn, HandlerResult, Request, Server, SocketConnection, SocketOptions, WebSocketRequest, } from './utils';
|
|
9
|
+
export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
|
|
9
10
|
type OutputRoutes = Record<string, string[]>;
|
|
10
11
|
type CloseFn = (force?: boolean) => Promise<void>;
|
|
11
12
|
export type App = {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAOV,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE5D,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEzD,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,cAAc,EACd,OAAO,EACP,UAAU,EACV,eAAe,EACf,MAAM,EACN,aAAa,EACb,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AA4C5C,KAAK,OAAO,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEjD,MAAM,MAAM,GAAG,GAAG;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAyaD,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,GAAG,CAAC,CAad"}
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAoB,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAoB,MAAM,KAAK,CAAA;AAE3D,OAAO,KAAK,EAIV,UAAU,EAGX,MAAM,SAAS,CAAA;AAEhB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;AAE5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAA;AAmF/C,wBAAgB,aAAa,IAAK,UAAU,CAoB3C;AAED,wBAAgB,oBAAoB,CAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAU3E;AAID,wBAAgB,2BAA2B,IAAK,IAAI,CAGnD;AAcD,wBAAgB,eAAe,CAAE,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAgCvE"}
|
package/dist/socket.d.ts
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
|
+
import { CloseReason } from './utils';
|
|
1
2
|
import type { WebSocketHandler } from 'bun';
|
|
2
|
-
import type { HttpMethod, Request,
|
|
3
|
+
import type { AsyncHandlerResult, HttpMethod, Request, MiddlewareChain, SocketData, ActiveSession, InactiveSession, AppOptions } from './utils';
|
|
4
|
+
type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult;
|
|
5
|
+
type FilterFn = (clientId: string, data: unknown) => boolean;
|
|
6
|
+
type SocketEndpoint = {
|
|
7
|
+
method: HttpMethod;
|
|
8
|
+
path: string;
|
|
9
|
+
handler: SocketHandler;
|
|
10
|
+
};
|
|
3
11
|
export type Ticket = {
|
|
4
12
|
clientId: string;
|
|
5
13
|
expiresAt: number;
|
|
14
|
+
data: unknown;
|
|
6
15
|
};
|
|
7
|
-
export type SocketConnection = {
|
|
8
|
-
data: SocketData;
|
|
9
|
-
send: (data: string) => unknown;
|
|
10
|
-
close: () => void;
|
|
11
|
-
};
|
|
12
|
-
export type ActiveSession = {
|
|
13
|
-
token: string;
|
|
14
|
-
ws: SocketConnection;
|
|
15
|
-
};
|
|
16
|
-
export type InactiveSession = {
|
|
17
|
-
token: string;
|
|
18
|
-
expiresAt: number;
|
|
19
|
-
};
|
|
20
|
-
export type Session = ActiveSession | InactiveSession;
|
|
21
16
|
export type SocketState = {
|
|
22
|
-
|
|
17
|
+
dropThreshold: number;
|
|
23
18
|
heartbeatInterval: number;
|
|
24
19
|
maxTickets: number;
|
|
25
20
|
reclaimTtl: number;
|
|
@@ -27,24 +22,24 @@ export type SocketState = {
|
|
|
27
22
|
tickets: Map<string, Ticket>;
|
|
28
23
|
activeSessions: Map<string, ActiveSession>;
|
|
29
24
|
inactiveSessions: Map<string, InactiveSession>;
|
|
25
|
+
onOpen: ((clientId: string) => void) | null;
|
|
26
|
+
onClose: ((clientId: string, reason: CloseReason) => void) | null;
|
|
30
27
|
};
|
|
31
28
|
export type SocketRoute = {
|
|
32
29
|
method: HttpMethod;
|
|
33
30
|
path: string;
|
|
34
31
|
segments: string[];
|
|
35
|
-
chain:
|
|
36
|
-
};
|
|
37
|
-
export type SocketEndpoint = {
|
|
38
|
-
method: HttpMethod;
|
|
39
|
-
path: string;
|
|
40
|
-
handler: (req: Request, res: unknown) => Response;
|
|
32
|
+
chain: MiddlewareChain;
|
|
41
33
|
};
|
|
42
34
|
export type SocketCommands = {
|
|
43
35
|
send: (clientId: string, event: string, body: unknown) => void;
|
|
36
|
+
sendToGroup: (fn: FilterFn, event: string, body: unknown) => void;
|
|
44
37
|
broadcast: (event: string, body: unknown) => void;
|
|
38
|
+
drop: (clientId: string, code?: number, reason?: string) => void;
|
|
45
39
|
};
|
|
46
40
|
export declare function buildSocketState(opts?: AppOptions): SocketState;
|
|
47
41
|
export declare function buildSocketServer(routes: SocketRoute[], state: SocketState): WebSocketHandler<SocketData>;
|
|
48
42
|
export declare function buildSocketHandlers(state: SocketState): SocketEndpoint[];
|
|
49
43
|
export declare function buildSocketCommands(state: SocketState): SocketCommands;
|
|
44
|
+
export {};
|
|
50
45
|
//# sourceMappingURL=socket.d.ts.map
|
package/dist/socket.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,WAAW,EAIZ,MAAM,SAAS,CAAA;AAchB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAE3C,OAAO,KAAK,EACV,kBAAkB,EAClB,UAAU,EACV,OAAO,EACP,eAAe,EAEf,UAAU,EAEV,aAAa,EAEb,eAAe,EAEf,UAAU,EACX,MAAM,SAAS,CAAA;AAShB,KAAK,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,kBAAkB,CAAA;AACvE,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAA;AAY5D,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,aAAa,CAAA;CACvB,CAAA;AAqBD,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IAC1C,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC9C,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3C,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;CAClE,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,EAAE,eAAe,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9D,WAAW,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACjE,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACjD,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACjE,CAAA;AAmTD,wBAAgB,gBAAgB,CAAE,IAAI,GAAE,UAAe,GAAG,WAAW,CAapE;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,WAAW,GACjB,gBAAgB,CAAC,UAAU,CAAC,CAoJ9B;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,EAAE,CAyIzE;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,CA6CvE"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -79,7 +79,23 @@ export type FormattedError = {
|
|
|
79
79
|
path: string;
|
|
80
80
|
message: string;
|
|
81
81
|
};
|
|
82
|
-
export type
|
|
82
|
+
export type AsyncHandlerResult = Promise<Response>;
|
|
83
|
+
export type HandlerResult = Response | AsyncHandlerResult;
|
|
84
|
+
export type NextFn = (data?: unknown) => HandlerResult;
|
|
85
|
+
export type Middleware = (req: Request, res: unknown, next: NextFn) => HandlerResult;
|
|
86
|
+
export type Handler = (req: Request, res: unknown) => HandlerResult;
|
|
87
|
+
export type MiddlewareChain = (Middleware | Handler)[];
|
|
88
|
+
export type ActiveSession = {
|
|
89
|
+
token: string;
|
|
90
|
+
ws: SocketConnection;
|
|
91
|
+
};
|
|
92
|
+
export type InactiveSession = {
|
|
93
|
+
token: string;
|
|
94
|
+
expiresAt: number;
|
|
95
|
+
app: unknown;
|
|
96
|
+
};
|
|
97
|
+
export type ActiveSessions = ReadonlyMap<string, ActiveSession>;
|
|
98
|
+
export type Session = ActiveSession | InactiveSession;
|
|
83
99
|
export type BaseRequest = {
|
|
84
100
|
method: HttpMethod;
|
|
85
101
|
route: string;
|
|
@@ -87,6 +103,9 @@ export type BaseRequest = {
|
|
|
87
103
|
params: Record<string, string>;
|
|
88
104
|
query: Record<string, unknown>;
|
|
89
105
|
json: () => Promise<unknown>;
|
|
106
|
+
ws: {
|
|
107
|
+
active: ActiveSessions;
|
|
108
|
+
};
|
|
90
109
|
};
|
|
91
110
|
export type EndpointRequest = BaseRequest & {
|
|
92
111
|
raw: BunRequest;
|
|
@@ -97,19 +116,39 @@ export type WebSocketRequest = BaseRequest & {
|
|
|
97
116
|
clientId: string;
|
|
98
117
|
};
|
|
99
118
|
export type Request = EndpointRequest | WebSocketRequest;
|
|
100
|
-
export
|
|
119
|
+
export declare const CloseCode: {
|
|
120
|
+
readonly Ok: 1000;
|
|
121
|
+
readonly Abnormal: 1006;
|
|
122
|
+
readonly Reaped: 4999;
|
|
123
|
+
};
|
|
124
|
+
export type CloseCode = typeof CloseCode[keyof typeof CloseCode];
|
|
125
|
+
export declare const CloseReason: {
|
|
126
|
+
readonly Ok: "ok";
|
|
127
|
+
readonly Dropped: "dropped";
|
|
128
|
+
readonly Reaped: "reaped";
|
|
129
|
+
readonly Superseded: "superseded";
|
|
130
|
+
};
|
|
131
|
+
export type CloseReason = typeof CloseReason[keyof typeof CloseReason];
|
|
101
132
|
export type SocketOptions = {
|
|
102
|
-
|
|
133
|
+
dropThreshold?: number;
|
|
103
134
|
heartbeatInterval?: number;
|
|
104
135
|
maxTickets?: number;
|
|
105
136
|
reclaimTtl?: number;
|
|
106
137
|
ticketTtl?: number;
|
|
138
|
+
onOpen?: (clientId: string) => void;
|
|
139
|
+
onClose?: (clientId: string, reason: CloseReason) => void;
|
|
107
140
|
};
|
|
108
141
|
export type SocketData = {
|
|
109
142
|
clientId: string;
|
|
110
143
|
superseded: boolean;
|
|
111
144
|
reaped: boolean;
|
|
112
145
|
reaperHandle: ReturnType<typeof setTimeout> | null;
|
|
146
|
+
app: unknown;
|
|
147
|
+
};
|
|
148
|
+
export type SocketConnection = {
|
|
149
|
+
data: SocketData;
|
|
150
|
+
send: (data: string) => unknown;
|
|
151
|
+
close: (code?: number, reason?: string) => void;
|
|
113
152
|
};
|
|
114
153
|
export type Server = BunServer<SocketData>;
|
|
115
154
|
export type AppOptions = {
|
|
@@ -121,5 +160,5 @@ export type AppOptions = {
|
|
|
121
160
|
};
|
|
122
161
|
export declare function toSegments(pathString: string): string[];
|
|
123
162
|
export declare function formatError(prefix: string, input: ValidationError): FormattedError;
|
|
124
|
-
export declare function executeMiddlewareChain(req: Request, chain:
|
|
163
|
+
export declare function executeMiddlewareChain(req: Request, chain: MiddlewareChain): AsyncHandlerResult;
|
|
125
164
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,KAAK,CAAA;AAE1D,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEnE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmEb,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEnE,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;AAElD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -384,13 +384,10 @@ export class InternalServerError extends RequestError {
|
|
|
384
384
|
return StatusCode.InternalServerError
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
constructor (message: string, ctx?: unknown) {
|
|
390
|
-
super(message)
|
|
387
|
+
constructor () {
|
|
388
|
+
super('An internal server error occurred')
|
|
391
389
|
|
|
392
390
|
this.name = 'InternalServerError'
|
|
393
|
-
this.ctx = ctx
|
|
394
391
|
}
|
|
395
392
|
}
|
|
396
393
|
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,11 @@ import querystring from 'querystring'
|
|
|
4
4
|
import readline from 'node:readline'
|
|
5
5
|
|
|
6
6
|
import { stdin, stdout } from 'node:process'
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
toSegments,
|
|
10
|
+
executeMiddlewareChain,
|
|
11
|
+
} from './utils'
|
|
8
12
|
|
|
9
13
|
import {
|
|
10
14
|
buildSocketState,
|
|
@@ -17,14 +21,18 @@ import {
|
|
|
17
21
|
RequestError,
|
|
18
22
|
NotFoundError,
|
|
19
23
|
MethodNotAllowedError,
|
|
24
|
+
InternalServerError,
|
|
20
25
|
} from './errors'
|
|
21
26
|
|
|
22
27
|
import type { BunRequest } from 'bun'
|
|
23
28
|
|
|
24
29
|
import type {
|
|
30
|
+
AsyncHandlerResult,
|
|
25
31
|
HttpMethod,
|
|
26
32
|
Middleware,
|
|
33
|
+
MiddlewareChain,
|
|
27
34
|
EndpointRequest,
|
|
35
|
+
ActiveSessions,
|
|
28
36
|
AppOptions,
|
|
29
37
|
Server,
|
|
30
38
|
} from './utils'
|
|
@@ -36,6 +44,7 @@ import type {
|
|
|
36
44
|
} from './socket'
|
|
37
45
|
|
|
38
46
|
export * from './errors'
|
|
47
|
+
export { StatusCode, CloseCode, CloseReason } from './utils'
|
|
39
48
|
|
|
40
49
|
export {
|
|
41
50
|
parseJsonBody,
|
|
@@ -43,16 +52,23 @@ export {
|
|
|
43
52
|
validateSchemas,
|
|
44
53
|
} from './middleware'
|
|
45
54
|
|
|
46
|
-
export {
|
|
55
|
+
export type { SocketCommands } from './socket'
|
|
56
|
+
export type { HttpMethod, ActiveSessions } from './utils'
|
|
47
57
|
|
|
48
58
|
export type {
|
|
49
59
|
AppOptions,
|
|
60
|
+
AsyncHandlerResult,
|
|
61
|
+
BaseRequest,
|
|
50
62
|
EndpointRequest,
|
|
51
63
|
FormattedError,
|
|
64
|
+
Handler,
|
|
52
65
|
Middleware,
|
|
66
|
+
MiddlewareChain,
|
|
53
67
|
NextFn,
|
|
68
|
+
HandlerResult,
|
|
54
69
|
Request,
|
|
55
70
|
Server,
|
|
71
|
+
SocketConnection,
|
|
56
72
|
SocketOptions,
|
|
57
73
|
WebSocketRequest,
|
|
58
74
|
} from './utils'
|
|
@@ -63,15 +79,13 @@ export type {
|
|
|
63
79
|
ValidationSchemas,
|
|
64
80
|
} from './middleware'
|
|
65
81
|
|
|
66
|
-
export type { SocketCommands } from './socket'
|
|
67
|
-
|
|
68
82
|
type OutputRoutes = Record<string, string[]>
|
|
69
83
|
type ServerRoutes = Record<string, Record<string, EndpointHandler>>
|
|
70
84
|
|
|
71
85
|
type EndpointHandler = (
|
|
72
86
|
bunReq: BunRequest,
|
|
73
87
|
server: Server,
|
|
74
|
-
) =>
|
|
88
|
+
) => AsyncHandlerResult
|
|
75
89
|
|
|
76
90
|
type DirEntry = {
|
|
77
91
|
path: string
|
|
@@ -88,7 +102,7 @@ type RoutePath = {
|
|
|
88
102
|
type ChainRoute = {
|
|
89
103
|
method: HttpMethod
|
|
90
104
|
path: string
|
|
91
|
-
chain:
|
|
105
|
+
chain: MiddlewareChain
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
type ModuleRoute = {
|
|
@@ -153,10 +167,20 @@ function defaultMethodMap (): Record<string, EndpointHandler> {
|
|
|
153
167
|
function buildEndpointRequest (
|
|
154
168
|
bunReq: BunRequest,
|
|
155
169
|
server: Server,
|
|
170
|
+
activeSessions: ActiveSessions,
|
|
156
171
|
): EndpointRequest {
|
|
157
172
|
const url = new URL(bunReq.url)
|
|
158
173
|
const qs = url.search.replace('?', '')
|
|
159
|
-
|
|
174
|
+
|
|
175
|
+
let bodyPromise: Promise<unknown> | null = null
|
|
176
|
+
|
|
177
|
+
const json = (): Promise<unknown> => {
|
|
178
|
+
if (!bodyPromise) {
|
|
179
|
+
bodyPromise = bunReq.json()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return bodyPromise
|
|
183
|
+
}
|
|
160
184
|
|
|
161
185
|
return {
|
|
162
186
|
method: bunReq.method as HttpMethod,
|
|
@@ -167,6 +191,9 @@ function buildEndpointRequest (
|
|
|
167
191
|
raw: bunReq,
|
|
168
192
|
server,
|
|
169
193
|
json,
|
|
194
|
+
ws: {
|
|
195
|
+
active: activeSessions,
|
|
196
|
+
},
|
|
170
197
|
}
|
|
171
198
|
}
|
|
172
199
|
|
|
@@ -278,9 +305,10 @@ function buildRoutePaths (
|
|
|
278
305
|
.join('') || '/'
|
|
279
306
|
|
|
280
307
|
const metaMiddlewarePath = selectMetaPaths(metadata, modulePath)
|
|
308
|
+
const rawMethod = segments[lastIndex].toUpperCase()
|
|
281
309
|
|
|
282
310
|
return {
|
|
283
|
-
method:
|
|
311
|
+
method: rawMethod as HttpMethod,
|
|
284
312
|
path: joinedPath,
|
|
285
313
|
metaMiddlewarePath,
|
|
286
314
|
modulePath,
|
|
@@ -375,10 +403,17 @@ function buildSocketRoutes (mergedRoutes: ChainRoute[]): SocketRoute[] {
|
|
|
375
403
|
}))
|
|
376
404
|
}
|
|
377
405
|
|
|
378
|
-
function buildModuleRoutes (
|
|
406
|
+
function buildModuleRoutes (
|
|
407
|
+
socketRoutes: SocketRoute[],
|
|
408
|
+
state: SocketState,
|
|
409
|
+
): ModuleRoute[] {
|
|
379
410
|
return socketRoutes.map(route => {
|
|
380
411
|
const handler: EndpointHandler = async (bunReq, server) => {
|
|
381
|
-
const req = buildEndpointRequest(
|
|
412
|
+
const req = buildEndpointRequest(
|
|
413
|
+
bunReq,
|
|
414
|
+
server,
|
|
415
|
+
state.activeSessions,
|
|
416
|
+
)
|
|
382
417
|
|
|
383
418
|
return executeMiddlewareChain(req, route.chain)
|
|
384
419
|
}
|
|
@@ -439,7 +474,7 @@ async function buildRoutes (
|
|
|
439
474
|
)
|
|
440
475
|
|
|
441
476
|
const socketRoutes = buildSocketRoutes(mergedRoutes)
|
|
442
|
-
const moduleRoutes = buildModuleRoutes(socketRoutes)
|
|
477
|
+
const moduleRoutes = buildModuleRoutes(socketRoutes, state)
|
|
443
478
|
const serverRoutes = buildServerRoutes(moduleRoutes)
|
|
444
479
|
const outputRoutes = buildOutputRoutes(moduleRoutes)
|
|
445
480
|
|
|
@@ -470,15 +505,11 @@ function buildServer (
|
|
|
470
505
|
error (err) {
|
|
471
506
|
console.error(err)
|
|
472
507
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
return Response.json(err.output, { status: ctor.status })
|
|
477
|
-
}
|
|
508
|
+
const isRequestError = !(err instanceof RequestError)
|
|
509
|
+
const httpError = isRequestError ? new InternalServerError() : err
|
|
510
|
+
const { status } = httpError.constructor as typeof RequestError
|
|
478
511
|
|
|
479
|
-
return
|
|
480
|
-
status: StatusCode.InternalServerError,
|
|
481
|
-
})
|
|
512
|
+
return Response.json(httpError.output, { status })
|
|
482
513
|
},
|
|
483
514
|
})
|
|
484
515
|
}
|
|
@@ -504,14 +535,11 @@ function processIO (
|
|
|
504
535
|
return shutdown
|
|
505
536
|
}
|
|
506
537
|
|
|
507
|
-
stdin.setRawMode(true)
|
|
508
|
-
|
|
509
538
|
const rl = readline.createInterface({
|
|
510
539
|
input: stdin,
|
|
511
540
|
output: stdout,
|
|
512
541
|
})
|
|
513
542
|
|
|
514
|
-
/* istanbul ignore next */
|
|
515
543
|
const handleClose = async (): Promise<void> => {
|
|
516
544
|
await shutdown()
|
|
517
545
|
process.exit(0)
|
|
@@ -522,7 +550,6 @@ function processIO (
|
|
|
522
550
|
return async (force = false) => {
|
|
523
551
|
rl.off('close', handleClose)
|
|
524
552
|
rl.close()
|
|
525
|
-
stdin.setRawMode(false)
|
|
526
553
|
|
|
527
554
|
await shutdown(force)
|
|
528
555
|
}
|
package/src/middleware.ts
CHANGED
|
@@ -10,7 +10,15 @@ import {
|
|
|
10
10
|
} from './errors'
|
|
11
11
|
|
|
12
12
|
import type { Format, Schema, ValidateFunction } from 'ajv'
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
AsyncHandlerResult,
|
|
16
|
+
FormattedError,
|
|
17
|
+
HandlerResult,
|
|
18
|
+
Middleware,
|
|
19
|
+
NextFn,
|
|
20
|
+
Request,
|
|
21
|
+
} from './utils'
|
|
14
22
|
|
|
15
23
|
export type FormatterField = {
|
|
16
24
|
type: string
|
|
@@ -28,14 +36,6 @@ export type ValidationSchemas = {
|
|
|
28
36
|
|
|
29
37
|
export type SchemaKey = keyof ValidationSchemas
|
|
30
38
|
|
|
31
|
-
function requireNext (next: NextFn | null): NextFn {
|
|
32
|
-
if (!next) {
|
|
33
|
-
throw new TypeError('Middleware cannot be the last entry in a chain')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return next
|
|
37
|
-
}
|
|
38
|
-
|
|
39
39
|
let _schemasCompiled = false
|
|
40
40
|
let _customFormats: Record<string, Format> | null = null
|
|
41
41
|
|
|
@@ -121,12 +121,12 @@ export function parseJsonBody (): Middleware {
|
|
|
121
121
|
return async (
|
|
122
122
|
req: Request,
|
|
123
123
|
res: unknown,
|
|
124
|
-
next: NextFn
|
|
125
|
-
):
|
|
124
|
+
next: NextFn,
|
|
125
|
+
): AsyncHandlerResult => {
|
|
126
126
|
const contentType = req.headers.get('content-type')
|
|
127
127
|
|
|
128
128
|
if (!contentType) {
|
|
129
|
-
return
|
|
129
|
+
return next(res)
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
if (!contentType.startsWith('application/json')) {
|
|
@@ -135,13 +135,11 @@ export function parseJsonBody (): Middleware {
|
|
|
135
135
|
|
|
136
136
|
const body = await parseBody(req)
|
|
137
137
|
|
|
138
|
-
return
|
|
138
|
+
return next(body)
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
export function setValidationFormats (
|
|
143
|
-
formats: Record<string, Format>,
|
|
144
|
-
): void {
|
|
142
|
+
export function setValidationFormats (formats: Record<string, Format>): void {
|
|
145
143
|
if (_customFormats) {
|
|
146
144
|
console.warn('setValidationFormats() - already initialized')
|
|
147
145
|
}
|
|
@@ -172,9 +170,7 @@ function buildValidationSource (
|
|
|
172
170
|
}
|
|
173
171
|
}
|
|
174
172
|
|
|
175
|
-
export function validateSchemas (
|
|
176
|
-
schemas: ValidationSchemas,
|
|
177
|
-
): Middleware {
|
|
173
|
+
export function validateSchemas (schemas: ValidationSchemas): Middleware {
|
|
178
174
|
const entries = compileSchemas(schemas)
|
|
179
175
|
|
|
180
176
|
_schemasCompiled = true
|
|
@@ -182,14 +178,14 @@ export function validateSchemas (
|
|
|
182
178
|
return (
|
|
183
179
|
req: Request,
|
|
184
180
|
res: unknown,
|
|
185
|
-
next: NextFn
|
|
186
|
-
):
|
|
181
|
+
next: NextFn,
|
|
182
|
+
): HandlerResult => {
|
|
187
183
|
const source = buildValidationSource(req, res)
|
|
188
184
|
|
|
189
|
-
const errors = entries.reduce((
|
|
190
|
-
|
|
191
|
-
validator,
|
|
192
|
-
|
|
185
|
+
const errors = entries.reduce((
|
|
186
|
+
accum: FormattedError[],
|
|
187
|
+
[key, validator]: [SchemaKey, ValidateFunction],
|
|
188
|
+
) => {
|
|
193
189
|
const valid = validator(source[key])
|
|
194
190
|
|
|
195
191
|
return !valid
|
|
@@ -204,6 +200,6 @@ export function validateSchemas (
|
|
|
204
200
|
throw new UnprocessableContentError(errors)
|
|
205
201
|
}
|
|
206
202
|
|
|
207
|
-
return
|
|
203
|
+
return next(res)
|
|
208
204
|
}
|
|
209
205
|
}
|
package/src/socket.ts
CHANGED
|
@@ -2,10 +2,16 @@ import Ajv from 'ajv'
|
|
|
2
2
|
import addFormats from 'ajv-formats'
|
|
3
3
|
import crypto from 'node:crypto'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
MessageType,
|
|
7
|
+
createMessage,
|
|
8
|
+
validateMessage,
|
|
9
|
+
} from './messages'
|
|
6
10
|
|
|
7
11
|
import {
|
|
8
12
|
StatusCode,
|
|
13
|
+
CloseCode,
|
|
14
|
+
CloseReason,
|
|
9
15
|
toSegments,
|
|
10
16
|
formatError,
|
|
11
17
|
executeMiddlewareChain,
|
|
@@ -13,6 +19,7 @@ import {
|
|
|
13
19
|
|
|
14
20
|
import {
|
|
15
21
|
RequestError,
|
|
22
|
+
BadRequestError,
|
|
16
23
|
NotFoundError,
|
|
17
24
|
UnauthorizedError,
|
|
18
25
|
MethodNotAllowedError,
|
|
@@ -21,15 +28,21 @@ import {
|
|
|
21
28
|
ServiceUnavailableError,
|
|
22
29
|
} from './errors'
|
|
23
30
|
|
|
24
|
-
import type { WebSocketHandler } from 'bun'
|
|
25
31
|
import type { ValidateFunction } from 'ajv'
|
|
32
|
+
import type { WebSocketHandler } from 'bun'
|
|
26
33
|
|
|
27
34
|
import type {
|
|
35
|
+
AsyncHandlerResult,
|
|
28
36
|
HttpMethod,
|
|
29
37
|
Request,
|
|
38
|
+
MiddlewareChain,
|
|
30
39
|
WebSocketRequest,
|
|
31
|
-
Middleware,
|
|
32
40
|
SocketData,
|
|
41
|
+
SocketConnection,
|
|
42
|
+
ActiveSession,
|
|
43
|
+
ActiveSessions,
|
|
44
|
+
InactiveSession,
|
|
45
|
+
Session,
|
|
33
46
|
AppOptions,
|
|
34
47
|
} from './utils'
|
|
35
48
|
|
|
@@ -40,57 +53,8 @@ import type {
|
|
|
40
53
|
ResponseMessage,
|
|
41
54
|
} from './messages'
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
expiresAt: number
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type SocketConnection = {
|
|
49
|
-
data: SocketData
|
|
50
|
-
send: (data: string) => unknown
|
|
51
|
-
close: () => void
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export type ActiveSession = {
|
|
55
|
-
token: string
|
|
56
|
-
ws: SocketConnection
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export type InactiveSession = {
|
|
60
|
-
token: string
|
|
61
|
-
expiresAt: number
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type Session = ActiveSession | InactiveSession
|
|
65
|
-
|
|
66
|
-
export type SocketState = {
|
|
67
|
-
disconnectThreshold: number
|
|
68
|
-
heartbeatInterval: number
|
|
69
|
-
maxTickets: number
|
|
70
|
-
reclaimTtl: number
|
|
71
|
-
ticketTtl: number
|
|
72
|
-
tickets: Map<string, Ticket>
|
|
73
|
-
activeSessions: Map<string, ActiveSession>
|
|
74
|
-
inactiveSessions: Map<string, InactiveSession>
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export type SocketRoute = {
|
|
78
|
-
method: HttpMethod
|
|
79
|
-
path: string
|
|
80
|
-
segments: string[]
|
|
81
|
-
chain: Middleware[]
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type SocketEndpoint = {
|
|
85
|
-
method: HttpMethod
|
|
86
|
-
path: string
|
|
87
|
-
handler: (req: Request, res: unknown) => Response
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export type SocketCommands = {
|
|
91
|
-
send: (clientId: string, event: string, body: unknown) => void
|
|
92
|
-
broadcast: (event: string, body: unknown) => void
|
|
93
|
-
}
|
|
56
|
+
type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult
|
|
57
|
+
type FilterFn = (clientId: string, data: unknown) => boolean
|
|
94
58
|
|
|
95
59
|
type UpgradeData = {
|
|
96
60
|
clientId?: string
|
|
@@ -102,6 +66,12 @@ type UpgradeContext = {
|
|
|
102
66
|
[key: string]: unknown
|
|
103
67
|
}
|
|
104
68
|
|
|
69
|
+
type SocketEndpoint = {
|
|
70
|
+
method: HttpMethod
|
|
71
|
+
path: string
|
|
72
|
+
handler: SocketHandler
|
|
73
|
+
}
|
|
74
|
+
|
|
105
75
|
type CreateSocketRequest = {
|
|
106
76
|
query: {
|
|
107
77
|
ticket: string
|
|
@@ -121,6 +91,39 @@ type UpdateTicketRequest = {
|
|
|
121
91
|
}
|
|
122
92
|
}
|
|
123
93
|
|
|
94
|
+
export type Ticket = {
|
|
95
|
+
clientId: string
|
|
96
|
+
expiresAt: number
|
|
97
|
+
data: unknown
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type SocketState = {
|
|
101
|
+
dropThreshold: number
|
|
102
|
+
heartbeatInterval: number
|
|
103
|
+
maxTickets: number
|
|
104
|
+
reclaimTtl: number
|
|
105
|
+
ticketTtl: number
|
|
106
|
+
tickets: Map<string, Ticket>
|
|
107
|
+
activeSessions: Map<string, ActiveSession>
|
|
108
|
+
inactiveSessions: Map<string, InactiveSession>
|
|
109
|
+
onOpen: ((clientId: string) => void) | null
|
|
110
|
+
onClose: ((clientId: string, reason: CloseReason) => void) | null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type SocketRoute = {
|
|
114
|
+
method: HttpMethod
|
|
115
|
+
path: string
|
|
116
|
+
segments: string[]
|
|
117
|
+
chain: MiddlewareChain
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type SocketCommands = {
|
|
121
|
+
send: (clientId: string, event: string, body: unknown) => void
|
|
122
|
+
sendToGroup: (fn: FilterFn, event: string, body: unknown) => void
|
|
123
|
+
broadcast: (event: string, body: unknown) => void
|
|
124
|
+
drop: (clientId: string, code?: number, reason?: string) => void
|
|
125
|
+
}
|
|
126
|
+
|
|
124
127
|
const ajv = new Ajv({
|
|
125
128
|
allErrors: true,
|
|
126
129
|
})
|
|
@@ -226,7 +229,7 @@ function isSessionActive (session: InactiveSession): boolean {
|
|
|
226
229
|
return !session.expiresAt || session.expiresAt > Date.now()
|
|
227
230
|
}
|
|
228
231
|
|
|
229
|
-
function
|
|
232
|
+
function randomTicketHash (): string {
|
|
230
233
|
return crypto.randomBytes(24).toString('base64url')
|
|
231
234
|
}
|
|
232
235
|
|
|
@@ -244,6 +247,30 @@ function parseMessage (raw: string | Buffer): RawMessage | undefined {
|
|
|
244
247
|
}
|
|
245
248
|
}
|
|
246
249
|
|
|
250
|
+
async function parseJsonBody (req: Request): Promise<unknown> {
|
|
251
|
+
try {
|
|
252
|
+
const body = await req.json()
|
|
253
|
+
|
|
254
|
+
return body
|
|
255
|
+
} catch {
|
|
256
|
+
throw new BadRequestError('Invalid JSON')
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function parseJsonBodyAppData (req: Request): Promise<unknown> {
|
|
261
|
+
const contentType = req.headers.get('content-type')
|
|
262
|
+
const usingJsonBody = contentType?.startsWith('application/json')
|
|
263
|
+
|
|
264
|
+
if (!usingJsonBody) {
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const rawBody = await parseJsonBody(req)
|
|
269
|
+
const appData = (rawBody as Record<string, unknown> | null)?.data ?? null
|
|
270
|
+
|
|
271
|
+
return appData
|
|
272
|
+
}
|
|
273
|
+
|
|
247
274
|
function sweepInactiveSessions (state: SocketState): void {
|
|
248
275
|
for (const [key, session] of state.inactiveSessions) {
|
|
249
276
|
if (!isSessionActive(session)) {
|
|
@@ -334,6 +361,7 @@ function buildParams (
|
|
|
334
361
|
function buildRequest (
|
|
335
362
|
params: Record<string, string>,
|
|
336
363
|
message: RequestMessage,
|
|
364
|
+
activeSessions: ActiveSessions,
|
|
337
365
|
): WebSocketRequest {
|
|
338
366
|
const { id, clientId, method, route } = message
|
|
339
367
|
const headers = new Headers(message.headers ?? {})
|
|
@@ -349,6 +377,9 @@ function buildRequest (
|
|
|
349
377
|
params,
|
|
350
378
|
query,
|
|
351
379
|
json,
|
|
380
|
+
ws: {
|
|
381
|
+
active: activeSessions,
|
|
382
|
+
},
|
|
352
383
|
}
|
|
353
384
|
}
|
|
354
385
|
|
|
@@ -360,13 +391,12 @@ async function buildOutgoingMessage (
|
|
|
360
391
|
const text = await response.text()
|
|
361
392
|
const contentType = response.headers.get('content-type') ?? ''
|
|
362
393
|
const usingJson = contentType.includes('application/json')
|
|
363
|
-
const body = usingJson ? JSON.parse(text) : text
|
|
364
394
|
|
|
365
395
|
return createMessage(clientId, MessageType.Response, {
|
|
366
396
|
id,
|
|
367
397
|
status: response.status,
|
|
368
398
|
headers: response.headers,
|
|
369
|
-
body,
|
|
399
|
+
body: usingJson ? JSON.parse(text) : text,
|
|
370
400
|
})
|
|
371
401
|
}
|
|
372
402
|
|
|
@@ -375,31 +405,33 @@ function buildErrorMessage (
|
|
|
375
405
|
err: unknown,
|
|
376
406
|
): ResponseMessage {
|
|
377
407
|
const { id, clientId } = message as Pick<BaseMessage, 'id' | 'clientId'>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return createMessage(clientId, MessageType.Response, {
|
|
383
|
-
id,
|
|
384
|
-
status: ctor.status,
|
|
385
|
-
headers: new Headers({
|
|
386
|
-
'content-type': 'application/json;charset=utf-8',
|
|
387
|
-
}),
|
|
388
|
-
body: err.output,
|
|
389
|
-
})
|
|
390
|
-
}
|
|
408
|
+
const isRequestError = err instanceof RequestError
|
|
409
|
+
const httpError = isRequestError ? err : new InternalServerError()
|
|
410
|
+
const { status } = httpError.constructor as typeof RequestError
|
|
391
411
|
|
|
392
412
|
return createMessage(clientId, MessageType.Response, {
|
|
393
413
|
id,
|
|
394
|
-
status
|
|
395
|
-
headers: new Headers(
|
|
396
|
-
|
|
414
|
+
status,
|
|
415
|
+
headers: new Headers({
|
|
416
|
+
'content-type': 'application/json;charset=utf-8',
|
|
417
|
+
}),
|
|
418
|
+
body: httpError.output,
|
|
397
419
|
})
|
|
398
420
|
}
|
|
399
421
|
|
|
422
|
+
function getCloseReason (ws: SocketConnection, code: number): CloseReason {
|
|
423
|
+
if (ws.data.reaped) {
|
|
424
|
+
return CloseReason.Reaped
|
|
425
|
+
} else if (code === CloseCode.Ok) {
|
|
426
|
+
return CloseReason.Ok
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return CloseReason.Dropped
|
|
430
|
+
}
|
|
431
|
+
|
|
400
432
|
export function buildSocketState (opts: AppOptions = {}): SocketState {
|
|
401
433
|
return {
|
|
402
|
-
|
|
434
|
+
dropThreshold: opts.ws?.dropThreshold ?? 120_000,
|
|
403
435
|
heartbeatInterval: opts.ws?.heartbeatInterval ?? 30_000,
|
|
404
436
|
maxTickets: opts.ws?.maxTickets ?? 100_000,
|
|
405
437
|
reclaimTtl: opts.ws?.reclaimTtl ?? 300_000,
|
|
@@ -407,6 +439,8 @@ export function buildSocketState (opts: AppOptions = {}): SocketState {
|
|
|
407
439
|
tickets: new Map(),
|
|
408
440
|
activeSessions: new Map(),
|
|
409
441
|
inactiveSessions: new Map(),
|
|
442
|
+
onOpen: opts.ws?.onOpen ?? null,
|
|
443
|
+
onClose: opts.ws?.onClose ?? null,
|
|
410
444
|
}
|
|
411
445
|
}
|
|
412
446
|
|
|
@@ -415,14 +449,16 @@ export function buildSocketServer (
|
|
|
415
449
|
state: SocketState,
|
|
416
450
|
): WebSocketHandler<SocketData> {
|
|
417
451
|
const {
|
|
418
|
-
|
|
452
|
+
dropThreshold,
|
|
419
453
|
heartbeatInterval,
|
|
420
454
|
reclaimTtl,
|
|
421
455
|
activeSessions,
|
|
422
456
|
inactiveSessions,
|
|
457
|
+
onOpen,
|
|
458
|
+
onClose,
|
|
423
459
|
} = state
|
|
424
460
|
|
|
425
|
-
function armReaper (ws: SocketConnection)
|
|
461
|
+
function armReaper (ws: SocketConnection) {
|
|
426
462
|
if (ws.data.reaperHandle) {
|
|
427
463
|
clearTimeout(ws.data.reaperHandle)
|
|
428
464
|
}
|
|
@@ -430,8 +466,28 @@ export function buildSocketServer (
|
|
|
430
466
|
ws.data.reaperHandle = setTimeout(() => {
|
|
431
467
|
ws.data.reaped = true
|
|
432
468
|
|
|
433
|
-
ws.close()
|
|
434
|
-
},
|
|
469
|
+
ws.close(CloseCode.Reaped)
|
|
470
|
+
}, dropThreshold)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function invokeOpen (ws: SocketConnection) {
|
|
474
|
+
if (onOpen) {
|
|
475
|
+
try {
|
|
476
|
+
onOpen(ws.data.clientId)
|
|
477
|
+
} catch (err) {
|
|
478
|
+
console.error(err)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function invokeClose (ws: SocketConnection, reason: CloseReason) {
|
|
484
|
+
if (onClose) {
|
|
485
|
+
try {
|
|
486
|
+
onClose(ws.data.clientId, reason)
|
|
487
|
+
} catch (err) {
|
|
488
|
+
console.error(err)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
435
491
|
}
|
|
436
492
|
|
|
437
493
|
return {
|
|
@@ -469,6 +525,7 @@ export function buildSocketServer (
|
|
|
469
525
|
)
|
|
470
526
|
|
|
471
527
|
ws.send(JSON.stringify(welcomeMessage))
|
|
528
|
+
invokeOpen(ws)
|
|
472
529
|
},
|
|
473
530
|
close (ws: SocketConnection, code: number): void {
|
|
474
531
|
if (ws.data.reaperHandle) {
|
|
@@ -476,23 +533,29 @@ export function buildSocketServer (
|
|
|
476
533
|
}
|
|
477
534
|
|
|
478
535
|
if (ws.data.superseded) {
|
|
536
|
+
invokeClose(ws, CloseReason.Superseded)
|
|
537
|
+
|
|
479
538
|
return
|
|
480
539
|
}
|
|
481
540
|
|
|
482
|
-
const
|
|
541
|
+
const reason = getCloseReason(ws, code)
|
|
542
|
+
const exists = activeSessions.get(ws.data.clientId)
|
|
483
543
|
|
|
484
|
-
if (!
|
|
544
|
+
if (!exists || exists.ws !== ws) {
|
|
485
545
|
return
|
|
486
546
|
}
|
|
487
547
|
|
|
488
548
|
activeSessions.delete(ws.data.clientId)
|
|
489
549
|
|
|
490
|
-
if (code !==
|
|
550
|
+
if (code !== CloseCode.Ok || ws.data.reaped) {
|
|
491
551
|
inactiveSessions.set(ws.data.clientId, {
|
|
492
|
-
token:
|
|
552
|
+
token: exists.token,
|
|
493
553
|
expiresAt: Date.now() + reclaimTtl,
|
|
554
|
+
app: ws.data.app,
|
|
494
555
|
})
|
|
495
556
|
}
|
|
557
|
+
|
|
558
|
+
invokeClose(ws, reason)
|
|
496
559
|
},
|
|
497
560
|
async message (ws: SocketConnection, raw: string | Buffer): Promise<void> {
|
|
498
561
|
const incomingMsg = parseMessage(raw)
|
|
@@ -518,7 +581,7 @@ export function buildSocketServer (
|
|
|
518
581
|
const { id, clientId } = message
|
|
519
582
|
const route = matchRoute(routes, message)
|
|
520
583
|
const params = buildParams(route, message)
|
|
521
|
-
const req = buildRequest(params, message)
|
|
584
|
+
const req = buildRequest(params, message, activeSessions)
|
|
522
585
|
const res = await executeMiddlewareChain(req, route.chain)
|
|
523
586
|
const outgoingMsg = await buildOutgoingMessage(id, clientId, res)
|
|
524
587
|
|
|
@@ -543,7 +606,7 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
|
|
|
543
606
|
inactiveSessions,
|
|
544
607
|
} = state
|
|
545
608
|
|
|
546
|
-
function
|
|
609
|
+
function issueTicket (clientId: string, data: unknown): string {
|
|
547
610
|
for (const [key, entry] of tickets) {
|
|
548
611
|
if (entry.expiresAt > Date.now()) {
|
|
549
612
|
break
|
|
@@ -556,38 +619,36 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
|
|
|
556
619
|
throw new ServiceUnavailableError('Unable to issue ticket')
|
|
557
620
|
}
|
|
558
621
|
|
|
559
|
-
const
|
|
622
|
+
const hash = randomTicketHash()
|
|
560
623
|
const expiresAt = Date.now() + ticketTtl
|
|
561
624
|
|
|
562
|
-
tickets.set(
|
|
625
|
+
tickets.set(hash, {
|
|
563
626
|
clientId,
|
|
564
627
|
expiresAt,
|
|
628
|
+
data,
|
|
565
629
|
})
|
|
566
630
|
|
|
567
|
-
return
|
|
631
|
+
return hash
|
|
568
632
|
}
|
|
569
633
|
|
|
570
|
-
function redeemTicket (
|
|
571
|
-
const entry =
|
|
634
|
+
function redeemTicket (hash: string): Ticket | undefined {
|
|
635
|
+
const entry = hash ? tickets.get(hash) : undefined
|
|
572
636
|
|
|
573
637
|
if (!entry) {
|
|
574
638
|
return undefined
|
|
575
639
|
}
|
|
576
640
|
|
|
577
|
-
tickets.delete(
|
|
641
|
+
tickets.delete(hash)
|
|
578
642
|
|
|
579
|
-
|
|
580
|
-
return undefined
|
|
581
|
-
}
|
|
643
|
+
return entry.expiresAt > Date.now() ? entry : undefined
|
|
582
644
|
|
|
583
|
-
return entry.clientId
|
|
584
645
|
}
|
|
585
646
|
|
|
586
647
|
return [
|
|
587
648
|
{
|
|
588
649
|
method: 'GET',
|
|
589
650
|
path: '/ws',
|
|
590
|
-
handler (req:
|
|
651
|
+
handler (req: Request, res: unknown): AsyncHandlerResult {
|
|
591
652
|
const validReq = validateSchema(req, createSocketValidator)
|
|
592
653
|
|
|
593
654
|
if (typeof res !== 'object') {
|
|
@@ -595,16 +656,18 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
|
|
|
595
656
|
}
|
|
596
657
|
|
|
597
658
|
const ctx: UpgradeContext = res ? { ...res } : {}
|
|
659
|
+
const ticket = redeemTicket(validReq.query.ticket)
|
|
660
|
+
|
|
661
|
+
if (!ticket) {
|
|
662
|
+
throw new NotFoundError()
|
|
663
|
+
}
|
|
598
664
|
|
|
599
665
|
ctx.data = ctx.data ?? {}
|
|
600
|
-
ctx.data.clientId =
|
|
666
|
+
ctx.data.clientId = ticket.clientId
|
|
601
667
|
ctx.data.superseded = false
|
|
602
668
|
ctx.data.reaped = false
|
|
603
669
|
ctx.data.reaperHandle = null
|
|
604
|
-
|
|
605
|
-
if (!ctx.data.clientId) {
|
|
606
|
-
throw new NotFoundError()
|
|
607
|
-
}
|
|
670
|
+
ctx.data.app = ticket.data
|
|
608
671
|
|
|
609
672
|
const useSocket = validReq.server.upgrade(validReq.raw, ctx)
|
|
610
673
|
|
|
@@ -612,20 +675,22 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
|
|
|
612
675
|
throw new NotFoundError()
|
|
613
676
|
}
|
|
614
677
|
|
|
615
|
-
return new Response()
|
|
678
|
+
return Promise.resolve(new Response())
|
|
616
679
|
},
|
|
617
680
|
},
|
|
618
681
|
{
|
|
619
682
|
method: 'POST',
|
|
620
683
|
path: '/ws',
|
|
621
|
-
handler (req:
|
|
684
|
+
async handler (req: Request, res: unknown): AsyncHandlerResult {
|
|
622
685
|
validateSchema(req, createTicketValidator)
|
|
623
686
|
|
|
624
687
|
const clientId = crypto.randomUUID()
|
|
688
|
+
const appData = await parseJsonBodyAppData(req)
|
|
689
|
+
const ticket = issueTicket(clientId, appData)
|
|
625
690
|
|
|
626
691
|
return Response.json({
|
|
627
692
|
clientId,
|
|
628
|
-
ticket
|
|
693
|
+
ticket,
|
|
629
694
|
data: res,
|
|
630
695
|
}, { status: StatusCode.Created })
|
|
631
696
|
},
|
|
@@ -633,10 +698,10 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
|
|
|
633
698
|
{
|
|
634
699
|
method: 'PUT',
|
|
635
700
|
path: '/ws/:clientId',
|
|
636
|
-
handler (req:
|
|
701
|
+
async handler (req: Request, res: unknown): AsyncHandlerResult {
|
|
637
702
|
const validReq = validateSchema(req, updateTicketValidator)
|
|
638
|
-
const authHeader = validReq.headers.get('authorization')
|
|
639
|
-
const token = authHeader
|
|
703
|
+
const authHeader = validReq.headers.get('authorization')!
|
|
704
|
+
const token = authHeader.slice('Bearer '.length)
|
|
640
705
|
|
|
641
706
|
let session: Session | undefined =
|
|
642
707
|
activeSessions.get(validReq.params.clientId)
|
|
@@ -659,9 +724,11 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
|
|
|
659
724
|
throw new UnauthorizedError('Invalid token')
|
|
660
725
|
}
|
|
661
726
|
|
|
727
|
+
const appData = 'ws' in session ? session.ws.data.app : session.app
|
|
728
|
+
|
|
662
729
|
return Response.json({
|
|
663
730
|
clientId: validReq.params.clientId,
|
|
664
|
-
ticket:
|
|
731
|
+
ticket: issueTicket(validReq.params.clientId, appData),
|
|
665
732
|
data: res,
|
|
666
733
|
})
|
|
667
734
|
},
|
|
@@ -670,15 +737,11 @@ export function buildSocketHandlers (state: SocketState): SocketEndpoint[] {
|
|
|
670
737
|
}
|
|
671
738
|
|
|
672
739
|
export function buildSocketCommands (state: SocketState): SocketCommands {
|
|
673
|
-
function sendToClient (
|
|
674
|
-
clientId: string,
|
|
675
|
-
event: string,
|
|
676
|
-
body: unknown,
|
|
677
|
-
): void {
|
|
740
|
+
function sendToClient (clientId: string, event: string, body: unknown) {
|
|
678
741
|
const session = state.activeSessions.get(clientId)
|
|
679
742
|
|
|
680
743
|
if (!session) {
|
|
681
|
-
throw new ReferenceError(`No
|
|
744
|
+
throw new ReferenceError(`No active socket for client: ${clientId}`)
|
|
682
745
|
}
|
|
683
746
|
|
|
684
747
|
const message = createMessage(clientId, MessageType.Notification, {
|
|
@@ -694,10 +757,28 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
|
|
|
694
757
|
send (clientId, event, body) {
|
|
695
758
|
sendToClient(clientId, event, body)
|
|
696
759
|
},
|
|
760
|
+
sendToGroup (fn, event, body) {
|
|
761
|
+
for (const [clientId, session] of state.activeSessions) {
|
|
762
|
+
const allow = fn(clientId, session.ws.data)
|
|
763
|
+
|
|
764
|
+
if (allow) {
|
|
765
|
+
sendToClient(clientId, event, body)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
},
|
|
697
769
|
broadcast (event, body) {
|
|
698
770
|
for (const clientId of state.activeSessions.keys()) {
|
|
699
771
|
sendToClient(clientId, event, body)
|
|
700
772
|
}
|
|
701
773
|
},
|
|
774
|
+
drop (clientId, code, reason) {
|
|
775
|
+
const session = state.activeSessions.get(clientId)
|
|
776
|
+
|
|
777
|
+
if (!session) {
|
|
778
|
+
throw new ReferenceError(`No active socket for client: ${clientId}`)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
session.ws.close(code, reason)
|
|
782
|
+
},
|
|
702
783
|
}
|
|
703
784
|
}
|
package/src/utils.ts
CHANGED
|
@@ -90,7 +90,36 @@ export type FormattedError = {
|
|
|
90
90
|
message: string
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
export type
|
|
93
|
+
export type AsyncHandlerResult = Promise<Response>
|
|
94
|
+
export type HandlerResult = Response | AsyncHandlerResult
|
|
95
|
+
export type NextFn = (data?: unknown) => HandlerResult
|
|
96
|
+
|
|
97
|
+
export type Middleware = (
|
|
98
|
+
req: Request,
|
|
99
|
+
res: unknown,
|
|
100
|
+
next: NextFn,
|
|
101
|
+
) => HandlerResult
|
|
102
|
+
|
|
103
|
+
export type Handler = (
|
|
104
|
+
req: Request,
|
|
105
|
+
res: unknown,
|
|
106
|
+
) => HandlerResult
|
|
107
|
+
|
|
108
|
+
export type MiddlewareChain = (Middleware | Handler)[]
|
|
109
|
+
|
|
110
|
+
export type ActiveSession = {
|
|
111
|
+
token: string
|
|
112
|
+
ws: SocketConnection
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type InactiveSession = {
|
|
116
|
+
token: string
|
|
117
|
+
expiresAt: number
|
|
118
|
+
app: unknown
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type ActiveSessions = ReadonlyMap<string, ActiveSession>
|
|
122
|
+
export type Session = ActiveSession | InactiveSession
|
|
94
123
|
|
|
95
124
|
export type BaseRequest = {
|
|
96
125
|
method: HttpMethod
|
|
@@ -99,6 +128,9 @@ export type BaseRequest = {
|
|
|
99
128
|
params: Record<string, string>
|
|
100
129
|
query: Record<string, unknown>
|
|
101
130
|
json: () => Promise<unknown>
|
|
131
|
+
ws: {
|
|
132
|
+
active: ActiveSessions
|
|
133
|
+
}
|
|
102
134
|
}
|
|
103
135
|
|
|
104
136
|
export type EndpointRequest = BaseRequest & {
|
|
@@ -113,18 +145,31 @@ export type WebSocketRequest = BaseRequest & {
|
|
|
113
145
|
|
|
114
146
|
export type Request = EndpointRequest | WebSocketRequest
|
|
115
147
|
|
|
116
|
-
export
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
148
|
+
export const CloseCode = {
|
|
149
|
+
Ok: 1000,
|
|
150
|
+
Abnormal: 1006,
|
|
151
|
+
Reaped: 4999,
|
|
152
|
+
} as const
|
|
153
|
+
|
|
154
|
+
export type CloseCode = typeof CloseCode[keyof typeof CloseCode]
|
|
155
|
+
|
|
156
|
+
export const CloseReason = {
|
|
157
|
+
Ok: 'ok',
|
|
158
|
+
Dropped: 'dropped',
|
|
159
|
+
Reaped: 'reaped',
|
|
160
|
+
Superseded: 'superseded',
|
|
161
|
+
} as const
|
|
162
|
+
|
|
163
|
+
export type CloseReason = typeof CloseReason[keyof typeof CloseReason]
|
|
121
164
|
|
|
122
165
|
export type SocketOptions = {
|
|
123
|
-
|
|
166
|
+
dropThreshold?: number
|
|
124
167
|
heartbeatInterval?: number
|
|
125
168
|
maxTickets?: number
|
|
126
169
|
reclaimTtl?: number
|
|
127
170
|
ticketTtl?: number
|
|
171
|
+
onOpen?: (clientId: string) => void
|
|
172
|
+
onClose?: (clientId: string, reason: CloseReason) => void
|
|
128
173
|
}
|
|
129
174
|
|
|
130
175
|
export type SocketData = {
|
|
@@ -132,6 +177,13 @@ export type SocketData = {
|
|
|
132
177
|
superseded: boolean
|
|
133
178
|
reaped: boolean
|
|
134
179
|
reaperHandle: ReturnType<typeof setTimeout> | null
|
|
180
|
+
app: unknown
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type SocketConnection = {
|
|
184
|
+
data: SocketData
|
|
185
|
+
send: (data: string) => unknown
|
|
186
|
+
close: (code?: number, reason?: string) => void
|
|
135
187
|
}
|
|
136
188
|
|
|
137
189
|
export type Server = BunServer<SocketData>
|
|
@@ -174,8 +226,8 @@ export function formatError (
|
|
|
174
226
|
|
|
175
227
|
export async function executeMiddlewareChain (
|
|
176
228
|
req: Request,
|
|
177
|
-
chain:
|
|
178
|
-
):
|
|
229
|
+
chain: MiddlewareChain,
|
|
230
|
+
): AsyncHandlerResult {
|
|
179
231
|
if (!chain.length) {
|
|
180
232
|
throw new RangeError('Middleware chain is empty')
|
|
181
233
|
}
|
|
@@ -183,15 +235,14 @@ export async function executeMiddlewareChain (
|
|
|
183
235
|
const executeMiddleware = async (
|
|
184
236
|
index: number,
|
|
185
237
|
res: unknown,
|
|
186
|
-
):
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const result = await currentMiddleware(req, res, next)
|
|
238
|
+
): AsyncHandlerResult => {
|
|
239
|
+
const isLast = index === chain.length - 1
|
|
240
|
+
const fn = chain[index]
|
|
241
|
+
const next = (data?: unknown) => executeMiddleware(index + 1, data)
|
|
242
|
+
|
|
243
|
+
const result = isLast
|
|
244
|
+
? await (fn as Handler)(req, res)
|
|
245
|
+
: await (fn as Middleware)(req, res, next)
|
|
195
246
|
|
|
196
247
|
if (result instanceof Response) {
|
|
197
248
|
return result
|