@atcute/xrpc-server 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/jwt.d.ts +10 -15
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/auth/jwt.js.map +1 -1
- package/dist/main/index.d.ts +1 -0
- package/dist/main/index.d.ts.map +1 -1
- package/dist/main/index.js +1 -0
- package/dist/main/index.js.map +1 -1
- package/dist/main/router.d.ts +12 -3
- package/dist/main/router.d.ts.map +1 -1
- package/dist/main/router.js +87 -7
- package/dist/main/router.js.map +1 -1
- package/dist/main/types/operation.d.ts +16 -1
- package/dist/main/types/operation.d.ts.map +1 -1
- package/dist/main/types/websocket.d.ts +10 -0
- package/dist/main/types/websocket.d.ts.map +1 -0
- package/dist/main/types/websocket.js +2 -0
- package/dist/main/types/websocket.js.map +1 -0
- package/dist/main/utils/event-emitter.d.ts +37 -0
- package/dist/main/utils/event-emitter.d.ts.map +1 -0
- package/dist/main/utils/event-emitter.js +96 -0
- package/dist/main/utils/event-emitter.js.map +1 -0
- package/dist/main/utils/frames.d.ts +5 -0
- package/dist/main/utils/frames.d.ts.map +1 -0
- package/dist/main/utils/frames.js +45 -0
- package/dist/main/utils/frames.js.map +1 -0
- package/dist/main/utils/websocket-mock.d.ts +24 -0
- package/dist/main/utils/websocket-mock.d.ts.map +1 -0
- package/dist/main/utils/websocket-mock.js +71 -0
- package/dist/main/utils/websocket-mock.js.map +1 -0
- package/dist/main/xrpc-error.d.ts +11 -0
- package/dist/main/xrpc-error.d.ts.map +1 -1
- package/dist/main/xrpc-error.js +11 -0
- package/dist/main/xrpc-error.js.map +1 -1
- package/lib/auth/jwt.ts +16 -5
- package/lib/main/index.ts +2 -0
- package/lib/main/router.ts +131 -10
- package/lib/main/types/operation.ts +33 -0
- package/lib/main/types/websocket.ts +14 -0
- package/lib/main/utils/event-emitter.ts +116 -0
- package/lib/main/utils/frames.ts +71 -0
- package/lib/main/utils/websocket-mock.ts +111 -0
- package/lib/main/xrpc-error.ts +20 -0
- package/package.json +6 -4
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { EventEmitter } from './event-emitter.js';
|
|
3
|
+
export class MockWebSocketAdapter {
|
|
4
|
+
#context = new AsyncLocalStorage();
|
|
5
|
+
upgrade(_request, handler) {
|
|
6
|
+
const ctx = this.#context.getStore();
|
|
7
|
+
if (!ctx) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
ctx.handler = handler;
|
|
11
|
+
return new Response(null);
|
|
12
|
+
}
|
|
13
|
+
attach(router) {
|
|
14
|
+
return {
|
|
15
|
+
subscribe: async (url) => {
|
|
16
|
+
const ctx = {
|
|
17
|
+
handler: null,
|
|
18
|
+
};
|
|
19
|
+
await this.#context.run(ctx, async () => {
|
|
20
|
+
const urlp = new URL(url, 'http://localhost');
|
|
21
|
+
const request = new Request(urlp, {
|
|
22
|
+
headers: {
|
|
23
|
+
upgrade: 'websocket',
|
|
24
|
+
connection: 'upgrade',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const response = await router.fetch(request);
|
|
28
|
+
return response;
|
|
29
|
+
});
|
|
30
|
+
if (!ctx.handler) {
|
|
31
|
+
throw new Error(`WebSocket upgrade succeeded but no handler was set`);
|
|
32
|
+
}
|
|
33
|
+
const events = new EventEmitter();
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const signal = controller.signal;
|
|
36
|
+
const connection = {
|
|
37
|
+
signal: signal,
|
|
38
|
+
send(data) {
|
|
39
|
+
events.emit('message', data);
|
|
40
|
+
},
|
|
41
|
+
close(code = 1000, reason = '') {
|
|
42
|
+
if (!signal.aborted) {
|
|
43
|
+
events.emit('close', { code, reason, wasClean: true });
|
|
44
|
+
controller.abort();
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
{
|
|
49
|
+
const handler = ctx.handler;
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
handler(connection);
|
|
52
|
+
}, 1);
|
|
53
|
+
}
|
|
54
|
+
const client = {
|
|
55
|
+
events,
|
|
56
|
+
dispose() {
|
|
57
|
+
if (!signal.aborted) {
|
|
58
|
+
events.emit('close', { code: 1000, reason: '', wasClean: true });
|
|
59
|
+
controller.abort();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
[Symbol.dispose]() {
|
|
63
|
+
this.dispose();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
return client;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=websocket-mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-mock.js","sourceRoot":"","sources":["../../../lib/main/utils/websocket-mock.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,4 +37,15 @@ export declare class NotEnoughResourcesError extends XRPCError {
|
|
|
37
37
|
export declare class UpstreamTimeoutError extends XRPCError {
|
|
38
38
|
constructor({ status, error, description }?: Partial<XRPCErrorOptions>);
|
|
39
39
|
}
|
|
40
|
+
export interface XRPCSubscriptionErrorOptions {
|
|
41
|
+
closeCode?: number;
|
|
42
|
+
error: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare class XRPCSubscriptionError extends Error {
|
|
46
|
+
readonly closeCode: number;
|
|
47
|
+
readonly error: string;
|
|
48
|
+
readonly description?: string;
|
|
49
|
+
constructor({ closeCode, error, description }: XRPCSubscriptionErrorOptions);
|
|
50
|
+
}
|
|
40
51
|
//# sourceMappingURL=xrpc-error.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xrpc-error.d.ts","sourceRoot":"","sources":["../../lib/main/xrpc-error.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,SAAU,SAAQ,KAAK;IACnC,sBAAsB;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,iBAAiB;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oBAAoB;IACpB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;gBAElB,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,gBAAgB;IAS5D,UAAU,IAAI,QAAQ;CAGtB;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACrC,EAAE,MAAY,EAAE,KAAwB,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGnG;AAED,qBAAa,iBAAkB,SAAQ,SAAS;gBACnC,EACX,MAAY,EACZ,KAAgC,EAChC,WAAW,GACX,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGjC;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAChC,EAAE,MAAY,EAAE,KAAmB,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAG9F;AAED,qBAAa,sBAAuB,SAAQ,SAAS;gBACxC,EAAE,MAAY,EAAE,KAA2B,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGtG;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACrC,EAAE,MAAY,EAAE,KAA6B,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGxG;AAED,qBAAa,oBAAqB,SAAQ,SAAS;gBACtC,EAAE,MAAY,EAAE,KAAyB,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGpG;AAED,qBAAa,uBAAwB,SAAQ,SAAS;gBACzC,EAAE,MAAY,EAAE,KAA4B,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGvG;AAED,qBAAa,oBAAqB,SAAQ,SAAS;gBACtC,EAAE,MAAY,EAAE,KAAyB,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGpG"}
|
|
1
|
+
{"version":3,"file":"xrpc-error.d.ts","sourceRoot":"","sources":["../../lib/main/xrpc-error.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,SAAU,SAAQ,KAAK;IACnC,sBAAsB;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,iBAAiB;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oBAAoB;IACpB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;gBAElB,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,gBAAgB;IAS5D,UAAU,IAAI,QAAQ;CAGtB;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACrC,EAAE,MAAY,EAAE,KAAwB,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGnG;AAED,qBAAa,iBAAkB,SAAQ,SAAS;gBACnC,EACX,MAAY,EACZ,KAAgC,EAChC,WAAW,GACX,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGjC;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAChC,EAAE,MAAY,EAAE,KAAmB,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAG9F;AAED,qBAAa,sBAAuB,SAAQ,SAAS;gBACxC,EAAE,MAAY,EAAE,KAA2B,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGtG;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACrC,EAAE,MAAY,EAAE,KAA6B,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGxG;AAED,qBAAa,oBAAqB,SAAQ,SAAS;gBACtC,EAAE,MAAY,EAAE,KAAyB,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGpG;AAED,qBAAa,uBAAwB,SAAQ,SAAS;gBACzC,EAAE,MAAY,EAAE,KAA4B,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGvG;AAED,qBAAa,oBAAqB,SAAQ,SAAS;gBACtC,EAAE,MAAY,EAAE,KAAyB,EAAE,WAAW,EAAE,GAAE,OAAO,CAAC,gBAAgB,CAAM;CAGpG;AAED,MAAM,WAAW,4BAA4B;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;gBAElB,EAAE,SAAgB,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,4BAA4B;CAOlF"}
|
package/dist/main/xrpc-error.js
CHANGED
|
@@ -55,4 +55,15 @@ export class UpstreamTimeoutError extends XRPCError {
|
|
|
55
55
|
super({ status, error, description });
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
export class XRPCSubscriptionError extends Error {
|
|
59
|
+
closeCode;
|
|
60
|
+
error;
|
|
61
|
+
description;
|
|
62
|
+
constructor({ closeCode = 1008, error, description }) {
|
|
63
|
+
super(`Subscription error: ${error}${description ? ` - ${description}` : ''}`);
|
|
64
|
+
this.closeCode = closeCode;
|
|
65
|
+
this.error = error;
|
|
66
|
+
this.description = description;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
58
69
|
//# sourceMappingURL=xrpc-error.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xrpc-error.js","sourceRoot":"","sources":["../../lib/main/xrpc-error.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,SAAU,SAAQ,KAAK;IACnC,sBAAsB;IACb,MAAM,CAAS;IAExB,iBAAiB;IACR,KAAK,CAAS;IACvB,oBAAoB;IACX,WAAW,CAAU;IAE9B,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAoB;QAC3D,KAAK,CAAC,GAAG,KAAK,MAAM,WAAW,IAAI,2BAA2B,EAAE,CAAC,CAAC;QAElE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAChC,CAAC;IAED,UAAU;QACT,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjG,CAAC;CACD;AAED,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IACjD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,gBAAgB,EAAE,WAAW,KAAgC,EAAE;QAClG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,iBAAkB,SAAQ,SAAS;IAC/C,YAAY,EACX,MAAM,GAAG,GAAG,EACZ,KAAK,GAAG,wBAAwB,EAChC,WAAW,MACmB,EAAE;QAChC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,cAAe,SAAQ,SAAS;IAC5C,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,WAAW,EAAE,WAAW,KAAgC,EAAE;QAC7F,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,sBAAuB,SAAQ,SAAS;IACpD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,mBAAmB,EAAE,WAAW,KAAgC,EAAE;QACrG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IACjD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,qBAAqB,EAAE,WAAW,KAAgC,EAAE;QACvG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IAClD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,iBAAiB,EAAE,WAAW,KAAgC,EAAE;QACnG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,uBAAwB,SAAQ,SAAS;IACrD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,oBAAoB,EAAE,WAAW,KAAgC,EAAE;QACtG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IAClD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,iBAAiB,EAAE,WAAW,KAAgC,EAAE;QACnG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD"}
|
|
1
|
+
{"version":3,"file":"xrpc-error.js","sourceRoot":"","sources":["../../lib/main/xrpc-error.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,SAAU,SAAQ,KAAK;IACnC,sBAAsB;IACb,MAAM,CAAS;IAExB,iBAAiB;IACR,KAAK,CAAS;IACvB,oBAAoB;IACX,WAAW,CAAU;IAE9B,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAoB;QAC3D,KAAK,CAAC,GAAG,KAAK,MAAM,WAAW,IAAI,2BAA2B,EAAE,CAAC,CAAC;QAElE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAChC,CAAC;IAED,UAAU;QACT,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjG,CAAC;CACD;AAED,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IACjD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,gBAAgB,EAAE,WAAW,KAAgC,EAAE;QAClG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,iBAAkB,SAAQ,SAAS;IAC/C,YAAY,EACX,MAAM,GAAG,GAAG,EACZ,KAAK,GAAG,wBAAwB,EAChC,WAAW,MACmB,EAAE;QAChC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,cAAe,SAAQ,SAAS;IAC5C,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,WAAW,EAAE,WAAW,KAAgC,EAAE;QAC7F,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,sBAAuB,SAAQ,SAAS;IACpD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,mBAAmB,EAAE,WAAW,KAAgC,EAAE;QACrG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IACjD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,qBAAqB,EAAE,WAAW,KAAgC,EAAE;QACvG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IAClD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,iBAAiB,EAAE,WAAW,KAAgC,EAAE;QACnG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,uBAAwB,SAAQ,SAAS;IACrD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,oBAAoB,EAAE,WAAW,KAAgC,EAAE;QACtG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAED,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IAClD,YAAY,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,iBAAiB,EAAE,WAAW,KAAgC,EAAE;QACnG,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,CAAC;CACD;AAQD,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACtC,SAAS,CAAS;IAClB,KAAK,CAAS;IACd,WAAW,CAAU;IAE9B,YAAY,EAAE,SAAS,GAAG,IAAI,EAAE,KAAK,EAAE,WAAW,EAAgC;QACjF,KAAK,CAAC,uBAAuB,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAE/E,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAChC,CAAC;CACD"}
|
package/lib/auth/jwt.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as v from '@badrap/valita';
|
|
2
2
|
|
|
3
|
+
import type { Did, Nsid } from '@atcute/lexicons';
|
|
3
4
|
import { isDid, isNsid } from '@atcute/lexicons/syntax';
|
|
4
5
|
import { fromBase64Url } from '@atcute/multibase';
|
|
5
6
|
import { decodeUtf8From, encodeUtf8 } from '@atcute/uint8array';
|
|
@@ -13,14 +14,26 @@ const nsidString = v.string().assert(isNsid, `must be an nsid`);
|
|
|
13
14
|
|
|
14
15
|
const integer = v.number().assert((input) => input >= 0 && Number.isSafeInteger(input), `must be an integer`);
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
export interface JwtHeader {
|
|
18
|
+
typ?: string;
|
|
19
|
+
alg: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const jwtHeader: v.Type<JwtHeader> = v.object({
|
|
17
23
|
typ: v.string().optional(),
|
|
18
24
|
alg: v.string(),
|
|
19
25
|
});
|
|
20
26
|
|
|
21
|
-
export interface
|
|
27
|
+
export interface JwtPayload {
|
|
28
|
+
iss: Did;
|
|
29
|
+
aud: Did;
|
|
30
|
+
exp: number;
|
|
31
|
+
iat?: number;
|
|
32
|
+
lxm?: Nsid;
|
|
33
|
+
jti?: string;
|
|
34
|
+
}
|
|
22
35
|
|
|
23
|
-
const jwtPayload = v
|
|
36
|
+
const jwtPayload: v.Type<JwtPayload> = v
|
|
24
37
|
.object({
|
|
25
38
|
/** issuer */
|
|
26
39
|
iss: didString,
|
|
@@ -40,8 +53,6 @@ const jwtPayload = v
|
|
|
40
53
|
path: ['exp'],
|
|
41
54
|
});
|
|
42
55
|
|
|
43
|
-
export interface JwtPayload extends v.Infer<typeof jwtPayload> {}
|
|
44
|
-
|
|
45
56
|
export interface ParsedJwt {
|
|
46
57
|
header: JwtHeader;
|
|
47
58
|
payload: JwtPayload;
|
package/lib/main/index.ts
CHANGED
package/lib/main/router.ts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
safeParse,
|
|
3
|
+
type XRPCProcedureMetadata,
|
|
4
|
+
type XRPCQueryMetadata,
|
|
5
|
+
type XRPCSubscriptionMetadata,
|
|
6
|
+
} from '@atcute/lexicons/validations';
|
|
2
7
|
|
|
3
8
|
import type { Literal, Promisable } from '../types/misc.js';
|
|
4
9
|
|
|
5
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
ProcedureConfig,
|
|
12
|
+
QueryConfig,
|
|
13
|
+
SubscriptionConfig,
|
|
14
|
+
UnknownOperationContext,
|
|
15
|
+
UnknownSubscriptionContext,
|
|
16
|
+
} from './types/operation.js';
|
|
17
|
+
import type { WebSocketAdapter } from './types/websocket.js';
|
|
18
|
+
import { encodeErrorFrame, encodeMessageFrame, extractMessageType, omitMessageType } from './utils/frames.js';
|
|
6
19
|
import { createAsyncMiddlewareRunner, type Middleware } from './utils/middlewares.js';
|
|
7
20
|
import { constructMimeValidator } from './utils/request-input.js';
|
|
8
21
|
import { constructParamsHandler } from './utils/request-params.js';
|
|
9
22
|
import { invalidRequest, validationError } from './utils/response.js';
|
|
10
|
-
|
|
11
|
-
import { XRPCError } from './xrpc-error.js';
|
|
23
|
+
import { XRPCError, XRPCSubscriptionError } from './xrpc-error.js';
|
|
12
24
|
|
|
13
25
|
type InternalRequestContext = {
|
|
14
26
|
url: URL;
|
|
@@ -26,6 +38,7 @@ export type FetchMiddleware = Middleware<[request: Request], Promise<Response>>;
|
|
|
26
38
|
|
|
27
39
|
export type NotFoundHandler = (request: Request) => Promisable<Response>;
|
|
28
40
|
export type ExceptionHandler = (error: unknown, request: Request) => Promisable<Response>;
|
|
41
|
+
export type SubscriptionExceptionHandler = (error: unknown, request: Request) => void;
|
|
29
42
|
|
|
30
43
|
export const defaultExceptionHandler: ExceptionHandler = (error: unknown) => {
|
|
31
44
|
if (error instanceof XRPCError) {
|
|
@@ -46,16 +59,24 @@ export const defaultNotFoundHandler: NotFoundHandler = () => {
|
|
|
46
59
|
return new Response('Not Found', { status: 404 });
|
|
47
60
|
};
|
|
48
61
|
|
|
62
|
+
export const defaultSubscriptionExceptionHandler: SubscriptionExceptionHandler = (error: unknown) => {
|
|
63
|
+
throw error;
|
|
64
|
+
};
|
|
65
|
+
|
|
49
66
|
export interface XRPCRouterOptions {
|
|
50
67
|
middlewares?: FetchMiddleware[];
|
|
51
68
|
handleNotFound?: NotFoundHandler;
|
|
52
69
|
handleException?: ExceptionHandler;
|
|
70
|
+
handleSubscriptionException?: SubscriptionExceptionHandler;
|
|
71
|
+
websocket?: WebSocketAdapter;
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
export class XRPCRouter {
|
|
56
75
|
#handlers: Record<string, InternalRouteData> = {};
|
|
57
76
|
#handleNotFound: NotFoundHandler;
|
|
58
77
|
#handleException: ExceptionHandler;
|
|
78
|
+
#handleSubscriptionException: SubscriptionExceptionHandler;
|
|
79
|
+
#websocket?: WebSocketAdapter;
|
|
59
80
|
|
|
60
81
|
fetch: (request: Request) => Promise<Response>;
|
|
61
82
|
|
|
@@ -63,12 +84,16 @@ export class XRPCRouter {
|
|
|
63
84
|
middlewares = [],
|
|
64
85
|
handleException = defaultExceptionHandler,
|
|
65
86
|
handleNotFound = defaultNotFoundHandler,
|
|
87
|
+
handleSubscriptionException = defaultSubscriptionExceptionHandler,
|
|
88
|
+
websocket,
|
|
66
89
|
}: XRPCRouterOptions = {}) {
|
|
67
90
|
const runner = createAsyncMiddlewareRunner([...middlewares, (request) => this.#dispatch(request)]);
|
|
68
91
|
|
|
69
92
|
this.fetch = (request) => runner(request);
|
|
70
93
|
this.#handleException = handleException;
|
|
71
94
|
this.#handleNotFound = handleNotFound;
|
|
95
|
+
this.#handleSubscriptionException = handleSubscriptionException;
|
|
96
|
+
this.#websocket = websocket;
|
|
72
97
|
}
|
|
73
98
|
|
|
74
99
|
async #dispatch(request: Request): Promise<Response> {
|
|
@@ -80,8 +105,8 @@ export class XRPCRouter {
|
|
|
80
105
|
}
|
|
81
106
|
|
|
82
107
|
const nsid = pathname.slice('/xrpc/'.length);
|
|
83
|
-
const route = this.#handlers[nsid];
|
|
84
108
|
|
|
109
|
+
const route = this.#handlers[nsid];
|
|
85
110
|
if (route === undefined) {
|
|
86
111
|
return this.#handleNotFound(request);
|
|
87
112
|
}
|
|
@@ -105,6 +130,7 @@ export class XRPCRouter {
|
|
|
105
130
|
}
|
|
106
131
|
}
|
|
107
132
|
|
|
133
|
+
/** @deprecated use `addQuery` and `addProcedure` instead */
|
|
108
134
|
add<TQuery extends XRPCQueryMetadata>(query: TQuery, config: QueryConfig<TQuery>): void;
|
|
109
135
|
add<TProcedure extends XRPCProcedureMetadata>(
|
|
110
136
|
procedure: TProcedure,
|
|
@@ -113,15 +139,18 @@ export class XRPCRouter {
|
|
|
113
139
|
add(operation: XRPCQueryMetadata | XRPCProcedureMetadata, config: any): void {
|
|
114
140
|
switch (operation.type) {
|
|
115
141
|
case 'xrpc_query': {
|
|
116
|
-
return this
|
|
142
|
+
return this.addQuery(operation, config);
|
|
117
143
|
}
|
|
118
144
|
case 'xrpc_procedure': {
|
|
119
|
-
return this
|
|
145
|
+
return this.addProcedure(operation, config);
|
|
120
146
|
}
|
|
121
147
|
}
|
|
122
148
|
}
|
|
123
149
|
|
|
124
|
-
|
|
150
|
+
addQuery<TQuery extends XRPCQueryMetadata, TConfig extends QueryConfig<TQuery>>(
|
|
151
|
+
query: TQuery,
|
|
152
|
+
config: TConfig,
|
|
153
|
+
): void {
|
|
125
154
|
const handleParams = query.params ? constructParamsHandler(query.params) : null;
|
|
126
155
|
|
|
127
156
|
const handler = config.handler;
|
|
@@ -158,9 +187,9 @@ export class XRPCRouter {
|
|
|
158
187
|
};
|
|
159
188
|
}
|
|
160
189
|
|
|
161
|
-
|
|
190
|
+
addProcedure<TProcedure extends XRPCProcedureMetadata, TConfig extends ProcedureConfig<TProcedure>>(
|
|
162
191
|
procedure: TProcedure,
|
|
163
|
-
config:
|
|
192
|
+
config: TConfig,
|
|
164
193
|
): void {
|
|
165
194
|
const handleParams = procedure.params ? constructParamsHandler(procedure.params) : null;
|
|
166
195
|
const validateInputType = procedure.input ? constructMimeValidator(procedure.input) : null;
|
|
@@ -236,4 +265,96 @@ export class XRPCRouter {
|
|
|
236
265
|
},
|
|
237
266
|
};
|
|
238
267
|
}
|
|
268
|
+
|
|
269
|
+
addSubscription<
|
|
270
|
+
TSubscription extends XRPCSubscriptionMetadata,
|
|
271
|
+
TConfig extends SubscriptionConfig<TSubscription>,
|
|
272
|
+
>(subscription: TSubscription, config: TConfig): void {
|
|
273
|
+
const websocket = this.#websocket;
|
|
274
|
+
if (websocket === undefined) {
|
|
275
|
+
throw new Error(`WebSocket adapter not configured`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const nsid = subscription.nsid;
|
|
279
|
+
|
|
280
|
+
const handleParams = subscription.params ? constructParamsHandler(subscription.params) : null;
|
|
281
|
+
const handler = config.handler;
|
|
282
|
+
|
|
283
|
+
this.#handlers[nsid] = {
|
|
284
|
+
method: 'GET',
|
|
285
|
+
handler: async ({ request, url }) => {
|
|
286
|
+
{
|
|
287
|
+
const con = request.headers.get('connection');
|
|
288
|
+
const req = request.headers.get('upgrade');
|
|
289
|
+
if (
|
|
290
|
+
con === null ||
|
|
291
|
+
req === null ||
|
|
292
|
+
con.toLowerCase() !== 'upgrade' ||
|
|
293
|
+
req.toLowerCase() !== 'websocket'
|
|
294
|
+
) {
|
|
295
|
+
return invalidRequest(`invalid WebSocket upgrade`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let params: Record<string, Literal | Literal[]>;
|
|
300
|
+
|
|
301
|
+
if (handleParams !== null) {
|
|
302
|
+
const result = handleParams(url.searchParams);
|
|
303
|
+
if (!result.ok) {
|
|
304
|
+
return validationError('params', result);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
params = result.value;
|
|
308
|
+
} else {
|
|
309
|
+
params = {};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const upgrade = await websocket.upgrade(request, async (ws) => {
|
|
313
|
+
const signal = ws.signal;
|
|
314
|
+
|
|
315
|
+
const context: UnknownSubscriptionContext = {
|
|
316
|
+
request: request,
|
|
317
|
+
params: params,
|
|
318
|
+
signal: signal,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
for await (const message of handler(context)) {
|
|
323
|
+
if (signal.aborted) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const type = extractMessageType(message, nsid);
|
|
328
|
+
const body = omitMessageType(message);
|
|
329
|
+
|
|
330
|
+
const frame = encodeMessageFrame(body, type);
|
|
331
|
+
await ws.send(frame);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
ws.close(1000);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
if (err instanceof XRPCSubscriptionError) {
|
|
337
|
+
const frame = encodeErrorFrame(err.error, err.description);
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
await ws.send(frame);
|
|
341
|
+
} catch {}
|
|
342
|
+
|
|
343
|
+
ws.close(err.closeCode, err.error);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
ws.close(1011, `internal server error`);
|
|
348
|
+
this.#handleSubscriptionException(err, request);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (upgrade !== undefined) {
|
|
353
|
+
return upgrade;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return invalidRequest(`WebSocket upgrade failed`);
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
239
360
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
InferOutput,
|
|
3
3
|
ObjectSchema,
|
|
4
|
+
VariantSchema,
|
|
4
5
|
XRPCBlobBodyParam,
|
|
5
6
|
XRPCLexBodyParam,
|
|
6
7
|
XRPCProcedureMetadata,
|
|
7
8
|
XRPCQueryMetadata,
|
|
9
|
+
XRPCSubscriptionMetadata,
|
|
8
10
|
} from '@atcute/lexicons/validations';
|
|
9
11
|
|
|
10
12
|
import type { Literal, Promisable } from '../../types/misc.js';
|
|
@@ -85,3 +87,34 @@ export type ProcedureHandler<TProcedure extends XRPCProcedureMetadata> = (
|
|
|
85
87
|
export type ProcedureConfig<TProcedure extends XRPCProcedureMetadata = XRPCProcedureMetadata> = {
|
|
86
88
|
handler: ProcedureHandler<TProcedure>;
|
|
87
89
|
};
|
|
90
|
+
|
|
91
|
+
// #region Subscription
|
|
92
|
+
|
|
93
|
+
export interface UnknownSubscriptionContext {
|
|
94
|
+
request: Request;
|
|
95
|
+
signal: AbortSignal;
|
|
96
|
+
params: Record<string, Literal | Literal[]>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type SubscriptionContext<TSubscription extends XRPCSubscriptionMetadata> = {
|
|
100
|
+
request: Request;
|
|
101
|
+
signal: AbortSignal;
|
|
102
|
+
} & (TSubscription['params'] extends ObjectSchema
|
|
103
|
+
? {
|
|
104
|
+
params: InferOutput<TSubscription['params']>;
|
|
105
|
+
}
|
|
106
|
+
: {
|
|
107
|
+
// params
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export type SubscriptionHandler<TSubscription extends XRPCSubscriptionMetadata> = (
|
|
111
|
+
context: SubscriptionContext<TSubscription>,
|
|
112
|
+
) => AsyncIterable<
|
|
113
|
+
TSubscription['message'] extends ObjectSchema | VariantSchema<any>
|
|
114
|
+
? InferOutput<TSubscription['message']>
|
|
115
|
+
: never
|
|
116
|
+
>;
|
|
117
|
+
|
|
118
|
+
export type SubscriptionConfig<TSubscription extends XRPCSubscriptionMetadata = XRPCSubscriptionMetadata> = {
|
|
119
|
+
handler: SubscriptionHandler<TSubscription>;
|
|
120
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Promisable } from '../../types/misc.js';
|
|
2
|
+
|
|
3
|
+
export interface WebSocketConnection {
|
|
4
|
+
signal: AbortSignal;
|
|
5
|
+
send(data: Uint8Array): void | Promise<void>;
|
|
6
|
+
close(code?: number, reason?: string): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface WebSocketAdapter {
|
|
10
|
+
upgrade(
|
|
11
|
+
request: Request,
|
|
12
|
+
handler: (ws: WebSocketConnection) => Promisable<void>,
|
|
13
|
+
): Promisable<Response | undefined>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** Converts a tuple into a listener function */
|
|
2
|
+
export type ListenerFor<T extends any[]> = (...args: T) => void;
|
|
3
|
+
|
|
4
|
+
/** Generic record of the event name and its argument tuple */
|
|
5
|
+
export type EventMap = {
|
|
6
|
+
[key: string | symbol]: any[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type MaybeArray<T> = T | T[];
|
|
10
|
+
|
|
11
|
+
/** Event emitter */
|
|
12
|
+
export class EventEmitter<Events extends EventMap> {
|
|
13
|
+
#events?: { [E in keyof Events]?: MaybeArray<ListenerFor<Events[E]>> };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Appends a listener for the specified event name
|
|
17
|
+
* @param name Name of the event
|
|
18
|
+
* @param listener Callback that should be invoked when an event is dispatched
|
|
19
|
+
* @returns Cleanup function that can be called to remove it
|
|
20
|
+
*/
|
|
21
|
+
on<E extends keyof Events>(name: E, listener: ListenerFor<Events[E]>): () => void {
|
|
22
|
+
let events = this.#events;
|
|
23
|
+
let existing: MaybeArray<ListenerFor<Events[E]>> | undefined;
|
|
24
|
+
|
|
25
|
+
if (events === undefined) {
|
|
26
|
+
events = this.#events = Object.create(null);
|
|
27
|
+
} else {
|
|
28
|
+
existing = events[name];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (existing === undefined) {
|
|
32
|
+
events![name] = listener;
|
|
33
|
+
} else if (typeof existing === 'function') {
|
|
34
|
+
events![name] = [existing, listener];
|
|
35
|
+
} else {
|
|
36
|
+
events![name] = existing.concat(listener);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// @ts-expect-error: complains about `listener`
|
|
40
|
+
return this.off.bind(this, name, listener);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove listener from the specified event name
|
|
45
|
+
* @param name Name of the event
|
|
46
|
+
* @param listener Callback to remove
|
|
47
|
+
*/
|
|
48
|
+
off<E extends keyof Events>(name: E, listener: ListenerFor<Events[E]>): void {
|
|
49
|
+
const events = this.#events;
|
|
50
|
+
|
|
51
|
+
if (events === undefined) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const list = events[name];
|
|
56
|
+
|
|
57
|
+
if (list == undefined) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (list === listener) {
|
|
62
|
+
delete events[name];
|
|
63
|
+
} else if (typeof list !== 'function') {
|
|
64
|
+
const index = list.indexOf(listener);
|
|
65
|
+
|
|
66
|
+
if (index !== -1) {
|
|
67
|
+
if (list.length === 2) {
|
|
68
|
+
// ^ flips the bit, it's either 0 or 1 here.
|
|
69
|
+
events[name] = list[index ^ 1];
|
|
70
|
+
} else {
|
|
71
|
+
events[name] = list.toSpliced(index, 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Emit an event with the specified name and its payload
|
|
79
|
+
* @param name Name of the event
|
|
80
|
+
* @param args Payload for the event
|
|
81
|
+
* @returns Whether a listener has been called
|
|
82
|
+
*/
|
|
83
|
+
emit<E extends keyof Events>(name: E, ...args: Events[E]): boolean {
|
|
84
|
+
const events = this.#events;
|
|
85
|
+
|
|
86
|
+
if (events === undefined) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const handler = events[name];
|
|
91
|
+
|
|
92
|
+
if (handler === undefined) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof handler === 'function') {
|
|
97
|
+
handler.apply(this, args);
|
|
98
|
+
} else {
|
|
99
|
+
for (let idx = 0, len = handler.length; idx < len; idx++) {
|
|
100
|
+
handler[idx].apply(this, args);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Determines if there is a listener on a specified event name
|
|
109
|
+
* @param name Name of the event
|
|
110
|
+
* @returns Whether there is a listener registered
|
|
111
|
+
*/
|
|
112
|
+
has(name: keyof Events): boolean {
|
|
113
|
+
const events = this.#events;
|
|
114
|
+
return events !== undefined && name in events;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { encode } from '@atcute/cbor';
|
|
2
|
+
import { concat } from '@atcute/uint8array';
|
|
3
|
+
|
|
4
|
+
interface MessageFrameHeader {
|
|
5
|
+
op: 1;
|
|
6
|
+
t?: string; // Type discriminator for union messages (relative to NSID)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ErrorFrameHeader {
|
|
10
|
+
op: -1;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ErrorFrameBody {
|
|
14
|
+
error: string;
|
|
15
|
+
message?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const encodeMessageFrame = (body: unknown, type?: string): Uint8Array => {
|
|
19
|
+
const header: MessageFrameHeader = {
|
|
20
|
+
op: 1,
|
|
21
|
+
t: type,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return concat([encode(header), encode(body)]);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const encodeErrorFrame = (error: string, message?: string): Uint8Array => {
|
|
28
|
+
const header: ErrorFrameHeader = {
|
|
29
|
+
op: -1,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const body: ErrorFrameBody = {
|
|
33
|
+
error: error,
|
|
34
|
+
message: message,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return concat([encode(header), encode(body)]);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const extractMessageType = (message: unknown, nsid: string): string | undefined => {
|
|
41
|
+
if (typeof message !== 'object' || message === null) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const obj = message as Record<string, unknown>;
|
|
46
|
+
const type = obj.$type;
|
|
47
|
+
|
|
48
|
+
if (typeof type !== 'string') {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// If type starts with the subscription NSID, make it relative
|
|
53
|
+
// e.g., "com.atproto.sync.subscribeRepos#commit" → "#commit"
|
|
54
|
+
if (type.startsWith(nsid + '#')) {
|
|
55
|
+
return type.slice(nsid.length);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Otherwise return the full type
|
|
59
|
+
return type;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const omitMessageType = (message: unknown): unknown => {
|
|
63
|
+
if (typeof message !== 'object' || message === null) {
|
|
64
|
+
return message;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const obj = message as Record<string, unknown>;
|
|
68
|
+
const { $type, ...rest } = obj;
|
|
69
|
+
|
|
70
|
+
return rest;
|
|
71
|
+
};
|