/** bookmarklet.js * This is the code for the bookmarklet that captures session data from sparkron.dk * and sends it to the server. */ (async function () { // DO-NOT-EDIT-INJECTION-POINT class AccountList { constructor(accounts) { this.accounts = accounts.map(account => new Account(account)); } count() { return this.accounts.length; } get(index) { if (index < 0 || index >= this.accounts.length) { throw new Error('Index out of bounds'); } return this.accounts[index]; } getKey() { if (this.accounts.length === 0) { throw new Error('No accounts found'); } return this.accounts[0].accountCombinedKey; } } class Account { accountCombinedKey; accountName; productType; productTypeDescription; accountNumber; agreementCombinedKey; isDefault; bicCode; isInvestmentAccount; lastPosting; constructor(data) { this.accountCombinedKey = data.accountCombinedKey; this.accountName = data.accountName; this.productType = data.productType; this.productTypeDescription = data.productTypeDescription; this.accountNumber = data.accountNumber; this.agreementCombinedKey = data.agreementCombinedKey; this.isDefault = data.isDefault; this.bicCode = data.bicCode; this.isInvestmentAccount = data.isInvestmentAccount; this.lastPosting = new Date(data.lastPosting); } } class TransactionList { constructor(transactions, scrollKey) { // Example of a scroll key // 2025-08-13-02.50.18.8225352025-08-132025-08-13000000000000000 0000000047193.532026-02-20-16.36.20.863120@400" this.scrollKey = scrollKey; this.transactions = transactions .map(transaction => new Transaction(transaction)); } } class TransactionAmount { amount; currencyCode; constructor(data) { this.amount = new Amount(data.amount); this.currencyCode = data.currencyCode; } } class Amount { decimalValue integerValue noOfDecimals constructor(data) { this.decimalValue = data.decimalValue; this.integerValue = data.integerValue; this.noOfDecimals = data.noOfDecimals; } } class Transaction { accountCombinedKey; transactionCombinedKey; bookingDate; valueDate; originalDate; availableDate; transactionText; individualText; statementText; transactionAmount; transactionType; corebankTimestamp cardDetails; isReconciled; constructor(data) { this.accountCombinedKey = data.accountCombinedKey; this.transactionCombinedKey = data.transactionCombinedKey; this.bookingDate = new Date(data.bookingDate); this.valueDate = new Date(data.valueDate); this.originalDate = new Date(data.originalDate); this.availableDate = new Date(data.availableDate); this.transactionText = data.transactionText; this.individualText = data.individualText; this.statementText = data.statementText; this.transactionAmount = new TransactionAmount(data.transactionAmount); this.transactionType = data.transactionType; this.corebankTimestamp = new Date(data.corebankTimestamp); this.cardDetails = data.cardDetails ? new CardDetails(data.cardDetails) : null; this.isReconciled = data.isReconciled; } } class CardDetails { cardTransactionTime; cardProductType; terminalId; merchant; constructor(data) { this.cardTransactionTime = new Date(data.cardTransactionTime); this.cardProductType = data.cardProductType; this.terminalId = data.terminalId; this.merchant = new Merchant(data.merchant); } } class Merchant { id; categoryCode; name; city; country; constructor(data) { this.id = data.id; this.categoryCode = data.categoryCode; this.name = data.name; this.city = data.city; this.country = data.country; } } class AccountConfig { lastPosting; constructor(data) { data = data || {}; this.lastPosting = data.lastPosting ? new Date(data.lastPosting) : null; } } function getHeaders() { const bearerToken = document.cookie.split('; ').find(row => row.startsWith('sdc_token=')).split('=')[1]; return { 'Content-Type': 'application/json', 'ocp-apim-subscription-key': '0515a3c47acc472ba340219c0a24628a', 'Authorization': `Bearer ${bearerToken}` } } const baseHeaders = getHeaders(); async function getTransactions(accountCombinedKey, fromDate) { const url = 'https://api-proxy-neos.sdc.eu/api/neos/ebanking-account/v1/api/transactions/list'; const body = { "accountCombinedKey": accountCombinedKey, // We use a page size of 100 "numTransPerPage": 100, // "searchArguments": { "fromDate": "2026-01-18" }, // "applyRegExp": true, "includeSenderInfo": true } console.log('Fetching transactions for account:', accountCombinedKey, 'from date:', fromDate); if (fromDate) { body.searchArguments = { fromDate: fromDate.toISOString().split('T')[0] } } const allTransactions = []; let scrollKey = null; while (true) { // We iterate with the last scrollKey used // this is the pagination cursor. // Each request gives a pointer to older records body.scrollKey = scrollKey; const resp = await fetch(url, { method: 'POST', headers: baseHeaders, body: JSON.stringify(body) }); if (!resp.ok) { console.error('Failed to fetch transactions:', resp.statusText); return []; } const data = await resp.json(); allTransactions.push(...(data.transactionList || [])); scrollKey = data.scrollKey; if (!data.scrollKey) { break; // No more transactions to fetch } } return new TransactionList(allTransactions, scrollKey); } async function listAccounts() { // Use the sdc_token cookie as the bearer token for auth. const bearerToken = document.cookie.split('; ').find(row => row.startsWith('sdc_token=')).split('=')[1]; const response = await fetch('https://api-proxy-neos.sdc.eu/api/neos/ebanking-account/v1/api/accounts/list', { method: 'POST', headers: baseHeaders, body: JSON.stringify({ "selfServiceAgreement": { }, "includeDataAboutBalances": true, "includeDataAboutLastPosting": true, "includeDataAboutOtherAccounts": true, "includeDataAboutOwners": true }) }); if (!response.ok) { console.error('Failed to fetch accounts:', response.statusText); return []; } const data = await response.json(); return new AccountList(data.accountList || []); } let accountsConfig = {"e2NsZWFyaW5nOiI2MTk0IixhY2NvdW50OiI5MzM1MzgwNDUwMTk5ODEiLGFncmVlbWVudDoiNjEyNDgyMDk2MDEyOTIyIix1c2VyOiI2MTI0OTUzMTIzNiJ9":{"lastPosting":"2026-03-06T00:00:00.000Z"},"e2NsZWFyaW5nOiI2MTk0IixhY2NvdW50OiI5MzM1NjkwNDUwMTk5ODEiLGFncmVlbWVudDoiNjEyNDgyMDk2MDEyOTIyIix1c2VyOiI2MTI0OTUzMTIzNiJ9":{"lastPosting":"2026-03-05T00:00:00.000Z"},"e2NsZWFyaW5nOiI2MTk0IixhY2NvdW50OiI2MTI0MjAxNDgwMTcxMDMiLGFncmVlbWVudDoiNjEyNDgyMDk2MDEyOTIyIix1c2VyOiI2MTI0OTUzMTIzNiJ9":{"lastPosting":"2026-01-30T00:00:00.000Z"},"e2NsZWFyaW5nOiI2MTk0IixhY2NvdW50OiI2MTI0MjAxNDgwMTcxMDMiLGFncmVlbWVudDoiNjEyNDIzMDk2MDEyOTI0Iix1c2VyOiI2MTI0OTUzMTIzNyJ9":{"lastPosting":"2026-02-27T00:00:00.000Z"},"e2NsZWFyaW5nOiI2MTk0IixhY2NvdW50OiI5MzM1NjkwNDUwMTk5ODEiLGFncmVlbWVudDoiNjEyNDIzMDk2MDEyOTI0Iix1c2VyOiI2MTI0OTUzMTIzNyJ9":{"lastPosting":"2026-02-27T00:00:00.000Z"},"e2NsZWFyaW5nOiI2MTk0IixhY2NvdW50OiI5MzM1NzAwNDUwMTk5OTAiLGFncmVlbWVudDoiNjEyNDIzMDk2MDEyOTI0Iix1c2VyOiI2MTI0OTUzMTIzNyJ9":{"lastPosting":"2026-03-02T00:00:00.000Z"},"4ce736c5-c138-f111-ab06-00109bb75668":{"lastPosting":"2026-03-31T00:00:00.000Z"},"4de736c5-c138-f111-ab06-00109bb75668":{"lastPosting":"2026-04-15T00:00:00.000Z"},"4ee736c5-c138-f111-ab06-00109bb75668":{"lastPosting":"2026-04-13T00:00:00.000Z"}}; // This will be replaced by the server with the actual accounts config accountsConfig = accountsConfig || {}; const accounts = await listAccounts(); const summary = {} for (let accI = 0; accI < accounts.count(); accI++) { const acc = accounts.get(accI); const config = new AccountConfig(accountsConfig[acc.accountCombinedKey]); const txContainer = await getTransactions(acc.accountCombinedKey, config.lastPosting); summary[acc.accountCombinedKey] = { account: acc, transactions: txContainer.transactions }; } const backend = "https://scribe.tuzelche.com"; // This will be replaced by the server with the actual backend URL const dumpResponse = await fetch(`${backend}/accounts-dump`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(summary) }); const dumpJson = await dumpResponse.json(); alert(dumpJson.message); })();