spooder 4.4.11 → 4.5.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 +54 -0
- package/package.json +1 -1
- package/src/api.ts +66 -0
- package/test/index.ts +29 -0
- package/test.ts +7 -0
package/README.md
CHANGED
|
@@ -57,6 +57,8 @@ The `CLI` component of `spooder` is a global command-line tool for running serve
|
|
|
57
57
|
- [`server.sse(path: string, handler: ServerSentEventHandler)`](#api-routing-server-sse)
|
|
58
58
|
- [API > Routing > Webhooks](#api-routing-webhooks)
|
|
59
59
|
- [`server.webhook(secret: string, path: string, handler: WebhookHandler)`](#api-routing-server-webhook)
|
|
60
|
+
- [API > Routing > WebSockets](#api-routing-websockets)
|
|
61
|
+
- [`server.websocket(path: string, handlers: WebsocketHandlers)`](#api-routing-server-websocket)
|
|
60
62
|
- [API > Server Control](#api-server-control)
|
|
61
63
|
- [`server.stop(immediate: boolean)`](#api-server-control-server-stop)
|
|
62
64
|
- [API > Error Handling](#api-error-handling)
|
|
@@ -915,6 +917,58 @@ A webhook callback will only be called if the following critera is met by a requ
|
|
|
915
917
|
> [!NOTE]
|
|
916
918
|
> Constant-time comparison is used to prevent timing attacks when comparing the HMAC signature.
|
|
917
919
|
|
|
920
|
+
<a id="api-routing-websockets"></a>
|
|
921
|
+
## API > Routing > WebSockets
|
|
922
|
+
|
|
923
|
+
<a id="api-routing-server-websocket"></a>
|
|
924
|
+
### 🔧 `server.websocket(path: string, handlers: WebSocketHandlers)`
|
|
925
|
+
|
|
926
|
+
Register a route which handles websocket connections.
|
|
927
|
+
|
|
928
|
+
```ts
|
|
929
|
+
server.websocket('/path/to/websocket', {
|
|
930
|
+
// all of these handlers are OPTIONAL
|
|
931
|
+
|
|
932
|
+
accept: (req) => {
|
|
933
|
+
// validates a request before it is upgraded
|
|
934
|
+
// returns HTTP 401 if FALSE is returned
|
|
935
|
+
// allows you to check headers/authentication
|
|
936
|
+
return true;
|
|
937
|
+
},
|
|
938
|
+
|
|
939
|
+
open: (ws) => {
|
|
940
|
+
// called when a websocket client connects
|
|
941
|
+
},
|
|
942
|
+
|
|
943
|
+
close: (ws, code, reason) => {
|
|
944
|
+
// called when a websocket client disconnects
|
|
945
|
+
},
|
|
946
|
+
|
|
947
|
+
message: (ws, message) => {
|
|
948
|
+
// called when a websocket message is received
|
|
949
|
+
// message is a string
|
|
950
|
+
},
|
|
951
|
+
|
|
952
|
+
message_json: (ws, data) => {
|
|
953
|
+
// called when a websocket message is received
|
|
954
|
+
// payload is parsed as JSON
|
|
955
|
+
|
|
956
|
+
// if payload cannot be parsed, socket will be
|
|
957
|
+
// closed with error 1003: Unsupported Data
|
|
958
|
+
|
|
959
|
+
// messages are only internally parsed if this
|
|
960
|
+
// handler is present
|
|
961
|
+
},
|
|
962
|
+
|
|
963
|
+
drain: (ws) => {
|
|
964
|
+
// called when a websocket with backpressure drains
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
> [!IMPORTANT]
|
|
970
|
+
> While it is possible to register multiple routes for websockets, the only handler which is unique per route is `accept()`. The last handlers provided to any route (with the exception of `accept()`) will apply to ALL websocket routes. This is a limitation in Bun.
|
|
971
|
+
|
|
918
972
|
<a id="api-server-control"></a>
|
|
919
973
|
## API > Server Control
|
|
920
974
|
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -104,6 +104,15 @@ export async function caution(err_message_or_obj: string | object, ...err: objec
|
|
|
104
104
|
await handle_error('caution: ', err_message_or_obj, ...err);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
type WebsocketHandlers = {
|
|
108
|
+
accept?: (req: Request) => boolean,
|
|
109
|
+
message?: (ws: WebSocket, message: string) => void,
|
|
110
|
+
message_json?: (ws: WebSocket, message: JsonSerializable) => void,
|
|
111
|
+
open?: (ws: WebSocket) => void,
|
|
112
|
+
close?: (ws: WebSocket, code: number, reason: string) => void,
|
|
113
|
+
drain?: (ws: WebSocket) => void
|
|
114
|
+
};
|
|
115
|
+
|
|
107
116
|
type CallableFunction = (...args: any[]) => any;
|
|
108
117
|
type Callable = Promise<any> | CallableFunction;
|
|
109
118
|
|
|
@@ -722,6 +731,12 @@ export function serve(port: number) {
|
|
|
722
731
|
|
|
723
732
|
const slow_requests = new WeakSet();
|
|
724
733
|
|
|
734
|
+
let ws_message_handler: any = undefined;
|
|
735
|
+
let ws_message_json_handler: any = undefined;
|
|
736
|
+
let ws_open_handler: any = undefined;
|
|
737
|
+
let ws_close_handler: any = undefined;
|
|
738
|
+
let ws_drain_handler: any = undefined;
|
|
739
|
+
|
|
725
740
|
const server = Bun.serve({
|
|
726
741
|
port,
|
|
727
742
|
development: false,
|
|
@@ -741,6 +756,38 @@ export function serve(port: number) {
|
|
|
741
756
|
slow_requests.delete(req);
|
|
742
757
|
|
|
743
758
|
return print_request_info(req, response, url, request_time);
|
|
759
|
+
},
|
|
760
|
+
|
|
761
|
+
websocket: {
|
|
762
|
+
message(ws, message) {
|
|
763
|
+
ws_message_handler?.(ws, message);
|
|
764
|
+
|
|
765
|
+
if (ws_message_json_handler) {
|
|
766
|
+
try {
|
|
767
|
+
if (message instanceof ArrayBuffer)
|
|
768
|
+
message = new TextDecoder().decode(message);
|
|
769
|
+
else if (message instanceof Buffer)
|
|
770
|
+
message = message.toString('utf8');
|
|
771
|
+
|
|
772
|
+
const parsed = JSON.parse(message as string);
|
|
773
|
+
ws_message_json_handler(ws, parsed);
|
|
774
|
+
} catch (e) {
|
|
775
|
+
ws.close(1003, 'Unsupported Data');
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
|
|
780
|
+
open(ws) {
|
|
781
|
+
ws_open_handler?.(ws);
|
|
782
|
+
},
|
|
783
|
+
|
|
784
|
+
close(ws, code, reason) {
|
|
785
|
+
ws_close_handler?.(ws, code, reason);
|
|
786
|
+
},
|
|
787
|
+
|
|
788
|
+
drain(ws) {
|
|
789
|
+
ws_drain_handler?.(ws);
|
|
790
|
+
}
|
|
744
791
|
}
|
|
745
792
|
});
|
|
746
793
|
|
|
@@ -760,6 +807,25 @@ export function serve(port: number) {
|
|
|
760
807
|
routes.push([[...path.split('/'), '*'], route_directory(path, dir, handler ?? default_directory_handler), method]);
|
|
761
808
|
},
|
|
762
809
|
|
|
810
|
+
/** Add a route to upgrade connections to websockets. */
|
|
811
|
+
websocket: (path: string, handlers: WebsocketHandlers): void => {
|
|
812
|
+
routes.push([path.split('/'), async (req: Request, url: URL) => {
|
|
813
|
+
if (handlers.accept?.(req) === false)
|
|
814
|
+
return 401; // Unauthorized
|
|
815
|
+
|
|
816
|
+
if (server.upgrade(req))
|
|
817
|
+
return 101; // Switching Protocols
|
|
818
|
+
|
|
819
|
+
return new Response('WebSocket upgrade failed', { status: 500 });
|
|
820
|
+
}, 'GET']);
|
|
821
|
+
|
|
822
|
+
ws_message_json_handler = handlers.message_json;
|
|
823
|
+
ws_open_handler = handlers.open;
|
|
824
|
+
ws_close_handler = handlers.close;
|
|
825
|
+
ws_message_handler = handlers.message;
|
|
826
|
+
ws_drain_handler = handlers.drain;
|
|
827
|
+
},
|
|
828
|
+
|
|
763
829
|
webhook: (secret: string, path: string, handler: WebhookHandler): void => {
|
|
764
830
|
routes.push([path.split('/'), async (req: Request, url: URL) => {
|
|
765
831
|
if (req.headers.get('Content-Type') !== 'application/json')
|
package/test/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { serve } from '../src/api.ts';
|
|
2
|
+
|
|
3
|
+
const server = serve(4000);
|
|
4
|
+
|
|
5
|
+
server.route('/test', () => {
|
|
6
|
+
return 'Hello world!';
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
server.websocket('/websocket', {
|
|
10
|
+
accept: (req) => {
|
|
11
|
+
return Math.random() > 0.5;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
open: (ws) => {
|
|
15
|
+
console.log('websocket opened');
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
close: (ws, code, reason) => {
|
|
19
|
+
console.log('websocket close');
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
message_json: (ws, message) => {
|
|
23
|
+
console.log(message);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
drain: (ws) => {
|
|
27
|
+
console.log('websocket drain');
|
|
28
|
+
}
|
|
29
|
+
});
|