diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 527a2ca1..8faa3fcc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: include: - os: windows-latest artifactName: activelogin-authentication-nuget-windows - - os: macos-latest + - os: macos-15-intel artifactName: activelogin-authentication-nuget-macos - os: ubuntu-latest artifactName: activelogin-authentication-nuget-ubuntu @@ -87,7 +87,7 @@ jobs: include: - os: windows-latest artifactName: activelogin-authentication-samples-windows - - os: macos-latest + - os: macos-15-intel artifactName: activelogin-authentication-samples-macos - os: ubuntu-latest artifactName: activelogin-authentication-samples-ubuntu diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79c182f1..7e607b80 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: matrix: include: - os: windows-latest - - os: macos-latest + - os: macos-15-intel - os: ubuntu-latest steps: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 83d69be0..e0123a26 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,9 +21,6 @@ stages: Windows: vmImage: 'windows-latest' artifactName: 'nuget-windows' - macOS: - vmImage: 'macOS-latest' - artifactName: 'nuget-macos' Linux: vmImage: 'ubuntu-latest' artifactName: 'nuget-linux' @@ -99,9 +96,6 @@ stages: Windows: vmImage: 'windows-latest' artifactName: 'samples-windows' - macOS: - vmImage: 'macOS-latest' - artifactName: 'samples-macos' Linux: vmImage: 'ubuntu-latest' artifactName: 'samples-linux' diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Client/activelogin-main.ts b/src/ActiveLogin.Authentication.BankId.AspNetCore/Client/activelogin-main.ts index 9353f876..1439fd20 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Client/activelogin-main.ts +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Client/activelogin-main.ts @@ -23,6 +23,13 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState: const fetchRetryCountDefault: number = 3; const fetchRetryDelayMs: number = 1000; + const retryOnHttpErrors = { + initialize: true, + status: false, // A second call to the BankID collect endpoint is not supported after a status complete or failed is returned. + qr: true, + cancel: true + }; + // Pre check const requiredFeatures = [window.fetch, window.sessionStorage]; @@ -123,7 +130,7 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState: requestVerificationToken, { "returnUrl": returnUrl - }, fetchRetryCountDefault) + }, fetchRetryCountDefault, retryOnHttpErrors.initialize) .then(data => { if (data.isAutoLaunch) { if (!data.checkStatus) { @@ -177,7 +184,9 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState: "orderRef": orderRef, "returnUrl": returnUrl, "autoStartAttempts": autoStartAttempts - }, fetchRetryCountDefault) + }, + fetchRetryCountDefault, + retryOnHttpErrors.status) .then(data => { if (data.retryLogin) { autoStartAttempts++; @@ -227,7 +236,7 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState: requestVerificationToken, { "qrStartState": qrStartState - }, fetchRetryCountDefault) + }, fetchRetryCountDefault, retryOnHttpErrors.qr) .then(data => { if (!!data.qrCodeAsBase64) { qrLastRefreshTimestamp = new Date(); @@ -278,7 +287,13 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState: // Helpers - function postJson(url: string, requestVerificationToken: string, data: any, retryCount: number = 0): Promise { + function postJson(url: string, requestVerificationToken: string, data: any, remaingRetryCount: number = 0, retryOnHttpError: boolean = true): Promise { + + const retry = () => { + return delay(fetchRetryDelayMs) + .then(() => postJson(url, requestVerificationToken, data, remaingRetryCount - 1, retryOnHttpError)); + }; + return fetch(url, { method: "POST", @@ -290,22 +305,10 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState: credentials: 'include', body: JSON.stringify(data) }) - .catch(error => { - if (retryCount > 0) { - return delay(fetchRetryDelayMs).then(() => { - return postJson(url, requestVerificationToken, data, retryCount - 1); - }); - } - - throw error; - }) .then(response => { - if (!response.ok && retryCount > 0) { - return delay(fetchRetryDelayMs).then(() => { - return postJson(url, requestVerificationToken, data, retryCount - 1) - }); + if (!response.ok && retryOnHttpError && remaingRetryCount > 0) { + return retry(); } - return response; }) .then(response => { @@ -321,6 +324,25 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState: throw Error(data.errorMessage); } return data; + }) + .catch(error => { + // A TypeError here means fetch failed before receiving any HTTP response + // (network down, DNS error, CORS block, connection aborted, etc.). + // These failures are transient and safe to retry. + const isNetworkError = error instanceof TypeError; + + // Other error types should NOT be retried here: + // - HTTP-level failures (e.g., non-2xx status codes) are already handled in the + // previous `.then` block, where retry behavior is controlled by `retryOnHttpError`. + // - Any error thrown after that stage represents a *valid server response*, + // meaning the request reached the API and should not be repeated. + // Retrying such cases here could cause duplicated API calls or break BankID flows + // (e.g.collect/status), which do not allow repeated calls after a completed/failed state. + if (isNetworkError && remaingRetryCount > 0) { + return retry(); + } + + throw error; }); }