@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.
Files changed (63) hide show
  1. package/README.md +81 -20
  2. package/dist/auth/jwt-creator.d.ts +4 -2
  3. package/dist/auth/jwt-creator.d.ts.map +1 -1
  4. package/dist/auth/jwt-creator.js +1 -1
  5. package/dist/auth/jwt-creator.js.map +1 -1
  6. package/dist/auth/jwt-verifier.d.ts +69 -8
  7. package/dist/auth/jwt-verifier.d.ts.map +1 -1
  8. package/dist/auth/jwt-verifier.js +131 -20
  9. package/dist/auth/jwt-verifier.js.map +1 -1
  10. package/dist/auth/jwt.d.ts +7 -2
  11. package/dist/auth/jwt.d.ts.map +1 -1
  12. package/dist/auth/jwt.js +14 -7
  13. package/dist/auth/jwt.js.map +1 -1
  14. package/dist/main/response.js.map +1 -1
  15. package/dist/main/router.d.ts +32 -4
  16. package/dist/main/router.d.ts.map +1 -1
  17. package/dist/main/router.js +66 -11
  18. package/dist/main/router.js.map +1 -1
  19. package/dist/main/types/operation.d.ts +8 -0
  20. package/dist/main/types/operation.d.ts.map +1 -1
  21. package/dist/main/types/websocket.d.ts +8 -0
  22. package/dist/main/types/websocket.d.ts.map +1 -1
  23. package/dist/main/utils/frames.d.ts +2 -2
  24. package/dist/main/utils/frames.d.ts.map +1 -1
  25. package/dist/main/utils/frames.js.map +1 -1
  26. package/dist/main/utils/middlewares.d.ts.map +1 -1
  27. package/dist/main/utils/middlewares.js.map +1 -1
  28. package/dist/main/utils/namespaced.d.ts.map +1 -1
  29. package/dist/main/utils/namespaced.js.map +1 -1
  30. package/dist/main/utils/request-input.d.ts +1 -1
  31. package/dist/main/utils/request-input.d.ts.map +1 -1
  32. package/dist/main/utils/request-input.js.map +1 -1
  33. package/dist/main/utils/request-params.d.ts +1 -1
  34. package/dist/main/utils/request-params.d.ts.map +1 -1
  35. package/dist/main/utils/request-params.js.map +1 -1
  36. package/dist/main/utils/response.d.ts +1 -1
  37. package/dist/main/utils/response.d.ts.map +1 -1
  38. package/dist/main/utils/response.js.map +1 -1
  39. package/dist/main/utils/websocket-mock.d.ts +8 -9
  40. package/dist/main/utils/websocket-mock.d.ts.map +1 -1
  41. package/dist/main/utils/websocket-mock.js +11 -6
  42. package/dist/main/utils/websocket-mock.js.map +1 -1
  43. package/dist/main/xrpc-error.d.ts +55 -15
  44. package/dist/main/xrpc-error.d.ts.map +1 -1
  45. package/dist/main/xrpc-error.js +66 -26
  46. package/dist/main/xrpc-error.js.map +1 -1
  47. package/dist/main/xrpc-handler.js.map +1 -1
  48. package/dist/middlewares/cors.d.ts.map +1 -1
  49. package/dist/middlewares/cors.js.map +1 -1
  50. package/lib/auth/jwt-creator.ts +5 -3
  51. package/lib/auth/jwt-verifier.ts +206 -26
  52. package/lib/auth/jwt.ts +21 -10
  53. package/lib/main/router.ts +98 -15
  54. package/lib/main/types/operation.ts +8 -0
  55. package/lib/main/types/websocket.ts +8 -0
  56. package/lib/main/utils/websocket-mock.ts +20 -14
  57. package/lib/main/xrpc-error.ts +107 -44
  58. package/package.json +20 -15
  59. package/dist/main/utils/event-emitter.d.ts +0 -37
  60. package/dist/main/utils/event-emitter.d.ts.map +0 -1
  61. package/dist/main/utils/event-emitter.js +0 -96
  62. package/dist/main/utils/event-emitter.js.map +0 -1
  63. 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
- events: EventEmitter<{
15
- message: [data: Uint8Array];
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 events = new EventEmitter<{
68
- message: [data: Uint8Array];
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
- events.emit('message', data);
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
- events.emit('close', { code, reason, wasClean: true });
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
- events,
101
+ onMessage,
102
+ onClose,
97
103
  dispose() {
98
104
  if (!signal.aborted) {
99
- events.emit('close', { code: 1000, reason: '', wasClean: true });
105
+ onClose.emit({ code: 1000, reason: '', wasClean: true });
100
106
  controller.abort();
101
107
  }
102
108
  },
@@ -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
- description?: string;
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, description, headers }: XRPCErrorOptions) {
20
- super(`${error} > ${description ?? '(unspecified description)'}`);
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.description },
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 = 400,
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
- description,
117
+ message,
53
118
  headers,
54
- }: Partial<XRPCErrorOptions> = {}) {
55
- super({ status, error, description, headers });
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', description, headers }: Partial<XRPCErrorOptions> = {}) {
61
- super({ status, error, description, headers });
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
- description,
144
+ message,
70
145
  headers,
71
146
  }: Partial<XRPCErrorOptions> = {}) {
72
- super({ status, error, description, headers });
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
- description,
155
+ message,
81
156
  headers,
82
157
  }: Partial<XRPCErrorOptions> = {}) {
83
- super({ status, error, description, headers });
158
+ super({ status, error, message, headers });
84
159
  }
85
160
  }
86
161
 
87
162
  export class UpstreamFailureError extends XRPCError {
88
- constructor({
89
- status = 502,
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
- description,
172
+ message,
103
173
  headers,
104
174
  }: Partial<XRPCErrorOptions> = {}) {
105
- super({ status, error, description, headers });
175
+ super({ status, error, message, headers });
106
176
  }
107
177
  }
108
178
 
109
179
  export class UpstreamTimeoutError extends XRPCError {
110
- constructor({
111
- status = 504,
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
- description?: string;
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, description }: XRPCSubscriptionErrorOptions) {
132
- super(`Subscription error: ${error}${description ? ` - ${description}` : ''}`);
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.1.11",
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.6",
28
- "@atcute/cbor": "^2.3.2",
29
- "@atcute/crypto": "^2.3.0",
30
- "@atcute/identity-resolver": "^1.2.2",
31
- "@atcute/lexicons": "^1.2.9",
32
- "@atcute/identity": "^1.1.3",
33
- "@atcute/multibase": "^1.1.8",
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
- "@types/node": "^25.3.0",
39
- "@vitest/coverage-v8": "^4.0.18",
40
- "vitest": "^4.0.18",
41
- "@atcute/atproto": "^3.1.10",
42
- "@atcute/bluesky": "^3.2.19"
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 --project tsconfig.build.json",
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
- }