Skip to content

Commit

Permalink
fix: clear timeout if token retrieved successfully
Browse files Browse the repository at this point in the history
This uses the promise based version of `setTimeout` from NodeJS and
registers the AbortController to handle cancellation signal. The http
server `.close()` method is also registered to the abort controller for
cleanup as `controller.abort()` is always called before returning the
result.
  • Loading branch information
cmackenzie1 committed Jan 6, 2025
1 parent 09ba266 commit 92842a6
Showing 1 changed file with 22 additions and 16 deletions.
38 changes: 22 additions & 16 deletions packages/wrangler/src/pipelines/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import assert from "node:assert";
import { createHash } from "node:crypto";
import http from "node:http";
import { setTimeout as setTimeoutPromise } from "node:timers/promises";
import { fetchResult } from "../cfetch";
import { getCloudflareApiEnvironmentFromEnv } from "../environment-variables/misc-variables";
import { UserError } from "../errors";
Expand Down Expand Up @@ -126,22 +127,15 @@ export async function generateR2ServiceToken(
pipelineName: string
): Promise<S3AccessKey> {
// TODO: Refactor into startHttpServerWithTimeout function and update `getOauthToken`
let server: http.Server;
let loginTimeoutHandle: ReturnType<typeof setTimeout>;
const timerPromise = new Promise<S3AccessKey>((_, reject) => {
loginTimeoutHandle = setTimeout(() => {
server.close();
clearTimeout(loginTimeoutHandle);
reject(
new UserError(
"Timed out waiting for authorization code, please try again."
)
);
}, 120000); // wait for 120 seconds for the user to authorize
});
const controller = new AbortController();
const signal = controller.signal;

// Create timeout promise to prevent hanging forever
const timeoutPromise = setTimeoutPromise(120000, "timeout", { signal });

const loginPromise = new Promise<S3AccessKey>((resolve, reject) => {
server = http.createServer(async (request, response) => {
// Create server promise to handle the callback and register the cleanup handler on the controller
const serverPromise = new Promise<S3AccessKey>((resolve, reject) => {
const server = http.createServer(async (request, response) => {
assert(request.url, "This request doesn't have a URL"); // This should never happen

if (request.method !== "GET") {
Expand Down Expand Up @@ -179,6 +173,10 @@ export async function generateR2ServiceToken(
response.end();
});

// Register cleanup handler
signal.addEventListener("abort", () => {
server.close();
});
server.listen(8976, "localhost");
});

Expand All @@ -192,7 +190,15 @@ export async function generateR2ServiceToken(
logger.log(`Opening a link in your default browser: ${urlToOpen}`);
await openInBrowser(urlToOpen);

return Promise.race([timerPromise, loginPromise]);
const result = await Promise.race([timeoutPromise, serverPromise]);
controller.abort();
if (result === "timeout") {
throw new UserError(
"Timed out waiting for authorization code, please try again."
);
}

return result as S3AccessKey;
}

// Get R2 bucket information from v4 API
Expand Down

0 comments on commit 92842a6

Please sign in to comment.