oauth-callback 1.2.0 → 1.2.2

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/src/server.ts CHANGED
@@ -5,10 +5,12 @@
5
5
  * SPDX-License-Identifier: MIT
6
6
  */
7
7
 
8
+ import type { Server as HttpServer } from "node:http";
9
+ import type { IncomingMessage } from "node:http";
8
10
  import { successTemplate, renderError } from "./templates";
9
11
 
10
12
  /**
11
- * Result object returned from OAuth callback containing authorization code or error details
13
+ * Result object returned from OAuth callback containing authorization code or error details.
12
14
  */
13
15
  export interface CallbackResult {
14
16
  /** Authorization code returned by OAuth provider */
@@ -26,7 +28,7 @@ export interface CallbackResult {
26
28
  }
27
29
 
28
30
  /**
29
- * Configuration options for the OAuth callback server
31
+ * Configuration options for the OAuth callback server.
30
32
  */
31
33
  export interface ServerOptions {
32
34
  /** Port number to bind the server to */
@@ -44,7 +46,7 @@ export interface ServerOptions {
44
46
  }
45
47
 
46
48
  /**
47
- * Interface for OAuth callback server implementations across different runtimes
49
+ * Interface for OAuth callback server implementations across different runtimes.
48
50
  */
49
51
  export interface CallbackServer {
50
52
  /** Start the HTTP server with the given options */
@@ -56,7 +58,7 @@ export interface CallbackServer {
56
58
  }
57
59
 
58
60
  /**
59
- * Generate HTML response for OAuth callback
61
+ * Generate HTML response for OAuth callback.
60
62
  * @param params - OAuth callback parameters (code, error, etc.)
61
63
  * @param successHtml - Custom success HTML template
62
64
  * @param errorHtml - Custom error HTML template with placeholder support
@@ -67,383 +69,217 @@ function generateCallbackHTML(
67
69
  successHtml?: string,
68
70
  errorHtml?: string,
69
71
  ): string {
70
- if (params.error) {
71
- // Use custom error HTML if provided
72
- if (errorHtml) {
73
- return errorHtml
74
- .replace(/{{error}}/g, params.error || "")
75
- .replace(/{{error_description}}/g, params.error_description || "")
76
- .replace(/{{error_uri}}/g, params.error_uri || "");
77
- }
78
- return renderError({
79
- error: params.error,
80
- error_description: params.error_description,
81
- error_uri: params.error_uri,
82
- });
83
- }
84
- // Use custom success HTML if provided
85
- return successHtml || successTemplate;
72
+ if (!params.error) return successHtml || successTemplate;
73
+
74
+ if (errorHtml)
75
+ return errorHtml
76
+ .replace(/{{error}}/g, params.error || "")
77
+ .replace(/{{error_description}}/g, params.error_description || "")
78
+ .replace(/{{error_uri}}/g, params.error_uri || "");
79
+
80
+ return renderError({
81
+ error: params.error,
82
+ error_description: params.error_description,
83
+ error_uri: params.error_uri,
84
+ });
86
85
  }
87
86
 
88
87
  /**
89
- * Bun runtime implementation using Bun.serve()
88
+ * Base class with shared logic for all runtime implementations.
90
89
  */
91
- class BunCallbackServer implements CallbackServer {
92
- private server: any; // Runtime-specific server type (Bun.Server)
93
- private callbackPromise?: {
94
- resolve: (result: CallbackResult) => void;
95
- reject: (error: Error) => void;
96
- };
97
- private callbackPath: string = "/callback";
98
- private successHtml?: string;
99
- private errorHtml?: string;
100
- private onRequest?: (req: Request) => void;
90
+ abstract class BaseCallbackServer implements CallbackServer {
91
+ // Use a Map to safely handle listeners for different paths.
92
+ // This is more robust than a single property, preventing potential race conditions.
93
+ protected callbackListeners = new Map<
94
+ string,
95
+ {
96
+ resolve: (result: CallbackResult) => void;
97
+ reject: (error: Error) => void;
98
+ }
99
+ >();
100
+ protected successHtml?: string;
101
+ protected errorHtml?: string;
102
+ protected onRequest?: (req: Request) => void;
101
103
  private abortHandler?: () => void;
104
+ private signal?: AbortSignal;
102
105
 
103
- async start(options: ServerOptions): Promise<void> {
104
- const {
105
- port,
106
- hostname = "localhost",
107
- successHtml,
108
- errorHtml,
109
- signal,
110
- onRequest,
111
- } = options;
106
+ // Abstract methods to be implemented by subclasses for runtime-specific logic.
107
+ public abstract start(options: ServerOptions): Promise<void>;
108
+ protected abstract stopServer(): Promise<void>;
112
109
 
110
+ /**
111
+ * Sets up common properties and handles the abort signal.
112
+ */
113
+ protected setup(options: ServerOptions): void {
114
+ const { successHtml, errorHtml, signal, onRequest } = options;
113
115
  this.successHtml = successHtml;
114
116
  this.errorHtml = errorHtml;
115
117
  this.onRequest = onRequest;
118
+ this.signal = signal;
116
119
 
117
- // Handle abort signal
118
- if (signal) {
119
- if (signal.aborted) {
120
- throw new Error("Operation aborted");
121
- }
122
- this.abortHandler = () => {
123
- this.stop();
124
- if (this.callbackPromise) {
125
- this.callbackPromise.reject(new Error("Operation aborted"));
126
- }
127
- };
128
- signal.addEventListener("abort", this.abortHandler);
129
- }
120
+ if (!signal) return;
121
+ if (signal.aborted) throw new Error("Operation aborted");
130
122
 
131
- // @ts-ignore - Bun global not available in TypeScript definitions
132
- this.server = Bun.serve({
133
- port,
134
- hostname,
135
- fetch: (request: Request) => this.handleRequest(request),
136
- });
123
+ // The abort handler now just calls stop(), which handles cleanup.
124
+ this.abortHandler = () => this.stop();
125
+ signal.addEventListener("abort", this.abortHandler);
137
126
  }
138
127
 
139
- private handleRequest(request: Request): Response {
140
- // Call onRequest callback if provided
141
- if (this.onRequest) {
142
- this.onRequest(request);
143
- }
128
+ /**
129
+ * Handles incoming HTTP requests using Web Standards APIs.
130
+ * This logic is the same for all runtimes.
131
+ */
132
+ protected handleRequest(request: Request): Response {
133
+ this.onRequest?.(request);
144
134
 
145
135
  const url = new URL(request.url);
136
+ const listener = this.callbackListeners.get(url.pathname);
146
137
 
147
- if (url.pathname === this.callbackPath) {
148
- const params: CallbackResult = {};
149
-
150
- // Parse all query parameters
151
- for (const [key, value] of url.searchParams) {
152
- params[key] = value;
153
- }
154
-
155
- // Resolve the callback promise
156
- if (this.callbackPromise) {
157
- this.callbackPromise.resolve(params);
158
- }
159
-
160
- // Return success or error HTML page
161
- return new Response(
162
- generateCallbackHTML(params, this.successHtml, this.errorHtml),
163
- {
164
- status: 200,
165
- headers: { "Content-Type": "text/html" },
166
- },
167
- );
168
- }
138
+ if (!listener) return new Response("Not Found", { status: 404 });
139
+
140
+ const params: CallbackResult = {};
141
+ for (const [key, value] of url.searchParams) params[key] = value;
142
+
143
+ // Resolve the promise for the waiting listener.
144
+ listener.resolve(params);
169
145
 
170
- return new Response("Not Found", { status: 404 });
146
+ return new Response(
147
+ generateCallbackHTML(params, this.successHtml, this.errorHtml),
148
+ {
149
+ status: 200,
150
+ headers: { "Content-Type": "text/html" },
151
+ },
152
+ );
171
153
  }
172
154
 
173
- async waitForCallback(
155
+ /**
156
+ * Waits for the OAuth callback on a specific path.
157
+ */
158
+ public async waitForCallback(
174
159
  path: string,
175
160
  timeout: number,
176
161
  ): Promise<CallbackResult> {
177
- this.callbackPath = path;
178
-
179
- return new Promise((resolve, reject) => {
180
- let isResolved = false;
181
-
182
- const timer = setTimeout(() => {
183
- if (!isResolved) {
184
- isResolved = true;
185
- this.callbackPromise = undefined;
186
- reject(
187
- new Error(
188
- `OAuth callback timeout after ${timeout}ms waiting for ${path}`,
189
- ),
190
- );
191
- }
192
- }, timeout);
193
-
194
- const wrappedResolve = (result: CallbackResult) => {
195
- if (!isResolved) {
196
- isResolved = true;
197
- clearTimeout(timer);
198
- this.callbackPromise = undefined;
199
- resolve(result);
200
- }
201
- };
202
-
203
- const wrappedReject = (error: Error) => {
204
- if (!isResolved) {
205
- isResolved = true;
206
- clearTimeout(timer);
207
- this.callbackPromise = undefined;
208
- reject(error);
209
- }
210
- };
162
+ if (this.callbackListeners.has(path))
163
+ return Promise.reject(
164
+ new Error(`A listener for the path "${path}" is already active.`),
165
+ );
211
166
 
212
- this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
213
- });
167
+ try {
168
+ // Race a promise that waits for the callback against a promise that rejects on timeout.
169
+ return await Promise.race([
170
+ // This promise is resolved or rejected by the handleRequest method.
171
+ new Promise<CallbackResult>((resolve, reject) => {
172
+ this.callbackListeners.set(path, { resolve, reject });
173
+ }),
174
+ // This promise rejects after the specified timeout.
175
+ new Promise<CallbackResult>((_, reject) => {
176
+ setTimeout(() => {
177
+ reject(
178
+ new Error(
179
+ `OAuth callback timeout after ${timeout}ms waiting for ${path}`,
180
+ ),
181
+ );
182
+ }, timeout);
183
+ }),
184
+ ]);
185
+ } finally {
186
+ // CRITICAL: Always clean up the listener to prevent memory leaks,
187
+ // regardless of whether the promise resolved or rejected.
188
+ this.callbackListeners.delete(path);
189
+ }
214
190
  }
215
191
 
216
- async stop(): Promise<void> {
217
- if (this.abortHandler) {
218
- // Remove abort handler if it was set
219
- const signal = this.server?.signal;
220
- if (signal) {
221
- signal.removeEventListener("abort", this.abortHandler);
222
- }
192
+ /**
193
+ * Stops the server and cleans up resources.
194
+ */
195
+ public async stop(): Promise<void> {
196
+ if (this.abortHandler && this.signal) {
197
+ this.signal.removeEventListener("abort", this.abortHandler);
223
198
  this.abortHandler = undefined;
224
199
  }
225
- if (this.callbackPromise) {
226
- this.callbackPromise.reject(
227
- new Error("Server stopped before callback received"),
228
- );
229
- this.callbackPromise = undefined;
230
- }
231
- if (this.server) {
232
- this.server.stop();
233
- this.server = undefined;
234
- }
200
+ // Reject any pending promises before stopping the server.
201
+ for (const listener of this.callbackListeners.values())
202
+ listener.reject(new Error("Server stopped before callback received"));
203
+
204
+ this.callbackListeners.clear();
205
+ await this.stopServer();
235
206
  }
236
207
  }
237
208
 
238
209
  /**
239
- * Deno runtime implementation using Deno.serve()
210
+ * Bun runtime implementation using Bun.serve().
240
211
  */
241
- class DenoCallbackServer implements CallbackServer {
242
- private server: any; // Runtime-specific server type (Deno.HttpServer)
243
- private callbackPromise?: {
244
- resolve: (result: CallbackResult) => void;
245
- reject: (error: Error) => void;
246
- };
247
- private callbackPath: string = "/callback";
248
- private abortController?: AbortController;
249
- private successHtml?: string;
250
- private errorHtml?: string;
251
- private onRequest?: (req: Request) => void;
252
- private abortHandler?: () => void;
212
+ class BunCallbackServer extends BaseCallbackServer {
213
+ private server?: Bun.Server<unknown>;
253
214
 
254
- async start(options: ServerOptions): Promise<void> {
255
- const {
215
+ public async start(options: ServerOptions): Promise<void> {
216
+ this.setup(options);
217
+ const { port, hostname = "localhost" } = options;
218
+
219
+ this.server = Bun.serve({
256
220
  port,
257
- hostname = "localhost",
258
- successHtml,
259
- errorHtml,
260
- signal,
261
- onRequest,
262
- } = options;
221
+ hostname,
222
+ fetch: (request: Request) => this.handleRequest(request),
223
+ });
224
+ }
263
225
 
264
- this.successHtml = successHtml;
265
- this.errorHtml = errorHtml;
266
- this.onRequest = onRequest;
226
+ protected async stopServer(): Promise<void> {
227
+ if (!this.server) return;
228
+ // Wait for pending requests to complete before stopping
229
+ while (this.server.pendingRequests > 0) {
230
+ await new Promise((resolve) => setTimeout(resolve, 10));
231
+ }
232
+ this.server.stop();
233
+ this.server = undefined;
234
+ }
235
+ }
267
236
 
237
+ /**
238
+ * Deno runtime implementation using Deno.serve().
239
+ */
240
+ class DenoCallbackServer extends BaseCallbackServer {
241
+ private abortController?: AbortController;
242
+
243
+ public async start(options: ServerOptions): Promise<void> {
244
+ this.setup(options);
245
+ const { port, hostname = "localhost" } = options;
268
246
  this.abortController = new AbortController();
269
247
 
270
- // Handle user's abort signal
271
- if (signal) {
272
- if (signal.aborted) {
273
- throw new Error("Operation aborted");
274
- }
275
- this.abortHandler = () => {
276
- this.abortController?.abort();
277
- if (this.callbackPromise) {
278
- this.callbackPromise.reject(new Error("Operation aborted"));
279
- }
280
- };
281
- signal.addEventListener("abort", this.abortHandler);
282
- }
248
+ // The user's signal will abort our internal controller.
249
+ options.signal?.addEventListener("abort", () =>
250
+ this.abortController?.abort(),
251
+ );
283
252
 
284
- // @ts-ignore - Deno global not available in TypeScript definitions
285
- this.server = Deno.serve(
253
+ Deno.serve(
286
254
  { port, hostname, signal: this.abortController.signal },
287
255
  (request: Request) => this.handleRequest(request),
288
256
  );
289
257
  }
290
258
 
291
- private handleRequest(request: Request): Response {
292
- // Call onRequest callback if provided
293
- if (this.onRequest) {
294
- this.onRequest(request);
295
- }
296
-
297
- const url = new URL(request.url);
298
-
299
- if (url.pathname === this.callbackPath) {
300
- const params: CallbackResult = {};
301
-
302
- // Parse all query parameters
303
- for (const [key, value] of url.searchParams) {
304
- params[key] = value;
305
- }
306
-
307
- // Resolve the callback promise
308
- if (this.callbackPromise) {
309
- this.callbackPromise.resolve(params);
310
- }
311
-
312
- // Return success or error HTML page
313
- return new Response(
314
- generateCallbackHTML(params, this.successHtml, this.errorHtml),
315
- {
316
- status: 200,
317
- headers: { "Content-Type": "text/html" },
318
- },
319
- );
320
- }
321
-
322
- return new Response("Not Found", { status: 404 });
323
- }
324
-
325
- async waitForCallback(
326
- path: string,
327
- timeout: number,
328
- ): Promise<CallbackResult> {
329
- this.callbackPath = path;
330
-
331
- return new Promise((resolve, reject) => {
332
- let isResolved = false;
333
-
334
- const timer = setTimeout(() => {
335
- if (!isResolved) {
336
- isResolved = true;
337
- this.callbackPromise = undefined;
338
- reject(
339
- new Error(
340
- `OAuth callback timeout after ${timeout}ms waiting for ${path}`,
341
- ),
342
- );
343
- }
344
- }, timeout);
345
-
346
- const wrappedResolve = (result: CallbackResult) => {
347
- if (!isResolved) {
348
- isResolved = true;
349
- clearTimeout(timer);
350
- this.callbackPromise = undefined;
351
- resolve(result);
352
- }
353
- };
354
-
355
- const wrappedReject = (error: Error) => {
356
- if (!isResolved) {
357
- isResolved = true;
358
- clearTimeout(timer);
359
- this.callbackPromise = undefined;
360
- reject(error);
361
- }
362
- };
363
-
364
- this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
365
- });
366
- }
367
-
368
- async stop(): Promise<void> {
369
- if (this.abortHandler) {
370
- // Remove abort handler if it was set
371
- const signal = this.server?.signal;
372
- if (signal) {
373
- signal.removeEventListener("abort", this.abortHandler);
374
- }
375
- this.abortHandler = undefined;
376
- }
377
- if (this.callbackPromise) {
378
- this.callbackPromise.reject(
379
- new Error("Server stopped before callback received"),
380
- );
381
- this.callbackPromise = undefined;
382
- }
383
- if (this.abortController) {
384
- this.abortController.abort();
385
- this.abortController = undefined;
386
- }
387
- this.server = undefined;
259
+ protected async stopServer(): Promise<void> {
260
+ if (!this.abortController) return;
261
+ this.abortController.abort();
262
+ this.abortController = undefined;
388
263
  }
389
264
  }
390
265
 
391
266
  /**
392
- * Node.js implementation using node:http with Web Standards APIs
393
- * Works with Node.js 18+ which has native Request/Response support
267
+ * Node.js implementation using node:http with Web Standards APIs.
394
268
  */
395
- class NodeCallbackServer implements CallbackServer {
396
- private server?: any; // Runtime-specific server type (http.Server)
397
- private callbackPromise?: {
398
- resolve: (result: CallbackResult) => void;
399
- reject: (error: Error) => void;
400
- };
401
- private callbackPath: string = "/callback";
402
- private successHtml?: string;
403
- private errorHtml?: string;
404
- private onRequest?: (req: Request) => void;
405
- private abortHandler?: () => void;
406
-
407
- async start(options: ServerOptions): Promise<void> {
408
- const {
409
- port,
410
- hostname = "localhost",
411
- successHtml,
412
- errorHtml,
413
- signal,
414
- onRequest,
415
- } = options;
416
-
417
- this.successHtml = successHtml;
418
- this.errorHtml = errorHtml;
419
- this.onRequest = onRequest;
420
-
421
- // Handle abort signal
422
- if (signal) {
423
- if (signal.aborted) {
424
- throw new Error("Operation aborted");
425
- }
426
- this.abortHandler = () => {
427
- this.stop();
428
- if (this.callbackPromise) {
429
- this.callbackPromise.reject(new Error("Operation aborted"));
430
- }
431
- };
432
- signal.addEventListener("abort", this.abortHandler);
433
- }
269
+ class NodeCallbackServer extends BaseCallbackServer {
270
+ private server?: HttpServer;
434
271
 
272
+ public async start(options: ServerOptions): Promise<void> {
273
+ this.setup(options);
274
+ const { port, hostname = "localhost" } = options;
435
275
  const { createServer } = await import("node:http");
436
276
 
437
277
  return new Promise((resolve, reject) => {
438
278
  this.server = createServer(async (req, res) => {
439
279
  try {
440
- // Convert Node.js IncomingMessage to Web Standards Request
441
- const request = this.nodeToWebRequest(req, port);
442
-
443
- // Handle request using Web Standards
444
- const response = await this.handleRequest(request);
280
+ const request = this.nodeToWebRequest(req, port, hostname);
281
+ const response = this.handleRequest(request);
445
282
 
446
- // Write Web Standards Response back to Node.js ServerResponse
447
283
  res.writeHead(
448
284
  response.status,
449
285
  Object.fromEntries(response.headers.entries()),
@@ -456,154 +292,58 @@ class NodeCallbackServer implements CallbackServer {
456
292
  }
457
293
  });
458
294
 
295
+ // Tie server closing to the abort signal if provided.
296
+ if (options.signal)
297
+ options.signal.addEventListener("abort", () => this.server?.close());
298
+
459
299
  this.server.listen(port, hostname, () => resolve());
460
300
  this.server.on("error", reject);
461
301
  });
462
302
  }
463
303
 
304
+ protected async stopServer(): Promise<void> {
305
+ if (!this.server) return;
306
+ this.server.closeAllConnections();
307
+ return new Promise((resolve) => {
308
+ this.server?.close(() => {
309
+ this.server = undefined;
310
+ resolve();
311
+ });
312
+ });
313
+ }
314
+
464
315
  /**
465
- * Convert Node.js IncomingMessage to Web Standards Request
316
+ * Converts a Node.js IncomingMessage to a Web Standards Request.
466
317
  */
467
- private nodeToWebRequest(req: any, port: number): Request {
468
- const url = new URL(req.url!, `http://localhost:${port}`);
318
+ private nodeToWebRequest(
319
+ req: IncomingMessage,
320
+ port: number,
321
+ hostname?: string,
322
+ ): Request {
323
+ const host = req.headers.host || `${hostname}:${port}`;
324
+ const url = new URL(req.url!, `http://${host}`);
469
325
 
470
- // Convert Node.js headers to Headers object
471
326
  const headers = new Headers();
472
327
  for (const [key, value] of Object.entries(req.headers)) {
473
- if (typeof value === "string") {
474
- headers.set(key, value);
475
- } else if (Array.isArray(value)) {
476
- headers.set(key, value.join(", "));
477
- }
328
+ if (typeof value === "string") headers.set(key, value);
329
+ else if (Array.isArray(value)) headers.set(key, value.join(", "));
478
330
  }
479
331
 
480
- // OAuth callbacks use GET requests without body
481
332
  return new Request(url.toString(), {
482
333
  method: req.method,
483
334
  headers,
484
335
  });
485
336
  }
486
-
487
- /**
488
- * Handle request using Web Standards APIs (same as Bun/Deno implementations)
489
- */
490
- private async handleRequest(request: Request): Promise<Response> {
491
- // Call onRequest callback if provided
492
- if (this.onRequest) {
493
- this.onRequest(request);
494
- }
495
-
496
- const url = new URL(request.url);
497
-
498
- if (url.pathname === this.callbackPath) {
499
- const params: CallbackResult = {};
500
-
501
- // Parse all query parameters
502
- for (const [key, value] of url.searchParams) {
503
- params[key] = value;
504
- }
505
-
506
- // Resolve the callback promise
507
- if (this.callbackPromise) {
508
- this.callbackPromise.resolve(params);
509
- }
510
-
511
- // Return success or error HTML page
512
- return new Response(
513
- generateCallbackHTML(params, this.successHtml, this.errorHtml),
514
- {
515
- status: 200,
516
- headers: { "Content-Type": "text/html" },
517
- },
518
- );
519
- }
520
-
521
- return new Response("Not Found", { status: 404 });
522
- }
523
-
524
- async waitForCallback(
525
- path: string,
526
- timeout: number,
527
- ): Promise<CallbackResult> {
528
- this.callbackPath = path;
529
-
530
- return new Promise((resolve, reject) => {
531
- let isResolved = false;
532
-
533
- const timer = setTimeout(() => {
534
- if (!isResolved) {
535
- isResolved = true;
536
- this.callbackPromise = undefined;
537
- reject(
538
- new Error(
539
- `OAuth callback timeout after ${timeout}ms waiting for ${path}`,
540
- ),
541
- );
542
- }
543
- }, timeout);
544
-
545
- const wrappedResolve = (result: CallbackResult) => {
546
- if (!isResolved) {
547
- isResolved = true;
548
- clearTimeout(timer);
549
- this.callbackPromise = undefined;
550
- resolve(result);
551
- }
552
- };
553
-
554
- const wrappedReject = (error: Error) => {
555
- if (!isResolved) {
556
- isResolved = true;
557
- clearTimeout(timer);
558
- this.callbackPromise = undefined;
559
- reject(error);
560
- }
561
- };
562
-
563
- this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
564
- });
565
- }
566
-
567
- async stop(): Promise<void> {
568
- if (this.abortHandler) {
569
- // Remove abort handler if it was set
570
- const signal = this.server?.signal;
571
- if (signal) {
572
- signal.removeEventListener("abort", this.abortHandler);
573
- }
574
- this.abortHandler = undefined;
575
- }
576
- if (this.callbackPromise) {
577
- this.callbackPromise.reject(
578
- new Error("Server stopped before callback received"),
579
- );
580
- this.callbackPromise = undefined;
581
- }
582
- if (this.server) {
583
- return new Promise((resolve) => {
584
- this.server.close(() => resolve());
585
- this.server = undefined;
586
- });
587
- }
588
- }
589
337
  }
590
338
 
591
339
  /**
592
- * Create a callback server for the current runtime (Bun, Deno, or Node.js)
593
- * Automatically detects the runtime and returns appropriate server implementation
594
- * @returns CallbackServer instance optimized for the current runtime
340
+ * Create a callback server for the current runtime (Bun, Deno, or Node.js).
341
+ * Automatically detects the runtime and returns the appropriate server implementation.
342
+ * @returns CallbackServer instance optimized for the current runtime.
595
343
  */
596
344
  export function createCallbackServer(): CallbackServer {
597
- // @ts-ignore - Bun global not available in TypeScript definitions
598
- if (typeof Bun !== "undefined") {
599
- return new BunCallbackServer();
600
- }
601
-
602
- // @ts-ignore - Deno global not available in TypeScript definitions
603
- if (typeof Deno !== "undefined") {
604
- return new DenoCallbackServer();
605
- }
345
+ if (typeof Bun !== "undefined") return new BunCallbackServer();
346
+ if (typeof Deno !== "undefined") return new DenoCallbackServer();
606
347
 
607
- // Default to Node.js
608
348
  return new NodeCallbackServer();
609
349
  }