Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState:

// QR

var qrLastRefreshTimestamp: Date = null;
var qrIsRefreshing = false;
var qrRefreshTimeoutId: number = null;
var qrRefreshIntervalId: number | null = null;

// OrderRef

Expand All @@ -63,6 +62,15 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState:
const uiResultForm = <HTMLFormElement>document.querySelector("form[name=activelogin-bankid-ui--result-form]");
const uiResultInput = <HTMLInputElement>uiResultForm.querySelector("input[name=uiResult]");

// Flow control flags
var autoStartAttempts = 0;
var flowIsCancelledByUser = false;
var flowIsFinished = false;

// Polling state
let statusPollingActive = false;
let currentOrderRef: string | null = null;

// Events

if (sessionOrderRef) {
Expand Down Expand Up @@ -108,19 +116,14 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState:
hide(qrCodeElement);
}

// BankID

var autoStartAttempts = 0;
var flowIsCancelledByUser = false;
var flowIsFinished = false;

function enableCancelButton(requestVerificationToken: string, cancelUrl: string, protectedUiOptions: string, orderRef: string = null) {
function enableCancelButton(requestVerificationToken: string, cancelUrl: string, protectedUiOptions: string, orderRef: string | null = null) {
var onCancelButtonClick = (event: Event) => {
cancel(requestVerificationToken, cancelUrl, protectedUiOptions, orderRef);
event.target.removeEventListener("click", onCancelButtonClick);
event.target?.removeEventListener("click", onCancelButtonClick);
};
cancelButtonElement.addEventListener("click", onCancelButtonClick);
}

function initialize(requestVerificationToken: string, returnUrl: string, cancelUrl: string, protectedUiOptions: string) {
flowIsCancelledByUser = false;

Expand All @@ -140,7 +143,7 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState:
var startBankIdAppButtonOnClick = (event: Event) => {
window.location.href = data.redirectUri;
hide(startBankIdAppButtonElement);
event.target.removeEventListener("click", startBankIdAppButtonOnClick);
event.target?.removeEventListener("click", startBankIdAppButtonOnClick);
};
startBankIdAppButtonElement.addEventListener("click", startBankIdAppButtonOnClick);

Expand All @@ -152,8 +155,7 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState:

if (!!data.qrStartState && !!data.qrCodeAsBase64) {
setQrCode(data.qrCodeAsBase64);
qrLastRefreshTimestamp = new Date();
refreshQrCode(requestVerificationToken, data.qrStartState);
startQrCodeRefresh(requestVerificationToken, data.qrStartState);
}

enableCancelButton(requestVerificationToken, cancelUrl, protectedUiOptions, data.orderRef);
Expand All @@ -172,101 +174,113 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState:
});
}

// Refactored: non-recursive status polling starter keeping original API name
function checkStatus(requestVerificationToken: string, returnUrl: string, protectedUiOptions: string, orderRef: string) {
if (flowIsCancelledByUser || flowIsFinished) {
return;
currentOrderRef = orderRef;
if (statusPollingActive) {
return; // Avoid starting multiple loops
}

postJson(configuration.bankIdStatusApiUrl,
requestVerificationToken,
{
"orderRef": orderRef,
"returnUrl": returnUrl,
"uiOptions": protectedUiOptions,
"autoStartAttempts": autoStartAttempts
}, fetchRetryCountDefault)
.then(data => {
if (data.retryLogin) {
autoStartAttempts++;
login();
} else if (data.isFinished) {
flowIsFinished = true;
clearTimeout(qrRefreshTimeoutId);
statusPollingActive = true;
(async () => {
while (!flowIsCancelledByUser && !flowIsFinished && currentOrderRef === orderRef) {
try {
const data = await postJson(configuration.bankIdStatusApiUrl,
requestVerificationToken,
{
"orderRef": orderRef,
"returnUrl": returnUrl,
"uiOptions": protectedUiOptions,
"autoStartAttempts": autoStartAttempts
},
fetchRetryCountDefault);

if (data.retryLogin) {
autoStartAttempts++;
statusPollingActive = false; // Stop current loop before re-login
login();
return;
} else if (data.isFinished) {
flowIsFinished = true;
stopQrCodeRefresh();
hide(qrCodeElement);
uiResultForm.setAttribute("action", data.redirectUri);
uiResultInput.value = data.result;
uiResultForm.submit();
return;
} else if (!flowIsCancelledByUser) {
autoStartAttempts = 0;
showProgressStatus(data.statusMessage);
await delay(configuration.statusRefreshIntervalMs);
}
} catch (error: any) {
stopQrCodeRefresh();
if (!flowIsCancelledByUser) {
showErrorStatus(error.message);
hide(startBankIdAppButtonElement);
}
hide(qrCodeElement);

uiResultForm.setAttribute("action", data.redirectUri);
uiResultInput.value = data.result;
uiResultForm.submit();
} else if (!flowIsCancelledByUser) {
autoStartAttempts = 0;
showProgressStatus(data.statusMessage);
setTimeout(() => {
checkStatus(requestVerificationToken, returnUrl, protectedUiOptions, orderRef);
}, configuration.statusRefreshIntervalMs);
}
})
.catch(error => {
clearTimeout(qrRefreshTimeoutId);
if (!flowIsCancelledByUser) {
showErrorStatus(error.message);
hide(startBankIdAppButtonElement);
statusPollingActive = false;
return;
}
hide(qrCodeElement);
});
}
statusPollingActive = false;
})();
}

function refreshQrCode(requestVerificationToken: string, qrStartState: string) {
if (flowIsCancelledByUser || flowIsFinished || qrIsRefreshing) {
return;
}
// Refactored: use setInterval instead of recursive timeouts
function startQrCodeRefresh(requestVerificationToken: string, qrStartState: string) {
stopQrCodeRefresh(); // Ensure single interval
qrRefreshIntervalId = window.setInterval(() => {
if (flowIsCancelledByUser || flowIsFinished) {
stopQrCodeRefresh();
return;
}
if (qrIsRefreshing) {
return;
}
qrIsRefreshing = true;
postJson(configuration.bankIdQrCodeApiUrl,
requestVerificationToken,
{
"qrStartState": qrStartState
}, fetchRetryCountDefault)
.then(data => {
if (!!data.qrCodeAsBase64) {
setQrCode(data.qrCodeAsBase64);
}
})
.catch(error => {
if (flowIsFinished) {
return;
}
if (!flowIsCancelledByUser) {
showErrorStatus(error.message);
hide(startBankIdAppButtonElement);
}
hide(qrCodeElement);
stopQrCodeRefresh();
})
.finally(() => {
qrIsRefreshing = false;
});
}, configuration.qrCodeRefreshIntervalMs);
}

const currentTime = new Date();
const timeSinceLastRefresh = currentTime.getTime() - qrLastRefreshTimestamp.getTime();
if (timeSinceLastRefresh < configuration.qrCodeRefreshIntervalMs) {
qrRefreshTimeoutId = setTimeout(() => {
refreshQrCode(requestVerificationToken, qrStartState);
}, configuration.qrCodeRefreshIntervalMs);
return;
function stopQrCodeRefresh() {
if (qrRefreshIntervalId !== null) {
clearInterval(qrRefreshIntervalId);
qrRefreshIntervalId = null;
}
qrIsRefreshing = true;

postJson(configuration.bankIdQrCodeApiUrl,
requestVerificationToken,
{
"qrStartState": qrStartState
}, fetchRetryCountDefault)
.then(data => {
if (!!data.qrCodeAsBase64) {
qrLastRefreshTimestamp = new Date();
setQrCode(data.qrCodeAsBase64);
qrRefreshTimeoutId = setTimeout(() => {
refreshQrCode(requestVerificationToken, qrStartState);
}, configuration.qrCodeRefreshIntervalMs);
}
})
.catch(error => {
if (flowIsFinished) {
return;
}

if (!flowIsCancelledByUser) {
showErrorStatus(error.message);
hide(startBankIdAppButtonElement);
}
hide(qrCodeElement);
})
.finally(() => {
qrIsRefreshing = false;
});
}

function setQrCode(qrCodeAsBase64: string) {
qrCodeElement.src = 'data:image/png;base64, ' + qrCodeAsBase64;
show(qrCodeElement);
}

function cancel(requestVerificationToken: string, cancelReturnUrl: string, protectedUiOptions: string, orderRef: string = null) {
function cancel(requestVerificationToken: string, cancelReturnUrl: string, protectedUiOptions: string, orderRef: string | null = null) {
flowIsCancelledByUser = true;
stopQrCodeRefresh();

if (!orderRef) {
window.location.href = cancelReturnUrl;
Expand All @@ -286,50 +300,48 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState:

// Helpers

function postJson(url: string, requestVerificationToken: string, data: any, retryCount: number = 0): Promise<any> {
return fetch(url,
{
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"RequestVerificationToken": requestVerificationToken
},
credentials: 'include',
body: JSON.stringify(data)
})
.catch(error => {
if (retryCount > 0) {
return delay(fetchRetryDelayMs).then(() => {
return postJson(url, requestVerificationToken, data, retryCount - 1);
});
// Refactored: iterative retry instead of recursion
async function postJson(url: string, requestVerificationToken: string, data: any, retryCount: number = 0): Promise<any> {
for (let attempt = 0; attempt <= retryCount; attempt++) {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"RequestVerificationToken": requestVerificationToken
},
credentials: 'include',
body: JSON.stringify(data)
});

if (!response.ok) {
if (attempt < retryCount) {
await delay(fetchRetryDelayMs);
continue;
}
throw new Error(response.statusText || configuration.unknownErrorMessage);
}

throw error;
})
.then(response => {
if (!response.ok && retryCount > 0) {
return delay(fetchRetryDelayMs).then(() => {
return postJson(url, requestVerificationToken, data, retryCount - 1)
});
const contentType = response.headers.get("content-type") || "";
if (!contentType.includes("application/json")) {
throw Error(configuration.unknownErrorMessage);
}

return response;
})
.then(response => {
const contentType = response.headers.get("content-type");
if (contentType && contentType.indexOf("application/json") !== -1) {
return response.json();
const json = await response.json();
if (!!json.errorMessage) {
throw Error(json.errorMessage);
}

throw Error(configuration.unknownErrorMessage);
})
.then(data => {
if (!!data.errorMessage) {
throw Error(data.errorMessage);
return json;
} catch (err) {
if (attempt < retryCount) {
await delay(fetchRetryDelayMs);
continue;
}
return data;
});
throw err;
}
}
throw new Error(configuration.unknownErrorMessage); // Fallback (should not reach)
}

function showProgressStatus(status: string) {
Expand All @@ -347,16 +359,16 @@ function activeloginInit(configuration: IBankIdUiScriptConfiguration, initState:
show(statusWrapperElement);
}

function setVisibility(element: HTMLElement, visible: boolean, display: string = null) {
function setVisibility(element: HTMLElement, visible: boolean, display: string | null = null) {
if (visible) {
show(element, display);
} else {
hide(element);
}
}

function show(element: HTMLElement, display: string = "block") {
if (!element) {
function show(element: HTMLElement, display: string | null = "block") {
if (!element || display === null) {
return;
}

Expand Down