• Registers IPC event listeners for all authentication-related operations. Handles OAuth flow, credentials management, and token exchange.

    Parameters

    • mainWindow: BrowserWindow

      The main application browser window.

    Returns void

    export function addAuthEventListeners(mainWindow: BrowserWindow) {
    // Open the OAuth window when requested by the renderer
    secureHandle(
    "auth:openOAuthWindow",
    async (event, oauthUrl: string, redirectUri: string) => {
    return withGroupAsync(
    `[AuthIPC] OAuth Flow: ${redirectUri}`,
    async () => {
    try {
    // Reset cancellation flag
    authCancelled = false;

    // Validate OAuth URL and parameters EARLY before allocating any resources
    const validation = validateOAuthUrl(oauthUrl, redirectUri);
    if (!validation.valid) {
    console.error(
    `[AuthIPC] ❌ OAuth URL validation failed: ${validation.error}`,
    );
    mainWindow.webContents.send(
    "auth:status",
    `Security error: ${validation.error}`,
    );
    return {
    success: false,
    error: validation.error,
    };
    }

    console.info(
    `[AuthIPC] ✅ OAuth URL validated: ${new URL(oauthUrl).hostname}`,
    );

    // Extract redirect URI parts
    const redirectUrl = new URL(redirectUri);
    // Use a non-privileged port by default
    const port = redirectUrl.port || DEFAULT_PORT.toString();

    // Update the redirectUri with our port if none was specified
    if (!redirectUrl.port) {
    redirectUrl.port = port;
    const updatedRedirectUri = redirectUrl.toString();

    // If the redirect URI in the oauth URL doesn't match the updated one,
    // we need to update the oauth URL too
    if (redirectUri !== updatedRedirectUri) {
    const oauthUrlObj = new URL(oauthUrl);
    const redirectParam =
    oauthUrlObj.searchParams.get("redirect_uri");
    if (redirectParam) {
    oauthUrlObj.searchParams.set(
    "redirect_uri",
    updatedRedirectUri,
    );
    oauthUrl = oauthUrlObj.toString();
    }
    }

    // Update the redirect URI to include the port
    redirectUri = updatedRedirectUri;
    }

    // Start the temporary HTTP server first
    try {
    await startAuthServer(port, redirectUrl.pathname, mainWindow);

    // Send status update
    mainWindow.webContents.send(
    "auth:status",
    `Server started on port ${port}, opening browser for authentication...`,
    );

    // IMPORTANT: Set up the auth code promise AFTER server is started
    const authCodePromise = new Promise<string>((resolve, reject) => {
    authResolve = resolve;
    authReject = reject;

    // Set timeout for the entire auth process and store handle for cleanup
    loadTimeout = createAuthTimeout();
    });

    // Open the authorization URL in the default browser
    await shell.openExternal(oauthUrl);

    // Notify the user about the browser
    mainWindow.webContents.send(
    "auth:status",
    "Browser opened for authentication. Please complete the process in your browser.",
    );

    // Set up the background handling of the auth code
    // This needs to be done after we return the response
    // to avoid the "reply was never sent" error
    setTimeout(() => {
    handleAuthCodePromise(authCodePromise, mainWindow, redirectUri);
    }, 100);

    // IMPORTANT: Return success immediately so the IPC call resolves
    // The actual code handling will happen via the auth:codeReceived event
    return { success: true };
    } catch (serverError) {
    const errorMessage =
    serverError instanceof Error
    ? serverError.message
    : "Failed to start authentication server";
    console.error("[AuthIPC] Server error:", serverError);
    mainWindow.webContents.send(
    "auth:status",
    `Authentication error: ${errorMessage}`,
    );
    return { success: false, error: errorMessage };
    }
    } catch (error: unknown) {
    const errorMessage =
    error instanceof Error ? error.message : "Unknown error";
    console.error("[AuthIPC] Failed to open OAuth window:", error);
    cleanupAuthServer();
    return { success: false, error: errorMessage };
    }
    },
    );
    },
    mainWindow,
    );

    // Handle storing and retrieving API credentials
    secureHandle(
    "auth:storeCredentials",
    async (event, credentials: AuthCredentials) => {
    try {
    console.debug(
    "[AuthIPC] Storing credentials for source:",
    credentials?.source,
    );
    // Store the credentials in memory
    if (credentials?.source) {
    storedCredentials[credentials.source] = credentials;
    }
    return { success: true };
    } catch (error: unknown) {
    const errorMessage =
    error instanceof Error ? error.message : "Unknown error";
    console.error("[AuthIPC] Failed to store credentials:", error);
    return { success: false, error: errorMessage };
    }
    },
    mainWindow,
    );

    // Get stored credentials
    secureHandle(
    "auth:getCredentials",
    async (event, source: string) => {
    try {
    console.debug("[AuthIPC] Retrieving credentials for source:", source);
    const credentials = storedCredentials[source];

    if (!credentials) {
    return {
    success: false,
    error: `No credentials found for source: ${source}`,
    };
    }

    return {
    success: true,
    credentials,
    };
    } catch (error: unknown) {
    const errorMessage =
    error instanceof Error ? error.message : "Unknown error";
    console.error("[AuthIPC] Failed to retrieve credentials:", error);
    return { success: false, error: errorMessage };
    }
    },
    mainWindow,
    );

    // Add a way to manually cancel auth
    secureHandle(
    "auth:cancel",
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    (event) => {
    authCancelled = true;
    authReject?.(new Error("Authentication cancelled by user"));
    cleanupAuthServer();
    return { success: true };
    },
    mainWindow,
    );

    // Add a handler to exchange auth code for token in the main process
    // This avoids network issues that can happen in the renderer process
    secureHandle(
    "auth:exchangeToken",
    async (
    _event,
    params: {
    code: string;
    clientId: string;
    clientSecret: string;
    redirectUri: string;
    },
    ) => {
    return withGroupAsync(
    `[AuthIPC] Token Exchange (${params.clientId.substring(0, 8)}...)`,
    async () => {
    try {
    const { clientId, clientSecret, redirectUri, code } = params;

    // Validate parameters
    const validation = validateTokenExchangeParams({
    clientId,
    clientSecret,
    redirectUri,
    code,
    });
    if (!validation.isValid) {
    return { success: false, error: validation.error };
    }

    console.info("[AuthIPC] Exchanging token in main process:", {
    clientIdLength: clientId.length,
    redirectUri,
    codeLength: code.length,
    });

    const result = await exchangeTokenWithRetries(
    { clientId, clientSecret, redirectUri, code },
    3,
    );
    return result;
    } catch (error) {
    const errorMessage =
    error instanceof Error ? error.message : String(error);
    console.error(
    "[AuthIPC] Token exchange handler error:",
    errorMessage,
    );
    return { success: false, error: errorMessage };
    }
    },
    );
    },
    mainWindow,
    );
    }