@atcute/xrpc-server 0.1.11 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -20
- package/dist/auth/jwt-creator.d.ts +4 -2
- package/dist/auth/jwt-creator.d.ts.map +1 -1
- package/dist/auth/jwt-creator.js +1 -1
- package/dist/auth/jwt-creator.js.map +1 -1
- package/dist/auth/jwt-verifier.d.ts +69 -8
- package/dist/auth/jwt-verifier.d.ts.map +1 -1
- package/dist/auth/jwt-verifier.js +131 -20
- package/dist/auth/jwt-verifier.js.map +1 -1
- package/dist/auth/jwt.d.ts +7 -2
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/auth/jwt.js +14 -7
- package/dist/auth/jwt.js.map +1 -1
- package/dist/main/response.js.map +1 -1
- package/dist/main/router.d.ts +32 -4
- package/dist/main/router.d.ts.map +1 -1
- package/dist/main/router.js +66 -11
- package/dist/main/router.js.map +1 -1
- package/dist/main/types/operation.d.ts +8 -0
- package/dist/main/types/operation.d.ts.map +1 -1
- package/dist/main/types/websocket.d.ts +8 -0
- package/dist/main/types/websocket.d.ts.map +1 -1
- package/dist/main/utils/frames.d.ts +2 -2
- package/dist/main/utils/frames.d.ts.map +1 -1
- package/dist/main/utils/frames.js.map +1 -1
- package/dist/main/utils/middlewares.d.ts.map +1 -1
- package/dist/main/utils/middlewares.js.map +1 -1
- package/dist/main/utils/namespaced.d.ts.map +1 -1
- package/dist/main/utils/namespaced.js.map +1 -1
- package/dist/main/utils/request-input.d.ts +1 -1
- package/dist/main/utils/request-input.d.ts.map +1 -1
- package/dist/main/utils/request-input.js.map +1 -1
- package/dist/main/utils/request-params.d.ts +1 -1
- package/dist/main/utils/request-params.d.ts.map +1 -1
- package/dist/main/utils/request-params.js.map +1 -1
- package/dist/main/utils/response.d.ts +1 -1
- package/dist/main/utils/response.d.ts.map +1 -1
- package/dist/main/utils/response.js.map +1 -1
- package/dist/main/utils/websocket-mock.d.ts +8 -9
- package/dist/main/utils/websocket-mock.d.ts.map +1 -1
- package/dist/main/utils/websocket-mock.js +11 -6
- package/dist/main/utils/websocket-mock.js.map +1 -1
- package/dist/main/xrpc-error.d.ts +55 -15
- package/dist/main/xrpc-error.d.ts.map +1 -1
- package/dist/main/xrpc-error.js +66 -26
- package/dist/main/xrpc-error.js.map +1 -1
- package/dist/main/xrpc-handler.js.map +1 -1
- package/dist/middlewares/cors.d.ts.map +1 -1
- package/dist/middlewares/cors.js.map +1 -1
- package/lib/auth/jwt-creator.ts +5 -3
- package/lib/auth/jwt-verifier.ts +206 -26
- package/lib/auth/jwt.ts +21 -10
- package/lib/main/router.ts +98 -15
- package/lib/main/types/operation.ts +8 -0
- package/lib/main/types/websocket.ts +8 -0
- package/lib/main/utils/websocket-mock.ts +20 -14
- package/lib/main/xrpc-error.ts +107 -44
- package/package.json +20 -15
- package/dist/main/utils/event-emitter.d.ts +0 -37
- package/dist/main/utils/event-emitter.d.ts.map +0 -1
- package/dist/main/utils/event-emitter.js +0 -96
- package/dist/main/utils/event-emitter.js.map +0 -1
- package/lib/main/utils/event-emitter.ts +0 -116
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
|
|
3
|
+
import { SimpleEventEmitter } from '@mary-ext/simple-event-emitter';
|
|
4
|
+
|
|
3
5
|
import type { Promisable } from '../../types/misc.ts';
|
|
4
6
|
import type { XRPCRouter } from '../router.ts';
|
|
5
7
|
import type { WebSocketAdapter, WebSocketConnection } from '../types/websocket.ts';
|
|
6
8
|
|
|
7
|
-
import { EventEmitter } from './event-emitter.ts';
|
|
8
|
-
|
|
9
9
|
interface WebSocketHandlerContext {
|
|
10
10
|
handler: ((ws: WebSocketConnection) => Promisable<void>) | null;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface CloseEvent {
|
|
14
|
+
code: number;
|
|
15
|
+
reason: string;
|
|
16
|
+
wasClean: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export interface SubscriptionClient extends Disposable {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
close: [event: { code: number; reason: string; wasClean: boolean }];
|
|
17
|
-
}>;
|
|
20
|
+
onMessage: SimpleEventEmitter<[data: Uint8Array]>;
|
|
21
|
+
onClose: SimpleEventEmitter<[event: CloseEvent]>;
|
|
18
22
|
dispose(): void;
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -64,10 +68,8 @@ export class MockWebSocketAdapter implements WebSocketAdapter {
|
|
|
64
68
|
throw new Error(`WebSocket upgrade succeeded but no handler was set`);
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
close: [event: { code: number; reason: string; wasClean: boolean }];
|
|
70
|
-
}>();
|
|
71
|
+
const onMessage = new SimpleEventEmitter<[data: Uint8Array]>();
|
|
72
|
+
const onClose = new SimpleEventEmitter<[event: CloseEvent]>();
|
|
71
73
|
|
|
72
74
|
const controller = new AbortController();
|
|
73
75
|
const signal = controller.signal;
|
|
@@ -75,11 +77,14 @@ export class MockWebSocketAdapter implements WebSocketAdapter {
|
|
|
75
77
|
const connection: WebSocketConnection = {
|
|
76
78
|
signal: signal,
|
|
77
79
|
send(data) {
|
|
78
|
-
|
|
80
|
+
onMessage.emit(data);
|
|
81
|
+
},
|
|
82
|
+
drain() {
|
|
83
|
+
// tests have no outgoing buffer to observe
|
|
79
84
|
},
|
|
80
85
|
close(code = 1000, reason = '') {
|
|
81
86
|
if (!signal.aborted) {
|
|
82
|
-
|
|
87
|
+
onClose.emit({ code, reason, wasClean: true });
|
|
83
88
|
controller.abort();
|
|
84
89
|
}
|
|
85
90
|
},
|
|
@@ -93,10 +98,11 @@ export class MockWebSocketAdapter implements WebSocketAdapter {
|
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
const client: SubscriptionClient = {
|
|
96
|
-
|
|
101
|
+
onMessage,
|
|
102
|
+
onClose,
|
|
97
103
|
dispose() {
|
|
98
104
|
if (!signal.aborted) {
|
|
99
|
-
|
|
105
|
+
onClose.emit({ code: 1000, reason: '', wasClean: true });
|
|
100
106
|
controller.abort();
|
|
101
107
|
}
|
|
102
108
|
},
|
package/lib/main/xrpc-error.ts
CHANGED
|
@@ -1,7 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* a single WWW-Authenticate challenge. exactly one of `params` or `token68` may
|
|
3
|
+
* be provided. a bare scheme (no params, no token) is valid and renders as just
|
|
4
|
+
* the scheme name.
|
|
5
|
+
*
|
|
6
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7235#section-4.1 | RFC 7235 §4.1}
|
|
7
|
+
*/
|
|
8
|
+
export interface WWWAuthenticateChallenge {
|
|
9
|
+
/** authentication scheme, e.g. `Bearer`, `DPoP`, `Basic`. */
|
|
10
|
+
scheme: string;
|
|
11
|
+
/** auth-param pairs. entries whose value is `undefined` are omitted. */
|
|
12
|
+
params?: Record<string, string | undefined>;
|
|
13
|
+
/**
|
|
14
|
+
* token68 value for schemes that carry one instead of auth-params (e.g.
|
|
15
|
+
* `Basic`). mutually exclusive with `params`.
|
|
16
|
+
*/
|
|
17
|
+
token68?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* formats one or more WWW-Authenticate challenges into a single header value.
|
|
22
|
+
*
|
|
23
|
+
* each challenge is emitted as `<scheme>` followed by its params or token68.
|
|
24
|
+
* multiple challenges are joined with `, `. auth-param values are quoted using
|
|
25
|
+
* `JSON.stringify` (RFC 7230 quoted-string semantics for ASCII content).
|
|
26
|
+
*
|
|
27
|
+
* @param challenges one challenge, or an ordered array of challenges
|
|
28
|
+
* @returns the formatted header value
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* formatWWWAuthenticate({ scheme: 'Bearer', params: { error: 'BadJwtSignature' } })
|
|
33
|
+
* // => `Bearer error="BadJwtSignature"`
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const formatWWWAuthenticate = (
|
|
37
|
+
challenges: WWWAuthenticateChallenge | WWWAuthenticateChallenge[],
|
|
38
|
+
): string => {
|
|
39
|
+
const list = Array.isArray(challenges) ? challenges : [challenges];
|
|
40
|
+
return list.map(formatChallenge).join(', ');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const formatChallenge = (challenge: WWWAuthenticateChallenge): string => {
|
|
44
|
+
if (challenge.token68 !== undefined) {
|
|
45
|
+
return `${challenge.scheme} ${challenge.token68}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (challenge.params !== undefined) {
|
|
49
|
+
const parts: string[] = [];
|
|
50
|
+
for (const name in challenge.params) {
|
|
51
|
+
const value = challenge.params[name];
|
|
52
|
+
if (value !== undefined) {
|
|
53
|
+
parts.push(`${name}=${JSON.stringify(value)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (parts.length > 0) {
|
|
58
|
+
return `${challenge.scheme} ${parts.join(', ')}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return challenge.scheme;
|
|
63
|
+
};
|
|
64
|
+
|
|
1
65
|
export interface XRPCErrorOptions {
|
|
2
66
|
status: number;
|
|
3
67
|
error: string;
|
|
4
|
-
|
|
68
|
+
message?: string;
|
|
5
69
|
headers?: HeadersInit;
|
|
6
70
|
}
|
|
7
71
|
|
|
@@ -11,54 +75,65 @@ export class XRPCError extends Error {
|
|
|
11
75
|
|
|
12
76
|
/** error name */
|
|
13
77
|
readonly error: string;
|
|
14
|
-
/** error message */
|
|
15
|
-
readonly description?: string;
|
|
16
78
|
/** response headers */
|
|
17
79
|
readonly headers?: HeadersInit;
|
|
18
80
|
|
|
19
|
-
constructor({ status, error,
|
|
20
|
-
super(
|
|
81
|
+
constructor({ status, error, message, headers }: XRPCErrorOptions) {
|
|
82
|
+
super(message);
|
|
21
83
|
|
|
22
84
|
this.status = status;
|
|
23
85
|
|
|
24
86
|
this.error = error;
|
|
25
|
-
this.description = description;
|
|
26
87
|
this.headers = headers;
|
|
27
88
|
}
|
|
28
89
|
|
|
29
90
|
toResponse(): Response {
|
|
30
91
|
return Response.json(
|
|
31
|
-
{ error: this.error, message: this.
|
|
92
|
+
{ error: this.error, message: this.message || undefined },
|
|
32
93
|
{ status: this.status, headers: this.headers },
|
|
33
94
|
);
|
|
34
95
|
}
|
|
35
96
|
}
|
|
36
97
|
|
|
37
98
|
export class InvalidRequestError extends XRPCError {
|
|
38
|
-
constructor({
|
|
39
|
-
status
|
|
40
|
-
error = 'InvalidRequest',
|
|
41
|
-
description,
|
|
42
|
-
headers,
|
|
43
|
-
}: Partial<XRPCErrorOptions> = {}) {
|
|
44
|
-
super({ status, error, description, headers });
|
|
99
|
+
constructor({ status = 400, error = 'InvalidRequest', message, headers }: Partial<XRPCErrorOptions> = {}) {
|
|
100
|
+
super({ status, error, message, headers });
|
|
45
101
|
}
|
|
46
102
|
}
|
|
47
103
|
|
|
104
|
+
export interface AuthRequiredErrorOptions extends Partial<XRPCErrorOptions> {
|
|
105
|
+
/**
|
|
106
|
+
* WWW-Authenticate challenge(s) to attach to the response. the formatted
|
|
107
|
+
* header is set on `headers` automatically, and `access-control-expose-headers`
|
|
108
|
+
* is appended so browsers can read it from CORS responses.
|
|
109
|
+
*/
|
|
110
|
+
wwwAuthenticate?: WWWAuthenticateChallenge | WWWAuthenticateChallenge[];
|
|
111
|
+
}
|
|
112
|
+
|
|
48
113
|
export class AuthRequiredError extends XRPCError {
|
|
49
114
|
constructor({
|
|
50
115
|
status = 401,
|
|
51
116
|
error = 'AuthenticationRequired',
|
|
52
|
-
|
|
117
|
+
message,
|
|
53
118
|
headers,
|
|
54
|
-
|
|
55
|
-
|
|
119
|
+
wwwAuthenticate,
|
|
120
|
+
}: AuthRequiredErrorOptions = {}) {
|
|
121
|
+
let mergedHeaders = headers;
|
|
122
|
+
|
|
123
|
+
if (wwwAuthenticate !== undefined) {
|
|
124
|
+
const target = new Headers(headers);
|
|
125
|
+
target.set('www-authenticate', formatWWWAuthenticate(wwwAuthenticate));
|
|
126
|
+
target.append('access-control-expose-headers', 'www-authenticate');
|
|
127
|
+
mergedHeaders = target;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
super({ status, error, message, headers: mergedHeaders });
|
|
56
131
|
}
|
|
57
132
|
}
|
|
58
133
|
|
|
59
134
|
export class ForbiddenError extends XRPCError {
|
|
60
|
-
constructor({ status = 403, error = 'Forbidden',
|
|
61
|
-
super({ status, error,
|
|
135
|
+
constructor({ status = 403, error = 'Forbidden', message, headers }: Partial<XRPCErrorOptions> = {}) {
|
|
136
|
+
super({ status, error, message, headers });
|
|
62
137
|
}
|
|
63
138
|
}
|
|
64
139
|
|
|
@@ -66,10 +141,10 @@ export class RateLimitExceededError extends XRPCError {
|
|
|
66
141
|
constructor({
|
|
67
142
|
status = 429,
|
|
68
143
|
error = 'RateLimitExceeded',
|
|
69
|
-
|
|
144
|
+
message,
|
|
70
145
|
headers,
|
|
71
146
|
}: Partial<XRPCErrorOptions> = {}) {
|
|
72
|
-
super({ status, error,
|
|
147
|
+
super({ status, error, message, headers });
|
|
73
148
|
}
|
|
74
149
|
}
|
|
75
150
|
|
|
@@ -77,21 +152,16 @@ export class InternalServerError extends XRPCError {
|
|
|
77
152
|
constructor({
|
|
78
153
|
status = 500,
|
|
79
154
|
error = 'InternalServerError',
|
|
80
|
-
|
|
155
|
+
message,
|
|
81
156
|
headers,
|
|
82
157
|
}: Partial<XRPCErrorOptions> = {}) {
|
|
83
|
-
super({ status, error,
|
|
158
|
+
super({ status, error, message, headers });
|
|
84
159
|
}
|
|
85
160
|
}
|
|
86
161
|
|
|
87
162
|
export class UpstreamFailureError extends XRPCError {
|
|
88
|
-
constructor({
|
|
89
|
-
status
|
|
90
|
-
error = 'UpstreamFailure',
|
|
91
|
-
description,
|
|
92
|
-
headers,
|
|
93
|
-
}: Partial<XRPCErrorOptions> = {}) {
|
|
94
|
-
super({ status, error, description, headers });
|
|
163
|
+
constructor({ status = 502, error = 'UpstreamFailure', message, headers }: Partial<XRPCErrorOptions> = {}) {
|
|
164
|
+
super({ status, error, message, headers });
|
|
95
165
|
}
|
|
96
166
|
}
|
|
97
167
|
|
|
@@ -99,40 +169,33 @@ export class NotEnoughResourcesError extends XRPCError {
|
|
|
99
169
|
constructor({
|
|
100
170
|
status = 503,
|
|
101
171
|
error = 'NotEnoughResources',
|
|
102
|
-
|
|
172
|
+
message,
|
|
103
173
|
headers,
|
|
104
174
|
}: Partial<XRPCErrorOptions> = {}) {
|
|
105
|
-
super({ status, error,
|
|
175
|
+
super({ status, error, message, headers });
|
|
106
176
|
}
|
|
107
177
|
}
|
|
108
178
|
|
|
109
179
|
export class UpstreamTimeoutError extends XRPCError {
|
|
110
|
-
constructor({
|
|
111
|
-
status
|
|
112
|
-
error = 'UpstreamTimeout',
|
|
113
|
-
description,
|
|
114
|
-
headers,
|
|
115
|
-
}: Partial<XRPCErrorOptions> = {}) {
|
|
116
|
-
super({ status, error, description, headers });
|
|
180
|
+
constructor({ status = 504, error = 'UpstreamTimeout', message, headers }: Partial<XRPCErrorOptions> = {}) {
|
|
181
|
+
super({ status, error, message, headers });
|
|
117
182
|
}
|
|
118
183
|
}
|
|
119
184
|
|
|
120
185
|
export interface XRPCSubscriptionErrorOptions {
|
|
121
186
|
closeCode?: number;
|
|
122
187
|
error: string;
|
|
123
|
-
|
|
188
|
+
message?: string;
|
|
124
189
|
}
|
|
125
190
|
|
|
126
191
|
export class XRPCSubscriptionError extends Error {
|
|
127
192
|
readonly closeCode: number;
|
|
128
193
|
readonly error: string;
|
|
129
|
-
readonly description?: string;
|
|
130
194
|
|
|
131
|
-
constructor({ closeCode = 1008, error,
|
|
132
|
-
super(
|
|
195
|
+
constructor({ closeCode = 1008, error, message }: XRPCSubscriptionErrorOptions) {
|
|
196
|
+
super(message);
|
|
133
197
|
|
|
134
198
|
this.closeCode = closeCode;
|
|
135
199
|
this.error = error;
|
|
136
|
-
this.description = description;
|
|
137
200
|
}
|
|
138
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atcute/xrpc-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "a small web framework for handling XRPC operations",
|
|
5
5
|
"license": "0BSD",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dist/",
|
|
12
12
|
"lib/",
|
|
13
13
|
"!lib/**/*.bench.ts",
|
|
14
|
-
"!lib/**/*.test.ts"
|
|
14
|
+
"!lib/**/*.test.ts",
|
|
15
|
+
"!dist/**/*.{test,bench}.*"
|
|
15
16
|
],
|
|
16
17
|
"type": "module",
|
|
17
18
|
"exports": {
|
|
@@ -24,25 +25,29 @@
|
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"@badrap/valita": "^0.4.6",
|
|
27
|
-
"nanoid": "^5.1.
|
|
28
|
-
"@atcute/cbor": "^2.3.
|
|
29
|
-
"@atcute/
|
|
30
|
-
"@atcute/
|
|
31
|
-
"@atcute/lexicons": "^1.
|
|
32
|
-
"@atcute/identity": "^1.1.
|
|
33
|
-
"@atcute/multibase": "^1.
|
|
28
|
+
"nanoid": "^5.1.11",
|
|
29
|
+
"@atcute/cbor": "^2.3.3",
|
|
30
|
+
"@atcute/identity-resolver": "^1.2.3",
|
|
31
|
+
"@atcute/crypto": "^2.4.1",
|
|
32
|
+
"@atcute/lexicons": "^1.3.1",
|
|
33
|
+
"@atcute/identity": "^1.1.5",
|
|
34
|
+
"@atcute/multibase": "^1.2.0",
|
|
34
35
|
"@atcute/uint8array": "^1.1.1"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@atcute/xrpc-server": "file:",
|
|
38
|
-
"@
|
|
39
|
-
"@
|
|
40
|
-
"vitest": "^4.
|
|
41
|
-
"
|
|
42
|
-
"@atcute/
|
|
39
|
+
"@mary-ext/simple-event-emitter": "^1.0.1",
|
|
40
|
+
"@types/node": "^25.6.0",
|
|
41
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
42
|
+
"vitest": "^4.1.5",
|
|
43
|
+
"@atcute/atproto": "^3.1.12",
|
|
44
|
+
"@atcute/bluesky": "^3.3.4"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@atcute/lexicons": "^1.0.0"
|
|
43
48
|
},
|
|
44
49
|
"scripts": {
|
|
45
|
-
"build": "tsgo
|
|
50
|
+
"build": "tsgo",
|
|
46
51
|
"test": "vitest --coverage",
|
|
47
52
|
"prepublish": "rm -rf dist; pnpm run build"
|
|
48
53
|
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/** Converts a tuple into a listener function */
|
|
2
|
-
export type ListenerFor<T extends any[]> = (...args: T) => void;
|
|
3
|
-
/** Generic record of the event name and its argument tuple */
|
|
4
|
-
export type EventMap = {
|
|
5
|
-
[key: string | symbol]: any[];
|
|
6
|
-
};
|
|
7
|
-
/** Event emitter */
|
|
8
|
-
export declare class EventEmitter<Events extends EventMap> {
|
|
9
|
-
#private;
|
|
10
|
-
/**
|
|
11
|
-
* Appends a listener for the specified event name
|
|
12
|
-
* @param name Name of the event
|
|
13
|
-
* @param listener Callback that should be invoked when an event is dispatched
|
|
14
|
-
* @returns Cleanup function that can be called to remove it
|
|
15
|
-
*/
|
|
16
|
-
on<E extends keyof Events>(name: E, listener: ListenerFor<Events[E]>): () => void;
|
|
17
|
-
/**
|
|
18
|
-
* Remove listener from the specified event name
|
|
19
|
-
* @param name Name of the event
|
|
20
|
-
* @param listener Callback to remove
|
|
21
|
-
*/
|
|
22
|
-
off<E extends keyof Events>(name: E, listener: ListenerFor<Events[E]>): void;
|
|
23
|
-
/**
|
|
24
|
-
* Emit an event with the specified name and its payload
|
|
25
|
-
* @param name Name of the event
|
|
26
|
-
* @param args Payload for the event
|
|
27
|
-
* @returns Whether a listener has been called
|
|
28
|
-
*/
|
|
29
|
-
emit<E extends keyof Events>(name: E, ...args: Events[E]): boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Determines if there is a listener on a specified event name
|
|
32
|
-
* @param name Name of the event
|
|
33
|
-
* @returns Whether there is a listener registered
|
|
34
|
-
*/
|
|
35
|
-
has(name: keyof Events): boolean;
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=event-emitter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"event-emitter.d.ts","sourceRoot":"","sources":["../../../lib/main/utils/event-emitter.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAEhE,8DAA8D;AAC9D,MAAM,MAAM,QAAQ,GAAG;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC;CAC9B,CAAC;AAIF,oBAAoB;AACpB,qBAAa,YAAY,CAAC,MAAM,SAAS,QAAQ;;IAGhD;;;;;OAKG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAoBhF;IAED;;;;OAIG;IACH,GAAG,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CA2B3E;IAED;;;;;OAKG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAsBjE;IAED;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,MAAM,GAAG,OAAO,CAG/B;CACD"}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/** Event emitter */
|
|
2
|
-
export class EventEmitter {
|
|
3
|
-
#events;
|
|
4
|
-
/**
|
|
5
|
-
* Appends a listener for the specified event name
|
|
6
|
-
* @param name Name of the event
|
|
7
|
-
* @param listener Callback that should be invoked when an event is dispatched
|
|
8
|
-
* @returns Cleanup function that can be called to remove it
|
|
9
|
-
*/
|
|
10
|
-
on(name, listener) {
|
|
11
|
-
let events = this.#events;
|
|
12
|
-
let existing;
|
|
13
|
-
if (events === undefined) {
|
|
14
|
-
events = this.#events = Object.create(null);
|
|
15
|
-
}
|
|
16
|
-
else {
|
|
17
|
-
existing = events[name];
|
|
18
|
-
}
|
|
19
|
-
if (existing === undefined) {
|
|
20
|
-
events[name] = listener;
|
|
21
|
-
}
|
|
22
|
-
else if (typeof existing === 'function') {
|
|
23
|
-
events[name] = [existing, listener];
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
events[name] = existing.concat(listener);
|
|
27
|
-
}
|
|
28
|
-
// @ts-expect-error: complains about `listener`
|
|
29
|
-
return this.off.bind(this, name, listener);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Remove listener from the specified event name
|
|
33
|
-
* @param name Name of the event
|
|
34
|
-
* @param listener Callback to remove
|
|
35
|
-
*/
|
|
36
|
-
off(name, listener) {
|
|
37
|
-
const events = this.#events;
|
|
38
|
-
if (events === undefined) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const list = events[name];
|
|
42
|
-
if (list == undefined) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (list === listener) {
|
|
46
|
-
delete events[name];
|
|
47
|
-
}
|
|
48
|
-
else if (typeof list !== 'function') {
|
|
49
|
-
const index = list.indexOf(listener);
|
|
50
|
-
if (index !== -1) {
|
|
51
|
-
if (list.length === 2) {
|
|
52
|
-
// ^ flips the bit, it's either 0 or 1 here.
|
|
53
|
-
events[name] = list[index ^ 1];
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
events[name] = list.toSpliced(index, 1);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Emit an event with the specified name and its payload
|
|
63
|
-
* @param name Name of the event
|
|
64
|
-
* @param args Payload for the event
|
|
65
|
-
* @returns Whether a listener has been called
|
|
66
|
-
*/
|
|
67
|
-
emit(name, ...args) {
|
|
68
|
-
const events = this.#events;
|
|
69
|
-
if (events === undefined) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const handler = events[name];
|
|
73
|
-
if (handler === undefined) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
if (typeof handler === 'function') {
|
|
77
|
-
handler.apply(this, args);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
for (let idx = 0, len = handler.length; idx < len; idx++) {
|
|
81
|
-
handler[idx].apply(this, args);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Determines if there is a listener on a specified event name
|
|
88
|
-
* @param name Name of the event
|
|
89
|
-
* @returns Whether there is a listener registered
|
|
90
|
-
*/
|
|
91
|
-
has(name) {
|
|
92
|
-
const events = this.#events;
|
|
93
|
-
return events !== undefined && name in events;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
//# sourceMappingURL=event-emitter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"event-emitter.js","sourceRoot":"","sources":["../../../lib/main/utils/event-emitter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,116 +0,0 @@
|
|
|
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
|
-
}
|