@atproto/lex-server 0.0.1
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/CHANGELOG.md +13 -0
- package/LICENSE.txt +7 -0
- package/README.md +598 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/errors.js.map +1 -0
- package/dist/example.d.ts +2 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +36 -0
- package/dist/example.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/lex-auth-error.d.ts +15 -0
- package/dist/lex-auth-error.d.ts.map +1 -0
- package/dist/lex-auth-error.js +52 -0
- package/dist/lex-auth-error.js.map +1 -0
- package/dist/lex-server.d.ts +80 -0
- package/dist/lex-server.d.ts.map +1 -0
- package/dist/lex-server.js +285 -0
- package/dist/lex-server.js.map +1 -0
- package/dist/lib/drain-websocket.d.ts +6 -0
- package/dist/lib/drain-websocket.d.ts.map +1 -0
- package/dist/lib/drain-websocket.js +16 -0
- package/dist/lib/drain-websocket.js.map +1 -0
- package/dist/lib/sleep.d.ts +2 -0
- package/dist/lib/sleep.d.ts.map +1 -0
- package/dist/lib/sleep.js +22 -0
- package/dist/lib/sleep.js.map +1 -0
- package/dist/lib/www-authenticate.d.ts +7 -0
- package/dist/lib/www-authenticate.d.ts.map +1 -0
- package/dist/lib/www-authenticate.js +22 -0
- package/dist/lib/www-authenticate.js.map +1 -0
- package/dist/nodejs.d.ts +35 -0
- package/dist/nodejs.d.ts.map +1 -0
- package/dist/nodejs.js +236 -0
- package/dist/nodejs.js.map +1 -0
- package/dist/subscripotion.d.ts +2 -0
- package/dist/subscripotion.d.ts.map +1 -0
- package/dist/subscripotion.js +36 -0
- package/dist/subscripotion.js.map +1 -0
- package/dist/test.d.mts +2 -0
- package/dist/test.d.mts.map +1 -0
- package/dist/test.mjs +52 -0
- package/dist/test.mjs.map +1 -0
- package/nodejs.js +5 -0
- package/package.json +64 -0
- package/src/errors.ts +54 -0
- package/src/index.ts +8 -0
- package/src/lex-server.test.ts +1621 -0
- package/src/lex-server.ts +551 -0
- package/src/lib/drain-websocket.ts +23 -0
- package/src/lib/sleep.ts +25 -0
- package/src/lib/www-authenticate.ts +26 -0
- package/src/nodejs.test.ts +107 -0
- package/src/nodejs.ts +367 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +8 -0
- package/tsconfig.tests.json +9 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abortableSleep = abortableSleep;
|
|
4
|
+
async function abortableSleep(ms, signal) {
|
|
5
|
+
signal.throwIfAborted();
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const cleanup = () => {
|
|
8
|
+
signal.removeEventListener('abort', onAbort);
|
|
9
|
+
clearTimeout(timeoutHandle);
|
|
10
|
+
};
|
|
11
|
+
const timeoutHandle = setTimeout(() => {
|
|
12
|
+
cleanup();
|
|
13
|
+
resolve();
|
|
14
|
+
}, ms);
|
|
15
|
+
const onAbort = () => {
|
|
16
|
+
cleanup();
|
|
17
|
+
reject(signal.reason);
|
|
18
|
+
};
|
|
19
|
+
signal.addEventListener('abort', onAbort);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=sleep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/lib/sleep.ts"],"names":[],"mappings":";;AAAA,wCAwBC;AAxBM,KAAK,UAAU,cAAc,CAClC,EAAU,EACV,MAAmB;IAEnB,MAAM,CAAC,cAAc,EAAE,CAAA;IAEvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC5C,YAAY,CAAC,aAAa,CAAC,CAAA;QAC7B,CAAC,CAAA;QAED,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,OAAO,EAAE,CAAA;YACT,OAAO,EAAE,CAAA;QACX,CAAC,EAAE,EAAE,CAAC,CAAA;QAEN,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,OAAO,EAAE,CAAA;YACT,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACvB,CAAC,CAAA;QAED,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["export async function abortableSleep(\n ms: number,\n signal: AbortSignal,\n): Promise<void> {\n signal.throwIfAborted()\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n signal.removeEventListener('abort', onAbort)\n clearTimeout(timeoutHandle)\n }\n\n const timeoutHandle = setTimeout(() => {\n cleanup()\n resolve()\n }, ms)\n\n const onAbort = () => {\n cleanup()\n reject(signal.reason)\n }\n\n signal.addEventListener('abort', onAbort)\n })\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"www-authenticate.d.ts","sourceRoot":"","sources":["../../src/lib/www-authenticate.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;KAC3B,UAAU,IAAI,MAAM,CAAC,CAAC,EACnB,MAAM,GACN;SAAG,SAAS,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM;KAAE;CACvC,CAAA;AAED,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,eAAe,GAC/B,MAAM,CAiBR"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatWWWAuthenticateHeader = formatWWWAuthenticateHeader;
|
|
4
|
+
function formatWWWAuthenticateHeader(wwwAuthenticate) {
|
|
5
|
+
return Object.entries(wwwAuthenticate)
|
|
6
|
+
.map(([authScheme, authParams]) => {
|
|
7
|
+
if (authParams === undefined)
|
|
8
|
+
return null;
|
|
9
|
+
const paramsEnc = typeof authParams === 'string'
|
|
10
|
+
? [authParams]
|
|
11
|
+
: Object.entries(authParams)
|
|
12
|
+
.filter(([_, val]) => val != null)
|
|
13
|
+
.map(([name, val]) => `${name}=${JSON.stringify(val)}`);
|
|
14
|
+
const authChallenge = paramsEnc?.length
|
|
15
|
+
? `${authScheme} ${paramsEnc.join(', ')}`
|
|
16
|
+
: authScheme;
|
|
17
|
+
return authChallenge;
|
|
18
|
+
})
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(', ');
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=www-authenticate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"www-authenticate.js","sourceRoot":"","sources":["../../src/lib/www-authenticate.ts"],"names":[],"mappings":";;AAMA,kEAmBC;AAnBD,SAAgB,2BAA2B,CACzC,eAAgC;IAEhC,OAAO,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC;SACnC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE;QAChC,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,IAAI,CAAA;QACzC,MAAM,SAAS,GACb,OAAO,UAAU,KAAK,QAAQ;YAC5B,CAAC,CAAC,CAAC,UAAU,CAAC;YACd,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;iBACvB,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC;iBACjC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC/D,MAAM,aAAa,GAAG,SAAS,EAAE,MAAM;YACrC,CAAC,CAAC,GAAG,UAAU,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACzC,CAAC,CAAC,UAAU,CAAA;QACd,OAAO,aAAa,CAAA;IACtB,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC","sourcesContent":["export type WWWAuthenticate = {\n [authScheme in string]?:\n | string // token68\n | { [authParam in string]?: string }\n}\n\nexport function formatWWWAuthenticateHeader(\n wwwAuthenticate: WWWAuthenticate,\n): string {\n return Object.entries(wwwAuthenticate)\n .map(([authScheme, authParams]) => {\n if (authParams === undefined) return null\n const paramsEnc =\n typeof authParams === 'string'\n ? [authParams]\n : Object.entries(authParams)\n .filter(([_, val]) => val != null)\n .map(([name, val]) => `${name}=${JSON.stringify(val)}`)\n const authChallenge = paramsEnc?.length\n ? `${authScheme} ${paramsEnc.join(', ')}`\n : authScheme\n return authChallenge\n })\n .filter(Boolean)\n .join(', ')\n}\n"]}
|
package/dist/nodejs.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { IncomingMessage, Server as HttpServer, ServerOptions, ServerResponse } from 'node:http';
|
|
2
|
+
import { ListenOptions } from 'node:net';
|
|
3
|
+
export declare function upgradeWebSocket(request: Request): {
|
|
4
|
+
response: Response;
|
|
5
|
+
socket: WebSocket;
|
|
6
|
+
};
|
|
7
|
+
export type NetAddr = {
|
|
8
|
+
hostname: string;
|
|
9
|
+
port: number;
|
|
10
|
+
transport: 'tcp';
|
|
11
|
+
};
|
|
12
|
+
export type NodeConnectionInfo = {
|
|
13
|
+
localAddr?: NetAddr;
|
|
14
|
+
remoteAddr?: NetAddr;
|
|
15
|
+
};
|
|
16
|
+
export interface HandlerFunction {
|
|
17
|
+
(req: Request, info: NodeConnectionInfo): Response | Promise<Response>;
|
|
18
|
+
}
|
|
19
|
+
export interface HandlerObject {
|
|
20
|
+
handle: HandlerFunction;
|
|
21
|
+
}
|
|
22
|
+
export declare function toRequestListener<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse>(handlerFn: HandlerFunction): (req: InstanceType<Request>, res: InstanceType<Response> & {
|
|
23
|
+
req: InstanceType<Request>;
|
|
24
|
+
}, next?: (err?: unknown) => void) => void;
|
|
25
|
+
export type CreateServerOptions<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse> = ServerOptions<Request, Response> & {
|
|
26
|
+
gracefulTerminationTimeout?: number;
|
|
27
|
+
};
|
|
28
|
+
export interface Server<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse> extends HttpServer<Request, Response>, AsyncDisposable {
|
|
29
|
+
terminate(): Promise<void>;
|
|
30
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export declare function createServer<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse>(handler: HandlerFunction | HandlerObject, options?: CreateServerOptions<Request, Response>): Server<Request, Response>;
|
|
33
|
+
export type StartServerOptions<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse> = ListenOptions & CreateServerOptions<Request, Response>;
|
|
34
|
+
export declare function serve<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse>(handler: HandlerFunction | HandlerObject, options?: StartServerOptions<Request, Response>): Promise<Server<Request, Response>>;
|
|
35
|
+
//# sourceMappingURL=nodejs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodejs.d.ts","sourceRoot":"","sources":["../src/nodejs.ts"],"names":[],"mappings":"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"}
|
package/dist/nodejs.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upgradeWebSocket = upgradeWebSocket;
|
|
4
|
+
exports.toRequestListener = toRequestListener;
|
|
5
|
+
exports.createServer = createServer;
|
|
6
|
+
exports.serve = serve;
|
|
7
|
+
const node_events_1 = require("node:events");
|
|
8
|
+
const node_http_1 = require("node:http");
|
|
9
|
+
const node_stream_1 = require("node:stream");
|
|
10
|
+
const promises_1 = require("node:stream/promises");
|
|
11
|
+
const http_terminator_1 = require("http-terminator");
|
|
12
|
+
const ws_1 = require("ws");
|
|
13
|
+
// @ts-expect-error
|
|
14
|
+
Symbol.asyncDispose ??= Symbol.for('Symbol.asyncDispose');
|
|
15
|
+
const kResponseWs = Symbol.for('@atproto/lex-server:WebSocket');
|
|
16
|
+
function isUpgradeRequest(request, upgrade) {
|
|
17
|
+
return (request.method === 'GET' &&
|
|
18
|
+
request.headers.get('connection')?.toLowerCase() === 'upgrade' &&
|
|
19
|
+
request.headers.get('upgrade')?.toLowerCase() === upgrade);
|
|
20
|
+
}
|
|
21
|
+
function upgradeWebSocket(request) {
|
|
22
|
+
if (!isUpgradeRequest(request, 'websocket')) {
|
|
23
|
+
throw new TypeError('upgradeWebSocket() expects a WebSocket upgrade');
|
|
24
|
+
}
|
|
25
|
+
// Placeholder response for WebSocket upgrade. The actual handling will happen
|
|
26
|
+
// through the handleWebSocketUpgrade function. Headers set on the response
|
|
27
|
+
// will be applied during the upgrade.
|
|
28
|
+
const response = new Response(null, { status: 200 });
|
|
29
|
+
// The Response constructor does not allow setting status 101, so we
|
|
30
|
+
// define it directly. The purpose of this response is just to signal
|
|
31
|
+
// that an upgrade is needed, and to carry any headers.
|
|
32
|
+
Object.defineProperty(response, 'status', {
|
|
33
|
+
value: 101,
|
|
34
|
+
enumerable: false,
|
|
35
|
+
configurable: false,
|
|
36
|
+
writable: false,
|
|
37
|
+
});
|
|
38
|
+
// @ts-expect-error
|
|
39
|
+
const socket = new ws_1.WebSocket(null, undefined, {
|
|
40
|
+
autoPong: true,
|
|
41
|
+
});
|
|
42
|
+
// Attach the WebSocket to the response for later retrieval
|
|
43
|
+
Object.defineProperty(response, kResponseWs, {
|
|
44
|
+
value: socket,
|
|
45
|
+
enumerable: false,
|
|
46
|
+
configurable: false,
|
|
47
|
+
writable: false,
|
|
48
|
+
});
|
|
49
|
+
return { response, socket };
|
|
50
|
+
}
|
|
51
|
+
function handleWebSocketUpgrade(req, response) {
|
|
52
|
+
const ws = response[kResponseWs];
|
|
53
|
+
if (!ws)
|
|
54
|
+
throw new TypeError('Response not created by upgradeWebSocket()');
|
|
55
|
+
// Create a one time use WebSocketServer to handle the upgrade
|
|
56
|
+
const wss = new ws_1.WebSocketServer({
|
|
57
|
+
autoPong: true,
|
|
58
|
+
noServer: true,
|
|
59
|
+
clientTracking: false,
|
|
60
|
+
perMessageDeflate: true,
|
|
61
|
+
// @ts-expect-error
|
|
62
|
+
WebSocket: function () {
|
|
63
|
+
// Return the websocket that was created earlier instead of a new instance
|
|
64
|
+
return ws;
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
// Apply headers that might have been set on the response object during
|
|
68
|
+
// handling. This will be called during wss.handleUpgrade().
|
|
69
|
+
wss.on('headers', (headers) => {
|
|
70
|
+
for (const [name, value] of response.headers) {
|
|
71
|
+
headers.push(`${name}: ${value}`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
wss.handleUpgrade(req, req.socket, Buffer.alloc(0), (_socket) => {
|
|
75
|
+
// @TODO find a way to properly "close" the _socket when the server is
|
|
76
|
+
// shutting down (might require replacing http-terminator with a local
|
|
77
|
+
// implementation)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function sendResponse(req, res, response) {
|
|
81
|
+
// Invalid usage
|
|
82
|
+
if (res.headersSent) {
|
|
83
|
+
throw new TypeError('Response has already been sent');
|
|
84
|
+
}
|
|
85
|
+
if (response.status === 101) {
|
|
86
|
+
return handleWebSocketUpgrade(req, response);
|
|
87
|
+
}
|
|
88
|
+
res.statusCode = response.status;
|
|
89
|
+
res.statusMessage = response.statusText;
|
|
90
|
+
for (const [key, value] of response.headers) {
|
|
91
|
+
res.appendHeader(key, value);
|
|
92
|
+
}
|
|
93
|
+
if (response.body != null && req.method !== 'HEAD') {
|
|
94
|
+
const stream = node_stream_1.Readable.fromWeb(response.body);
|
|
95
|
+
await (0, promises_1.pipeline)(stream, res);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
await response.body?.cancel();
|
|
99
|
+
res.end();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function toRequest(req) {
|
|
103
|
+
const host = req.headers.host ?? req.socket?.localAddress ?? 'localhost';
|
|
104
|
+
const isEncrypted = req.socket.encrypted === true;
|
|
105
|
+
const protocol = isEncrypted ? 'https' : 'http';
|
|
106
|
+
const url = new URL(req.url ?? '/', `${protocol}://${host}`);
|
|
107
|
+
const headers = toHeaders(req.headers);
|
|
108
|
+
const body = toBody(req);
|
|
109
|
+
const abortController = new AbortController();
|
|
110
|
+
const abort = (err) => abortController.abort(err);
|
|
111
|
+
req.on('close', abort);
|
|
112
|
+
req.on('error', abort);
|
|
113
|
+
req.on('end', abort);
|
|
114
|
+
abortController.signal.addEventListener('abort', () => {
|
|
115
|
+
req.off('close', abort);
|
|
116
|
+
req.off('error', abort);
|
|
117
|
+
req.off('end', abort);
|
|
118
|
+
}, { once: true });
|
|
119
|
+
return new Request(url, {
|
|
120
|
+
signal: abortController.signal,
|
|
121
|
+
method: req.method,
|
|
122
|
+
headers,
|
|
123
|
+
body,
|
|
124
|
+
referrer: headers.get('referrer') ?? headers.get('referer') ?? undefined,
|
|
125
|
+
redirect: 'manual',
|
|
126
|
+
// @ts-expect-error
|
|
127
|
+
duplex: body ? 'half' : undefined,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function toHeaders(headers) {
|
|
131
|
+
const result = new Headers();
|
|
132
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
133
|
+
if (value === undefined)
|
|
134
|
+
continue;
|
|
135
|
+
if (Array.isArray(value)) {
|
|
136
|
+
for (const v of value)
|
|
137
|
+
result.append(key, v);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
result.set(key, value);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
function toBody(req) {
|
|
146
|
+
if (req.method === 'GET' ||
|
|
147
|
+
req.method === 'HEAD' ||
|
|
148
|
+
req.method === 'OPTIONS') {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
if (req.headers['content-type'] == null &&
|
|
152
|
+
req.headers['transfer-encoding'] == null &&
|
|
153
|
+
req.headers['content-length'] == null) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return node_stream_1.Readable.toWeb(req);
|
|
157
|
+
}
|
|
158
|
+
async function handleRequest(req, res, handlerFn) {
|
|
159
|
+
const request = toRequest(req);
|
|
160
|
+
const info = toConnectionInfo(req);
|
|
161
|
+
const response = await handlerFn(request, info);
|
|
162
|
+
await sendResponse(req, res, response);
|
|
163
|
+
}
|
|
164
|
+
function toConnectionInfo(req) {
|
|
165
|
+
const { socket } = req;
|
|
166
|
+
return {
|
|
167
|
+
localAddr: socket.localAddress != null
|
|
168
|
+
? {
|
|
169
|
+
hostname: socket.localAddress,
|
|
170
|
+
port: socket.localPort,
|
|
171
|
+
transport: 'tcp',
|
|
172
|
+
}
|
|
173
|
+
: undefined,
|
|
174
|
+
remoteAddr: socket.remoteAddress != null
|
|
175
|
+
? {
|
|
176
|
+
hostname: socket.remoteAddress,
|
|
177
|
+
port: socket.remotePort,
|
|
178
|
+
transport: 'tcp',
|
|
179
|
+
}
|
|
180
|
+
: undefined,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function toRequestListener(handlerFn) {
|
|
184
|
+
return ((req, res, next) => {
|
|
185
|
+
handleRequest(req, res, handlerFn).catch((err) => {
|
|
186
|
+
if (next)
|
|
187
|
+
next(err);
|
|
188
|
+
else {
|
|
189
|
+
if (!res.headersSent) {
|
|
190
|
+
res.statusCode = 500;
|
|
191
|
+
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
192
|
+
res.end('Internal Server Error');
|
|
193
|
+
}
|
|
194
|
+
else if (!res.writableEnded) {
|
|
195
|
+
res.destroy();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function createServer(handler, options = {}) {
|
|
202
|
+
const handlerFn = typeof handler === 'function' ? handler : handler.handle.bind(handler);
|
|
203
|
+
const listener = toRequestListener(handlerFn);
|
|
204
|
+
const server = (0, node_http_1.createServer)(options, listener);
|
|
205
|
+
const terminator = (0, http_terminator_1.createHttpTerminator)({
|
|
206
|
+
server: server,
|
|
207
|
+
gracefulTerminationTimeout: options?.gracefulTerminationTimeout,
|
|
208
|
+
});
|
|
209
|
+
const terminate = async function terminate() {
|
|
210
|
+
if (this !== server) {
|
|
211
|
+
throw new TypeError('Server.terminate called with incorrect context');
|
|
212
|
+
}
|
|
213
|
+
// @TODO properly close all active WebSocket connections
|
|
214
|
+
return terminator.terminate();
|
|
215
|
+
};
|
|
216
|
+
Object.defineProperty(server, 'terminate', {
|
|
217
|
+
value: terminate,
|
|
218
|
+
enumerable: false,
|
|
219
|
+
configurable: false,
|
|
220
|
+
writable: false,
|
|
221
|
+
});
|
|
222
|
+
Object.defineProperty(server, Symbol.asyncDispose, {
|
|
223
|
+
value: terminate,
|
|
224
|
+
enumerable: false,
|
|
225
|
+
configurable: false,
|
|
226
|
+
writable: false,
|
|
227
|
+
});
|
|
228
|
+
return server;
|
|
229
|
+
}
|
|
230
|
+
async function serve(handler, options) {
|
|
231
|
+
const server = createServer(handler, options);
|
|
232
|
+
server.listen(options);
|
|
233
|
+
await (0, node_events_1.once)(server, 'listening');
|
|
234
|
+
return server;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=nodejs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodejs.js","sourceRoot":"","sources":["../src/nodejs.ts"],"names":[],"mappings":"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","sourcesContent":["import { once } from 'node:events'\nimport {\n IncomingHttpHeaders,\n IncomingMessage,\n RequestListener,\n Server as HttpServer,\n ServerOptions,\n ServerResponse,\n createServer as createHttpServer,\n} from 'node:http'\nimport { ListenOptions } from 'node:net'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport { createHttpTerminator } from 'http-terminator'\nimport { WebSocket as WebSocketPonyfill, WebSocketServer } from 'ws'\n\n// @ts-expect-error\nSymbol.asyncDispose ??= Symbol.for('Symbol.asyncDispose')\n\nconst kResponseWs = Symbol.for('@atproto/lex-server:WebSocket')\n\nfunction isUpgradeRequest(request: Request, upgrade: string): boolean {\n return (\n request.method === 'GET' &&\n request.headers.get('connection')?.toLowerCase() === 'upgrade' &&\n request.headers.get('upgrade')?.toLowerCase() === upgrade\n )\n}\n\nexport function upgradeWebSocket(request: Request): {\n response: Response\n socket: WebSocket\n} {\n if (!isUpgradeRequest(request, 'websocket')) {\n throw new TypeError('upgradeWebSocket() expects a WebSocket upgrade')\n }\n\n // Placeholder response for WebSocket upgrade. The actual handling will happen\n // through the handleWebSocketUpgrade function. Headers set on the response\n // will be applied during the upgrade.\n const response = new Response(null, { status: 200 })\n\n // The Response constructor does not allow setting status 101, so we\n // define it directly. The purpose of this response is just to signal\n // that an upgrade is needed, and to carry any headers.\n Object.defineProperty(response, 'status', {\n value: 101,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n // @ts-expect-error\n const socket: WebSocket = new WebSocketPonyfill(null, undefined, {\n autoPong: true,\n })\n\n // Attach the WebSocket to the response for later retrieval\n Object.defineProperty(response, kResponseWs, {\n value: socket,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n return { response, socket }\n}\n\nfunction handleWebSocketUpgrade(\n req: IncomingMessage,\n response: Response,\n): void {\n const ws = (response as { [kResponseWs]?: WebSocketPonyfill })[kResponseWs]\n if (!ws) throw new TypeError('Response not created by upgradeWebSocket()')\n\n // Create a one time use WebSocketServer to handle the upgrade\n const wss = new WebSocketServer({\n autoPong: true,\n noServer: true,\n clientTracking: false,\n perMessageDeflate: true,\n // @ts-expect-error\n WebSocket: function () {\n // Return the websocket that was created earlier instead of a new instance\n return ws\n },\n })\n\n // Apply headers that might have been set on the response object during\n // handling. This will be called during wss.handleUpgrade().\n wss.on('headers', (headers) => {\n for (const [name, value] of response.headers) {\n headers.push(`${name}: ${value}`)\n }\n })\n\n wss.handleUpgrade(req, req.socket, Buffer.alloc(0), (_socket) => {\n // @TODO find a way to properly \"close\" the _socket when the server is\n // shutting down (might require replacing http-terminator with a local\n // implementation)\n })\n}\n\nasync function sendResponse(\n req: IncomingMessage,\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n // Invalid usage\n if (res.headersSent) {\n throw new TypeError('Response has already been sent')\n }\n\n if (response.status === 101) {\n return handleWebSocketUpgrade(req, response)\n }\n\n res.statusCode = response.status\n res.statusMessage = response.statusText\n\n for (const [key, value] of response.headers) {\n res.appendHeader(key, value)\n }\n\n if (response.body != null && req.method !== 'HEAD') {\n const stream = Readable.fromWeb(response.body as any)\n await pipeline(stream, res)\n } else {\n await response.body?.cancel()\n res.end()\n }\n}\n\nfunction toRequest(req: IncomingMessage): Request {\n const host = req.headers.host ?? req.socket?.localAddress ?? 'localhost'\n const isEncrypted = (req.socket as any).encrypted === true\n const protocol = isEncrypted ? 'https' : 'http'\n const url = new URL(req.url ?? '/', `${protocol}://${host}`)\n const headers = toHeaders(req.headers)\n const body = toBody(req)\n\n const abortController = new AbortController()\n const abort = (err?: Error) => abortController.abort(err)\n\n req.on('close', abort)\n req.on('error', abort)\n req.on('end', abort)\n\n abortController.signal.addEventListener(\n 'abort',\n () => {\n req.off('close', abort)\n req.off('error', abort)\n req.off('end', abort)\n },\n { once: true },\n )\n\n return new Request(url, {\n signal: abortController.signal,\n method: req.method,\n headers,\n body,\n referrer: headers.get('referrer') ?? headers.get('referer') ?? undefined,\n redirect: 'manual',\n // @ts-expect-error\n duplex: body ? 'half' : undefined,\n })\n}\n\nfunction toHeaders(headers: IncomingHttpHeaders): Headers {\n const result = new Headers()\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n for (const v of value) result.append(key, v)\n } else {\n result.set(key, value)\n }\n }\n return result\n}\n\nfunction toBody(req: IncomingMessage): null | ReadableStream<Uint8Array> {\n if (\n req.method === 'GET' ||\n req.method === 'HEAD' ||\n req.method === 'OPTIONS'\n ) {\n return null\n }\n\n if (\n req.headers['content-type'] == null &&\n req.headers['transfer-encoding'] == null &&\n req.headers['content-length'] == null\n ) {\n return null\n }\n\n return Readable.toWeb(req) as ReadableStream<Uint8Array>\n}\n\nexport type NetAddr = {\n hostname: string\n port: number\n transport: 'tcp'\n}\n\nexport type NodeConnectionInfo = {\n localAddr?: NetAddr\n remoteAddr?: NetAddr\n}\n\nexport interface HandlerFunction {\n (req: Request, info: NodeConnectionInfo): Response | Promise<Response>\n}\n\nexport interface HandlerObject {\n handle: HandlerFunction\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n handlerFn: HandlerFunction,\n) {\n const request = toRequest(req)\n const info = toConnectionInfo(req)\n const response = await handlerFn(request, info)\n await sendResponse(req, res, response)\n}\n\nfunction toConnectionInfo(req: IncomingMessage): NodeConnectionInfo {\n const { socket } = req\n return {\n localAddr:\n socket.localAddress != null\n ? {\n hostname: socket.localAddress,\n port: socket.localPort!,\n transport: 'tcp',\n }\n : undefined,\n remoteAddr:\n socket.remoteAddress != null\n ? {\n hostname: socket.remoteAddress,\n port: socket.remotePort!,\n transport: 'tcp',\n }\n : undefined,\n }\n}\n\nexport function toRequestListener<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(handlerFn: HandlerFunction) {\n return ((\n req: InstanceType<Request>,\n res: InstanceType<Response> & { req: InstanceType<Request> },\n next?: (err?: unknown) => void,\n ): void => {\n handleRequest(req, res, handlerFn).catch((err) => {\n if (next) next(err)\n else {\n if (!res.headersSent) {\n res.statusCode = 500\n res.setHeader('content-type', 'text/plain; charset=utf-8')\n res.end('Internal Server Error')\n } else if (!res.writableEnded) {\n res.destroy()\n }\n }\n })\n }) satisfies RequestListener<Request, Response>\n}\n\nexport type CreateServerOptions<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> = ServerOptions<Request, Response> & {\n gracefulTerminationTimeout?: number\n}\n\nexport interface Server<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> extends HttpServer<Request, Response>,\n AsyncDisposable {\n terminate(): Promise<void>\n [Symbol.asyncDispose](): Promise<void>\n}\n\nexport function createServer<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(\n handler: HandlerFunction | HandlerObject,\n options: CreateServerOptions<Request, Response> = {},\n): Server<Request, Response> {\n const handlerFn =\n typeof handler === 'function' ? handler : handler.handle.bind(handler)\n\n const listener = toRequestListener(handlerFn)\n const server = createHttpServer(options, listener)\n\n const terminator = createHttpTerminator({\n server: server as HttpServer,\n gracefulTerminationTimeout: options?.gracefulTerminationTimeout,\n })\n\n const terminate = async function terminate(this: Server<Request, Response>) {\n if (this !== server) {\n throw new TypeError('Server.terminate called with incorrect context')\n }\n // @TODO properly close all active WebSocket connections\n return terminator.terminate()\n }\n\n Object.defineProperty(server, 'terminate', {\n value: terminate,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n Object.defineProperty(server, Symbol.asyncDispose, {\n value: terminate,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n return server as Server<Request, Response>\n}\n\nexport type StartServerOptions<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> = ListenOptions & CreateServerOptions<Request, Response>\n\nexport async function serve<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(\n handler: HandlerFunction | HandlerObject,\n options?: StartServerOptions<Request, Response>,\n): Promise<Server<Request, Response>> {\n const server = createServer(handler, options)\n server.listen(options)\n await once(server, 'listening')\n return server\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscripotion.d.ts","sourceRoot":"","sources":["../src/subscripotion.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
3
|
+
/* eslint-disable n/no-extraneous-import */
|
|
4
|
+
/* eslint-disable import/no-unresolved */
|
|
5
|
+
/* eslint-env node */
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const lex_1 = require("@atproto/lex");
|
|
8
|
+
const index_js_1 = require("./index.js");
|
|
9
|
+
const nodejs_js_1 = require("./nodejs.js");
|
|
10
|
+
var com;
|
|
11
|
+
(function (com) {
|
|
12
|
+
let example;
|
|
13
|
+
(function (example) {
|
|
14
|
+
let echo;
|
|
15
|
+
(function (echo) {
|
|
16
|
+
echo.nsid = 'com.example.echo';
|
|
17
|
+
echo.message = lex_1.l.typedObject(echo.nsid, 'message', lex_1.l.object({
|
|
18
|
+
message: lex_1.l.string(),
|
|
19
|
+
}));
|
|
20
|
+
echo.main = lex_1.l.subscription(echo.nsid, lex_1.l.params({
|
|
21
|
+
message: lex_1.l.string({ minLength: 1 }),
|
|
22
|
+
interval: lex_1.l.optional(lex_1.l.integer({ minimum: 0, default: 500 })),
|
|
23
|
+
}), lex_1.l.typedUnion([lex_1.l.typedRef(() => echo.message)], false));
|
|
24
|
+
})(echo = example.echo || (example.echo = {}));
|
|
25
|
+
})(example = com.example || (com.example = {}));
|
|
26
|
+
})(com || (com = {}));
|
|
27
|
+
const router = new index_js_1.LexRouter({ upgradeWebSocket: nodejs_js_1.upgradeWebSocket })
|
|
28
|
+
//
|
|
29
|
+
.add(com.example.echo, async function* ({ params: { interval, message } }) {
|
|
30
|
+
while (true) {
|
|
31
|
+
yield com.example.echo.message.$build({ message });
|
|
32
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
(0, nodejs_js_1.serve)(router, { port: 8080, host: '0.0.0.0' });
|
|
36
|
+
//# sourceMappingURL=subscripotion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscripotion.js","sourceRoot":"","sources":["../src/subscripotion.ts"],"names":[],"mappings":";AAAA,oDAAoD;AACpD,2CAA2C;AAC3C,yCAAyC;AACzC,qBAAqB;;AAErB,sCAAgC;AAChC,yCAAsC;AACtC,2CAAqD;AAErD,IAAU,GAAG,CAuBZ;AAvBD,WAAU,GAAG;IACX,IAAiB,OAAO,CAqBvB;IArBD,WAAiB,OAAO;QACtB,IAAiB,IAAI,CAmBpB;QAnBD,WAAiB,IAAI;YACN,SAAI,GAAG,kBAAkB,CAAA;YAEzB,YAAO,GAAG,OAAC,CAAC,WAAW,CAClC,KAAA,IAAI,EACJ,SAAS,EACT,OAAC,CAAC,MAAM,CAAC;gBACP,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;aACpB,CAAC,CACH,CAAA;YAEY,SAAI,GAAG,OAAC,CAAC,YAAY,CAChC,KAAA,IAAI,EACJ,OAAC,CAAC,MAAM,CAAC;gBACP,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;gBACnC,QAAQ,EAAE,OAAC,CAAC,QAAQ,CAAC,OAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;aAC9D,CAAC,EACF,OAAC,CAAC,UAAU,CAAC,CAAC,OAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAA,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CACjD,CAAA;QACH,CAAC,EAnBgB,IAAI,GAAJ,YAAI,KAAJ,YAAI,QAmBpB;IACH,CAAC,EArBgB,OAAO,GAAP,WAAO,KAAP,WAAO,QAqBvB;AACH,CAAC,EAvBS,GAAG,KAAH,GAAG,QAuBZ;AAED,MAAM,MAAM,GAAG,IAAI,oBAAS,CAAC,EAAE,gBAAgB,EAAhB,4BAAgB,EAAE,CAAC;IAChD,EAAE;KACD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IACvE,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;QAClD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,IAAA,iBAAK,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA","sourcesContent":["/* eslint-disable @typescript-eslint/no-namespace */\n/* eslint-disable n/no-extraneous-import */\n/* eslint-disable import/no-unresolved */\n/* eslint-env node */\n\nimport { l } from '@atproto/lex'\nimport { LexRouter } from './index.js'\nimport { serve, upgradeWebSocket } from './nodejs.js'\n\nnamespace com {\n export namespace example {\n export namespace echo {\n export const nsid = 'com.example.echo'\n\n export const message = l.typedObject(\n nsid,\n 'message',\n l.object({\n message: l.string(),\n }),\n )\n\n export const main = l.subscription(\n nsid,\n l.params({\n message: l.string({ minLength: 1 }),\n interval: l.optional(l.integer({ minimum: 0, default: 500 })),\n }),\n l.typedUnion([l.typedRef(() => message)], false),\n )\n }\n }\n}\n\nconst router = new LexRouter({ upgradeWebSocket })\n //\n .add(com.example.echo, async function* ({ params: { interval, message } }) {\n while (true) {\n yield com.example.echo.message.$build({ message })\n await new Promise((resolve) => setTimeout(resolve, interval))\n }\n })\n\nserve(router, { port: 8080, host: '0.0.0.0' })\n"]}
|
package/dist/test.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.d.mts","sourceRoot":"","sources":["../src/test.mjs"],"names":[],"mappings":""}
|
package/dist/test.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-env node */
|
|
3
|
+
import { l } from '@atproto/lex';
|
|
4
|
+
import { LexRouter } from './lex-server.js';
|
|
5
|
+
import { startServer, upgradeWebSocket } from './nodejs.js';
|
|
6
|
+
const echoMessage = l.typedObject('com.example.echo', 'echoMessage', l.object({
|
|
7
|
+
message: l.string(),
|
|
8
|
+
}));
|
|
9
|
+
const sub = l.subscription('com.example.echo', l.params({
|
|
10
|
+
message: l.string({ minLength: 1 }),
|
|
11
|
+
}), l.typedUnion([
|
|
12
|
+
//
|
|
13
|
+
l.typedRef(() => echoMessage),
|
|
14
|
+
], false));
|
|
15
|
+
const router = new LexRouter({
|
|
16
|
+
upgradeWebSocket,
|
|
17
|
+
onMethodNotFound: () => {
|
|
18
|
+
return new Response('<h1>404 Not Found</h1>', {
|
|
19
|
+
status: 404,
|
|
20
|
+
headers: { 'Content-Type': 'text/html' },
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
//
|
|
25
|
+
.add(sub, async function* ({ params: { message } }) {
|
|
26
|
+
try {
|
|
27
|
+
while (true) {
|
|
28
|
+
/** @type {l.TypedObject} */
|
|
29
|
+
const item = {
|
|
30
|
+
$type: 'com.example.echo#ff',
|
|
31
|
+
// @ts-expect-error
|
|
32
|
+
message,
|
|
33
|
+
};
|
|
34
|
+
yield item;
|
|
35
|
+
yield echoMessage.$build({ message });
|
|
36
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
console.log('Subscription ended');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
startServer(router, {
|
|
44
|
+
port: 8080,
|
|
45
|
+
onError: (err) => {
|
|
46
|
+
console.error('Server error:', err);
|
|
47
|
+
},
|
|
48
|
+
}).then((server) => {
|
|
49
|
+
const { port } = /** @type {import('net').AddressInfo} */ (server.address());
|
|
50
|
+
console.log(`Server is running on http://localhost:${port}`);
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.mjs","sourceRoot":"","sources":["../src/test.mjs"],"names":[],"mappings":";AAAA,qBAAqB;AAErB,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE3D,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAC/B,kBAAkB,EAClB,aAAa,EACb,CAAC,CAAC,MAAM,CAAC;IACP,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;CACpB,CAAC,CACH,CAAA;AAED,MAAM,GAAG,GAAG,CAAC,CAAC,YAAY,CACxB,kBAAkB,EAClB,CAAC,CAAC,MAAM,CAAC;IACP,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;CACpC,CAAC,EACF,CAAC,CAAC,UAAU,CACV;IACE,EAAE;IACF,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC;CAC9B,EACD,KAAK,CACN,CACF,CAAA;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,gBAAgB;IAChB,gBAAgB,EAAE,GAAG,EAAE;QACrB,OAAO,IAAI,QAAQ,CAAC,wBAAwB,EAAE;YAC5C,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE;SACzC,CAAC,CAAA;IACJ,CAAC;CACF,CAAC;IACA,EAAE;KACD,GAAG,CAAC,GAAG,EAAE,KAAK,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,4BAA4B;YAC5B,MAAM,IAAI,GAAG;gBACX,KAAK,EAAE,qBAAqB;gBAC5B,mBAAmB;gBACnB,OAAO;aACR,CAAA;YAED,MAAM,IAAI,CAAA;YACV,MAAM,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;YACrC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;QAC1D,CAAC;IACH,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IACnC,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,WAAW,CAAC,MAAM,EAAE;IAClB,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACf,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;IACrC,CAAC;CACF,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;IACjB,MAAM,EAAE,IAAI,EAAE,GAAG,wCAAwC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IAC5E,OAAO,CAAC,GAAG,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAA;AAC9D,CAAC,CAAC,CAAA","sourcesContent":["/* eslint-env node */\n\nimport { l } from '@atproto/lex'\nimport { LexRouter } from './lex-server.js'\nimport { startServer, upgradeWebSocket } from './nodejs.js'\n\nconst echoMessage = l.typedObject(\n 'com.example.echo',\n 'echoMessage',\n l.object({\n message: l.string(),\n }),\n)\n\nconst sub = l.subscription(\n 'com.example.echo',\n l.params({\n message: l.string({ minLength: 1 }),\n }),\n l.typedUnion(\n [\n //\n l.typedRef(() => echoMessage),\n ],\n false,\n ),\n)\n\nconst router = new LexRouter({\n upgradeWebSocket,\n onMethodNotFound: () => {\n return new Response('<h1>404 Not Found</h1>', {\n status: 404,\n headers: { 'Content-Type': 'text/html' },\n })\n },\n})\n //\n .add(sub, async function* ({ params: { message } }) {\n try {\n while (true) {\n /** @type {l.TypedObject} */\n const item = {\n $type: 'com.example.echo#ff',\n // @ts-expect-error\n message,\n }\n\n yield item\n yield echoMessage.$build({ message })\n await new Promise((resolve) => setTimeout(resolve, 500))\n }\n } finally {\n console.log('Subscription ended')\n }\n })\n\nstartServer(router, {\n port: 8080,\n onError: (err) => {\n console.error('Server error:', err)\n },\n}).then((server) => {\n const { port } = /** @type {import('net').AddressInfo} */ (server.address())\n console.log(`Server is running on http://localhost:${port}`)\n})\n"]}
|
package/nodejs.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atproto/lex-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Request router for Atproto Lexicon protocols and schemas",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"atproto",
|
|
8
|
+
"lexicon",
|
|
9
|
+
"router",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://atproto.com",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/bluesky-social/atproto",
|
|
16
|
+
"directory": "packages/lex/lex-server"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"./src",
|
|
20
|
+
"./tsconfig.build.json",
|
|
21
|
+
"./tsconfig.tests.json",
|
|
22
|
+
"./tsconfig.json",
|
|
23
|
+
"./dist",
|
|
24
|
+
"./nodejs.js",
|
|
25
|
+
"./CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"type": "commonjs",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"browser": "./dist/index.js",
|
|
35
|
+
"import": "./dist/index.js",
|
|
36
|
+
"require": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./nodejs": {
|
|
39
|
+
"types": "./dist/nodejs.d.ts",
|
|
40
|
+
"import": "./dist/nodejs.js",
|
|
41
|
+
"require": "./dist/nodejs.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"http-terminator": "^3.2.0",
|
|
46
|
+
"tslib": "^2.8.1",
|
|
47
|
+
"ws": "^8.18.3",
|
|
48
|
+
"@atproto/lex-cbor": "0.0.5",
|
|
49
|
+
"@atproto/lex-data": "0.0.5",
|
|
50
|
+
"@atproto/lex-schema": "0.0.6",
|
|
51
|
+
"@atproto/lex-json": "0.0.5"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/ws": "^8.18.1",
|
|
55
|
+
"@vitest/coverage-v8": "4.0.16",
|
|
56
|
+
"vitest": "^4.0.16",
|
|
57
|
+
"@atproto/lex": "0.0.8",
|
|
58
|
+
"@atproto/lex-client": "0.0.6"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsc --build tsconfig.build.json",
|
|
62
|
+
"test": "vitest run"
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { LexError, LexErrorCode } from '@atproto/lex-data'
|
|
2
|
+
import {
|
|
3
|
+
WWWAuthenticate,
|
|
4
|
+
formatWWWAuthenticateHeader,
|
|
5
|
+
} from './lib/www-authenticate.js'
|
|
6
|
+
|
|
7
|
+
export type { WWWAuthenticate }
|
|
8
|
+
|
|
9
|
+
export class LexServerAuthError<
|
|
10
|
+
N extends LexErrorCode = LexErrorCode,
|
|
11
|
+
> extends LexError<N> {
|
|
12
|
+
name = 'LexServerAuthError'
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
error: N,
|
|
16
|
+
message: string,
|
|
17
|
+
readonly wwwAuthenticate: WWWAuthenticate = {},
|
|
18
|
+
options?: ErrorOptions,
|
|
19
|
+
) {
|
|
20
|
+
super(error, message, options)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get wwwAuthenticateHeader(): string {
|
|
24
|
+
return formatWWWAuthenticateHeader(this.wwwAuthenticate)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
toJSON() {
|
|
28
|
+
const { cause } = this
|
|
29
|
+
return cause instanceof LexError ? cause.toJSON() : super.toJSON()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toResponse(): Response {
|
|
33
|
+
const { wwwAuthenticateHeader } = this
|
|
34
|
+
|
|
35
|
+
const headers = wwwAuthenticateHeader
|
|
36
|
+
? new Headers({
|
|
37
|
+
'WWW-Authenticate': wwwAuthenticateHeader,
|
|
38
|
+
'Access-Control-Expose-Headers': 'WWW-Authenticate', // CORS
|
|
39
|
+
})
|
|
40
|
+
: undefined
|
|
41
|
+
|
|
42
|
+
return Response.json(this.toJSON(), { status: 401, headers })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static from(
|
|
46
|
+
cause: LexError,
|
|
47
|
+
wwwAuthenticate?: WWWAuthenticate,
|
|
48
|
+
): LexServerAuthError {
|
|
49
|
+
if (cause instanceof LexServerAuthError) return cause
|
|
50
|
+
return new LexServerAuthError(cause.error, cause.message, wwwAuthenticate, {
|
|
51
|
+
cause,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|