@actual-app/sync-server 26.6.0-nightly.20260601 → 26.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-DSbHHHGd.js","names":["app","app","UserService.getOwnerCount","UserService.getAllUsers","UserService.validateRole","UserService.getUserByUsername","uuidv4","UserService.getUserById","UserService.getOwnerId","UserService.deleteUser","UserService.checkFilePermission","UserService.getFileById","UserService.getUserAccess","UserService.countUserAccess","UserService.deleteUserAccessByFileId","UserService.getAllUserAccess","app","config","#secretId","#secretKey","#token","#request","app","getDate","app","app","messagesSql","merkle.insert","merkle.prune"],"sources":["../../src/util/middlewares.ts","../../src/app-account.js","../../src/app-admin.js","../../src/app-cors-proxy.js","../../src/app-gocardless/util/handle-error.ts","../../src/services/secrets-service.js","../../src/app-enablebanking/utils/errors.ts","../../src/app-enablebanking/utils/jwt.ts","../../src/app-enablebanking/services/enablebanking-service.ts","../../src/app-enablebanking/app-enablebanking.ts","../../src/util/hash.ts","../../src/app-gocardless/errors.ts","../../src/app-gocardless/utils.ts","../../src/util/title/lower-case.js","../../src/util/title/specials.js","../../src/util/title/index.js","../../src/util/payee-name.ts","../../src/app-gocardless/banks/integration-bank.ts","../../src/app-gocardless/banks/abanca_caglesmm.ts","../../src/app-gocardless/banks/abnamro_abnanl2a.ts","../../src/app-gocardless/banks/american_express_aesudef1.ts","../../src/app-gocardless/banks/bancsabadell_bsabesbbb.ts","../../src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.ts","../../src/app-gocardless/banks/bankinter_bkbkesmm.ts","../../src/app-gocardless/banks/belfius_gkccbebb.ts","../../src/app-gocardless/banks/berliner_sparkasse_beladebexxx.ts","../../src/app-gocardless/banks/bnp_be_gebabebb.ts","../../src/app-gocardless/banks/boursobank_bousfrppxxx.ts","../../src/app-gocardless/banks/bper_retail_bpmoit22.ts","../../src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.ts","../../src/app-gocardless/banks/cbc_cregbebb.ts","../../src/app-gocardless/banks/cetelem_cetmptp1xxx.ts","../../src/app-gocardless/banks/util/escape-regexp.ts","../../src/app-gocardless/banks/commerzbank_cobadeff.ts","../../src/app-gocardless/banks/danskebank_privat.ts","../../src/app-gocardless/banks/direkt_heladef1822.ts","../../src/app-gocardless/banks/easybank_bawaatww.ts","../../src/app-gocardless/banks/entercard_swednokk.ts","../../src/app-gocardless/banks/fortuneo_ftnofrp1xxx.ts","../../src/app-gocardless/banks/hype_hyeeit22.ts","../../src/app-gocardless/banks/ing_ingbrobu.ts","../../src/app-gocardless/banks/ing_ingddeff.ts","../../src/app-gocardless/banks/ing_pl_ingbplpw.ts","../../src/app-gocardless/banks/isybank_itbbitmm.ts","../../src/app-gocardless/banks/kbc_kredbebb.ts","../../src/app-gocardless/banks/lhv_lhvbee22.ts","../../src/app-gocardless/banks/mbank_retail_brexplpw.ts","../../src/app-gocardless/banks/nationwide_naiagb21.ts","../../src/app-gocardless/banks/nbg_ethngraaxxx.ts","../../src/app-gocardless/banks/norwegian_xx_norwnok1.ts","../../src/app-gocardless/banks/raiffeisen_at_rzbaatww.ts","../../src/app-gocardless/banks/revolut_revolt21.ts","../../src/app-gocardless/banks/sandboxfinance_sfin0000.ts","../../src/app-gocardless/banks/seb_kort_bank_ab.ts","../../src/app-gocardless/banks/seb_privat.ts","../../src/app-gocardless/banks/sparnord_spnodk22.ts","../../src/app-gocardless/banks/spk_karlsruhe_karsde66.ts","../../src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.ts","../../src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.ts","../../src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.ts","../../src/app-gocardless/banks/ssk_munchen.ts","../../src/app-gocardless/banks/swedbank_habalv22.ts","../../src/app-gocardless/banks/virgin_nrnbgb22.ts","../../src/app-gocardless/bank-factory.ts","../../src/app-gocardless/services/gocardless-api.ts","../../src/app-gocardless/services/gocardless-service.ts","../../src/app-gocardless/app-gocardless.ts","../../src/app-openid.ts","../../src/app-pluggyai/pluggyai-service.js","../../src/app-pluggyai/app-pluggyai.js","../../src/app-secrets.js","../../src/app-simplefin/app-simplefin.js","../../../crdt/src/crdt/merkle.ts","../../../crdt/src/crdt/timestamp.ts","../../../crdt/src/proto/sync_pb.ts","../../src/app-sync/errors.js","../../src/util/paths.ts","../../src/app-sync/services/files-service.ts","../../src/app-sync/validation.js","../../src/sql/messages.sql?raw","../../src/sync-simple.js","../../src/app-sync.ts","../../src/app.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\nimport * as expressWinston from 'express-winston';\nimport * as winston from 'winston';\n\nimport { validateSession } from './validate-user';\n\nasync function errorMiddleware(\n err: Error,\n req: Request,\n res: Response,\n next: NextFunction,\n) {\n if (res.headersSent) {\n // If you call next() with an error after you have started writing the response\n // (for example, if you encounter an error while streaming the response\n // to the client), the Express default error handler closes\n // the connection and fails the request.\n\n // So when you add a custom error handler, you must delegate\n // to the default Express error handler, when the headers\n // have already been sent to the client\n // Source: https://expressjs.com/en/guide/error-handling.html\n return next(err);\n }\n\n console.log(`Error on endpoint %s`, {\n requestUrl: req.url,\n stacktrace: err.stack,\n });\n res.status(500).send({ status: 'error', reason: 'internal-error' });\n}\n\nconst validateSessionMiddleware = async (\n req: Request,\n res: Response,\n next: NextFunction,\n) => {\n const session = await validateSession(req, res);\n if (!session) {\n return;\n }\n\n res.locals = session;\n next();\n};\n\nconst requestLoggerMiddleware = expressWinston.logger({\n transports: [new winston.transports.Console()],\n format: winston.format.combine(\n ...(Object.prototype.hasOwnProperty.call(process.env, 'NO_COLOR')\n ? []\n : [winston.format.colorize()]),\n winston.format.timestamp(),\n winston.format.printf(args => {\n const { timestamp, level, meta } = args;\n const { res, req } = meta as { res: Response; req: Request };\n\n return `${String(timestamp)} ${String(level)}: ${req.method} ${res.statusCode} ${req.url}`;\n }),\n ),\n});\n\nexport { validateSessionMiddleware, errorMiddleware, requestLoggerMiddleware };\n","import express from 'express';\nimport rateLimit from 'express-rate-limit';\n\nimport {\n bootstrap,\n getActiveLoginMethod,\n getLoginMethod,\n getServerPrefs,\n getUserInfo,\n isAdmin,\n listLoginMethods,\n needsBootstrap,\n setServerPrefs,\n} from './account-db';\nimport { isValidRedirectUrl, loginWithOpenIdSetup } from './accounts/openid';\nimport { changePassword, loginWithPassword } from './accounts/password';\nimport { errorMiddleware, requestLoggerMiddleware } from './util/middlewares';\nimport { validateAuthHeader, validateSession } from './util/validate-user';\n\nconst app = express();\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(errorMiddleware);\napp.use(requestLoggerMiddleware);\n\nconst authRateLimiter = rateLimit({\n windowMs: 15 * 60 * 1000, // 15 minutes\n max: 5, // 5 attempts per window\n legacyHeaders: false,\n standardHeaders: true,\n skipSuccessfulRequests: true,\n message: { status: 'error', reason: 'too-many-requests' },\n});\n\nexport { app as handlers, authRateLimiter };\n\n// Non-authenticated endpoints:\n//\n// /needs-bootstrap\n// /boostrap (special endpoint for setting up the instance, cant call again)\n// /login\n\napp.get('/needs-bootstrap', (req, res) => {\n const availableLoginMethods = listLoginMethods();\n res.send({\n status: 'ok',\n data: {\n bootstrapped: !needsBootstrap(),\n loginMethod:\n availableLoginMethods.length === 1\n ? availableLoginMethods[0].method\n : getLoginMethod(),\n availableLoginMethods,\n multiuser: getActiveLoginMethod() === 'openid',\n },\n });\n});\n\napp.post('/bootstrap', authRateLimiter, async (req, res) => {\n const boot = await bootstrap(req.body);\n\n if (boot?.error) {\n res.status(400).send({ status: 'error', reason: boot?.error });\n return;\n }\n res.send({ status: 'ok', data: boot });\n});\n\napp.get('/login-methods', (req, res) => {\n const methods = listLoginMethods();\n res.send({ status: 'ok', methods });\n});\n\napp.post('/login', authRateLimiter, async (req, res) => {\n const loginMethod = getLoginMethod(req);\n console.log('Logging in via ' + loginMethod);\n let tokenRes = null;\n switch (loginMethod) {\n case 'header': {\n const headerVal = req.get('x-actual-password') || '';\n const obfuscated =\n '*'.repeat(headerVal.length) || 'No password provided.';\n console.debug('HEADER VALUE: ' + obfuscated);\n if (headerVal === '') {\n res.send({ status: 'error', reason: 'invalid-header' });\n return;\n } else {\n if (validateAuthHeader(req)) {\n tokenRes = loginWithPassword(headerVal);\n } else {\n res.send({ status: 'error', reason: 'proxy-not-trusted' });\n return;\n }\n }\n break;\n }\n case 'openid': {\n if (!isValidRedirectUrl(req.body.returnUrl)) {\n res\n .status(400)\n .send({ status: 'error', reason: 'Invalid redirect URL' });\n return;\n }\n\n const { error, url } = await loginWithOpenIdSetup(\n req.body.returnUrl,\n req.body.password,\n );\n if (error) {\n res.status(400).send({ status: 'error', reason: error });\n return;\n }\n res.send({ status: 'ok', data: { returnUrl: url } });\n return;\n }\n\n default:\n tokenRes = loginWithPassword(req.body.password);\n break;\n }\n const { error, token } = tokenRes;\n\n if (error) {\n res.status(400).send({ status: 'error', reason: error });\n return;\n }\n\n res.send({ status: 'ok', data: { token } });\n});\n\napp.post('/change-password', (req, res) => {\n const session = validateSession(req, res);\n if (!session) return;\n\n if (!isAdmin(session.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n if (session.auth_method !== 'password') {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'password-auth-not-active',\n });\n return;\n }\n\n const { error } = changePassword(req.body.password);\n\n if (error) {\n res.status(400).send({ status: 'error', reason: error });\n return;\n }\n\n res.send({ status: 'ok', data: {} });\n});\n\napp.post('/server-prefs', (req, res) => {\n const session = validateSession(req, res);\n if (!session) return;\n\n if (!isAdmin(session.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { prefs } = req.body || {};\n\n if (!prefs || typeof prefs !== 'object') {\n res.status(400).send({ status: 'error', reason: 'invalid-prefs' });\n return;\n }\n\n setServerPrefs(prefs);\n\n res.send({ status: 'ok', data: {} });\n});\n\napp.get('/validate', (req, res) => {\n const session = validateSession(req, res);\n if (session) {\n const user = getUserInfo(session.user_id);\n if (!user) {\n res.status(400).send({ status: 'error', reason: 'User not found' });\n return;\n }\n\n res.send({\n status: 'ok',\n data: {\n validated: true,\n userName: user?.user_name,\n permission: user?.role,\n userId: session?.user_id,\n displayName: user?.display_name,\n loginMethod: session?.auth_method,\n prefs: getServerPrefs(),\n },\n });\n }\n});\n","import express from 'express';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { isAdmin } from './account-db';\nimport * as UserService from './services/user-service';\nimport {\n errorMiddleware,\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from './util/middlewares';\nimport { validateSession } from './util/validate-user';\n\nconst app = express();\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(requestLoggerMiddleware);\n\nexport { app as handlers };\n\napp.get('/owner-created/', (req, res) => {\n try {\n const ownerCount = UserService.getOwnerCount();\n res.json(ownerCount > 0);\n } catch {\n res.status(500).json({ error: 'Failed to retrieve owner count' });\n }\n});\n\napp.get('/users/', validateSessionMiddleware, (req, res) => {\n const users = UserService.getAllUsers();\n res.json(\n users.map(u => ({\n ...u,\n owner: u.owner === 1,\n enabled: u.enabled === 1,\n })),\n );\n});\n\napp.post('/users', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { userName, role, displayName, enabled } = req.body || {};\n\n if (!userName || !role) {\n res.status(400).send({\n status: 'error',\n reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,\n details: `${!userName ? 'Username' : 'Role'} cannot be empty`,\n });\n return;\n }\n\n const roleIdFromDb = UserService.validateRole(role);\n if (!roleIdFromDb) {\n res.status(400).send({\n status: 'error',\n reason: 'role-does-not-exists',\n details: 'Selected role does not exist',\n });\n return;\n }\n\n const userIdInDb = UserService.getUserByUsername(userName);\n if (userIdInDb) {\n res.status(400).send({\n status: 'error',\n reason: 'user-already-exists',\n details: `User ${userName} already exists`,\n });\n return;\n }\n\n const userId = uuidv4();\n UserService.insertUser(\n userId,\n userName,\n displayName || null,\n enabled ? 1 : 0,\n );\n\n res.status(200).send({ status: 'ok', data: { id: userId } });\n});\n\napp.patch('/users', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { id, userName, role, displayName, enabled } = req.body || {};\n\n if (!userName || !role) {\n res.status(400).send({\n status: 'error',\n reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,\n details: `${!userName ? 'Username' : 'Role'} cannot be empty`,\n });\n return;\n }\n\n const roleIdFromDb = UserService.validateRole(role);\n if (!roleIdFromDb) {\n res.status(400).send({\n status: 'error',\n reason: 'role-does-not-exists',\n details: 'Selected role does not exist',\n });\n return;\n }\n\n const userIdInDb = UserService.getUserById(id);\n if (!userIdInDb) {\n res.status(400).send({\n status: 'error',\n reason: 'cannot-find-user-to-update',\n details: `Cannot find user ${userName} to update`,\n });\n return;\n }\n\n UserService.updateUserWithRole(\n userIdInDb,\n userName,\n displayName || null,\n enabled ? 1 : 0,\n role,\n );\n\n res.status(200).send({ status: 'ok', data: { id: userIdInDb } });\n});\n\napp.delete('/users', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { ids } = req.body || {};\n let totalDeleted = 0;\n ids.forEach(item => {\n const ownerId = UserService.getOwnerId();\n\n if (item === ownerId) return;\n\n UserService.deleteUserAccess(item);\n UserService.transferAllFilesFromUser(ownerId, item);\n const usersDeleted = UserService.deleteUser(item);\n totalDeleted += usersDeleted;\n });\n\n if (ids.length === totalDeleted) {\n res\n .status(200)\n .send({ status: 'ok', data: { someDeletionsFailed: false } });\n } else {\n res.status(400).send({\n status: 'error',\n reason: 'not-all-deleted',\n details: '',\n });\n }\n});\n\napp.get('/access', validateSessionMiddleware, (req, res) => {\n const fileId = req.query.fileId;\n\n const { granted } = UserService.checkFilePermission(\n fileId,\n res.locals.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return false;\n }\n\n const fileIdInDb = UserService.getFileById(fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return false;\n }\n\n const accesses = UserService.getUserAccess(\n fileId,\n res.locals.user_id,\n isAdmin(res.locals.user_id),\n );\n\n res.json(accesses);\n});\n\napp.post('/access', (req, res) => {\n const userAccess = req.body || {};\n const session = validateSession(req, res);\n\n if (!session) return;\n\n const { granted } = UserService.checkFilePermission(\n userAccess.fileId,\n session.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(session.user_id)) {\n res.status(400).send({\n status: 'error',\n reason: 'file-denied',\n details: \"You don't have permissions over this file\",\n });\n return;\n }\n\n const fileIdInDb = UserService.getFileById(userAccess.fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return;\n }\n\n if (!userAccess.userId) {\n res.status(400).send({\n status: 'error',\n reason: 'user-cant-be-empty',\n details: 'User cannot be empty',\n });\n return;\n }\n\n if (UserService.countUserAccess(userAccess.fileId, userAccess.userId) > 0) {\n res.status(400).send({\n status: 'error',\n reason: 'user-already-have-access',\n details: 'User already have access',\n });\n return;\n }\n\n UserService.addUserAccess(userAccess.userId, userAccess.fileId);\n\n res.status(200).send({ status: 'ok', data: {} });\n});\n\napp.delete('/access', (req, res) => {\n const fileId = req.query.fileId;\n const session = validateSession(req, res);\n if (!session) return;\n\n const { granted } = UserService.checkFilePermission(\n fileId,\n session.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(session.user_id)) {\n res.status(400).send({\n status: 'error',\n reason: 'file-denied',\n details: \"You don't have permissions over this file\",\n });\n return;\n }\n\n const fileIdInDb = UserService.getFileById(fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return;\n }\n\n const { ids } = req.body || {};\n const totalDeleted = UserService.deleteUserAccessByFileId(ids, fileId);\n\n if (ids.length === totalDeleted) {\n res\n .status(200)\n .send({ status: 'ok', data: { someDeletionsFailed: false } });\n } else {\n res.status(400).send({\n status: 'error',\n reason: 'not-all-deleted',\n details: '',\n });\n }\n});\n\napp.get('/access/users', validateSessionMiddleware, async (req, res) => {\n const fileId = req.query.fileId;\n\n const { granted } = UserService.checkFilePermission(\n fileId,\n res.locals.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(res.locals.user_id)) {\n res.status(400).send({\n status: 'error',\n reason: 'file-denied',\n details: \"You don't have permissions over this file\",\n });\n return;\n }\n\n const fileIdInDb = UserService.getFileById(fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return;\n }\n\n const users = UserService.getAllUserAccess(fileId);\n res.json(users);\n});\n\napp.post(\n '/access/transfer-ownership/',\n validateSessionMiddleware,\n (req, res) => {\n const newUserOwner = req.body || {};\n\n const { granted } = UserService.checkFilePermission(\n newUserOwner.fileId,\n res.locals.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(res.locals.user_id)) {\n res.status(400).send({\n status: 'error',\n reason: 'file-denied',\n details: \"You don't have permissions over this file\",\n });\n return;\n }\n\n const fileIdInDb = UserService.getFileById(newUserOwner.fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return;\n }\n\n if (!newUserOwner.newUserId) {\n res.status(400).send({\n status: 'error',\n reason: 'user-cant-be-empty',\n details: 'Username cannot be empty',\n });\n return;\n }\n\n const newUserIdFromDb = UserService.getUserById(newUserOwner.newUserId);\n if (newUserIdFromDb === 0) {\n res.status(400).send({\n status: 'error',\n reason: 'new-user-not-found',\n details: 'New user not found',\n });\n return;\n }\n\n UserService.updateFileOwner(newUserOwner.newUserId, newUserOwner.fileId);\n\n res.status(200).send({ status: 'ok', data: {} });\n },\n);\n\napp.use(errorMiddleware);\n","import express from 'express';\nimport rateLimit from 'express-rate-limit';\nimport ipaddr from 'ipaddr.js';\n\nimport { config } from './load-config';\nimport { requestLoggerMiddleware } from './util/middlewares';\nimport { validateSession } from './util/validate-user';\n\nconst app = express();\n\napp.use(express.json());\napp.use(requestLoggerMiddleware);\napp.use(\n rateLimit({\n windowMs: 60 * 1000,\n max: 25,\n legacyHeaders: false,\n standardHeaders: true,\n }),\n);\n\n// Cache for the allowlist to avoid fetching it on every request\nlet allowlistedRepos = [];\nlet lastAllowlistFetch = 0;\nconst ALLOWLIST_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n// Export cache clearing function for testing\nexport const clearAllowlistCache = () => {\n allowlistedRepos = [];\n lastAllowlistFetch = 0;\n};\n\nasync function fetchAllowlist() {\n const now = Date.now();\n if (\n now - lastAllowlistFetch < ALLOWLIST_CACHE_TTL &&\n allowlistedRepos.length > 0\n ) {\n return allowlistedRepos;\n }\n\n try {\n const response = await fetch(\n 'https://raw.githubusercontent.com/actualbudget/plugin-store/refs/heads/main/plugins.json',\n );\n if (!response.ok) {\n throw new Error(`Failed to fetch allowlist: ${response.status}`);\n }\n const plugins = await response.json();\n allowlistedRepos = plugins.map(plugin => plugin.url);\n lastAllowlistFetch = now;\n console.log('Updated plugin allowlist:', allowlistedRepos);\n return allowlistedRepos;\n } catch (error) {\n console.error('Failed to fetch plugin allowlist:', error);\n // Return empty array if fetch fails to be safe\n allowlistedRepos = [];\n return allowlistedRepos;\n }\n}\n\n/**\n * Return true only if the URL is on an allowlist and not a local/private address.\n */\nfunction isUrlAllowed(targetUrl) {\n try {\n const url = new URL(targetUrl);\n const hostname = url.hostname;\n\n // Block private/local IP addresses\n if (ipaddr.isValid(hostname)) {\n const ip = ipaddr.parse(hostname);\n if (\n [\n 'private',\n 'loopback',\n 'linkLocal',\n 'uniqueLocal',\n 'unspecified',\n ].includes(ip.range())\n ) {\n console.warn(`Blocked request to private/localhost IP: ${hostname}`);\n return false;\n }\n }\n\n // Always allow the specific plugin-store URL\n if (\n targetUrl ===\n 'https://raw.githubusercontent.com/actualbudget/plugin-store/refs/heads/main/plugins.json'\n ) {\n return true;\n }\n\n // Check against allowlisted repositories\n for (const repoUrl of allowlistedRepos) {\n try {\n const { pathname } = new URL(repoUrl);\n const [, repoOwner, repoName] = pathname.split('/');\n\n if (\n targetUrl === repoUrl ||\n targetUrl.startsWith(repoUrl + '/') ||\n (hostname === 'api.github.com' &&\n url.pathname.startsWith(`/repos/${repoOwner}/${repoName}`)) ||\n (hostname === 'raw.githubusercontent.com' &&\n url.pathname.startsWith(`/${repoOwner}/${repoName}/`)) ||\n (hostname === 'github.com' &&\n url.pathname.startsWith(`/${repoOwner}/${repoName}/releases/`))\n ) {\n return true;\n }\n } catch (e) {\n console.warn(\n 'Invalid repository URL in allowlist:',\n repoUrl,\n e.message,\n );\n }\n }\n\n return false;\n } catch (e) {\n console.warn('Invalid target URL:', targetUrl, e.message);\n return false;\n }\n}\n\napp.use('/', async (req, res) => {\n // CORS preflight\n if (req.method === 'OPTIONS') {\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type, X-Actual-Token');\n res.set('Access-Control-Max-Age', '600');\n return res.status(204).end();\n }\n\n const targetUrlString = req.query.url;\n\n if (!targetUrlString) {\n return res.status(400).json({ error: 'Missing url parameter' });\n }\n\n // Validate session/token\n const session = await validateSession(req, res);\n if (!session) {\n return; // validateSession already sent the response\n }\n\n let url;\n try {\n url = new URL(targetUrlString);\n } catch {\n return res.status(400).json({ error: 'Invalid url parameter' });\n }\n\n // Fetch the latest allowlist\n try {\n await fetchAllowlist();\n } catch (error) {\n console.error('Failed to fetch allowlist:', error);\n return res.status(403).json({\n error: 'URL not allowed',\n message: 'Unable to verify allowlist',\n });\n }\n\n // Check if the URL is allowed\n if (!isUrlAllowed(url.href)) {\n console.warn('Blocked request to unauthorized URL:', url.href);\n return res.status(403).json({\n error: 'URL not allowed',\n message:\n 'Only allowlisted plugin repositories are allowed (localhost only in development)',\n });\n }\n\n try {\n const { method = 'GET', headers: customHeaders = {} } = req.body || {};\n\n if (typeof method !== 'string') {\n return res.status(400).json({ error: 'Invalid method parameter' });\n }\n const methodNormalized = method.toUpperCase();\n if (!['GET', 'HEAD'].includes(methodNormalized)) {\n return res.status(405).json({ error: 'Method not allowed' });\n }\n\n const requestHeaders = {\n ...req.headers,\n ...customHeaders,\n host: url.host,\n };\n\n // Remove headers that shouldn't be forwarded\n delete requestHeaders['x-actual-token'];\n delete requestHeaders['content-length'];\n delete requestHeaders['cookie'];\n delete requestHeaders['cookie2'];\n\n // Add GitHub authentication if token is configured and request is to GitHub\n const githubToken = config.get('github.token');\n if (\n githubToken &&\n (url.hostname === 'api.github.com' ||\n url.hostname === 'raw.githubusercontent.com' ||\n (url.hostname === 'github.com' && url.pathname.includes('/releases/')))\n ) {\n requestHeaders['Authorization'] = `Bearer ${githubToken}`;\n requestHeaders['User-Agent'] = 'Actual-Budget-Plugin-System';\n console.log(\n `Using GitHub authentication for request to: ${url.hostname}`,\n );\n }\n\n const response = await fetch(url.href, {\n method: methodNormalized,\n headers: requestHeaders,\n });\n\n const contentType =\n response.headers.get('content-type') || 'application/octet-stream';\n\n res.set('Access-Control-Allow-Origin', '*');\n res.status(response.status);\n\n // Try to detect if this might be JSON content based on URL or content\n const urlString = url.toString().toLowerCase();\n const isLikelyJson =\n contentType?.includes('application/json') ||\n urlString.includes('.json') ||\n urlString.includes('/manifest') ||\n urlString.includes('manifest.json') ||\n urlString.includes('package.json');\n\n if (isLikelyJson) {\n // For JSON responses, return the actual content\n res.set('Content-Type', 'application/json');\n const text = await response.text();\n try {\n res.json(JSON.parse(text));\n } catch {\n // If it's not valid JSON, treat as text\n res.set('Content-Type', contentType || 'text/plain');\n res.send(text);\n }\n } else if (contentType?.includes('text/')) {\n // For text responses, return as plain text\n res.set('Content-Type', contentType);\n const text = await response.text();\n res.send(text);\n } else {\n // For actual binary responses, return as JSON format\n res.set('Content-Type', 'application/json');\n const buffer = await response.arrayBuffer();\n const binaryData = {\n data: Array.from(new Uint8Array(buffer)),\n contentType,\n isBinary: true,\n };\n res.json(binaryData);\n }\n } catch (err) {\n res\n .status(500)\n .json({ error: 'Error proxying request', details: err.message });\n }\n});\n\nexport { app as handlers };\n","import type { Request, Response } from 'express';\n\nexport function handleError(\n func: (req: Request, res: Response) => Promise<unknown>,\n) {\n return (req: Request, res: Response) => {\n func(req, res).catch(err => {\n console.log('Error', req.originalUrl, err.message || String(err));\n res.send({\n status: 'ok',\n data: {\n error_code: 'INTERNAL_ERROR',\n error_type: err.message ? err.message : 'internal-error',\n },\n });\n });\n };\n}\n","import createDebug from 'debug';\n\nimport { getAccountDb } from '#account-db';\n\n/**\n * An enum of valid secret names.\n * @readonly\n * @enum {string}\n */\nexport const SecretName = {\n gocardless_secretId: 'gocardless_secretId',\n gocardless_secretKey: 'gocardless_secretKey',\n simplefin_token: 'simplefin_token',\n simplefin_accessKey: 'simplefin_accessKey',\n pluggyai_clientId: 'pluggyai_clientId',\n pluggyai_clientSecret: 'pluggyai_clientSecret',\n pluggyai_itemIds: 'pluggyai_itemIds',\n enablebanking_applicationId: 'enablebanking_applicationId',\n enablebanking_secretKey: 'enablebanking_secretKey',\n};\n\nclass SecretsDb {\n constructor() {\n this.debug = createDebug('actual:secrets-db');\n this.db = null;\n }\n\n open() {\n return getAccountDb();\n }\n\n set(name, value) {\n if (!this.db) {\n this.db = this.open();\n }\n\n this.debug(`setting secret '${name}' to '${value}'`);\n const result = this.db.mutate(\n `INSERT OR REPLACE INTO secrets (name, value) VALUES (?,?)`,\n [name, value],\n );\n return result;\n }\n\n get(name) {\n if (!this.db) {\n this.db = this.open();\n }\n\n this.debug(`getting secret '${name}'`);\n const result = this.db.first(`SELECT value FROM secrets WHERE name =?`, [\n name,\n ]);\n return result;\n }\n}\n\nconst secretsDb = new SecretsDb();\nconst _cachedSecrets = new Map();\n/**\n * A service for managing secrets stored in `secretsDb`.\n */\nexport const secretsService = {\n /**\n * Retrieves the value of a secret by name.\n * @param {SecretName} name - The name of the secret to retrieve.\n * @returns {string|null} The value of the secret, or null if the secret does not exist.\n */\n get: name => {\n return _cachedSecrets.get(name) ?? secretsDb.get(name)?.value ?? null;\n },\n\n /**\n * Sets the value of a secret by name.\n * @param {SecretName} name - The name of the secret to set.\n * @param {string} value - The value to set for the secret.\n * @returns {Object}\n */\n set: (name, value) => {\n const result = secretsDb.set(name, value);\n\n if (result.changes === 1) {\n _cachedSecrets.set(name, value);\n }\n return result;\n },\n\n /**\n * Determines whether a secret with the given name exists.\n * @param {SecretName} name - The name of the secret to check for existence.\n * @returns {boolean} True if a secret with the given name exists, false otherwise.\n */\n exists: name => {\n return Boolean(secretsService.get(name));\n },\n};\n","import createDebug from 'debug';\n\nconst debug = createDebug('actual:enable-banking:errors');\n\nexport class EnableBankingError extends Error {\n error_type: string;\n error_code: string;\n\n constructor(error_type: string, error_code: string, message?: string) {\n super(message || `Enable Banking error: ${error_type} - ${error_code}`);\n this.name = 'EnableBankingError';\n this.error_type = error_type;\n this.error_code = error_code;\n }\n}\n\nexport function handleEnableBankingError(\n statusCode: number,\n body: unknown,\n): EnableBankingError {\n const bodyStr =\n typeof body === 'string' ? body : JSON.stringify(body ?? 'unknown');\n debug('Enable Banking API error: status=%d body=%s', statusCode, bodyStr);\n\n const parsed: Record<string, unknown> =\n typeof body === 'object' && body !== null\n ? Object.fromEntries(Object.entries(body))\n : {};\n const message = typeof parsed.message === 'string' ? parsed.message : bodyStr;\n const errorType = typeof parsed.error === 'string' ? parsed.error : 'UNKNOWN';\n\n if (statusCode === 401 || statusCode === 403) {\n return new EnableBankingError(message, 'INVALID_ACCESS_TOKEN', message);\n }\n\n if (statusCode === 429) {\n return new EnableBankingError(message, 'RATE_LIMIT_EXCEEDED', message);\n }\n\n if (statusCode === 404) {\n return new EnableBankingError(message, 'NOT_FOUND', message);\n }\n\n if (statusCode >= 400 && statusCode < 500) {\n // Check for closed/expired session errors (case-insensitive)\n const lowerErrorType = (errorType || '').toLowerCase();\n const lowerMessage = (message || '').toLowerCase();\n if (\n lowerErrorType === 'closed_session' ||\n lowerErrorType === 'expired_session' ||\n lowerMessage.includes('session') ||\n lowerMessage.includes('expired')\n ) {\n return new EnableBankingError(message, 'INVALID_ACCESS_TOKEN', message);\n }\n return new EnableBankingError(message, 'INVALID_INPUT', message);\n }\n\n return new EnableBankingError(message, 'INTERNAL_ERROR', message);\n}\n","import { sign } from 'jws';\nimport type { Algorithm } from 'jws';\n\ntype Header = { typ: string; alg: Algorithm; kid: string };\n\ntype JWTPayload = {\n iss: string;\n aud: string;\n iat: number;\n exp: number;\n};\n\nfunction getJWTHeader(applicationId: string): Header {\n return { typ: 'JWT', alg: 'RS256', kid: applicationId };\n}\n\nfunction getJWTBody(exp = 3600): JWTPayload {\n const timestamp = Math.floor(Date.now() / 1000);\n return {\n iss: 'enablebanking.com',\n aud: 'api.enablebanking.com',\n iat: timestamp,\n exp: timestamp + exp,\n };\n}\n\nexport function getJWT(\n applicationId: string,\n secretKey: string,\n exp = 3600,\n): string {\n return sign({\n header: getJWTHeader(applicationId),\n payload: getJWTBody(exp),\n secret: secretKey,\n });\n}\n","import createDebug from 'debug';\n\nimport {\n EnableBankingError,\n handleEnableBankingError,\n} from '#app-enablebanking/utils/errors';\nimport { getJWT } from '#app-enablebanking/utils/jwt';\nimport { SecretName, secretsService } from '#services/secrets-service';\n\nconst debug = createDebug('actual:enable-banking:service');\n\nconst BASE_URL = 'https://api.enablebanking.com';\n\n// --- Type definitions ---\n\nexport type EnableBankingTransaction = {\n entry_reference?: string;\n transaction_id?: string;\n transaction_amount: { currency: string; amount: string };\n creditor?: { name?: string };\n debtor?: { name?: string };\n credit_debit_indicator?: 'CRDT' | 'DBIT';\n status?: 'BOOK' | 'PDNG';\n booking_date?: string;\n value_date?: string;\n transaction_date?: string;\n remittance_information?: string[];\n};\n\ntype EnableBankingBalance = {\n balance_amount: { currency: string; amount: string };\n balance_type: string;\n reference_date?: string;\n};\n\nexport type EnableBankingSessionAccount = {\n account_id?: { iban?: string };\n account_servicer?: { bic_fi?: string; name?: string };\n name?: string;\n currency?: string;\n uid: string;\n};\n\nexport type EnableBankingSession = {\n session_id: string;\n accounts: EnableBankingSessionAccount[];\n aspsp?: { name?: string; country?: string };\n};\n\ntype EnableBankingAspsp = {\n name: string;\n country: string;\n [key: string]: unknown;\n};\n\ntype EnableBankingAuthResponse = {\n url: string;\n authorization_id: string;\n};\n\ntype BankSyncTransaction = EnableBankingTransaction & {\n transactionId: string;\n date: string;\n bookingDate: string;\n valueDate?: string;\n transactionAmount: { amount: string; currency: string };\n payeeName: string;\n notes?: string;\n remittanceInformationUnstructured?: string;\n booked: boolean;\n};\n\ntype BankSyncBalance = {\n balanceAmount: { amount: number; currency: string };\n balanceType: string;\n referenceDate?: string;\n};\n\ntype NormalizedAccount = {\n account_id: string;\n name: string;\n institution: string;\n currency?: string;\n iban?: string;\n};\n\n// --- PSU headers ---\n\nexport type PsuHeaders = {\n 'Psu-Ip-Address'?: string;\n 'Psu-User-Agent'?: string;\n};\n\n// --- Helper functions ---\n\nfunction getCredentials(): { applicationId: string; secretKey: string } {\n const applicationId = secretsService.get(\n SecretName.enablebanking_applicationId,\n );\n const secretKey = secretsService.get(SecretName.enablebanking_secretKey);\n\n if (!applicationId || !secretKey) {\n throw new EnableBankingError(\n 'INVALID_INPUT',\n 'NOT_CONFIGURED',\n 'Enable Banking is not configured',\n );\n }\n\n return { applicationId, secretKey };\n}\n\nfunction getAuthorizationHeader(): string {\n const { applicationId, secretKey } = getCredentials();\n const token = getJWT(applicationId, secretKey);\n return `Bearer ${token}`;\n}\n\nconst REQUEST_TIMEOUT_MS = 30_000; // 30 seconds\n\nasync function request<T>(\n method: string,\n path: string,\n body?: unknown,\n authHeaderOverride?: string,\n psuHeaders?: PsuHeaders,\n): Promise<T> {\n const url = `${BASE_URL}${path}`;\n debug('%s %s', method, url);\n\n const headers: Record<string, string> = {\n Authorization: authHeaderOverride ?? getAuthorizationHeader(),\n 'Content-Type': 'application/json',\n };\n\n // Forward PSU headers to signal the end-user is online.\n // This exempts the request from background data-fetch rate limits\n // that many ASPSPs enforce (e.g. 4 requests/day).\n if (psuHeaders) {\n for (const [key, value] of Object.entries(psuHeaders)) {\n if (value) {\n headers[key] = value;\n }\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n const options: RequestInit = { method, headers, signal: controller.signal };\n if (body !== undefined) {\n options.body = JSON.stringify(body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, options);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new EnableBankingError(\n 'TIMED_OUT',\n 'TIMED_OUT',\n 'Request timed out',\n );\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n\n if (!response.ok) {\n let responseBody: unknown;\n try {\n responseBody = await response.json();\n } catch {\n responseBody = await response.text().catch(() => 'unknown');\n }\n throw handleEnableBankingError(response.status, responseBody);\n }\n\n // eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- generic API wrapper, type is validated by caller\n return (await response.json()) as T;\n}\n\n// --- Normalization functions ---\n\n// SEPA / ISO 20022 structured remittance prefixes (e.g. `EREF+invoice-42`).\n// They are metadata for clearing systems, not user-facing text, so we strip\n// them from the front of each remittance line. The list is an allowlist of\n// known prefixes rather than a catch-all `[A-Z]{3,}\\+` so we don't accidentally\n// strip merchant tokens like `BMW+` or `USB+` that legitimately start a\n// description.\nconst SEPA_PREFIX_RE =\n /^(?:EREF|KREF|MREF|CRED|DBTR|CDTR|SVWZ|SVCL|PURP|RTRN|REJT|REFE|SDVA|INDA|NTAV|ULTC|ULTD|ULTB|ABWA|ABWE|IBAN|BIC|COAM|OAMT|REMI|SQTP|ROC)\\+/;\n\nfunction stripSepaPrefix(s: string): string {\n return s.replace(SEPA_PREFIX_RE, '').trim();\n}\n\nfunction cleanRemittanceArray(arr: string[]): string[] {\n return arr.map(stripSepaPrefix).filter(Boolean);\n}\n\nexport function normalizeTransaction(\n tx: EnableBankingTransaction,\n): BankSyncTransaction {\n const transactionId = tx.entry_reference || tx.transaction_id || '';\n const bookingDate =\n tx.booking_date || tx.value_date || tx.transaction_date || '';\n const valueDate = tx.value_date;\n\n let payeeName = '';\n if (tx.credit_debit_indicator === 'CRDT' && tx.debtor?.name) {\n payeeName = tx.debtor.name;\n } else if (tx.credit_debit_indicator === 'DBIT' && tx.creditor?.name) {\n payeeName = tx.creditor.name;\n } else if (tx.creditor?.name) {\n payeeName = tx.creditor.name;\n } else if (tx.debtor?.name) {\n payeeName = tx.debtor.name;\n } else if (\n tx.remittance_information &&\n tx.remittance_information.length > 0\n ) {\n const cleanedFallback = cleanRemittanceArray(tx.remittance_information);\n if (cleanedFallback.length > 0) {\n payeeName = cleanedFallback[0];\n }\n }\n\n const cleanedAll = tx.remittance_information\n ? cleanRemittanceArray(tx.remittance_information)\n : [];\n const remittanceInformationUnstructured =\n cleanedAll.length > 0 ? cleanedAll.join(' ') : undefined;\n\n // Normalize amount based on credit/debit indicator.\n // When indicator is present, strip existing sign and apply the correct one.\n // When absent, preserve the original sign from the bank.\n const trimmedAmount = tx.transaction_amount.amount.trim();\n let signedAmount: string;\n if (tx.credit_debit_indicator === 'DBIT') {\n signedAmount = '-' + trimmedAmount.replace(/^[+-]/, '');\n } else if (tx.credit_debit_indicator === 'CRDT') {\n signedAmount = trimmedAmount.replace(/^[+-]/, '');\n } else {\n signedAmount = trimmedAmount;\n }\n\n return {\n ...tx,\n transactionId,\n date: bookingDate,\n bookingDate,\n valueDate,\n transactionAmount: {\n amount: signedAmount,\n currency: tx.transaction_amount.currency,\n },\n payeeName,\n notes: remittanceInformationUnstructured,\n remittanceInformationUnstructured,\n booked: tx.status !== 'PDNG',\n };\n}\n\nexport function normalizeBalance(bal: EnableBankingBalance): BankSyncBalance {\n const amount = Math.round(parseFloat(bal.balance_amount.amount) * 100);\n return {\n balanceAmount: {\n amount,\n currency: bal.balance_amount.currency,\n },\n balanceType: bal.balance_type,\n referenceDate: bal.reference_date,\n };\n}\n\nexport function normalizeAccount(\n account: EnableBankingSessionAccount,\n aspsp?: { name?: string },\n): NormalizedAccount {\n return {\n account_id: account.uid,\n name: account.name || account.account_id?.iban || account.uid,\n institution: aspsp?.name || account.account_servicer?.name || 'Unknown',\n currency: account.currency,\n iban: account.account_id?.iban,\n };\n}\n\n// --- Service ---\n\nexport const enableBankingService = {\n isConfigured(): boolean {\n const applicationId = secretsService.get(\n SecretName.enablebanking_applicationId,\n );\n const secretKey = secretsService.get(SecretName.enablebanking_secretKey);\n return !!(applicationId && secretKey);\n },\n\n async validateCredentials(\n applicationId: string,\n secretKey: string,\n ): Promise<unknown> {\n const token = getJWT(applicationId, secretKey);\n return request<unknown>(\n 'GET',\n '/application',\n undefined,\n `Bearer ${token}`,\n );\n },\n\n async getApplication(): Promise<unknown> {\n return request<unknown>('GET', '/application');\n },\n\n async getAspsps(country?: string): Promise<EnableBankingAspsp[]> {\n const query = country ? `?country=${encodeURIComponent(country)}` : '';\n return request<EnableBankingAspsp[]>('GET', `/aspsps${query}`);\n },\n\n async startAuth(\n aspsp: { name: string; country: string },\n redirectUrl: string,\n state: string,\n maxConsentValidity?: number,\n ): Promise<EnableBankingAuthResponse> {\n const DEFAULT_CONSENT_DAYS = 90;\n const defaultMs = DEFAULT_CONSENT_DAYS * 24 * 60 * 60 * 1000;\n\n // Respect the ASPSP's maximum_consent_validity (in seconds) if provided,\n // capping at our default of 90 days.\n const consentMs =\n maxConsentValidity != null && maxConsentValidity > 0\n ? Math.min(maxConsentValidity * 1000, defaultMs)\n : defaultMs;\n\n const validUntil = new Date(Date.now() + consentMs);\n\n return request<EnableBankingAuthResponse>('POST', '/auth', {\n aspsp: { name: aspsp.name, country: aspsp.country },\n redirect_url: redirectUrl,\n state,\n access: {\n valid_until: validUntil.toISOString(),\n },\n });\n },\n\n async createSession(code: string): Promise<EnableBankingSession> {\n return request<EnableBankingSession>('POST', '/sessions', { code });\n },\n\n async getSession(sessionId: string): Promise<EnableBankingSession> {\n return request<EnableBankingSession>(\n 'GET',\n `/sessions/${encodeURIComponent(sessionId)}`,\n );\n },\n\n async getBalances(\n accountUid: string,\n psuHeaders?: PsuHeaders,\n ): Promise<{ balances: EnableBankingBalance[] }> {\n return request<{ balances: EnableBankingBalance[] }>(\n 'GET',\n `/accounts/${encodeURIComponent(accountUid)}/balances`,\n undefined,\n undefined,\n psuHeaders,\n );\n },\n\n async getTransactions(\n accountUid: string,\n dateFrom: string,\n dateTo: string,\n continuationKey?: string,\n psuHeaders?: PsuHeaders,\n ): Promise<{\n transactions: EnableBankingTransaction[];\n continuation_key?: string;\n }> {\n let path = `/accounts/${encodeURIComponent(accountUid)}/transactions?date_from=${encodeURIComponent(dateFrom)}&date_to=${encodeURIComponent(dateTo)}`;\n if (continuationKey) {\n path += `&continuation_key=${encodeURIComponent(continuationKey)}`;\n }\n return request<{\n transactions: EnableBankingTransaction[];\n continuation_key?: string;\n }>('GET', path, undefined, undefined, psuHeaders);\n },\n\n async getAllTransactions(\n accountUid: string,\n dateFrom: string,\n dateTo: string,\n psuHeaders?: PsuHeaders,\n ): Promise<EnableBankingTransaction[]> {\n const allTransactions: EnableBankingTransaction[] = [];\n let continuationKey: string | undefined;\n const maxIterations = 100;\n let iteration = 0;\n\n do {\n const result = await enableBankingService.getTransactions(\n accountUid,\n dateFrom,\n dateTo,\n continuationKey,\n psuHeaders,\n );\n allTransactions.push(...result.transactions);\n\n if (\n result.continuation_key &&\n result.continuation_key === continuationKey\n ) {\n break;\n }\n\n continuationKey = result.continuation_key;\n iteration++;\n } while (continuationKey && iteration < maxIterations);\n\n return allTransactions;\n },\n};\n","import createDebug from 'debug';\nimport type { Request, Response } from 'express';\nimport express from 'express';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { handleError } from '#app-gocardless/util/handle-error';\nimport { SecretName, secretsService } from '#services/secrets-service';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from '#util/middlewares';\n\nimport type {\n EnableBankingSession,\n PsuHeaders,\n} from './services/enablebanking-service';\nimport {\n enableBankingService,\n normalizeAccount,\n normalizeBalance,\n normalizeTransaction,\n} from './services/enablebanking-service';\nimport { EnableBankingError } from './utils/errors';\n\nconst debug = createDebug('actual:enable-banking:app');\n\nconst app = express();\nexport { app as handlers };\napp.use(requestLoggerMiddleware);\napp.use(express.json());\n\n// --- Shared helpers ---\n\nfunction extractPsuHeaders(req: Request): PsuHeaders {\n const ip = req.ip;\n const ua =\n typeof req.headers['user-agent'] === 'string'\n ? req.headers['user-agent']\n : undefined;\n\n const headers: PsuHeaders = {};\n if (ip) headers['Psu-Ip-Address'] = ip;\n if (ua) headers['Psu-User-Agent'] = ua;\n return headers;\n}\n\nasync function buildSessionResult(\n session: EnableBankingSession,\n psuHeaders?: PsuHeaders,\n) {\n const accountsWithBalances = await Promise.all(\n session.accounts.map(async account => {\n const normalized = normalizeAccount(account, session.aspsp);\n\n let balances: ReturnType<typeof normalizeBalance>[] = [];\n try {\n const balanceResult = await enableBankingService.getBalances(\n account.uid,\n psuHeaders,\n );\n balances = balanceResult.balances.map(normalizeBalance);\n } catch (err) {\n debug('Failed to fetch balances for account %s: %s', account.uid, err);\n }\n\n const preferredBalance =\n balances.find(b => b.balanceType === 'CLAV') ?? balances[0];\n\n return {\n ...normalized,\n balance: preferredBalance ? preferredBalance.balanceAmount.amount : 0,\n balances,\n };\n }),\n );\n\n return {\n session_id: session.session_id,\n accounts: accountsWithBalances,\n aspsp: session.aspsp,\n };\n}\n\n// Auth callback from bank redirect — must be before validateSessionMiddleware\n// since the bank redirects here directly (no auth token available)\napp.get('/auth_callback', async (req: Request, res: Response) => {\n const code = typeof req.query.code === 'string' ? req.query.code : undefined;\n const state =\n typeof req.query.state === 'string' ? req.query.state : undefined;\n\n if (!code) {\n res\n .status(400)\n .send(\n '<html><body><p>Authorization failed: missing code.</p></body></html>',\n );\n return;\n }\n\n if (!state) {\n res\n .status(400)\n .send(\n '<html><body><p>Authorization failed: missing state parameter.</p></body></html>',\n );\n return;\n }\n\n try {\n const session = await enableBankingService.createSession(code);\n debug(\n 'Callback session created: %s with %d accounts',\n session.session_id,\n session.accounts.length,\n );\n\n const result = await buildSessionResult(session, extractPsuHeaders(req));\n\n // Always cache the result so retries within TTL can read it\n completedAuths.set(state, result);\n setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n const pending = pendingAuths.get(state);\n if (pending) {\n pending.resolve(result);\n cleanupPendingAuth(state);\n }\n\n res.send(\n '<html><body><p>Authorization successful. This window will close.</p>' +\n '<script>setTimeout(function(){window.close()},1000)</script></body></html>',\n );\n } catch (error) {\n const errorResult = {\n error: error instanceof Error ? error.message : 'unknown error',\n };\n\n completedAuths.set(state, errorResult);\n setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n const pending = pendingAuths.get(state);\n if (pending) {\n pending.reject(error);\n cleanupPendingAuth(state);\n }\n\n debug('Callback auth error: %s', error);\n res\n .status(500)\n .send(\n '<html><body><p>Authorization failed. You can close this window and try again.</p></body></html>',\n );\n }\n});\n\napp.use(validateSessionMiddleware);\n\n// --- Poll/complete-auth coordination ---\n\ntype PendingAuth = {\n id: string;\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n// NOTE: These in-memory maps make the auth handoff process-local.\n// Multi-instance deployments require sticky routing so the same instance\n// handles both the callback and client poll for a given state.\nconst pendingAuths = new Map<string, PendingAuth>();\nconst completedAuths = new Map<string, unknown>();\nlet nextWaiterId = 0;\n\nconst POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\nconst COMPLETED_AUTH_TTL_MS = 30 * 1000; // 30 seconds\n\nfunction cleanupPendingAuth(state: string, waiterId?: string) {\n const entry = pendingAuths.get(state);\n if (entry && (waiterId == null || entry.id === waiterId)) {\n clearTimeout(entry.timer);\n pendingAuths.delete(state);\n }\n}\n\n// --- Routes ---\n\napp.post(\n '/status',\n handleError(async (req: Request, res: Response) => {\n const configured = enableBankingService.isConfigured();\n\n res.send({\n status: 'ok',\n data: {\n configured,\n },\n });\n }),\n);\n\napp.post(\n '/configure',\n handleError(async (req: Request, res: Response) => {\n const { applicationId, secretKey } = req.body || {};\n\n if (!applicationId || !secretKey) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing applicationId or secretKey',\n },\n });\n return;\n }\n\n // Validate credentials before persisting to avoid exposing\n // transient bad creds to concurrent requests\n try {\n const appInfo = await enableBankingService.validateCredentials(\n applicationId,\n secretKey,\n );\n debug('Enable Banking application validated: %o', appInfo);\n } catch (error) {\n debug('Enable Banking configuration validation failed: %s', error);\n res.send({\n status: 'ok',\n data: {\n error_code: 'CONFIGURATION_FAILED',\n error_type: error instanceof Error ? error.message : 'unknown error',\n },\n });\n return;\n }\n\n // Only persist after successful validation\n secretsService.set(SecretName.enablebanking_applicationId, applicationId);\n secretsService.set(SecretName.enablebanking_secretKey, secretKey);\n\n res.send({\n status: 'ok',\n data: {\n configured: true,\n },\n });\n }),\n);\n\napp.post(\n '/aspsps',\n handleError(async (req: Request, res: Response) => {\n const { country } = req.body || {};\n\n try {\n const aspsps = await enableBankingService.getAspsps(country);\n\n res.send({\n status: 'ok',\n data: aspsps,\n });\n } catch (error) {\n res.send({\n status: 'ok',\n data: {\n error: error instanceof Error ? error.message : 'unknown error',\n },\n });\n }\n }),\n);\n\napp.post(\n '/start-auth',\n handleError(async (req: Request, res: Response) => {\n const { aspsp, redirectUrl, maxConsentValidity } = req.body || {};\n\n if (!aspsp || !redirectUrl) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing aspsp or redirectUrl',\n },\n });\n return;\n }\n\n const state = uuidv4();\n\n try {\n const authResponse = await enableBankingService.startAuth(\n aspsp,\n redirectUrl,\n state,\n typeof maxConsentValidity === 'number' ? maxConsentValidity : undefined,\n );\n\n res.send({\n status: 'ok',\n data: {\n url: authResponse.url,\n state,\n },\n });\n } catch (error) {\n res.send({\n status: 'ok',\n data: {\n error: error instanceof Error ? error.message : 'unknown error',\n },\n });\n }\n }),\n);\n\napp.post(\n '/complete-auth',\n handleError(async (req: Request, res: Response) => {\n const { code, state } = req.body || {};\n\n if (!code) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing code',\n },\n });\n return;\n }\n\n try {\n const session = await enableBankingService.createSession(code);\n debug(\n 'Session created: %s with %d accounts',\n session.session_id,\n session.accounts.length,\n );\n\n const result = await buildSessionResult(session, extractPsuHeaders(req));\n\n // Always cache so retries within TTL can read the result\n if (state) {\n completedAuths.set(state, result);\n setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n const pending = pendingAuths.get(state);\n if (pending) {\n pending.resolve(result);\n cleanupPendingAuth(state);\n }\n }\n\n res.send({\n status: 'ok',\n data: result,\n });\n } catch (error) {\n const errorResult = {\n error: error instanceof Error ? error.message : 'unknown error',\n };\n\n if (state) {\n completedAuths.set(state, errorResult);\n setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n const pending = pendingAuths.get(state);\n if (pending) {\n pending.reject(error);\n cleanupPendingAuth(state);\n }\n }\n\n res.send({\n status: 'ok',\n data: errorResult,\n });\n }\n }),\n);\n\napp.post(\n '/poll-auth',\n handleError(async (req: Request, res: Response) => {\n const { state } = req.body || {};\n\n if (!state) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing state',\n },\n });\n return;\n }\n\n const waiterId = String(++nextWaiterId);\n let hasClientDisconnected = false;\n\n try {\n // If complete-auth already fired before poll-auth, return immediately\n if (completedAuths.has(state)) {\n const result = completedAuths.get(state);\n completedAuths.delete(state);\n res.send({ status: 'ok', data: result });\n return;\n }\n\n const result = await new Promise((resolve, reject) => {\n // Clean up any existing waiter for this state\n const existing = pendingAuths.get(state);\n if (existing) {\n clearTimeout(existing.timer);\n existing.reject(new Error('Poll superseded'));\n }\n\n let settled = false;\n const safeResolve = (value: unknown) => {\n if (settled) return;\n settled = true;\n resolve(value);\n };\n const safeReject = (reason: unknown) => {\n if (settled) return;\n settled = true;\n reject(reason);\n };\n\n const timer = setTimeout(() => {\n cleanupPendingAuth(state, waiterId);\n safeReject(new Error('Polling timed out'));\n }, POLL_TIMEOUT_MS);\n\n pendingAuths.set(state, {\n id: waiterId,\n resolve: safeResolve,\n reject: safeReject,\n timer,\n });\n\n // Clean up if client disconnects before resolution\n res.on('close', () => {\n if (!res.writableFinished && !settled) {\n hasClientDisconnected = true;\n cleanupPendingAuth(state, waiterId);\n safeReject(new Error('Client disconnected'));\n }\n });\n });\n\n if (hasClientDisconnected || res.destroyed || res.writableEnded) {\n return;\n }\n\n res.send({\n status: 'ok',\n data: result,\n });\n } catch (error) {\n cleanupPendingAuth(state, waiterId);\n if (hasClientDisconnected || res.destroyed || res.writableEnded) {\n return;\n }\n res.send({\n status: 'ok',\n data: {\n error: error instanceof Error ? error.message : 'unknown error',\n },\n });\n }\n }),\n);\n\napp.post(\n '/transactions',\n handleError(async (req: Request, res: Response) => {\n const { accountId, startDate } = req.body || {};\n\n if (!accountId || !startDate) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing accountId or startDate',\n },\n });\n return;\n }\n\n const psuHeaders = extractPsuHeaders(req);\n\n try {\n const dateTo = new Date().toISOString().split('T')[0];\n const dateFrom =\n typeof startDate === 'string'\n ? startDate\n : new Date(startDate).toISOString().split('T')[0];\n\n // Fetch balances\n const balanceResult = await enableBankingService.getBalances(\n accountId,\n psuHeaders,\n );\n const balances = balanceResult.balances.map(normalizeBalance);\n\n // Determine starting balance, preferring CLAV balance type\n let startingBalance = 0;\n if (balances.length > 0) {\n const preferredBalance =\n balances.find(b => b.balanceType === 'CLAV') ?? balances[0];\n startingBalance = preferredBalance.balanceAmount.amount;\n }\n\n // Fetch all paginated transactions\n const rawTransactions = await enableBankingService.getAllTransactions(\n accountId,\n dateFrom,\n dateTo,\n psuHeaders,\n );\n\n const all: ReturnType<typeof normalizeTransaction>[] = [];\n const booked: ReturnType<typeof normalizeTransaction>[] = [];\n const pending: ReturnType<typeof normalizeTransaction>[] = [];\n\n for (const tx of rawTransactions) {\n const normalized = normalizeTransaction(tx);\n all.push(normalized);\n if (normalized.booked) {\n booked.push(normalized);\n } else {\n pending.push(normalized);\n }\n }\n\n res.send({\n status: 'ok',\n data: {\n transactions: {\n all,\n booked,\n pending,\n },\n balances,\n startingBalance,\n },\n });\n } catch (error) {\n debug('Error fetching transactions: %s', error);\n\n // Return structured error codes so the client can show\n // appropriate UI (e.g. re-auth prompt for expired sessions)\n if (error instanceof EnableBankingError) {\n if (error.error_code === 'INVALID_ACCESS_TOKEN') {\n res.send({\n status: 'ok',\n data: {\n error_type: 'ITEM_ERROR',\n error_code: 'ITEM_LOGIN_REQUIRED',\n },\n });\n return;\n }\n\n // The bank-sync wire format expects `error_type` to be a broad\n // machine-readable category (matched by AccountSyncCheck's switch),\n // not the human message we now keep on `EnableBankingError.error_type`.\n const wireErrorType =\n error.error_code === 'NOT_FOUND' ? 'INVALID_INPUT' : error.error_code;\n\n res.send({\n status: 'ok',\n data: {\n error_type: wireErrorType,\n error_code: error.error_code,\n },\n });\n return;\n }\n\n res.send({\n status: 'ok',\n data: {\n error_type: 'INTERNAL_ERROR',\n error_code: 'INTERNAL_ERROR',\n },\n });\n }\n }),\n);\n","import crypto from 'crypto';\n\nexport function sha256String(str: string) {\n return crypto.createHash('sha256').update(str).digest('base64');\n}\n","import type { GoCardlessRequisitionId } from './gocardless-node.types';\n\nexport class RequisitionNotLinked extends Error {\n details: unknown;\n\n constructor(params: unknown = {}) {\n super('Requisition not linked yet');\n this.details = params;\n }\n}\n\nexport class AccountNotLinkedToRequisition extends Error {\n details: {\n accountId: string;\n requisitionId: GoCardlessRequisitionId;\n };\n\n constructor(accountId: string, requisitionId: GoCardlessRequisitionId) {\n super('Provided account id is not linked to given requisition');\n this.details = { accountId, requisitionId };\n }\n}\n\nexport class GenericGoCardlessError extends Error {\n details: unknown;\n\n constructor(data: unknown = {}) {\n super('GoCardless returned error');\n this.details = data;\n }\n}\n\nexport class GoCardlessClientError extends Error {\n details: unknown;\n\n constructor(message: string, details: unknown) {\n super(message);\n this.details = details;\n }\n}\n\nexport class InvalidInputDataError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Invalid provided parameters', response);\n }\n}\n\nexport class InvalidGoCardlessTokenError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Token is invalid or expired', response);\n }\n}\n\nexport class AccessDeniedError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('IP address access denied', response);\n }\n}\n\nexport class NotFoundError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Resource not found', response);\n }\n}\n\nexport class ResourceSuspended extends GoCardlessClientError {\n constructor(response: unknown) {\n super(\n 'Resource was suspended due to numerous errors that occurred while accessing it',\n response,\n );\n }\n}\n\nexport class RateLimitError extends GoCardlessClientError {\n constructor(response: unknown) {\n super(\n 'Daily request limit set by the Institution has been exceeded',\n response,\n );\n }\n}\n\nexport class UnknownError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Request to Institution returned an error', response);\n }\n}\n\nexport class ServiceError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Institution service unavailable', response);\n }\n}\n","import type { Transaction } from './gocardless-node.types';\n\nexport const printIban = (account: { iban?: string | null }): string => {\n if (account.iban) {\n return '(XXX ' + account.iban.slice(-4) + ')';\n } else {\n return '';\n }\n};\n\nconst compareDates = (\n a: string | number | Date | undefined,\n b: string | number | Date | undefined,\n): number => {\n if (a == null && b == null) {\n return 0;\n } else if (a == null) {\n return 1;\n } else if (b == null) {\n return -1;\n }\n\n return +new Date(a) - +new Date(b);\n};\n\nconst compareFunctions: ((a: Transaction, b: Transaction) => number)[] = [\n (a, b) => compareDates(a.bookingDate, b.bookingDate),\n (a, b) => compareDates(a.bookingDateTime, b.bookingDateTime),\n (a, b) => compareDates(a.valueDate, b.valueDate),\n (a, b) => compareDates(a.valueDateTime, b.valueDateTime),\n];\n\nexport const sortByBookingDateOrValueDate = <T extends Transaction>(\n transactions: T[] = [],\n): T[] =>\n transactions.sort((a, b) => {\n for (const sortFunction of compareFunctions) {\n const result = sortFunction(b, a);\n if (result !== 0) {\n return result;\n }\n }\n return 0;\n });\n\nexport const amountToInteger = (n: string | number): number =>\n Math.round(Number(n) * 100);\n","const conjunctions = [\n 'for', //\n 'and',\n 'nor',\n 'but',\n 'or',\n 'yet',\n 'so',\n];\n\nconst articles = [\n 'a', //\n 'an',\n 'the',\n];\n\nconst prepositions = [\n 'aboard',\n 'about',\n 'above',\n 'across',\n 'after',\n 'against',\n 'along',\n 'amid',\n 'among',\n 'anti',\n 'around',\n 'as',\n 'at',\n 'before',\n 'behind',\n 'below',\n 'beneath',\n 'beside',\n 'besides',\n 'between',\n 'beyond',\n 'but',\n 'by',\n 'concerning',\n 'considering',\n 'despite',\n 'down',\n 'during',\n 'except',\n 'excepting',\n 'excluding',\n 'following',\n 'for',\n 'from',\n 'in',\n 'inside',\n 'into',\n 'like',\n 'minus',\n 'near',\n 'of',\n 'off',\n 'on',\n 'onto',\n 'opposite',\n 'over',\n 'past',\n 'per',\n 'plus',\n 'regarding',\n 'round',\n 'save',\n 'since',\n 'than',\n 'through',\n 'to',\n 'toward',\n 'towards',\n 'under',\n 'underneath',\n 'unlike',\n 'until',\n 'up',\n 'upon',\n 'versus',\n 'via',\n 'with',\n 'within',\n 'without',\n];\n\nexport const lowerCaseSet = new Set([\n ...conjunctions,\n ...articles,\n ...prepositions,\n]);\n","export const specials = [\n 'CLI',\n 'API',\n 'HTTP',\n 'HTTPS',\n 'JSX',\n 'DNS',\n 'URL',\n 'CI',\n 'CDN',\n 'GitHub',\n 'CSS',\n 'JS',\n 'JavaScript',\n 'TypeScript',\n 'HTML',\n 'WordPress',\n 'JavaScript',\n 'Next.js',\n 'Node.js',\n];\n","// Utilities\nimport { lowerCaseSet } from './lower-case';\nimport { specials } from './specials';\n\nconst character =\n '[0-9\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58-\\u0C59\\u0C60-\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0-\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E87-\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA-\\u0EAB\\u0EAD-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]';\nconst regex = new RegExp(\n `(?:(?:(\\\\s?(?:^|[.\\\\(\\\\)!?;:\"-])\\\\s*)(${character}))|(${character}))(${character}*[’']*${character}*)`,\n 'g',\n);\n\nconst convertToRegExp = specials =>\n specials.map(s => [new RegExp(`\\\\b${s}\\\\b`, 'gi'), s]);\n\nfunction parseMatch(match) {\n const firstCharacter = match[0];\n\n // test first character\n if (/\\s/.test(firstCharacter)) {\n // if whitespace - trim and return\n return match.substr(1);\n }\n if (/[()]/.test(firstCharacter)) {\n // if parens - this shouldn't be replaced\n return null;\n }\n\n return match;\n}\n\nexport function title(str, options = { special: undefined }) {\n str = str\n .toLowerCase()\n .replace(regex, (m, lead = '', forced, lower, rest) => {\n const parsedMatch = parseMatch(m);\n if (!parsedMatch) {\n return m;\n }\n if (!forced) {\n const fullLower = lower + rest;\n\n if (lowerCaseSet.has(fullLower)) {\n return parsedMatch;\n }\n }\n\n return lead + (lower || forced).toUpperCase() + rest;\n });\n\n const customSpecials = options.special || [];\n const replace = [...specials, ...customSpecials];\n const replaceRegExp = convertToRegExp(replace);\n\n replaceRegExp.forEach(([pattern, s]) => {\n str = str.replace(pattern, s);\n });\n\n return str;\n}\n","import type { Transaction } from '#app-gocardless/gocardless-node.types';\n\nimport { title } from './title/index';\n\nfunction formatPayeeIban(iban: string) {\n return '(' + iban.slice(0, 4) + ' XXX ' + iban.slice(-4) + ')';\n}\n\nexport const formatPayeeName = (trans: Transaction) => {\n const amount = Number(trans.transactionAmount.amount);\n const nameParts = [];\n\n // get the correct name and account fields for the transaction amount\n let name;\n let account;\n if (amount > 0 || Object.is(amount, 0)) {\n name = trans.debtorName;\n account = trans.debtorAccount;\n } else {\n name = trans.creditorName;\n account = trans.creditorAccount;\n }\n\n // use the correct name field if it was found\n // if not, use whatever we can find\n\n // if the primary name option is set, prevent the account from falling back\n account = name ? account : trans.debtorAccount || trans.creditorAccount;\n\n name =\n name ||\n trans.debtorName ||\n trans.creditorName ||\n trans.remittanceInformationUnstructured ||\n (trans.remittanceInformationUnstructuredArray || []).join(', ') ||\n trans.additionalInformation;\n\n if (name) {\n nameParts.push(title(name));\n }\n\n if (typeof account === 'object' && account && account.iban) {\n nameParts.push(formatPayeeIban(account.iban));\n }\n\n return nameParts.join(' ');\n};\n","import * as d from 'date-fns';\n\nimport {\n amountToInteger,\n printIban,\n sortByBookingDateOrValueDate,\n} from '#app-gocardless/utils';\nimport { formatPayeeName } from '#util/payee-name';\n\nimport type { IBank } from './bank.interface';\n\nconst SORTED_BALANCE_TYPE_LIST = [\n 'closingBooked',\n 'expected',\n 'forwardAvailable',\n 'interimAvailable',\n 'interimBooked',\n 'nonInvoiced',\n 'openingBooked',\n];\n\nexport default {\n institutionIds: ['IntegrationBank'],\n\n normalizeAccount(account) {\n return {\n account_id: account.id,\n institution: account.institution,\n mask: (account?.iban || '0000').slice(-4),\n iban: account?.iban || null,\n name: [\n account.name ?? account.displayName ?? account.product,\n printIban(account),\n account.currency,\n ]\n .filter(Boolean)\n .join(' '),\n official_name: account.product ?? `integration-${account.institution_id}`,\n type: 'checking',\n };\n },\n\n normalizeTransaction(transaction, _booked, editedTransaction?) {\n const trans = editedTransaction ?? transaction;\n\n const date =\n trans.date ||\n transaction.bookingDate ||\n transaction.bookingDateTime ||\n transaction.valueDate ||\n transaction.valueDateTime;\n\n // If we couldn't find a valid date field we filter out this transaction\n // and hope that we will import it again once the bank has processed the\n // transaction further.\n if (!date) {\n return null;\n }\n\n const notes =\n trans.notes ??\n trans.remittanceInformationUnstructured ??\n trans.remittanceInformationUnstructuredArray?.join(' ') ??\n '';\n\n transaction.remittanceInformationUnstructuredArrayString =\n transaction.remittanceInformationUnstructuredArray?.join(',');\n transaction.remittanceInformationStructuredArrayString =\n transaction.remittanceInformationStructuredArray?.join(',');\n\n return {\n ...transaction,\n payeeName: trans.payeeName ?? formatPayeeName(trans),\n date: d.format(d.parseISO(date), 'yyyy-MM-dd'),\n notes,\n };\n },\n\n sortTransactions(transactions = []) {\n return sortByBookingDateOrValueDate(transactions);\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances\n .filter(item => SORTED_BALANCE_TYPE_LIST.includes(item.balanceType))\n .sort(\n (a, b) =>\n SORTED_BALANCE_TYPE_LIST.indexOf(a.balanceType) -\n SORTED_BALANCE_TYPE_LIST.indexOf(b.balanceType),\n )[0];\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'ABANCA_CAGLESMM',\n 'ABANCA_CAGLPTPL',\n 'ABANCA_CORP_CAGLPTPL',\n ],\n\n // Abanca transactions doesn't get the creditorName/debtorName properly\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.creditorName = transaction.remittanceInformationStructured;\n editedTrans.debtorName = transaction.remittanceInformationStructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ABNAMRO_ABNANL2A'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const infoArray = transaction.remittanceInformationUnstructuredArray ?? [];\n\n // There is no remittanceInformationUnstructured, so we'll make it\n editedTrans.remittanceInformationUnstructured = infoArray.join(', ');\n\n // Remove clutter to extract the payee from remittanceInformationUnstructured ...\n // ... when not otherwise provided.\n const payeeName = infoArray\n .map(el => el.match(/^(?:.*\\*)?(.+),PAS\\d+$/))\n .find(match => match)?.[1];\n\n editedTrans.debtorName = transaction.debtorName || payeeName;\n editedTrans.creditorName = transaction.creditorName || payeeName;\n\n editedTrans.date = (transaction.valueDateTime ?? '').slice(0, 10);\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n sortTransactions(transactions = []) {\n return transactions.sort(\n (a, b) =>\n +new Date(b.valueDateTime ?? '') - +new Date(a.valueDateTime ?? ''),\n );\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n if (sortedTransactions.length) {\n const oldestTransaction =\n sortedTransactions[sortedTransactions.length - 1];\n const oldestKnownBalance = amountToInteger(\n oldestTransaction.balanceAfterTransaction?.balanceAmount.amount || 0,\n );\n const oldestTransactionAmount = amountToInteger(\n oldestTransaction.transactionAmount.amount,\n );\n\n return oldestKnownBalance - oldestTransactionAmount;\n } else {\n return amountToInteger(\n balances.find(balance => 'interimBooked' === balance.balanceType)\n ?.balanceAmount.amount || 0,\n );\n }\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['AMERICAN_EXPRESS_AESUDEF1'],\n\n normalizeAccount(account) {\n return {\n ...Fallback.normalizeAccount(account),\n // The `iban` field for these American Express cards is actually a masked\n // version of the PAN. No IBAN is provided.\n mask: account.iban.slice(-5),\n iban: null,\n name: [account.details, `(${account.iban.slice(-5)})`].join(' '),\n official_name: account.details ?? '',\n };\n },\n\n /**\n * For AMERICAN_EXPRESS_AESUDEF1 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use the non-standard `information` balance type\n * which is the only one provided for American Express.\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'information' === balance.balanceType.toString(),\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BANCSABADELL_BSABESBB'],\n\n // Sabadell transactions don't get the creditorName/debtorName properly\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const amount = transaction.transactionAmount.amount;\n\n // The amount is negative for outgoing transactions, positive for incoming transactions.\n const isCreditorPayee = Number.parseFloat(amount) < 0;\n\n const payeeName = (transaction.remittanceInformationUnstructuredArray ?? [])\n .join(' ')\n .trim();\n\n // The payee name is the creditor name for outgoing transactions and the debtor name for incoming transactions.\n editedTrans.creditorName = isCreditorPayee ? payeeName : undefined;\n editedTrans.debtorName = isCreditorPayee ? undefined : payeeName;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BANK_OF_IRELAND_B365_BOFIIE2D'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured = fixupPayee(\n transaction.remittanceInformationUnstructured ?? '',\n );\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n\nfunction fixupPayee(payee: string) {\n let fixedPayee = payee;\n\n // remove all duplicate whitespace\n fixedPayee = fixedPayee.replace(/\\s+/g, ' ').trim();\n\n // remove date prefix\n fixedPayee = fixedPayee.replace(/^(POS)?(C)?[0-9]{1,2}\\w{3}/, '').trim();\n\n // remove direct debit postfix\n fixedPayee = fixedPayee.replace(/sepa dd$/i, '').trim();\n\n // remove bank transfer prefix\n fixedPayee = fixedPayee.replace(/^365 online/i, '').trim();\n\n // remove curve card prefix\n fixedPayee = fixedPayee.replace(/^CRV\\*/, '').trim();\n\n return fixedPayee;\n}\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BANKINTER_BKBKESMM'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured = (\n transaction.remittanceInformationUnstructured ?? ''\n )\n .replaceAll(/\\/Txt\\/(\\w\\|)?/gi, '')\n .replaceAll(';', ' ');\n\n editedTrans.debtorName = transaction.debtorName?.replaceAll(';', ' ');\n editedTrans.creditorName =\n transaction.creditorName?.replaceAll(';', ' ') ??\n editedTrans.remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BELFIUS_GKCCBEBB'],\n\n // The problem is that we have transaction with duplicated transaction ids.\n // This is not expected and the nordigen api has a work-around for some backs\n // They will set an internalTransactionId which is unique\n normalizeTransaction(transaction, booked) {\n transaction.transactionId = transaction.internalTransactionId;\n\n return Fallback.normalizeTransaction(transaction, booked);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BERLINER_SPARKASSE_BELADEBEXXX'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let remittanceInformationUnstructured;\n\n if (transaction.remittanceInformationUnstructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured;\n } else if (transaction.remittanceInformationStructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructured;\n } else if (\n (transaction.remittanceInformationStructuredArray?.length ?? 0) > 0\n ) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructuredArray?.join(' ');\n }\n\n if (transaction.additionalInformation) {\n remittanceInformationUnstructured +=\n ' ' + transaction.additionalInformation;\n }\n\n const usefulCreditorName =\n transaction.ultimateCreditor ||\n transaction.creditorName ||\n transaction.debtorName;\n\n editedTrans.creditorName = usefulCreditorName;\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n /**\n * For SANDBOXFINANCE_SFIN0000 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'FINTRO_BE_GEBABEBB',\n 'HELLO_BE_GEBABEBB',\n 'BNP_BE_GEBABEBB',\n ],\n\n /** BNP_BE_GEBABEBB provides a lot of useful information via the 'additionalField'\n * There does not seem to be a specification of this field, but the following information is contained in its subfields:\n * - for pending transactions: the 'atmPosName'\n * - for booked transactions: the 'narrative'.\n * This narrative subfield is most useful as it contains information required to identify the transaction,\n * especially in case of debit card or instant payment transactions.\n * Do note that the narrative subfield ALSO contains the remittance information if any.\n * The goal of the normalization is to place any relevant information of the additionalInformation\n * field in the remittanceInformationUnstructuredArray field.\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Extract the creditor name to fill it in with information from the\n // additionalInformation field in case it's not yet defined.\n let creditorName = transaction.creditorName;\n\n if (transaction.additionalInformation) {\n const additionalInformationObject: Record<string, string> = {};\n const additionalInfoRegex = /(, )?([^:]+): ((\\[.*?\\])|([^,]*))/g;\n const matches =\n transaction.additionalInformation.matchAll(additionalInfoRegex);\n if (matches) {\n let creditorNameFromNarrative; // Possible value for creditorName\n for (const match of matches) {\n const key = match[2].trim();\n let value = (match[4] || match[5]).trim();\n if (key === 'narrative') {\n // Set narrativeName to the first element in the \"narrative\" array.\n const first_value = value.matchAll(/'(.+?)'/g)?.next().value;\n creditorNameFromNarrative = first_value\n ? first_value[1].trim()\n : undefined;\n }\n // Remove square brackets and single quotes and commas\n value = value.replace(/[[\\]',]/g, '');\n additionalInformationObject[key] = value;\n }\n // Keep existing unstructuredArray and add atmPosName and narrative\n editedTrans.remittanceInformationUnstructuredArray = [\n ...(transaction.remittanceInformationUnstructuredArray ?? []),\n additionalInformationObject?.atmPosName,\n additionalInformationObject?.narrative,\n ].filter(Boolean) as string[];\n\n // If the creditor name doesn't exist in the original transactions,\n // set it to the atmPosName or narrativeName if they exist; otherwise\n // leave empty and let the default rules handle it.\n creditorName =\n creditorName ??\n additionalInformationObject?.atmPosName ??\n creditorNameFromNarrative ??\n null;\n }\n }\n\n editedTrans.creditorName = creditorName;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { title } from '#util/title';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nconst regexCard =\n /^CARTE (?<date>\\d{2}\\/\\d{2}\\/\\d{2}) (?<payeeName>.+?)( \\d+)?( CB\\*\\d{4})?$/;\nconst regexAtmWithdrawal =\n /^RETRAIT DAB (?<date>\\d{2}\\/\\d{2}\\/\\d{2}) (?<locationName>.+?) CB\\*\\d{4,}/;\nconst regexTransfer = /^VIR /;\nconst regexInstantTransfer = /^VIR INST /;\nconst regexSepa = /^(PRLV|VIR) SEPA /;\nconst regexLoan = /^ECH PRET:/;\nconst regexCreditNote =\n /^AVOIR (?<date>\\d{2}\\/\\d{2}\\/\\d{2}) (?<payeeName>.+?) CB\\*\\d{4,}/;\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BOURSORAMA_BOUSFRPP'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructuredArray = (\n transaction.remittanceInformationUnstructuredArray ?? []\n )\n // Remove the localisation with backslashes that are sometimes present\n .map(line => line.replace(/\\\\.+/g, ''))\n // Remove an unwanted line that pollutes the remittance information\n .filter(line => line.startsWith('Réf : ') === false);\n\n const infoArray = editedTrans.remittanceInformationUnstructuredArray;\n\n let match: string | undefined;\n\n // Transactions can have their identifier in any line, as the order of lines is not guaranteed.\n // This is why we check all lines for specific patterns.\n if ((match = infoArray.find(line => regexCard.test(line)))) {\n // Card transaction\n const cardMatch = match.match(regexCard);\n editedTrans.payeeName = title(cardMatch?.groups?.payeeName ?? '');\n editedTrans.notes = `Carte ${cardMatch?.groups?.date}`;\n if (infoArray.length > 1) {\n editedTrans.notes += ' ' + infoArray.filter(l => l !== match).join(' ');\n }\n } else if ((match = infoArray.find(line => regexLoan.test(line)))) {\n // Loan\n editedTrans.payeeName = 'Prêt bancaire';\n editedTrans.notes = match;\n } else if (\n (match = infoArray.find(line => regexAtmWithdrawal.test(line)))\n ) {\n // ATM withdrawal\n const atmMatch = match.match(regexAtmWithdrawal);\n editedTrans.payeeName = 'Retrait DAB';\n editedTrans.notes = `Retrait ${atmMatch?.groups?.date} ${atmMatch?.groups?.locationName}`;\n if (infoArray.length > 1) {\n editedTrans.notes += ' ' + infoArray.filter(l => l !== match).join(' ');\n }\n } else if ((match = infoArray.find(line => regexCreditNote.test(line)))) {\n // Credit note (refund)\n const creditMatch = match.match(regexCreditNote);\n editedTrans.payeeName = title(creditMatch?.groups?.payeeName ?? '');\n editedTrans.notes = `Avoir ${creditMatch?.groups?.date}`;\n if (infoArray.length > 1) {\n editedTrans.notes += ' ' + infoArray.filter(l => l !== match).join(' ');\n }\n } else if (\n (match = infoArray.find(line => regexInstantTransfer.test(line)))\n ) {\n // Instant transfer\n editedTrans.payeeName = title(match.replace(regexInstantTransfer, ''));\n editedTrans.notes = infoArray.filter(l => l !== match).join(' ');\n } else if ((match = infoArray.find(line => regexSepa.test(line)))) {\n // SEPA transfer\n editedTrans.payeeName = title(match.replace(regexSepa, ''));\n editedTrans.notes = infoArray.filter(l => l !== match).join(' ');\n } else if ((match = infoArray.find(line => regexTransfer.test(line)))) {\n // Other transfer\n // Must be evaluated after the other transfers as they're more specific\n // (here VIR only)\n const infoArrayWithoutLine = infoArray.filter(l => l !== match);\n editedTrans.payeeName = title(infoArrayWithoutLine.join(' '));\n editedTrans.notes = match.replace(regexTransfer, '');\n } else {\n // Unknown transaction type\n editedTrans.payeeName = title(infoArray[0].replace(/ \\d+$/, ''));\n editedTrans.notes = infoArray.slice(1).join(' ');\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","/**\n * Normalize BPER Retail BPMOIT22 transactions by extracting a friendly payee\n * while keeping the raw description in the notes field.\n */\n\nimport type { Transaction } from '#app-gocardless/gocardless-node.types';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nconst CARD_PAYMENT_PREFIX = 'PAGAMENTO SU CIRCUITO INTERNAZIONALE';\nconst CARD_PAYMENT_SUFFIX = 'Operazione carta';\nconst BONIFICO_PREFIX = 'BONIFICO';\nconst BONIFICO_ESTERI_PREFIX = 'BONIFICI ESTERI';\nconst SDD_PREFIX = 'ADDEBITO SDD';\nconst BOLLETTINO_MARKER = 'CREDITORE:';\n\nconst BONIFICO_ORIGINATOR_REGEX =\n /o\\/c:\\s*([A-Z0-9\\s.'/&-]+?)(?:ABI|BIC|IBAN|a favore di|Num|EUR|$)/i;\nconst SDD_PAYEE_REGEX = /ADDEBITO SDD\\s+([A-Z0-9\\s.'/&-]+?)(?:N:|ID:|$)/i;\nconst BOLLETTINO_PAYEE_REGEX = /CREDITORE:\\s*([A-Z0-9\\s.'/&-]+)/i;\n\n// Extract payee for card transactions\nfunction parseCardPayee(description: string) {\n const [beforeSuffix] = description.split(CARD_PAYMENT_SUFFIX);\n\n return beforeSuffix.replace(CARD_PAYMENT_PREFIX, '').trim();\n}\n\n// Extract originator for bonifico (domestic/foreign transfers)\nfunction parseBonificoOriginator(description: string) {\n const match = description.match(BONIFICO_ORIGINATOR_REGEX);\n\n return match ? match[1].trim() : '';\n}\n\n// Extract creditor for SDD direct debits\nfunction parseSddPayee(description: string) {\n const match = description.match(SDD_PAYEE_REGEX);\n\n return match ? match[1].trim() : '';\n}\n\n// Extract creditor for bollettini / utilities\nfunction parseBollettinoPayee(description: string) {\n const match = description.match(BOLLETTINO_PAYEE_REGEX);\n\n return match ? match[1].trim() : '';\n}\n\nfunction setPayee(editedTransaction: Transaction, payee: string) {\n if (!payee) {\n return;\n }\n\n editedTransaction.creditorName = payee;\n editedTransaction.debtorName = payee;\n}\n\nconst BperRetailBank = {\n ...Fallback,\n\n institutionIds: ['BPER_RETAIL_BPMOIT22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n const description = (\n transaction.remittanceInformationUnstructured || ''\n ).trim();\n\n if (description) {\n editedTrans.remittanceInformationUnstructured = description;\n }\n\n let payee = '';\n\n if (description.startsWith(CARD_PAYMENT_PREFIX)) {\n payee = parseCardPayee(description);\n } else if (\n description.startsWith(BONIFICO_PREFIX) ||\n description.startsWith(BONIFICO_ESTERI_PREFIX)\n ) {\n payee = parseBonificoOriginator(description);\n } else if (description.startsWith(SDD_PREFIX)) {\n payee = parseSddPayee(description);\n } else if (description.includes(BOLLETTINO_MARKER)) {\n payee = parseBollettinoPayee(description);\n }\n\n setPayee(editedTrans, payee);\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n\nexport default BperRetailBank;\n","/**\n * Extracts the payee name from the unstructured remittance information string based on pattern detection.\n *\n * This function scans the `remittanceInformationUnstructured` string for the presence of\n * any of the specified patterns and removes the substring from the position of the last\n * occurrence of the most relevant pattern. If no patterns are found, it returns the original string.\n *\n * @param remittanceInformationUnstructured - The unstructured remittance information from which to extract the payee name.\n * @param patterns - An array of patterns to look for within the remittance information.\n * These patterns are used to identify and remove unwanted parts of the remittance information.\n * @returns The extracted payee name, cleaned of any matched patterns, or the original\n * remittance information if no patterns are found.\n *\n * @example\n * const remittanceInfo = 'John Doe Paiement Maestro par Carte de débit CBC 05-09-2024 à 15.43 heures 6703 19XX XXXX X...';\n * const patterns = ['Paiement', 'Domiciliation', 'Transfert', 'Ordre permanent'];\n * const payeeName = extractPayeeNameFromRemittanceInfo(remittanceInfo, patterns); // --> 'John Doe'\n */\nexport function extractPayeeNameFromRemittanceInfo(\n remittanceInformationUnstructured: string,\n patterns: string[],\n): string {\n if (!remittanceInformationUnstructured || !patterns.length) {\n return remittanceInformationUnstructured;\n }\n\n const indexForRemoval = patterns.reduce((maxIndex, pattern) => {\n const index = remittanceInformationUnstructured.lastIndexOf(pattern);\n return index > maxIndex ? index : maxIndex;\n }, -1);\n\n return indexForRemoval > -1\n ? remittanceInformationUnstructured.substring(0, indexForRemoval).trim()\n : remittanceInformationUnstructured;\n}\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\nimport { extractPayeeNameFromRemittanceInfo } from './util/extract-payeeName-from-remittanceInfo';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['CBC_CREGBEBB'],\n\n /**\n * For negative amounts, the only payee information we have is returned in\n * remittanceInformationUnstructured.\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n if (Number(transaction.transactionAmount.amount) > 0) {\n editedTrans.payeeName =\n transaction.debtorName ||\n transaction.remittanceInformationUnstructured ||\n 'undefined';\n } else {\n editedTrans.payeeName =\n transaction.creditorName ||\n extractPayeeNameFromRemittanceInfo(\n transaction.remittanceInformationUnstructured ?? '',\n ['Paiement', 'Domiciliation', 'Transfert', 'Ordre permanent'],\n ) ||\n 'undefined';\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['CETELEM_CETMPTP1XXX'],\n\n /**\n * Sign of transaction amount needs to be flipped for Cetelem Black credit cards\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n transaction.transactionAmount = {\n // Flip transaction amount sign\n ...transaction.transactionAmount,\n amount: (-parseFloat(transaction.transactionAmount.amount)).toString(),\n };\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","// escape special characters in the string to create a valid regular expression\nexport function escapeRegExp(string: string): string {\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\nimport { escapeRegExp } from './util/escape-regexp';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['COMMERZBANK_COBADEFF'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // remittanceInformationUnstructured is limited to 140 chars thus ...\n // ... missing information form remittanceInformationUnstructuredArray ...\n // ... so we recreate it.\n editedTrans.remittanceInformationUnstructured = (\n transaction.remittanceInformationUnstructuredArray ?? []\n ).join(' ');\n\n // The limitations of remittanceInformationUnstructuredArray ...\n // ... can result in split keywords. We fix these. Other ...\n // ... splits will need to be fixed by user with rules.\n const keywords = [\n 'End-to-End-Ref.:',\n 'Mandatsref:',\n 'Gläubiger-ID:',\n 'SEPA-BASISLASTSCHRIFT',\n 'Kartenzahlung',\n 'Dauerauftrag',\n ];\n keywords.forEach(keyword => {\n editedTrans.remittanceInformationUnstructured = (\n editedTrans.remittanceInformationUnstructured ?? ''\n ).replace(\n // There can be spaces in keywords\n RegExp(keyword.split('').join('\\\\s*'), 'gi'),\n ', ' + keyword + ' ',\n );\n });\n\n // Clean up remittanceInformation, deduplicate payee (removing slashes ...\n // ... that are added to the remittanceInformation field), and ...\n // ... remove clutter like \"End-to-End-Ref.: NOTPROVIDED\"\n const payee = escapeRegExp(\n transaction.creditorName || transaction.debtorName || '',\n );\n editedTrans.remittanceInformationUnstructured =\n editedTrans.remittanceInformationUnstructured\n .replace(/\\s*(,)?\\s+/g, '$1 ')\n .replace(RegExp(payee.split(' ').join('(/*| )'), 'gi'), ' ')\n .replace(', End-to-End-Ref.: NOTPROVIDED', '')\n .trim();\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n // TODO: Add other Danske Bank BICs?\n // https://danskeci.com/ci/transaction-banking/bank-identifier-code\n institutionIds: ['DANSKEBANK_DABADKKK', 'DANSKEBANK_DABANO22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n /**\n * Danske Bank appends the EndToEndID: NOTPROVIDED to\n * remittanceInformationUnstructured, cluttering the data.\n *\n * We clean thais up by removing any instances of this string from all transactions.\n *\n */\n editedTrans.remittanceInformationUnstructured = (\n transaction.remittanceInformationUnstructured ?? ''\n ).replace('\\nEndToEndID: NOTPROVIDED', '');\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => balance.balanceType === 'interimAvailable',\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['DIREKT_HELADEF1822'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ??\n transaction.remittanceInformationStructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { Transaction } from '#app-gocardless/gocardless-node.types';\nimport { formatPayeeName } from '#util/payee-name';\nimport { title } from '#util/title';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['EASYBANK_BAWAATWW'],\n\n // If date is same, sort by transactionId\n sortTransactions: (transactions = []) =>\n transactions.sort((a, b) => {\n const diff =\n +new Date(b.valueDate || b.bookingDate || '') -\n +new Date(a.valueDate || a.bookingDate || '');\n if (diff !== 0) return diff;\n return parseInt(b.transactionId ?? '') - parseInt(a.transactionId ?? '');\n }),\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let payeeName = formatPayeeName(transaction);\n if (!payeeName) payeeName = extractPayeeName(transaction);\n editedTrans.payeeName = payeeName;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n\n// extracts the payee name from the remittanceInformationStructured\nfunction extractPayeeName(transaction: Transaction) {\n const structured = transaction.remittanceInformationStructured ?? '';\n // The payee name is betweeen the transaction timestamp (11.07. 11:36) and the location, that starts with \\\\\n const regex = /\\d{2}\\.\\d{2}\\. \\d{2}:\\d{2}(.*)\\\\\\\\/;\n const matches = structured.match(regex);\n if (matches && matches.length > 1 && matches[1]) {\n return title(matches[1]);\n } else {\n // As a fallback if still no payee is found, the whole information is used\n return structured;\n }\n}\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ENTERCARD_SWEDNOKK'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // GoCardless's Entercard integration returns forex transactions with the\n // foreign amount in `transactionAmount`, but at least the amount actually\n // billed to the account is now available in\n // `remittanceInformationUnstructured`.\n const remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ?? '';\n if (remittanceInformationUnstructured.startsWith('billingAmount: ')) {\n transaction.transactionAmount = {\n amount: remittanceInformationUnstructured.substring(15),\n currency: 'SEK',\n };\n }\n\n editedTrans.date = transaction.valueDate;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(balances[0]?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['FORTUNEO_FTNOFRP1XXX'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Most of the information from the transaction is in the remittanceInformationUnstructuredArray field.\n // We extract the creditor and debtor names from this field.\n // The remittanceInformationUnstructuredArray field usually contain keywords like \"Vir\" for\n // bank transfers or \"Carte 03/06\" for card payments, as well as the date.\n // We remove these keywords to get a cleaner payee name.\n const keywordsToRemove = [\n 'VIR INST',\n 'VIR',\n 'PRLV',\n 'ANN CARTE',\n 'CARTE \\\\d{2}\\\\/\\\\d{2}',\n ];\n\n const details = (\n transaction.remittanceInformationUnstructuredArray ?? []\n ).join(' ');\n const amount = transaction.transactionAmount.amount;\n\n const regex = new RegExp(keywordsToRemove.join('|'), 'g');\n const payeeName = details.replace(regex, '').trim();\n\n // The amount is negative for outgoing transactions, positive for incoming transactions.\n const isCreditorPayee = parseFloat(amount) < 0;\n\n // The payee name is the creditor name for outgoing transactions and the debtor name for incoming transactions.\n editedTrans.creditorName = isCreditorPayee ? payeeName : undefined;\n editedTrans.debtorName = isCreditorPayee ? undefined : payeeName;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['HYPE_HYEEIT22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n /** Online card payments - identified by \"crd\" transaction code\n * always start with PAGAMENTO PRESSO + <payee name>\n */\n if (transaction.proprietaryBankTransactionCode === 'crd') {\n // remove PAGAMENTO PRESSO and set payee name\n editedTrans.debtorName =\n transaction.remittanceInformationUnstructured?.slice(\n 'PAGAMENTO PRESSO '.length,\n );\n }\n /**\n * In-app money transfers (p2p) and bank transfers (bon) have remittance info structure like\n * DENARO (INVIATO/RICEVUTO) (A/DA) {payee_name} - {payment_info} (p2p)\n * HAI (INVIATO/RICEVUTO) UN BONIFICO (A/DA) {payee_name} - {payment_info} (bon)\n */\n if (\n transaction.proprietaryBankTransactionCode === 'p2p' ||\n transaction.proprietaryBankTransactionCode === 'bon'\n ) {\n // keep only {payment_info} portion of remittance info\n // NOTE: if {payee_name} contains dashes (unlikely / impossible?), this probably gets bugged!\n const remittance = transaction.remittanceInformationUnstructured ?? '';\n const idx = remittance.indexOf(' - ');\n editedTrans.remittanceInformationUnstructured =\n idx === -1 ? remittance : remittance.slice(idx + 3).trim();\n }\n /**\n * CONVERT ESCAPED UNICODE TO CODEPOINTS\n * p2p payments allow user to write arbitrary unicode strings as messages\n * gocardless reports unicode codepoints as \\Uxxxx\n * so it groups them in 4bytes bundles\n * the code below assumes this is always the case\n */\n if (transaction.proprietaryBankTransactionCode === 'p2p') {\n let str = transaction.remittanceInformationUnstructured ?? '';\n let idx = str.indexOf('\\\\U');\n let start_idx = idx;\n let codepoints = [];\n while (idx !== -1) {\n codepoints.push(parseInt(str.slice(idx + 2, idx + 6), 16));\n const next_idx = str.indexOf('\\\\U', idx + 6);\n if (next_idx === idx + 6) {\n idx = next_idx;\n continue;\n }\n str =\n str.slice(0, start_idx) +\n String.fromCodePoint(...codepoints) +\n str.slice(idx + 6);\n codepoints = [];\n idx = str.indexOf('\\\\U'); // slight inefficiency?\n start_idx = idx;\n }\n editedTrans.remittanceInformationUnstructured = str;\n }\n\n editedTrans.date = transaction.valueDate || transaction.bookingDate;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ING_INGBROBU'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n //Merchant transactions all have the same transactionId of 'NOTPROVIDED'.\n //For booked transactions, this can be set to the internalTransactionId\n //For pending transactions, this needs to be removed for them to show up in Actual\n\n //For deduplication to work better, payeeName needs to be standardized\n //and converted from a pending transaction form (\"payeeName\":\"Card no: xxxxxxxxxxxx1111\"') to a booked transaction form (\"payeeName\":\"Card no: Xxxx Xxxx Xxxx 1111\")\n if (transaction.transactionId === 'NOTPROVIDED') {\n //Some corner case transactions only have the `proprietaryBankTransactionCode` field, this need to be copied to `remittanceInformationUnstructured`\n if (\n transaction.proprietaryBankTransactionCode &&\n !transaction.remittanceInformationUnstructured\n ) {\n editedTrans.remittanceInformationUnstructured =\n transaction.proprietaryBankTransactionCode;\n }\n\n if (booked) {\n transaction.transactionId = transaction.internalTransactionId;\n if (\n transaction.remittanceInformationUnstructured &&\n transaction.remittanceInformationUnstructured\n .toLowerCase()\n .includes('card no:')\n ) {\n editedTrans.creditorName =\n transaction.remittanceInformationUnstructured.split(',')[0];\n //Catch all case for other types of payees\n } else {\n editedTrans.creditorName =\n transaction.remittanceInformationUnstructured;\n }\n } else {\n transaction.transactionId = undefined;\n\n if (\n transaction.remittanceInformationUnstructured &&\n transaction.remittanceInformationUnstructured\n .toLowerCase()\n .includes('card no:')\n ) {\n editedTrans.creditorName =\n transaction.remittanceInformationUnstructured.replace(\n /x{4}/g,\n 'Xxxx ',\n );\n //Catch all case for other types of payees\n } else {\n editedTrans.creditorName =\n transaction.remittanceInformationUnstructured;\n }\n //Remove remittanceInformationUnstructured from pending transactions, so the `notes` field remains empty (there is no merchant information)\n //Once booked, the right `notes` (containing the merchant) will be populated\n editedTrans.remittanceInformationUnstructured = undefined;\n }\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ING_INGDDEFF'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const remittanceInformationMatch = /remittanceinformation:(.*)$/.exec(\n transaction.remittanceInformationUnstructured ?? '',\n );\n\n editedTrans.remittanceInformationUnstructured = remittanceInformationMatch\n ? remittanceInformationMatch[1]\n : transaction.remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n sortTransactions(transactions = []) {\n return transactions.sort((a, b) => {\n const diff =\n +new Date(b.valueDate || b.bookingDate || '') -\n +new Date(a.valueDate || a.bookingDate || '');\n if (diff) return diff;\n const idA = parseInt(a.transactionId ?? '');\n const idB = parseInt(b.transactionId ?? '');\n if (!isNaN(idA) && !isNaN(idB)) return idB - idA;\n return 0;\n });\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimBooked' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ING_PL_INGBPLPW'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.date = transaction.valueDate;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n sortTransactions(transactions = []) {\n return transactions.sort((a, b) => {\n return (\n Number((b.transactionId ?? '').substr(2)) -\n Number((a.transactionId ?? '').substr(2))\n );\n });\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n if (sortedTransactions.length) {\n const oldestTransaction =\n sortedTransactions[sortedTransactions.length - 1];\n const oldestKnownBalance = amountToInteger(\n oldestTransaction.balanceAfterTransaction?.balanceAmount.amount || 0,\n );\n const oldestTransactionAmount = amountToInteger(\n oldestTransaction.transactionAmount.amount,\n );\n\n return oldestKnownBalance - oldestTransactionAmount;\n } else {\n return amountToInteger(\n balances.find(balance => 'interimBooked' === balance.balanceType)\n ?.balanceAmount.amount || 0,\n );\n }\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ISYBANK_ITBBITMM'],\n\n // It has been reported that valueDate is more accurate than booking date\n // when it is provided\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.date = transaction.valueDate ?? transaction.bookingDate;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\nimport { extractPayeeNameFromRemittanceInfo } from './util/extract-payeeName-from-remittanceInfo';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['KBC_KREDBEBB', 'KBC_BRUSSELS_KREDBEBB'],\n\n /**\n * For negative amounts, the only payee information we have is returned in\n * remittanceInformationUnstructured.\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n if (Number(transaction.transactionAmount.amount) > 0) {\n editedTrans.payeeName =\n transaction.debtorName ||\n transaction.remittanceInformationUnstructured ||\n 'undefined';\n } else {\n editedTrans.payeeName =\n transaction.creditorName ||\n extractPayeeNameFromRemittanceInfo(\n transaction.remittanceInformationUnstructured ?? '',\n ['Betaling met', 'Domiciliëring', 'Overschrijving'],\n );\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import * as d from 'date-fns';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['LHV_LHVBEE22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // extract bookingDate and creditorName for card transactions, e.g.\n // (..1234) 2025-01-02 09:32 CrustumOU\\Poordi 3\\Tallinn\\10156 ESTEST\n // bookingDate: 2025-01-02\n // creditorName: CrustumOU\n const cardTxRegex =\n /^\\(\\.\\.(\\d{4})\\) (\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}) (.+)$/g;\n const cardTxMatch = cardTxRegex.exec(\n transaction?.remittanceInformationUnstructured ?? '',\n );\n\n if (cardTxMatch) {\n const extractedDate = d.parse(cardTxMatch[2], 'yyyy-MM-dd', new Date());\n\n editedTrans.payeeName = cardTxMatch[4].split('\\\\')[0].trim();\n\n if (booked && d.isValid(extractedDate)) {\n editedTrans.date = d.format(extractedDate, 'yyyy-MM-dd');\n }\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['MBANK_RETAIL_BREXPLPW'],\n\n /**\n * When requesting transaction details for MBANK_RETAIL_BREXPLPW\n * using gocardless API, it seems that bookingDate and valueDate are swapped.\n * valueDate will always come before bookingDate, so as a simple fix,\n * I have overwritten integration-bank.normalizeTransaction() here,\n * swapped dates back (by giving valueDate higher priority) and\n * called parent method with edited transaction as argument\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const date =\n transaction.valueDate ||\n transaction.valueDateTime ||\n transaction.bookingDate ||\n transaction.bookingDateTime;\n\n editedTrans.date = date;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n sortTransactions(transactions = []) {\n return transactions.sort(\n (a, b) => Number(b.transactionId) - Number(a.transactionId),\n );\n },\n\n /**\n * For MBANK_RETAIL_BREXPLPW we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimBooked' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['NATIONWIDE_NAIAGB21'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Nationwide can sometimes return pending transactions with a date\n // representing the latest a transaction could be booked. This stops\n // actual's deduplication logic from working as it only checks 7 days\n // ahead/behind and the transactionID from Nationwide changes when a\n // transaction is booked\n if (!booked) {\n const useDate = new Date(\n Math.min(\n new Date(transaction.bookingDate ?? '').getTime(),\n new Date().getTime(),\n ),\n );\n editedTrans.date = useDate.toISOString().slice(0, 10);\n }\n\n // Nationwide also occasionally returns erroneous transaction_ids\n // that are malformed and can even change after import. This will ignore\n // these ids and unset them. When a correct ID is returned then it will\n // update via the deduplication logic\n const debitCreditRegex = /^00(DEB|CRED)IT.+$/;\n const validLengths = [\n 40, // Nationwide credit cards\n 32, // Nationwide current accounts\n ];\n\n if (\n transaction.transactionId?.match(debitCreditRegex) ||\n !validLengths.includes(transaction.transactionId?.length ?? -1)\n ) {\n transaction.transactionId = undefined;\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['NBG_ETHNGRAAXXX'],\n\n /**\n * Fixes for the pending transactions:\n * - Corrects amount to negative (nbg erroneously omits the minus sign in pending transactions)\n * - Removes prefix 'ΑΓΟΡΑ' from remittance information to align with the booked transaction (necessary for fuzzy matching to work)\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n if (\n !transaction.transactionId &&\n (transaction.remittanceInformationUnstructured ?? '').startsWith('ΑΓΟΡΑ ')\n ) {\n transaction.transactionAmount = {\n amount: '-' + transaction.transactionAmount.amount,\n currency: transaction.transactionAmount.currency,\n };\n editedTrans.remittanceInformationUnstructured = (\n transaction.remittanceInformationUnstructured ?? ''\n ).substring(6);\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n /**\n * For NBG_ETHNGRAAXXX we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'NORWEGIAN_NO_NORWNOK1',\n 'NORWEGIAN_SE_NORWNOK1',\n 'NORWEGIAN_DE_NORWNOK1',\n 'NORWEGIAN_DK_NORWNOK1',\n 'NORWEGIAN_ES_NORWNOK1',\n 'NORWEGIAN_FI_NORWNOK1',\n ],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n if (booked) {\n editedTrans.date = transaction.bookingDate;\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n }\n\n /**\n * For pending transactions there are two possibilities:\n *\n * - Either a `valueDate` was set, in which case it corresponds to when the\n * transaction actually occurred, or\n * - There is no date field, in which case we try to parse the correct date\n * out of the `remittanceInformationStructured` field.\n *\n * If neither case succeeds then we return `null` causing this transaction\n * to be filtered out for now, and hopefully we'll be able to import it\n * once the bank has processed it further.\n */\n if (transaction.valueDate !== undefined) {\n editedTrans.date = transaction.valueDate;\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n }\n\n if (transaction.remittanceInformationStructured) {\n const remittanceInfoRegex = / (\\d{4}-\\d{2}-\\d{2}) /;\n const matches =\n transaction.remittanceInformationStructured.match(remittanceInfoRegex);\n if (matches) {\n editedTrans.date = matches[1];\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n }\n }\n\n return null;\n },\n\n /**\n * For NORWEGIAN_XX_NORWNOK1 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `expected` balance type because it\n * corresponds to the current running balance, whereas `interimAvailable`\n * holds the remaining credit limit.\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'expected' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { Transaction } from '#app-gocardless/gocardless-node.types';\nimport { formatPayeeName } from '#util/payee-name';\nimport { title } from '#util/title';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['RAIFFEISEN_AT_RZBAATWW'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let payeeName = formatPayeeName(transaction);\n if (!payeeName) {\n payeeName = extractPayeeName(transaction);\n }\n editedTrans.payeeName = payeeName;\n\n // avoid empty notes if payee is set but no information in unstructured information\n // if no structured or unstructured information is provided, return the endToEndId instead\n editedTrans.remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ??\n transaction.remittanceInformationStructured ??\n transaction.endToEndId;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n\n// extracts the payee name from the remittanceInformationStructured\nfunction extractPayeeName(transaction: Transaction) {\n const structured = transaction.remittanceInformationStructured ?? '';\n // The payee name is at the beginning and has a max length of 12 characters\n // (if structured information is actually structured ...).\n const regex = /(.{12}) \\d{4} .* \\d{2}\\.\\d{2}\\. \\d{2}:\\d{2}/;\n const matches = structured.match(regex);\n if (matches && matches.length > 1 && matches[1]) {\n const name = title(matches[1]);\n // These transactions never contained creditor information in my tests, thus no\n // attempt to add the IBAN to the name...\n return name;\n } else {\n // As a fallback if still no payee is found, the whole information is used\n return structured;\n }\n}\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['REVOLUT_REVOLT21'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const infoArray = transaction.remittanceInformationUnstructuredArray ?? [];\n\n if (infoArray[0]?.startsWith('Bizum payment from: ')) {\n editedTrans.payeeName = infoArray[0].replace('Bizum payment from: ', '');\n editedTrans.remittanceInformationUnstructured = infoArray[1];\n }\n\n if (infoArray[0]?.startsWith('Bizum payment to: ')) {\n editedTrans.remittanceInformationUnstructured = infoArray[1];\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SANDBOXFINANCE_SFIN0000'],\n\n /**\n * For SANDBOXFINANCE_SFIN0000 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'SEB_KORT_AB_NO_SKHSFI21',\n 'SEB_KORT_AB_SE_SKHSFI21',\n 'SEB_CARD_ESSESESS',\n 'NORDIC_CHOICE_CLUB_NO_SKHSFI21',\n 'NORDIC_CHOICE_CLUB_SE_SKHSFI21',\n 'EUROCARD_SE_SKHSFI21',\n 'EUROCARD_DK_SKHSFI21',\n 'EUROCARD_FI_SKHSFI21',\n 'EUROCARD_NO_SKHSFI21',\n 'GLOBECARD_DK_SKHSFI21',\n 'GLOBECARD_NO_SKHSFI21',\n 'OPEL_MASTERCARD_SKHSFI21',\n 'SAAB_MASTERCARD_SKHSFI21',\n 'SAS_MASTERCARD_NO_SKHSFI21',\n 'SAS_MASTERCARD_SE_SKHSFI21',\n 'SAS_MASTERCARD_FI_SKHSFI21',\n 'SAS_MASTERCARD_DK_SKHSFI21',\n 'SJ_PRIO_MASTERCARD_SKHSFI21',\n 'CIRCLE_K_MASTERCARD_NO_SKHSFI21',\n 'CIRCLE_K_MASTERCARD_SE_SKHSFI21',\n 'CIRCLE_K_MASTERCARD_DK_SKHSFI21',\n 'WALLET_SKHSFI21',\n 'INGO_MASTERCARD_SKHSFI21',\n 'SCANDIC_SKHSFI21',\n ],\n\n /**\n * Sign of transaction amount needs to be flipped for SEB credit cards\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Creditor name is stored in additionInformation for SEB\n editedTrans.creditorName = transaction.additionalInformation;\n transaction.transactionAmount = {\n // Flip transaction amount sign\n amount: (-parseFloat(transaction.transactionAmount.amount)).toString(),\n currency: transaction.transactionAmount.currency,\n };\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n /**\n * For SEB_KORT_AB_NO_SKHSFI21 and SEB_KORT_AB_SE_SKHSFI21 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `expected` and `nonInvoiced` balance types because it\n * corresponds to the current running balance, whereas `interimAvailable`\n * holds the remaining credit limit.\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'expected' === balance.balanceType,\n );\n\n const nonInvoiced = balances.find(\n balance => 'nonInvoiced' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n -amountToInteger(currentBalance?.balanceAmount.amount || 0) +\n amountToInteger(nonInvoiced?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SEB_ESSESESS_PRIVATE'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Creditor name is stored in additionInformation for SEB\n editedTrans.creditorName = transaction.additionalInformation;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimBooked' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'SPARNORD_SPNODK22',\n 'LAGERNES_BANK_LAPNDKK1',\n 'ANDELSKASSEN_FALLESKASSEN_FAELDKK1',\n ],\n\n /**\n * Banks on the BEC backend only give information regarding the transaction in additionalInformation\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured =\n transaction.additionalInformation;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SPK_KARLSRUHE_KARSDE66XXX'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let remittanceInformationUnstructured;\n\n if (transaction.remittanceInformationUnstructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured;\n } else if (transaction.remittanceInformationStructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructured;\n } else if (\n (transaction.remittanceInformationStructuredArray?.length ?? 0) > 0\n ) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructuredArray?.join(' ');\n }\n\n if (transaction.additionalInformation) {\n remittanceInformationUnstructured +=\n ' ' + transaction.additionalInformation;\n }\n\n const usefulCreditorName =\n transaction.ultimateCreditor ||\n transaction.creditorName ||\n transaction.debtorName;\n\n editedTrans.creditorName = usefulCreditorName;\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n /**\n * For SANDBOXFINANCE_SFIN0000 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SPK_MARBURG_BIEDENKOPF_HELADEF1MAR'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let remittanceInformationUnstructured;\n\n if (transaction.remittanceInformationUnstructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured;\n } else if (transaction.remittanceInformationStructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructured;\n } else if (\n (transaction.remittanceInformationStructuredArray?.length ?? 0) > 0\n ) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructuredArray?.join(' ');\n }\n\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SPK_WORMS_ALZEY_RIED_MALADE51WOR'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ??\n transaction.remittanceInformationStructured ??\n transaction.remittanceInformationStructuredArray?.join(' ');\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SSK_DUSSELDORF_DUSSDEDDXXX'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // If the transaction is not booked yet by the bank, don't import it.\n // Reason being that the transaction doesn't have the information yet\n // to make the payee and notes field be of any use. It's filled with\n // a placeholder text and wouldn't be corrected on the next sync.\n if (!booked) {\n console.debug(\n 'Skipping unbooked transaction:',\n transaction.transactionId,\n );\n return null;\n }\n\n // Prioritize unstructured information, falling back to structured formats\n let remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ??\n transaction.remittanceInformationStructured ??\n transaction.remittanceInformationStructuredArray?.join(' ');\n\n if (transaction.additionalInformation) {\n remittanceInformationUnstructured = [\n remittanceInformationUnstructured,\n transaction.additionalInformation,\n ]\n .filter(Boolean)\n .join(' ');\n }\n\n const usefulCreditorName =\n transaction.ultimateCreditor ||\n transaction.creditorName ||\n transaction.debtorName;\n\n editedTrans.creditorName = usefulCreditorName;\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","/**\n * Credit for this code goes to Nebukadneza at https://github.com/Nebukadneza\n */\nimport { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n institutionIds: ['SSK_MUNCHEN_SSKMDEMMXXX'],\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n let remittanceInformationUnstructured;\n if (transaction.remittanceInformationUnstructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured;\n } else if (transaction.remittanceInformationStructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructured;\n } else if (\n (transaction.remittanceInformationStructuredArray?.length ?? 0) > 0\n ) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructuredArray?.join(' ');\n }\n if (transaction.additionalInformation) {\n remittanceInformationUnstructured +=\n ' ' + transaction.additionalInformation;\n }\n const usefulCreditorName =\n transaction.ultimateCreditor ||\n transaction.creditorName ||\n transaction.debtorName;\n editedTrans.creditorName = usefulCreditorName;\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n /**\n * For SANDBOXFINANCE_SFIN0000 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import * as d from 'date-fns';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SWEDBANK_HABALV22'],\n\n /**\n * The actual transaction date for card transactions is only available in the remittanceInformationUnstructured field when the transaction is booked.\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const isCardTransaction =\n transaction.remittanceInformationUnstructured?.startsWith('PIRKUMS');\n\n if (isCardTransaction) {\n if (!booked && !transaction.creditorName) {\n const creditorNameMatch =\n transaction.remittanceInformationUnstructured?.match(\n /PIRKUMS [\\d*]+ \\d{2}\\.\\d{2}\\.\\d{2} \\d{2}:\\d{2} [\\d.]+ \\w{3} \\(\\d+\\) (.+)/,\n );\n\n if (creditorNameMatch) {\n editedTrans.creditorName = creditorNameMatch[1];\n }\n }\n\n const dateMatch = transaction.remittanceInformationUnstructured?.match(\n /PIRKUMS [\\d*]+ (\\d{2}\\.\\d{2}\\.\\d{4})/,\n );\n\n if (dateMatch) {\n const extractedDate = d\n .parse(dateMatch[1], 'dd.MM.yyyy', new Date())\n .toISOString();\n\n editedTrans.date = extractedDate;\n }\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['VIRGIN_NRNBGB22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const transferPrefixes = ['MOB', 'FPS'];\n const methodRegex = /^(Card|WLT)\\s\\d+/;\n\n const parts = (transaction.remittanceInformationUnstructured ?? '').split(\n ', ',\n );\n\n if (transferPrefixes.includes(parts[0])) {\n // Transfer remittance information begins with either \"MOB\" or \"FPS\"\n // the second field contains the payee and the third contains the\n // reference\n\n editedTrans.creditorName = parts[1];\n editedTrans.debtorName = parts[1];\n editedTrans.remittanceInformationUnstructured = parts[2];\n } else if (parts[0].match(methodRegex)) {\n // The payee is prefixed with the payment method, eg \"Card 11, {payee}\"\n\n editedTrans.creditorName = parts[1];\n editedTrans.debtorName = parts[1];\n } else {\n // Simple payee name\n\n editedTrans.creditorName = transaction.remittanceInformationUnstructured;\n editedTrans.debtorName = transaction.remittanceInformationUnstructured;\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './banks/bank.interface';\nimport IntegrationBank from './banks/integration-bank';\n\n// Filename convention: <name>_<bic>.{ts,js} (skips bank.interface,\n// integration-bank, and any other helper without an underscore).\nconst bankModules = import.meta.glob<{ default: IBank }>(\n './banks/*_*.{ts,js}',\n {\n eager: true,\n },\n);\n\nexport const banks: IBank[] = Object.values(bankModules).map(m => m.default);\n\nexport function BankFactory(institutionId: string): IBank {\n return (\n banks.find(b => b.institutionIds.includes(institutionId)) || IntegrationBank\n );\n}\n","import type {\n GoCardlessAccountDetails,\n GoCardlessAccountId,\n GoCardlessAccountMetadata,\n GoCardlessAgreementId,\n GoCardlessInstitutionId,\n GoCardlessRequisitionId,\n Institution,\n Requisition,\n} from '#app-gocardless/gocardless-node.types';\nimport type {\n GetBalances,\n GetTransactionsResponse,\n} from '#app-gocardless/gocardless.types';\n\nconst BASE_URL = 'https://bankaccountdata.gocardless.com/api/v2';\nconst ALLOWED_ORIGIN = new URL(BASE_URL).origin;\n\nexport type TokenResponse = {\n access: string;\n refresh: string;\n access_expires: number;\n refresh_expires: number;\n};\n\ntype AgreementResponse = {\n id: GoCardlessAgreementId;\n created: string;\n max_historical_days: number;\n access_valid_for_days: number;\n access_scope: string[];\n accepted: string | null;\n institution_id: GoCardlessInstitutionId;\n};\n\nexport type AccountDetailsResponse = {\n account: GoCardlessAccountDetails;\n};\n\nexport class GoCardlessApiError extends Error {\n response: {\n status: number;\n headers: Record<string, string>;\n data?: unknown;\n };\n\n constructor(\n message: string,\n status: number,\n headers: Record<string, string>,\n ) {\n super(message);\n this.response = { status, headers };\n }\n}\n\nexport class GoCardlessApi {\n #secretId: string | null;\n #secretKey: string | null;\n #token: string | null = null;\n\n constructor({\n secretId,\n secretKey,\n }: {\n secretId: string | null;\n secretKey: string | null;\n }) {\n this.#secretId = secretId;\n this.#secretKey = secretKey;\n }\n\n get secretId(): string | null {\n return this.#secretId;\n }\n\n get secretKey(): string | null {\n return this.#secretKey;\n }\n\n get token(): string | null {\n return this.#token;\n }\n\n set token(value: string | null) {\n this.#token = value;\n }\n\n async #request<T>(\n endpoint: string,\n {\n method = 'GET',\n body,\n }: {\n method?: 'GET' | 'POST' | 'DELETE';\n body?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n 'Content-Type': 'application/json',\n };\n\n if (this.#token) {\n headers.Authorization = `Bearer ${this.#token}`;\n }\n\n const url = new URL(`${BASE_URL}${endpoint}`);\n if (url.origin !== ALLOWED_ORIGIN || !url.pathname.startsWith('/api/v2/')) {\n throw new Error(`Invalid GoCardless API endpoint: ${endpoint}`);\n }\n\n const response = await fetch(url, {\n method,\n headers,\n signal: AbortSignal.timeout(20000),\n ...(body\n ? {\n body: JSON.stringify(\n Object.fromEntries(\n Object.entries(body).filter(([, v]) => v != null),\n ),\n ),\n }\n : {}),\n });\n\n if (!response.ok) {\n const error = new GoCardlessApiError(\n `GoCardless API error: ${response.status}`,\n response.status,\n Object.fromEntries(response.headers.entries()),\n );\n try {\n error.response.data = await response.json();\n } catch {}\n console.log(\n `GoCardless ${method} ${endpoint} ${response.status}`,\n error.response.data ? JSON.stringify(error.response.data) : '(no body)',\n );\n throw error;\n }\n\n return response.json() as Promise<T>;\n }\n\n async generateToken(): Promise<TokenResponse> {\n const data = await this.#request<TokenResponse>('/token/new/', {\n method: 'POST',\n body: {\n secret_id: this.#secretId,\n secret_key: this.#secretKey,\n },\n });\n this.#token = data.access;\n return data;\n }\n\n async exchangeToken({\n refreshToken,\n }: {\n refreshToken: string;\n }): Promise<TokenResponse> {\n const data = await this.#request<TokenResponse>('/token/refresh/', {\n method: 'POST',\n body: { refresh: refreshToken },\n });\n this.#token = data.access;\n return data;\n }\n\n async getInstitutions({\n country,\n }: {\n country: string;\n }): Promise<Institution[]> {\n return this.#request<Institution[]>(`/institutions/?country=${country}`);\n }\n\n async getInstitutionById(id: GoCardlessInstitutionId): Promise<Institution> {\n return this.#request<Institution>(`/institutions/${id}/`);\n }\n\n async createRequisition({\n redirectUrl,\n institutionId,\n agreement,\n userLanguage,\n reference,\n ssn,\n redirectImmediate,\n accountSelection,\n }: {\n redirectUrl: string;\n institutionId: GoCardlessInstitutionId;\n agreement: GoCardlessAgreementId;\n userLanguage: string;\n reference: string | null;\n ssn: string | null;\n redirectImmediate: boolean;\n accountSelection: boolean;\n }): Promise<Requisition> {\n return this.#request<Requisition>('/requisitions/', {\n method: 'POST',\n body: {\n redirect: redirectUrl,\n institution_id: institutionId,\n agreement,\n user_language: userLanguage,\n reference,\n ssn,\n redirect_immediate: redirectImmediate,\n account_selection: accountSelection,\n },\n });\n }\n\n async getRequisitionById(\n requisitionId: GoCardlessRequisitionId,\n ): Promise<Requisition> {\n return this.#request<Requisition>(`/requisitions/${requisitionId}/`);\n }\n\n async deleteRequisition(\n requisitionId: GoCardlessRequisitionId,\n ): Promise<{ summary: string; detail: string }> {\n return this.#request(`/requisitions/${requisitionId}/`, {\n method: 'DELETE',\n });\n }\n\n async createAgreement({\n institutionId,\n maxHistoricalDays = 90,\n accessValidForDays = 90,\n accessScope = ['balances', 'details', 'transactions'],\n }: {\n institutionId: GoCardlessInstitutionId;\n maxHistoricalDays?: number;\n accessValidForDays?: number;\n accessScope?: string[];\n }): Promise<AgreementResponse> {\n return this.#request<AgreementResponse>('/agreements/enduser/', {\n method: 'POST',\n body: {\n institution_id: institutionId,\n max_historical_days: maxHistoricalDays,\n access_valid_for_days: accessValidForDays,\n access_scope: accessScope,\n },\n });\n }\n\n async getAccountMetadata(\n accountId: GoCardlessAccountId,\n ): Promise<GoCardlessAccountMetadata> {\n return this.#request<GoCardlessAccountMetadata>(`/accounts/${accountId}/`);\n }\n\n async getAccountDetails(\n accountId: GoCardlessAccountId,\n ): Promise<AccountDetailsResponse> {\n return this.#request<AccountDetailsResponse>(\n `/accounts/${accountId}/details/`,\n );\n }\n\n async getAccountBalances(\n accountId: GoCardlessAccountId,\n ): Promise<GetBalances> {\n return this.#request<GetBalances>(`/accounts/${accountId}/balances/`);\n }\n\n async getAccountTransactions({\n accountId,\n dateFrom,\n dateTo,\n }: {\n accountId: GoCardlessAccountId;\n dateFrom?: string;\n dateTo?: string;\n }): Promise<GetTransactionsResponse> {\n const params = new URLSearchParams();\n if (dateFrom) params.set('date_from', dateFrom);\n if (dateTo) params.set('date_to', dateTo);\n const query = params.toString();\n return this.#request<GetTransactionsResponse>(\n `/accounts/${accountId}/transactions/${query ? `?${query}` : ''}`,\n );\n }\n\n async initSession({\n redirectUrl,\n institutionId,\n maxHistoricalDays = 90,\n accessValidForDays = 90,\n userLanguage = 'en',\n referenceId = null,\n ssn = null,\n redirectImmediate = false,\n accountSelection = false,\n }: {\n redirectUrl: string;\n institutionId: GoCardlessInstitutionId;\n maxHistoricalDays?: number | string;\n accessValidForDays?: number | string;\n userLanguage?: string;\n referenceId?: string | null;\n ssn?: string | null;\n redirectImmediate?: boolean;\n accountSelection?: boolean;\n }): Promise<Requisition> {\n const agreement = await this.createAgreement({\n institutionId,\n maxHistoricalDays: Number(maxHistoricalDays),\n accessValidForDays: Number(accessValidForDays),\n });\n\n return this.createRequisition({\n redirectUrl,\n institutionId,\n agreement: agreement.id,\n userLanguage,\n reference: referenceId,\n ssn,\n redirectImmediate,\n accountSelection,\n });\n }\n}\n","import { v4 as uuidv4 } from 'uuid';\n\nimport { BankFactory } from '#app-gocardless/bank-factory';\nimport type { IBank } from '#app-gocardless/banks/bank.interface';\nimport {\n AccessDeniedError,\n AccountNotLinkedToRequisition,\n GenericGoCardlessError,\n InvalidGoCardlessTokenError,\n InvalidInputDataError,\n NotFoundError,\n RateLimitError,\n RequisitionNotLinked,\n ResourceSuspended,\n ServiceError,\n UnknownError,\n} from '#app-gocardless/errors';\nimport type {\n Balance,\n GoCardlessAccountId,\n GoCardlessAccountMetadata,\n GoCardlessInstitutionId,\n GoCardlessRequisitionId,\n Institution,\n Requisition,\n Transaction,\n} from '#app-gocardless/gocardless-node.types';\nimport type {\n CreateRequisitionParams,\n DetailedAccount,\n DetailedAccountWithInstitution,\n GetBalances,\n GetTransactionsParams,\n GetTransactionsResponse,\n NormalizedAccountDetails,\n TransactionWithBookedStatus,\n} from '#app-gocardless/gocardless.types';\nimport { SecretName, secretsService } from '#services/secrets-service';\n\nimport type { AccountDetailsResponse, TokenResponse } from './gocardless-api';\nimport { GoCardlessApi, GoCardlessApiError } from './gocardless-api';\n\nconst clients = new Map<string, GoCardlessApi>();\n\nconst getGocardlessClient = (): GoCardlessApi => {\n const secrets = {\n secretId: secretsService.get(SecretName.gocardless_secretId),\n secretKey: secretsService.get(SecretName.gocardless_secretKey),\n };\n\n const hash = JSON.stringify(secrets);\n\n let client = clients.get(hash);\n if (!client) {\n client = new GoCardlessApi(secrets);\n clients.set(hash, client);\n }\n\n return client;\n};\n\nexport const handleGoCardlessError = (error: unknown): never => {\n const status =\n error instanceof GoCardlessApiError ? error.response.status : undefined;\n\n switch (status) {\n case 400:\n throw new InvalidInputDataError(error);\n case 401:\n throw new InvalidGoCardlessTokenError(error);\n case 403:\n throw new AccessDeniedError(error);\n case 404:\n throw new NotFoundError(error);\n case 409:\n throw new ResourceSuspended(error);\n case 429:\n throw new RateLimitError(error);\n case 500:\n throw new UnknownError(error);\n case 503:\n throw new ServiceError(error);\n default:\n throw new GenericGoCardlessError(error);\n }\n};\n\nexport const goCardlessService = {\n isConfigured: (): boolean => {\n return !!(\n getGocardlessClient().secretId && getGocardlessClient().secretKey\n );\n },\n\n setToken: async (): Promise<void> => {\n const isExpiredJwtToken = (token: string | null): boolean => {\n if (!token) return true;\n try {\n const payload = JSON.parse(\n Buffer.from(token.split('.')[1], 'base64url').toString(),\n );\n const clockTimestamp = Math.floor(Date.now() / 1000);\n return clockTimestamp >= payload.exp;\n } catch {\n return true;\n }\n };\n\n if (isExpiredJwtToken(getGocardlessClient().token)) {\n await client.generateToken().catch(handleGoCardlessError);\n }\n },\n\n getLinkedRequisition: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<Requisition> => {\n const requisition = await goCardlessService.getRequisition(requisitionId);\n\n const { status } = requisition;\n\n // Continue only if status of requisition is \"LN\" which\n // means the account has been successfully linked to the requisition\n if (status !== 'LN') {\n throw new RequisitionNotLinked({ requisitionStatus: status });\n }\n\n return requisition;\n },\n\n getRequisitionWithAccounts: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<{\n requisition: Requisition;\n accounts: NormalizedAccountDetails[];\n }> => {\n const requisition =\n await goCardlessService.getLinkedRequisition(requisitionId);\n\n console.log('GoCardless requisition linked:', {\n institutionId: requisition.institution_id,\n requisitionId,\n agreementId: requisition.agreement,\n accountIds: requisition.accounts,\n });\n\n const institutionIdSet = new Set<GoCardlessInstitutionId>();\n const detailedAccounts = await Promise.all(\n requisition.accounts.map(async (accountId: GoCardlessAccountId) => {\n const account = await goCardlessService.getDetailedAccount(accountId);\n institutionIdSet.add(account.institution_id);\n return account;\n }),\n );\n\n const institutions = await Promise.all(\n Array.from(institutionIdSet).map(\n async (institutionId: GoCardlessInstitutionId) => {\n return await goCardlessService.getInstitution(institutionId);\n },\n ),\n );\n\n const extendedAccounts =\n await goCardlessService.extendAccountsAboutInstitutions({\n accounts: detailedAccounts,\n institutions,\n });\n\n const normalizedAccounts = extendedAccounts.map(account => {\n const bank: IBank = BankFactory(account.institution_id);\n return bank.normalizeAccount(account);\n });\n\n return { requisition, accounts: normalizedAccounts };\n },\n\n getTransactionsWithBalance: async (\n requisitionId: GoCardlessRequisitionId,\n accountId: GoCardlessAccountId,\n startDate: string | undefined,\n endDate: string | undefined,\n ): Promise<{\n balances: Balance[];\n institutionId: GoCardlessInstitutionId;\n startingBalance: number;\n transactions: {\n booked: Transaction[];\n pending: Transaction[];\n all: TransactionWithBookedStatus[];\n };\n }> => {\n const { institution_id, accounts: accountIds } =\n await goCardlessService.getLinkedRequisition(requisitionId);\n\n if (!accountIds.includes(accountId)) {\n throw new AccountNotLinkedToRequisition(accountId, requisitionId);\n }\n\n const [normalizedTransactions, accountBalance] = await Promise.all([\n goCardlessService.getNormalizedTransactions(\n requisitionId,\n accountId,\n startDate,\n endDate,\n ),\n goCardlessService.getBalances(accountId),\n ]);\n\n const transactions = normalizedTransactions.transactions;\n\n const bank: IBank = BankFactory(institution_id);\n\n const startingBalance = bank.calculateStartingBalance(\n transactions.booked,\n accountBalance.balances,\n );\n\n return {\n balances: accountBalance.balances,\n institutionId: institution_id,\n startingBalance,\n transactions,\n };\n },\n\n getNormalizedTransactions: async (\n requisitionId: GoCardlessRequisitionId,\n accountId: GoCardlessAccountId,\n startDate: string | undefined,\n endDate: string | undefined,\n ): Promise<{\n institutionId: GoCardlessInstitutionId;\n transactions: {\n booked: Transaction[];\n pending: Transaction[];\n all: TransactionWithBookedStatus[];\n };\n }> => {\n const { institution_id, accounts: accountIds } =\n await goCardlessService.getLinkedRequisition(requisitionId);\n\n if (!accountIds.includes(accountId)) {\n throw new AccountNotLinkedToRequisition(accountId, requisitionId);\n }\n\n const transactions = await goCardlessService.getTransactions({\n institutionId: institution_id,\n accountId,\n startDate,\n endDate,\n });\n\n const bank: IBank = BankFactory(institution_id);\n const sortedBookedTransactions = bank.sortTransactions(\n transactions.transactions.booked,\n );\n const sortedPendingTransactions = bank.sortTransactions(\n transactions.transactions.pending,\n );\n const allTransactions: TransactionWithBookedStatus[] =\n sortedBookedTransactions.map(t => ({\n ...t,\n booked: true,\n }));\n sortedPendingTransactions.forEach(t =>\n allTransactions.push({ ...t, booked: false }),\n );\n const sortedAllTransactions = bank.sortTransactions(allTransactions);\n\n return {\n institutionId: institution_id,\n transactions: {\n booked: sortedBookedTransactions,\n pending: sortedPendingTransactions,\n all: sortedAllTransactions,\n },\n };\n },\n\n createRequisition: async ({\n institutionId,\n host,\n }: CreateRequisitionParams): Promise<{\n link: string;\n requisitionId: GoCardlessRequisitionId;\n }> => {\n await goCardlessService.setToken();\n\n const institution = await goCardlessService.getInstitution(institutionId);\n const accountSelection =\n institution.supported_features?.includes('account_selection') ?? false;\n const separateContinuousHistoryConsent =\n institution.supported_features?.includes(\n 'separate_continuous_history_consent',\n ) ?? false;\n\n const body = {\n redirectUrl: host + '/gocardless/link',\n institutionId,\n referenceId: uuidv4(),\n accessValidForDays: institution.max_access_valid_for_days,\n maxHistoricalDays: separateContinuousHistoryConsent\n ? 90\n : institution.transaction_total_days,\n userLanguage: 'en',\n ssn: null,\n redirectImmediate: false,\n accountSelection,\n };\n\n console.log('GoCardless requisition request:', {\n institutionId,\n accessValidForDays: body.accessValidForDays,\n maxHistoricalDays: body.maxHistoricalDays,\n transactionTotalDays: institution.transaction_total_days,\n separateContinuousHistoryConsent,\n accountSelection,\n supportedFeatures: institution.supported_features,\n });\n\n const response = await client.initSession(body).catch(async () => {\n console.log('Failed to link using:');\n console.log(body);\n console.log(\n 'Falling back to accessValidForDays = 90 ' +\n 'and maxHistoricalDays = 89',\n );\n\n return await client\n .initSession({\n ...body,\n accessValidForDays: 90,\n maxHistoricalDays: 89,\n })\n .catch(handleGoCardlessError);\n });\n\n const { link, id: requisitionId } = response;\n\n console.log('GoCardless requisition created:', {\n institutionId,\n requisitionId,\n agreementId: response.agreement,\n });\n\n return {\n link,\n requisitionId,\n };\n },\n\n deleteRequisition: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<{ summary: string; detail: string }> => {\n await goCardlessService.getRequisition(requisitionId);\n return client.deleteRequisition(requisitionId).catch(handleGoCardlessError);\n },\n\n getRequisition: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<Requisition> => {\n await goCardlessService.setToken();\n return client\n .getRequisitionById(requisitionId)\n .catch(handleGoCardlessError);\n },\n\n getDetailedAccount: async (\n accountId: GoCardlessAccountId,\n ): Promise<DetailedAccount> => {\n const [detailedAccount, metadataAccount] = await Promise.all([\n client.getDetails(accountId),\n client.getMetadata(accountId),\n ]).catch(handleGoCardlessError);\n\n const accountDetails = detailedAccount.account ?? {};\n const metadata = metadataAccount ?? {};\n\n // Some banks provide additional data in both fields, but can do yucky things like have an empty\n // string in one place but not the other. We'll fix this by merging the two objects, but preferring truthy values\n // from the metadata object over the details object.\n const truthyMetadata = Object.fromEntries(\n Object.entries(metadata).filter(([, v]) => v),\n );\n return {\n ...accountDetails,\n ...truthyMetadata,\n } as unknown as DetailedAccount;\n },\n\n getAccountMetadata: async (\n accountId: GoCardlessAccountId,\n ): Promise<GoCardlessAccountMetadata> =>\n client.getMetadata(accountId).catch(handleGoCardlessError),\n\n getInstitutions: async (country: string): Promise<Institution[]> =>\n client.getInstitutions(country).catch(handleGoCardlessError),\n\n getInstitution: async (\n institutionId: GoCardlessInstitutionId,\n ): Promise<Institution> =>\n client.getInstitutionById(institutionId).catch(handleGoCardlessError),\n\n extendAccountsAboutInstitutions: async ({\n accounts,\n institutions,\n }: {\n accounts: DetailedAccount[];\n institutions: Institution[];\n }): Promise<DetailedAccountWithInstitution[]> => {\n const institutionsById = institutions.reduce<Record<string, Institution>>(\n (acc, institution) => {\n acc[institution.id] = institution;\n return acc;\n },\n {},\n );\n\n return accounts.map(account => {\n const institution = institutionsById[account.institution_id] ?? null;\n return {\n ...account,\n institution,\n };\n });\n },\n\n getTransactions: async ({\n institutionId,\n accountId,\n startDate,\n endDate,\n }: GetTransactionsParams): Promise<GetTransactionsResponse> => {\n const response = await client\n .getTransactions({\n accountId,\n dateFrom: startDate,\n dateTo: endDate,\n })\n .catch(handleGoCardlessError);\n\n const bank: IBank = BankFactory(institutionId);\n response.transactions.booked = response.transactions.booked\n .map(transaction => bank.normalizeTransaction(transaction, true))\n .filter(t => t != null);\n response.transactions.pending = response.transactions.pending\n .map(transaction => bank.normalizeTransaction(transaction, false))\n .filter(t => t != null);\n\n return response;\n },\n\n getBalances: async (accountId: GoCardlessAccountId): Promise<GetBalances> =>\n client.getBalances(accountId).catch(handleGoCardlessError),\n};\n\n// All GoCardless API calls go through this object so tests can mock it easily.\nexport const client = {\n getBalances: async (accountId: GoCardlessAccountId): Promise<GetBalances> =>\n await getGocardlessClient().getAccountBalances(accountId),\n getTransactions: async ({\n accountId,\n dateFrom,\n dateTo,\n }: {\n accountId: GoCardlessAccountId;\n dateFrom?: string;\n dateTo?: string;\n }): Promise<GetTransactionsResponse> =>\n await getGocardlessClient().getAccountTransactions({\n accountId,\n dateFrom,\n dateTo,\n }),\n getInstitutions: async (country: string): Promise<Institution[]> =>\n await getGocardlessClient().getInstitutions({ country }),\n getInstitutionById: async (\n institutionId: GoCardlessInstitutionId,\n ): Promise<Institution> =>\n await getGocardlessClient().getInstitutionById(institutionId),\n getDetails: async (\n accountId: GoCardlessAccountId,\n ): Promise<AccountDetailsResponse> =>\n await getGocardlessClient().getAccountDetails(accountId),\n getMetadata: async (\n accountId: GoCardlessAccountId,\n ): Promise<GoCardlessAccountMetadata> =>\n await getGocardlessClient().getAccountMetadata(accountId),\n getRequisitionById: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<Requisition> =>\n await getGocardlessClient().getRequisitionById(requisitionId),\n deleteRequisition: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<{ summary: string; detail: string }> =>\n await getGocardlessClient().deleteRequisition(requisitionId),\n initSession: async ({\n redirectUrl,\n institutionId,\n referenceId,\n accessValidForDays,\n maxHistoricalDays,\n userLanguage,\n ssn,\n redirectImmediate,\n accountSelection,\n }: {\n redirectUrl: string;\n institutionId: GoCardlessInstitutionId;\n referenceId: string | null;\n accessValidForDays: number | string;\n maxHistoricalDays: number | string;\n userLanguage: string;\n ssn: string | null;\n redirectImmediate: boolean;\n accountSelection: boolean;\n }): Promise<Requisition> =>\n await getGocardlessClient().initSession({\n redirectUrl,\n institutionId,\n referenceId,\n accessValidForDays,\n maxHistoricalDays,\n userLanguage,\n ssn,\n redirectImmediate,\n accountSelection,\n }),\n generateToken: async (): Promise<TokenResponse> =>\n await getGocardlessClient().generateToken(),\n exchangeToken: async ({\n refreshToken,\n }: {\n refreshToken: string;\n }): Promise<TokenResponse> =>\n await getGocardlessClient().exchangeToken({ refreshToken }),\n};\n","import path from 'path';\n\nimport express from 'express';\n\nimport { sha256String } from '#util/hash';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from '#util/middlewares';\n\nimport {\n AccountNotLinkedToRequisition,\n GenericGoCardlessError,\n GoCardlessClientError,\n RateLimitError,\n RequisitionNotLinked,\n} from './errors';\nimport type {\n GoCardlessAccountId,\n GoCardlessInstitutionId,\n GoCardlessRequisitionId,\n} from './gocardless-node.types';\nimport { goCardlessService } from './services/gocardless-service';\nimport { handleError } from './util/handle-error';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction validateOrigin(origin: string | undefined) {\n let url;\n try {\n url = new URL(origin ?? '');\n } catch {\n throw new Error('Invalid Origin header');\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error('Invalid Origin header');\n }\n return url.origin;\n}\n\nconst SAFE_ID = /^[a-zA-Z0-9_-]+$/;\nfunction sanitizeId<T extends string = string>(id: unknown): T {\n if (typeof id !== 'string' || !SAFE_ID.test(id)) {\n throw new Error(`Invalid GoCardless identifier: ${String(id)}`);\n }\n return id as T;\n}\n\nconst app = express();\napp.use(requestLoggerMiddleware);\n\napp.get('/link', function (req, res) {\n res.sendFile('link.html', { root: path.resolve('./src/app-gocardless') });\n});\n\nexport { app as handlers };\napp.use(express.json());\napp.use(validateSessionMiddleware);\n\napp.post('/status', async (req, res) => {\n res.send({\n status: 'ok',\n data: {\n configured: goCardlessService.isConfigured(),\n },\n });\n});\n\napp.post(\n '/create-web-token',\n handleError(async (req, res) => {\n const { institutionId: rawInstitutionId } = req.body || {};\n const institutionId = sanitizeId<GoCardlessInstitutionId>(rawInstitutionId);\n const host = validateOrigin(req.headers.origin);\n\n const { link, requisitionId } = await goCardlessService.createRequisition({\n institutionId,\n host,\n });\n\n res.send({\n status: 'ok',\n data: {\n link,\n requisitionId,\n },\n });\n }),\n);\n\napp.post(\n '/get-accounts',\n handleError(async (req, res) => {\n const requisitionId = sanitizeId<GoCardlessRequisitionId>(\n (req.body || {}).requisitionId,\n );\n\n try {\n const { requisition, accounts } =\n await goCardlessService.getRequisitionWithAccounts(requisitionId);\n\n res.send({\n status: 'ok',\n data: {\n ...requisition,\n accounts: await Promise.all(\n accounts.map(async account =>\n account?.iban\n ? { ...account, iban: sha256String(account.iban) }\n : account,\n ),\n ),\n },\n });\n } catch (error) {\n if (error instanceof RequisitionNotLinked) {\n res.send({\n status: 'ok',\n requisitionStatus: isRecord(error.details)\n ? error.details.requisitionStatus\n : undefined,\n });\n } else {\n throw error;\n }\n }\n }),\n);\n\napp.post(\n '/get-banks',\n handleError(async (req, res) => {\n const { country: rawCountry, showDemo = false } = req.body || {};\n const country = sanitizeId(rawCountry);\n\n await goCardlessService.setToken();\n const data = await goCardlessService.getInstitutions(country);\n\n res.send({\n status: 'ok',\n data: showDemo\n ? [\n {\n id: 'SANDBOXFINANCE_SFIN0000',\n name: 'DEMO bank (used for testing bank-sync)',\n },\n ...data,\n ]\n : data,\n });\n }),\n);\n\napp.post(\n '/remove-account',\n handleError(async (req, res) => {\n const requisitionId = sanitizeId<GoCardlessRequisitionId>(\n (req.body || {}).requisitionId,\n );\n\n const data = await goCardlessService.deleteRequisition(requisitionId);\n if (data.summary === 'Requisition deleted') {\n res.send({\n status: 'ok',\n data,\n });\n } else {\n res.send({\n status: 'error',\n data: {\n data,\n reason: 'Can not delete requisition',\n },\n });\n }\n }),\n);\n\napp.post(\n '/transactions',\n handleError(async (req, res) => {\n const {\n requisitionId: rawRequisitionId,\n startDate,\n endDate,\n accountId: rawAccountId,\n includeBalance = true,\n } = req.body || {};\n const requisitionId = sanitizeId<GoCardlessRequisitionId>(rawRequisitionId);\n const accountId = sanitizeId<GoCardlessAccountId>(rawAccountId);\n\n try {\n if (includeBalance) {\n const {\n balances,\n institutionId,\n startingBalance,\n transactions: { booked, pending, all },\n } = await goCardlessService.getTransactionsWithBalance(\n requisitionId,\n accountId,\n startDate,\n endDate,\n );\n\n res.send({\n status: 'ok',\n data: {\n balances,\n institutionId,\n startingBalance,\n transactions: {\n booked,\n pending,\n all,\n },\n },\n });\n } else {\n const {\n institutionId,\n transactions: { booked, pending, all },\n } = await goCardlessService.getNormalizedTransactions(\n requisitionId,\n accountId,\n startDate,\n endDate,\n );\n\n res.send({\n status: 'ok',\n data: {\n institutionId,\n transactions: {\n booked,\n pending,\n all,\n },\n },\n });\n }\n } catch (error) {\n const errorDetails =\n error instanceof RequisitionNotLinked ||\n error instanceof GenericGoCardlessError ||\n error instanceof GoCardlessClientError ||\n error instanceof AccountNotLinkedToRequisition\n ? error.details\n : undefined;\n\n const responseHeaders =\n isRecord(errorDetails) && isRecord(errorDetails.response)\n ? errorDetails.response.headers\n : undefined;\n\n const rateLimitHeaders = isRecord(responseHeaders)\n ? Object.fromEntries(\n Object.entries(responseHeaders).filter(([key]) =>\n key.startsWith('x-ratelimit-'),\n ),\n )\n : {};\n\n const errorMessage =\n error instanceof Error && error.message ? error.message : String(error);\n\n const sendErrorResponse = (data: Record<string, unknown>) =>\n res.send({\n status: 'ok',\n data: { ...data, details: errorDetails, rateLimitHeaders },\n });\n\n switch (true) {\n case error instanceof RequisitionNotLinked:\n sendErrorResponse({\n error_type: 'ITEM_ERROR',\n error_code: 'ITEM_LOGIN_REQUIRED',\n status: 'expired',\n reason:\n 'Access to account has expired as set in End User Agreement',\n });\n break;\n case error instanceof AccountNotLinkedToRequisition:\n sendErrorResponse({\n error_type: 'INVALID_INPUT',\n error_code: 'INVALID_ACCESS_TOKEN',\n status: 'rejected',\n reason: 'Account not linked with this requisition',\n });\n break;\n case error instanceof RateLimitError:\n sendErrorResponse({\n error_type: 'RATE_LIMIT_EXCEEDED',\n error_code: 'NORDIGEN_ERROR',\n status: 'rejected',\n reason: 'Rate limit exceeded',\n });\n break;\n case error instanceof GenericGoCardlessError:\n console.log('Something went wrong', errorMessage);\n sendErrorResponse({\n error_type: 'SYNC_ERROR',\n error_code: 'NORDIGEN_ERROR',\n });\n break;\n default:\n console.log('Something went wrong', errorMessage);\n sendErrorResponse({\n error_type: 'UNKNOWN',\n error_code: 'UNKNOWN',\n reason: 'Something went wrong',\n });\n break;\n }\n }\n }),\n);\n","import express from 'express';\nimport rateLimit from 'express-rate-limit';\n\nimport { disableOpenID, enableOpenID, isAdmin } from './account-db';\nimport { isValidRedirectUrl, loginWithOpenIdFinalize } from './accounts/openid';\nimport { checkPassword } from './accounts/password';\nimport * as UserService from './services/user-service';\nimport {\n errorMiddleware,\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from './util/middlewares';\n\nconst app = express();\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(requestLoggerMiddleware);\n\nconst openIdConfigRateLimiter = rateLimit({\n windowMs: 15 * 60 * 1000,\n max: 5,\n legacyHeaders: false,\n standardHeaders: true,\n message: { status: 'error', reason: 'too-many-requests' },\n});\n\nexport { app as handlers, openIdConfigRateLimiter };\n\napp.post('/enable', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { error } = (await enableOpenID(req.body)) || {};\n\n if (error) {\n res.status(500).send({ status: 'error', reason: error });\n return;\n }\n res.send({ status: 'ok' });\n});\n\napp.post('/disable', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { error } = (await disableOpenID(req.body)) || {};\n\n if (error) {\n res.status(401).send({ status: 'error', reason: error });\n return;\n }\n res.send({ status: 'ok' });\n});\n\napp.post('/config', openIdConfigRateLimiter, async (req, res) => {\n const ownerCount = UserService.getOwnerCount();\n\n if (ownerCount > 0) {\n res.status(400).send({ status: 'error', reason: 'already-bootstraped' });\n return;\n }\n\n if (!checkPassword(req.body.password)) {\n res.status(400).send({ status: 'error', reason: 'invalid-password' });\n return;\n }\n\n const auth = UserService.getOpenIDConfig();\n\n if (!auth) {\n res\n .status(500)\n .send({ status: 'error', reason: 'OpenID configuration not found' });\n return;\n }\n\n try {\n const openIdConfig = JSON.parse(auth.extra_data);\n res.send({ status: 'ok', data: { openId: openIdConfig } });\n } catch {\n res\n .status(500)\n .send({ status: 'error', reason: 'Invalid OpenID configuration' });\n }\n});\n\napp.get('/callback', async (req, res) => {\n const { error, url } = await loginWithOpenIdFinalize(req.query);\n\n if (error) {\n res.status(400).send({ status: 'error', reason: error });\n return;\n }\n\n if (!isValidRedirectUrl(url)) {\n res.status(400).send({ status: 'error', reason: 'Invalid redirect URL' });\n return;\n }\n\n res.redirect(url);\n});\n\napp.use(errorMiddleware);\n","import { PluggyClient } from 'pluggy-sdk';\n\nimport { SecretName, secretsService } from '#services/secrets-service';\n\nlet pluggyClient = null;\n\nfunction getPluggyClient() {\n if (!pluggyClient) {\n const clientId = secretsService.get(SecretName.pluggyai_clientId);\n const clientSecret = secretsService.get(SecretName.pluggyai_clientSecret);\n\n pluggyClient = new PluggyClient({\n clientId,\n clientSecret,\n });\n }\n\n return pluggyClient;\n}\n\nexport const pluggyaiService = {\n isConfigured: () => {\n return !!(\n secretsService.get(SecretName.pluggyai_clientId) &&\n secretsService.get(SecretName.pluggyai_clientSecret) &&\n secretsService.get(SecretName.pluggyai_itemIds)\n );\n },\n\n getAccountsByItemId: async itemId => {\n try {\n const client = getPluggyClient();\n const { results, total, ...rest } = await client.fetchAccounts(itemId);\n return {\n results,\n total,\n ...rest,\n hasError: false,\n errors: {},\n };\n } catch (error) {\n console.error(`Error fetching accounts: ${error.message}`);\n throw error;\n }\n },\n getAccountById: async accountId => {\n try {\n const client = getPluggyClient();\n const account = await client.fetchAccount(accountId);\n return {\n ...account,\n hasError: false,\n errors: {},\n };\n } catch (error) {\n console.error(`Error fetching account: ${error.message}`);\n throw error;\n }\n },\n\n getTransactionsByAccountId: async (accountId, startDate, pageSize, page) => {\n try {\n const client = getPluggyClient();\n\n const account = await pluggyaiService.getAccountById(accountId);\n\n // the sandbox data doesn't move the dates automatically so the\n // transactions are often older than 90 days. The owner on one of the\n // sandbox accounts is set to John Doe so in these cases we'll ignore\n // the start date.\n const sandboxAccount = account.owner === 'John Doe';\n\n if (sandboxAccount) startDate = '2000-01-01';\n\n const transactions = await client.fetchTransactions(accountId, {\n from: startDate,\n pageSize,\n page,\n });\n\n if (sandboxAccount) {\n transactions.results = transactions.results.map(t => ({\n ...t,\n sandbox: true,\n }));\n }\n\n return {\n ...transactions,\n hasError: false,\n errors: {},\n };\n } catch (error) {\n console.error(`Error fetching transactions: ${error.message}`);\n throw error;\n }\n },\n getTransactions: async (accountId, startDate) => {\n let transactions = [];\n let result = await pluggyaiService.getTransactionsByAccountId(\n accountId,\n startDate,\n 500,\n 1,\n );\n transactions = transactions.concat(result.results);\n const totalPages = result.totalPages;\n while (result.page !== totalPages) {\n result = await pluggyaiService.getTransactionsByAccountId(\n accountId,\n startDate,\n 500,\n result.page + 1,\n );\n transactions = transactions.concat(result.results);\n }\n\n return transactions;\n },\n};\n","import express from 'express';\n\nimport { handleError } from '#app-gocardless/util/handle-error';\nimport { SecretName, secretsService } from '#services/secrets-service';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from '#util/middlewares';\n\nimport { pluggyaiService } from './pluggyai-service';\n\nconst app = express();\nexport { app as handlers };\napp.use(requestLoggerMiddleware);\napp.use(express.json());\napp.use(validateSessionMiddleware);\n\napp.post(\n '/status',\n handleError(async (req, res) => {\n const clientId = secretsService.get(SecretName.pluggyai_clientId);\n const configured = clientId != null;\n\n res.send({\n status: 'ok',\n data: {\n configured,\n },\n });\n }),\n);\n\napp.post(\n '/accounts',\n handleError(async (req, res) => {\n try {\n const itemIds = secretsService\n .get(SecretName.pluggyai_itemIds)\n .split(',')\n .map(item => item.trim());\n\n let accounts = [];\n\n for (const item of itemIds) {\n const partial = await pluggyaiService.getAccountsByItemId(item);\n accounts = accounts.concat(partial.results);\n }\n\n res.send({\n status: 'ok',\n data: {\n accounts,\n },\n });\n } catch (error) {\n res.send({\n status: 'ok',\n data: {\n error: error.message,\n },\n });\n }\n }),\n);\n\napp.post(\n '/transactions',\n handleError(async (req, res) => {\n const { accountId, startDate } = req.body || {};\n\n try {\n const transactions = await pluggyaiService.getTransactions(\n accountId,\n startDate,\n );\n\n const account = await pluggyaiService.getAccountById(accountId);\n\n let startingBalance = parseInt(\n Math.round(account.balance * 100).toString(),\n );\n if (account.type === 'CREDIT') {\n startingBalance = -startingBalance;\n }\n const date = getDate(new Date(account.updatedAt));\n\n const balances = [\n {\n balanceAmount: {\n amount: startingBalance,\n currency: account.currencyCode,\n },\n balanceType: 'expected',\n referenceDate: date,\n },\n ];\n\n const all = [];\n const booked = [];\n const pending = [];\n\n for (const trans of transactions) {\n const newTrans = {};\n\n newTrans.booked = !(trans.status === 'PENDING');\n\n const transactionDate = new Date(trans.date);\n\n if (transactionDate < startDate && !trans.sandbox) {\n continue;\n }\n\n newTrans.date = getDate(transactionDate);\n newTrans.payeeName = getPayeeName(trans);\n newTrans.notes = trans.descriptionRaw || trans.description;\n\n if (account.type === 'CREDIT') {\n if (trans.amountInAccountCurrency) {\n trans.amountInAccountCurrency *= -1;\n }\n\n trans.amount *= -1;\n }\n\n let amountInCurrency = trans.amountInAccountCurrency ?? trans.amount;\n amountInCurrency = Math.round(amountInCurrency * 100) / 100;\n\n newTrans.transactionAmount = {\n amount: amountInCurrency,\n currency: trans.currencyCode,\n };\n\n newTrans.transactionId = trans.id;\n newTrans.sortOrder = transactionDate.getTime();\n\n delete trans.amount;\n\n const finalTrans = { ...flattenObject(trans), ...newTrans };\n if (newTrans.booked) {\n booked.push(finalTrans);\n } else {\n pending.push(finalTrans);\n }\n all.push(finalTrans);\n }\n\n const sortFunction = (a, b) => b.sortOrder - a.sortOrder;\n\n const bookedSorted = booked.sort(sortFunction);\n const pendingSorted = pending.sort(sortFunction);\n const allSorted = all.sort(sortFunction);\n\n res.send({\n status: 'ok',\n data: {\n balances,\n startingBalance,\n transactions: {\n all: allSorted,\n booked: bookedSorted,\n pending: pendingSorted,\n },\n },\n });\n } catch (error) {\n res.send({\n status: 'ok',\n data: {\n error: error.message,\n },\n });\n }\n return;\n }),\n);\n\nfunction getDate(date) {\n return date.toISOString().split('T')[0];\n}\n\nfunction flattenObject(obj, prefix = '') {\n const result = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const newKey = prefix ? `${prefix}.${key}` : key;\n\n if (value === null) {\n continue;\n }\n\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n Object.assign(result, flattenObject(value, newKey));\n } else {\n result[newKey] = value;\n }\n }\n\n return result;\n}\n\nfunction getPayeeName(trans) {\n if (trans.merchant && (trans.merchant.name || trans.merchant.businessName)) {\n return trans.merchant.name || trans.merchant.businessName || '';\n }\n\n if (trans.paymentData) {\n const { receiver, payer } = trans.paymentData;\n\n if (trans.type === 'DEBIT' && receiver) {\n return receiver.name || receiver.documentNumber?.value || '';\n }\n\n if (trans.type === 'CREDIT' && payer) {\n return payer.name || payer.documentNumber?.value || '';\n }\n }\n\n return '';\n}\n","import express from 'express';\n\nimport { getActiveLoginMethod, isAdmin } from './account-db';\nimport { SecretName, secretsService } from './services/secrets-service';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from './util/middlewares';\n\nconst app = express();\n\nexport { app as handlers };\napp.use(express.json());\napp.use(requestLoggerMiddleware);\napp.use(validateSessionMiddleware);\n\n// In OpenID mode the secrets store is admin-managed; non-admins must be\n// blocked from both reads and writes, otherwise they can enumerate which\n// integrations are configured.\nfunction canManageSecrets(userId) {\n return getActiveLoginMethod() !== 'openid' || isAdmin(userId);\n}\n\napp.post('/', async (req, res) => {\n if (!canManageSecrets(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'not-admin',\n details: 'You have to be admin to set secrets',\n });\n return;\n }\n\n const { name, value } = req.body || {};\n\n if (!(name in SecretName)) {\n res.status(400).send({\n status: 'error',\n reason: 'invalid-secret-name',\n details: 'Unknown secret name',\n });\n return;\n }\n\n secretsService.set(name, value);\n\n res.status(200).send({ status: 'ok' });\n});\n\napp.get('/:name', async (req, res) => {\n if (!canManageSecrets(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'not-admin',\n details: 'You have to be admin to read secrets',\n });\n return;\n }\n\n const name = req.params.name;\n if (!(name in SecretName)) {\n res.status(404).send('key not found');\n return;\n }\n\n if (secretsService.exists(name)) {\n res.sendStatus(204);\n } else {\n res.status(404).send('key not found');\n }\n});\n","import https from 'https';\n\nimport express from 'express';\n\nimport { handleError } from '#app-gocardless/util/handle-error';\nimport { SecretName, secretsService } from '#services/secrets-service';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from '#util/middlewares';\n\nconst app = express();\nexport { app as handlers };\napp.use(requestLoggerMiddleware);\napp.use(express.json());\napp.use(validateSessionMiddleware);\n\napp.post(\n '/status',\n handleError(async (req, res) => {\n const token = secretsService.get(SecretName.simplefin_token);\n const configured = token != null && token !== 'Forbidden';\n\n res.send({\n status: 'ok',\n data: {\n configured,\n },\n });\n }),\n);\n\napp.post(\n '/accounts',\n handleError(async (req, res) => {\n let accessKey = secretsService.get(SecretName.simplefin_accessKey);\n\n try {\n if (accessKey == null || accessKey === 'Forbidden') {\n const token = secretsService.get(SecretName.simplefin_token);\n if (token == null || token === 'Forbidden') {\n throw new Error('No token');\n } else {\n accessKey = await getAccessKey(token);\n secretsService.set(SecretName.simplefin_accessKey, accessKey);\n if (accessKey == null || accessKey === 'Forbidden') {\n throw new Error('No access key');\n }\n }\n }\n } catch {\n invalidToken(res);\n return;\n }\n\n try {\n const accounts = await getAccounts(accessKey, null, null, null, true);\n\n res.send({\n status: 'ok',\n data: {\n accounts: accounts.accounts,\n },\n });\n } catch (e) {\n serverDown(e, res);\n return;\n }\n }),\n);\n\napp.post(\n '/transactions',\n handleError(async (req, res) => {\n const { accountId, startDate } = req.body || {};\n\n const accessKey = secretsService.get(SecretName.simplefin_accessKey);\n\n if (accessKey == null || accessKey === 'Forbidden') {\n invalidToken(res);\n return;\n }\n\n if (Array.isArray(accountId) !== Array.isArray(startDate)) {\n console.log({ accountId, startDate });\n throw new Error(\n 'accountId and startDate must either both be arrays or both be strings',\n );\n }\n if (Array.isArray(accountId) && accountId.length !== startDate.length) {\n console.log({ accountId, startDate });\n throw new Error('accountId and startDate arrays must be the same length');\n }\n\n const earliestStartDate = Array.isArray(startDate)\n ? startDate.reduce((a, b) => (a < b ? a : b))\n : startDate;\n let results;\n try {\n results = await getTransactions(\n accessKey,\n Array.isArray(accountId) ? accountId : [accountId],\n new Date(earliestStartDate),\n );\n } catch (e) {\n if (e.message === 'Forbidden') {\n invalidToken(res);\n } else {\n serverDown(e, res);\n }\n return;\n }\n\n let response = {};\n if (Array.isArray(accountId)) {\n for (let i = 0; i < accountId.length; i++) {\n const id = accountId[i];\n response[id] = getAccountResponse(results, id, new Date(startDate[i]));\n }\n } else {\n response = getAccountResponse(results, accountId, new Date(startDate));\n }\n\n if (results.hasError) {\n res.send({\n status: 'ok',\n data: !Array.isArray(accountId)\n ? results.errors[accountId][0]\n : {\n ...response,\n errors: results.errors,\n },\n });\n return;\n }\n\n res.send({\n status: 'ok',\n data: response,\n });\n }),\n);\n\nfunction logAccountError(results, accountId, data) {\n const errors = results.errors[accountId] || [];\n errors.push(data);\n results.errors[accountId] = errors;\n results.hasError = true;\n}\n\nfunction getAccountResponse(results, accountId, startDate) {\n const account =\n !results?.accounts || results.accounts.find(a => a.id === accountId);\n if (!account) {\n console.log(\n `The account \"${accountId}\" was not found. Here were the accounts returned:`,\n );\n if (results?.accounts) {\n results.accounts.forEach(a => console.log(`${a.id} - ${a.org.name}`));\n }\n logAccountError(results, accountId, {\n error_type: 'ACCOUNT_MISSING',\n error_code: 'ACCOUNT_MISSING',\n reason: `The account \"${accountId}\" was not found. Try unlinking and relinking the account.`,\n });\n return;\n }\n\n const needsAttention = results.sferrors.find(e =>\n e.startsWith(`Connection to ${account.org.name} may need attention`),\n );\n if (needsAttention) {\n logAccountError(results, accountId, {\n error_type: 'ACCOUNT_NEEDS_ATTENTION',\n error_code: 'ACCOUNT_NEEDS_ATTENTION',\n reason:\n 'The account needs your attention at <a href=\"https://bridge.simplefin.org/auth/login\">SimpleFIN</a>.',\n });\n }\n\n const startingBalance = parseInt(account.balance.replace('.', ''));\n const date = getDate(new Date(account['balance-date'] * 1000));\n\n const balances = [\n {\n balanceAmount: {\n amount: account.balance,\n currency: account.currency,\n },\n balanceType: 'expected',\n referenceDate: date,\n },\n {\n balanceAmount: {\n amount: account.balance,\n currency: account.currency,\n },\n balanceType: 'interimAvailable',\n referenceDate: date,\n },\n ];\n\n const all = [];\n const booked = [];\n const pending = [];\n\n for (const trans of account.transactions) {\n const newTrans = {};\n\n let dateToUse = 0;\n\n if (trans.pending ?? trans.posted === 0) {\n newTrans.booked = false;\n dateToUse = trans.transacted_at;\n } else {\n newTrans.booked = true;\n dateToUse = trans.posted;\n }\n\n const transactionDate = new Date(dateToUse * 1000);\n\n if (transactionDate < startDate) {\n continue;\n }\n\n newTrans.sortOrder = dateToUse;\n newTrans.date = getDate(transactionDate);\n newTrans.payeeName = trans.payee;\n newTrans.notes = trans.description;\n newTrans.transactionAmount = { amount: trans.amount, currency: 'USD' };\n newTrans.transactionId = trans.id;\n newTrans.valueDate = newTrans.bookingDate;\n\n if (trans.transacted_at) {\n newTrans.transactedDate = getDate(new Date(trans.transacted_at * 1000));\n }\n\n if (trans.posted) {\n newTrans.postedDate = getDate(new Date(trans.posted * 1000));\n }\n\n if (newTrans.booked) {\n booked.push(newTrans);\n } else {\n pending.push(newTrans);\n }\n all.push(newTrans);\n }\n\n const sortFunction = (a, b) => b.sortOrder - a.sortOrder;\n\n const bookedSorted = booked.sort(sortFunction);\n const pendingSorted = pending.sort(sortFunction);\n const allSorted = all.sort(sortFunction);\n\n return {\n balances,\n startingBalance,\n transactions: {\n all: allSorted,\n booked: bookedSorted,\n pending: pendingSorted,\n },\n };\n}\n\nfunction invalidToken(res) {\n res.send({\n status: 'ok',\n data: {\n error_type: 'INVALID_ACCESS_TOKEN',\n error_code: 'INVALID_ACCESS_TOKEN',\n status: 'rejected',\n reason:\n 'Invalid SimpleFIN access token. Reset the token and re-link any broken accounts.',\n },\n });\n}\n\nfunction serverDown(e, res) {\n console.log(e);\n res.send({\n status: 'ok',\n data: {\n error_type: 'SERVER_DOWN',\n error_code: 'SERVER_DOWN',\n status: 'rejected',\n reason: 'There was an error communicating with SimpleFIN.',\n },\n });\n}\n\nfunction parseAccessKey(accessKey) {\n let scheme = null;\n let rest = null;\n let auth = null;\n let username = null;\n let password = null;\n let baseUrl = null;\n if (!accessKey || !accessKey.match(/^.*\\/\\/.*:.*@.*$/)) {\n console.log('Invalid SimpleFIN access key');\n throw new Error(`Invalid access key`);\n }\n [scheme, rest] = accessKey.split('//');\n [auth, rest] = rest.split('@');\n [username, password] = auth.split(':');\n baseUrl = `${scheme}//${rest}`;\n return {\n baseUrl,\n username,\n password,\n };\n}\n\nasync function getAccessKey(base64Token) {\n const token = Buffer.from(base64Token, 'base64').toString();\n const options = {\n method: 'POST',\n port: 443,\n headers: { 'Content-Length': 0 },\n };\n return new Promise((resolve, reject) => {\n const req = https.request(new URL(token), options, res => {\n res.on('data', d => {\n resolve(d.toString());\n });\n });\n req.on('error', e => {\n reject(e);\n });\n req.end();\n });\n}\n\nasync function getTransactions(accessKey, accounts, startDate, endDate) {\n const now = new Date();\n startDate = startDate || new Date(now.getFullYear(), now.getMonth(), 1);\n endDate = endDate || new Date(now.getFullYear(), now.getMonth() + 1, 1);\n console.log(`${getDate(startDate)} - ${getDate(endDate)}`);\n return await getAccounts(accessKey, accounts, startDate, endDate);\n}\n\nfunction getDate(date) {\n return date.toISOString().split('T')[0];\n}\n\nfunction normalizeDate(date) {\n return (date.valueOf() - date.getTimezoneOffset() * 60 * 1000) / 1000;\n}\n\nasync function getAccounts(\n accessKey,\n accounts,\n startDate,\n endDate,\n noTransactions = false,\n) {\n const sfin = parseAccessKey(accessKey);\n\n const headers = {\n Authorization: `Basic ${Buffer.from(\n `${sfin.username}:${sfin.password}`,\n ).toString('base64')}`,\n };\n\n const params = new URLSearchParams();\n if (!noTransactions) {\n if (startDate) {\n params.append('start-date', normalizeDate(startDate));\n }\n if (endDate) {\n params.append('end-date', normalizeDate(endDate));\n }\n params.append('pending', '1');\n } else {\n params.append('balances-only', '1');\n }\n\n if (accounts) {\n for (const id of accounts) {\n params.append('account', id);\n }\n }\n\n const url = new URL(`${sfin.baseUrl}/accounts`);\n url.search = params.toString();\n\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers,\n redirect: 'follow',\n });\n\n if (response.status === 403) {\n throw new Error('Forbidden');\n }\n\n const text = await response.text();\n try {\n const results = JSON.parse(text);\n results.sferrors = results.errors;\n results.hasError = false;\n results.errors = {};\n return results;\n } catch (e) {\n console.log(`Error parsing JSON response: ${text}`);\n throw e;\n }\n}\n","// TODO: Ok, several problems:\n//\n// * If nothing matches between two merkle trees, we should fallback\n// * to the last window instead the front one (use 0 instead of the\n// * key)\n//\n// * Need to check to make sure if account exists when handling\n// * transaction changes in syncing\n\nimport type { Timestamp } from './timestamp';\n\n/**\n * Represents a node within a trinary radix trie.\n */\nexport type TrieNode = {\n '0'?: TrieNode;\n '1'?: TrieNode;\n '2'?: TrieNode;\n hash?: number;\n};\n\ntype NumberTrieNodeKey = keyof Omit<TrieNode, 'hash'>;\n\nexport function emptyTrie(): TrieNode {\n return { hash: 0 };\n}\n\nfunction isNumberTrieNodeKey(input: string): input is NumberTrieNodeKey {\n return ['0', '1', '2'].includes(input);\n}\n\nexport function getKeys(trie: TrieNode): NumberTrieNodeKey[] {\n return Object.keys(trie).filter(isNumberTrieNodeKey);\n}\n\nexport function keyToTimestamp(key: string): number {\n // 16 is the length of the base 3 value of the current time in\n // minutes. Ensure it's padded to create the full value\n const fullkey = key + '0'.repeat(16 - key.length);\n\n // Parse the base 3 representation\n return parseInt(fullkey, 3) * 1000 * 60;\n}\n\n/**\n * Mutates `trie` to insert a node at `timestamp`\n */\nexport function insert(trie: TrieNode, timestamp: Timestamp) {\n const hash = timestamp.hash();\n const key = Number(Math.floor(timestamp.millis() / 1000 / 60)).toString(3);\n\n trie = Object.assign({}, trie, { hash: (trie.hash || 0) ^ hash });\n return insertKey(trie, key, hash);\n}\n\nfunction insertKey(trie: TrieNode, key: string, hash: number): TrieNode {\n if (key.length === 0) {\n return trie;\n }\n const c = key[0];\n const t = isNumberTrieNodeKey(c) ? trie[c] : undefined;\n const n = t || {};\n return Object.assign({}, trie, {\n [c]: Object.assign({}, n, insertKey(n, key.slice(1), hash), {\n hash: (n.hash || 0) ^ hash,\n }),\n });\n}\n\nexport function build(timestamps: Timestamp[]) {\n const trie = emptyTrie();\n for (const timestamp of timestamps) {\n insert(trie, timestamp);\n }\n return trie;\n}\n\nexport function diff(trie1: TrieNode, trie2: TrieNode): number | null {\n if (trie1.hash === trie2.hash) {\n return null;\n }\n\n let node1 = trie1;\n let node2 = trie2;\n let k = '';\n\n // This loop will eventually stop when it traverses down to find\n // where the hashes differ, or otherwise when there are no leaves\n // left (this shouldn't happen, if that's the case the hash check at\n // the top of this function should pass)\n while (true) {\n const keyset = new Set([...getKeys(node1), ...getKeys(node2)]);\n const keys = [...keyset.values()];\n keys.sort((a, b) => a.localeCompare(b));\n\n let diffkey: null | '0' | '1' | '2' = null;\n\n // Traverse down the trie through keys that aren't the same. We\n // traverse down the keys in order. Stop in two cases: either one\n // of the nodes doesn't have the key, or a different key isn't\n // found. For the former case, we have to that because pruning is\n // lossy. We don't know if we've pruned off a changed key so we\n // can't traverse down anymore. For the latter case, it means two\n // things: either we've hit the bottom of the tree, or the changed\n // key has been pruned off. In the latter case we have a \"partial\"\n // key and will fill the rest with 0s. Note that if multiple older\n // messages were added into one trie, it's possible we will\n // generate a time that only encompasses *some* of the those\n // messages. Pruning is lossy, and we traverse down the left-most\n // changed time that we know of, because of pruning it might take\n // multiple passes to sync up a trie.\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n\n const next1 = node1[key];\n const next2 = node2[key];\n\n if (!next1 || !next2) {\n break;\n }\n\n if (next1.hash !== next2.hash) {\n diffkey = key;\n break;\n }\n }\n\n if (!diffkey) {\n return keyToTimestamp(k);\n }\n\n k += diffkey;\n node1 = node1[diffkey] || emptyTrie();\n node2 = node2[diffkey] || emptyTrie();\n }\n\n // oxlint-disable-next-line no-unreachable\n return null;\n}\n\nexport function prune(trie: TrieNode, n = 2): TrieNode {\n // Do nothing if empty\n if (!trie.hash) {\n return trie;\n }\n\n const keys = getKeys(trie);\n keys.sort((a, b) => a.localeCompare(b));\n\n const next: TrieNode = { hash: trie.hash };\n\n // Prune child nodes.\n for (const k of keys.slice(-n)) {\n const node = trie[k];\n\n if (!node) {\n throw new Error(`TrieNode for key ${k} could not be found`);\n }\n\n next[k] = prune(node, n);\n }\n\n return next;\n}\n\nexport function debug(trie: TrieNode, k = '', indent = 0): string {\n const str =\n ' '.repeat(indent) +\n (k !== '' ? `k: ${k} ` : '') +\n `hash: ${trie.hash || '(empty)'}\\n`;\n return (\n str +\n getKeys(trie)\n .map(key => {\n const node = trie[key];\n if (!node) return '';\n return debug(node, key, indent + 2);\n })\n .join('')\n );\n}\n","import murmurhash from 'murmurhash';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport type { TrieNode } from './merkle';\n\n/**\n * Hybrid Unique Logical Clock (HULC) timestamp generator\n *\n * Globally-unique, monotonic timestamps are generated from the\n * combination of the unreliable system time, a counter, and an\n * identifier for the current node (instance, machine, process, etc.).\n * These timestamps can accommodate clock stuttering (duplicate values),\n * regression, and node differences within the configured maximum drift.\n *\n * In order to generate timestamps locally or for transmission to another\n * node, use the send() method. For global causality, timestamps must\n * be included in each message processed by the system. Whenever a\n * message is received, its timestamp must be passed to the recv()\n * method.\n *\n * Timestamps serialize into a 46-character collatable string\n * example: 2015-04-24T22:23:42.123Z-1000-0123456789ABCDEF\n * example: 2015-04-24T22:23:42.123Z-1000-A219E7A71CC18912\n *\n * The 64-bit hybrid clock is based on the HLC specification,\n * http://www.cse.buffalo.edu/tech-reports/2014-04.pdf\n */\n\nexport type Clock = {\n timestamp: MutableTimestamp;\n merkle: TrieNode;\n};\n\n// A mutable global clock\nlet clock: Clock;\n\nexport function setClock(clock_: Clock): void {\n clock = clock_;\n}\n\nexport function getClock(): Clock {\n return clock;\n}\n\nexport function makeClock(timestamp: Timestamp, merkle: TrieNode = {}) {\n return { timestamp: MutableTimestamp.from(timestamp), merkle };\n}\n\nexport function serializeClock(clock: Clock): string {\n return JSON.stringify({\n timestamp: clock.timestamp.toString(),\n merkle: clock.merkle,\n });\n}\n\nexport function deserializeClock(clock: string): Clock {\n let data;\n try {\n data = JSON.parse(clock);\n } catch {\n data = {\n timestamp: '1970-01-01T00:00:00.000Z-0000-' + makeClientId(),\n merkle: {},\n };\n }\n\n const ts = Timestamp.parse(data.timestamp);\n\n if (!ts) {\n throw new Timestamp.InvalidError(data.timestamp);\n }\n\n return {\n timestamp: MutableTimestamp.from(ts),\n merkle: data.merkle,\n };\n}\n\nexport function makeClientId() {\n return uuidv4().replace(/-/g, '').slice(-16);\n}\n\nconst config = {\n // Allow 5 minutes of clock drift\n maxDrift: 5 * 60 * 1000,\n};\n\nconst MAX_COUNTER = parseInt('0xFFFF');\nconst MAX_NODE_LENGTH = 16;\n\n/**\n * timestamp instance class\n */\nexport class Timestamp {\n _state: { millis: number; counter: number; node: string };\n\n constructor(millis: number, counter: number, node: string) {\n this._state = {\n millis,\n counter,\n node,\n };\n }\n\n valueOf() {\n return this.toString();\n }\n\n toString() {\n return [\n new Date(this.millis()).toISOString(),\n ('0000' + this.counter().toString(16).toUpperCase()).slice(-4),\n ('0000000000000000' + this.node()).slice(-16),\n ].join('-');\n }\n\n millis() {\n return this._state.millis;\n }\n\n counter() {\n return this._state.counter;\n }\n\n node() {\n return this._state.node;\n }\n\n hash() {\n return murmurhash.v3(this.toString());\n }\n\n // Timestamp generator initialization\n // * sets the node ID to an arbitrary value\n // * useful for mocking/unit testing\n static init(options: { maxDrift?: number; node?: string } = {}) {\n if (options.maxDrift) {\n config.maxDrift = options.maxDrift;\n }\n\n setClock(\n makeClock(\n new Timestamp(\n 0,\n 0,\n options.node\n ? ('0000000000000000' + options.node).toString().slice(-16)\n : '',\n ),\n ),\n );\n }\n\n /**\n * maximum timestamp\n */\n\n static max = Timestamp.parse(\n '9999-12-31T23:59:59.999Z-FFFF-FFFFFFFFFFFFFFFF',\n )!;\n\n /**\n * timestamp parsing\n * converts a fixed-length string timestamp to the structured value\n */\n static parse(timestamp: string | Timestamp): Timestamp | null {\n if (timestamp instanceof Timestamp) {\n return timestamp;\n }\n if (typeof timestamp === 'string') {\n const parts = timestamp.split('-');\n if (parts && parts.length === 5) {\n const millis = Date.parse(parts.slice(0, 3).join('-')).valueOf();\n const counter = parseInt(parts[3], 16);\n const node = parts[4];\n if (\n !isNaN(millis) &&\n millis >= 0 &&\n !isNaN(counter) &&\n counter <= MAX_COUNTER &&\n typeof node === 'string' &&\n node.length <= MAX_NODE_LENGTH\n ) {\n return new Timestamp(millis, counter, node);\n }\n }\n }\n return null;\n }\n\n /**\n * Timestamp send. Generates a unique, monotonic timestamp suitable\n * for transmission to another system in string format\n */\n static send(): Timestamp | null {\n if (!clock) {\n return null;\n }\n\n // retrieve the local wall time\n const phys = Date.now();\n\n // unpack the clock.timestamp logical time and counter\n const lOld = clock.timestamp.millis();\n const cOld = clock.timestamp.counter();\n\n // calculate the next logical time and counter\n // * ensure that the logical time never goes backward\n // * increment the counter if phys time does not advance\n const lNew = Math.max(lOld, phys);\n const cNew = lOld === lNew ? cOld + 1 : 0;\n\n // check the result for drift and counter overflow\n if (lNew - phys > config.maxDrift) {\n throw new Timestamp.ClockDriftError(lNew, phys, config.maxDrift);\n }\n if (cNew > MAX_COUNTER) {\n throw new Timestamp.OverflowError();\n }\n\n // repack the logical time/counter\n clock.timestamp.setMillis(lNew);\n clock.timestamp.setCounter(cNew);\n\n return new Timestamp(\n clock.timestamp.millis(),\n clock.timestamp.counter(),\n clock.timestamp.node(),\n );\n }\n\n // Timestamp receive. Parses and merges a timestamp from a remote\n // system with the local timeglobal uniqueness and monotonicity are\n // preserved\n static recv(msg: Timestamp): Timestamp | null {\n if (!clock) {\n return null;\n }\n\n // retrieve the local wall time\n const phys = Date.now();\n\n // unpack the message wall time/counter\n const lMsg = msg.millis();\n const cMsg = msg.counter();\n\n // assert the node id and remote clock drift\n // if (msg.node() === clock.timestamp.node()) {\n // throw new Timestamp.DuplicateNodeError(clock.timestamp.node());\n // }\n if (lMsg - phys > config.maxDrift) {\n throw new Timestamp.ClockDriftError();\n }\n\n // unpack the clock.timestamp logical time and counter\n const lOld = clock.timestamp.millis();\n const cOld = clock.timestamp.counter();\n\n // calculate the next logical time and counter\n // . ensure that the logical time never goes backward\n // . if all logical clocks are equal, increment the max counter\n // . if max = old > message, increment local counter\n // . if max = messsage > old, increment message counter\n // . otherwise, clocks are monotonic, reset counter\n const lNew = Math.max(Math.max(lOld, phys), lMsg);\n const cNew =\n lNew === lOld && lNew === lMsg\n ? Math.max(cOld, cMsg) + 1\n : lNew === lOld\n ? cOld + 1\n : lNew === lMsg\n ? cMsg + 1\n : 0;\n\n // check the result for drift and counter overflow\n if (lNew - phys > config.maxDrift) {\n throw new Timestamp.ClockDriftError();\n }\n if (cNew > MAX_COUNTER) {\n throw new Timestamp.OverflowError();\n }\n\n // repack the logical time/counter\n clock.timestamp.setMillis(lNew);\n clock.timestamp.setCounter(cNew);\n\n return new Timestamp(\n clock.timestamp.millis(),\n clock.timestamp.counter(),\n clock.timestamp.node(),\n );\n }\n\n /**\n * zero/minimum timestamp\n */\n\n static zero = Timestamp.parse(\n '1970-01-01T00:00:00.000Z-0000-0000000000000000',\n )!;\n\n static since = (isoString: string) => isoString + '-0000-0000000000000000';\n\n /**\n * error classes\n */\n static DuplicateNodeError = class DuplicateNodeError extends Error {\n constructor(node: string) {\n super('duplicate node identifier ' + node);\n this.name = 'DuplicateNodeError';\n }\n };\n\n static ClockDriftError = class ClockDriftError extends Error {\n constructor(...args: unknown[]) {\n super(['maximum clock drift exceeded', ...args.map(String)].join(' '));\n this.name = 'ClockDriftError';\n }\n };\n\n static OverflowError = class OverflowError extends Error {\n constructor() {\n super('timestamp counter overflow');\n this.name = 'OverflowError';\n }\n };\n\n static InvalidError = class InvalidError extends Error {\n constructor(...args: unknown[]) {\n super(['timestamp is not valid'].concat(args.map(String)).join(' '));\n this.name = 'InvalidError';\n }\n };\n}\n\nclass MutableTimestamp extends Timestamp {\n static from(timestamp: Timestamp) {\n return new MutableTimestamp(\n timestamp.millis(),\n timestamp.counter(),\n timestamp.node(),\n );\n }\n\n setMillis(n: number) {\n this._state.millis = n;\n }\n\n setCounter(n: number) {\n this._state.counter = n;\n }\n\n setNode(n: string) {\n this._state.node = n;\n }\n}\n","// @generated by protoc-gen-es v2.11.0 with parameter \"target=ts\"\n// @generated from file sync.proto (syntax proto3)\n/* eslint-disable */\n\nimport type { Message as Message$1 } from '@bufbuild/protobuf';\nimport type { GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2';\nimport { fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2';\n\n/**\n * Describes the file sync.proto.\n */\nexport const file_sync: GenFile /*@__PURE__*/ = fileDesc(\n 'CgpzeW5jLnByb3RvIjoKDUVuY3J5cHRlZERhdGESCgoCaXYYASABKAwSDwoHYXV0aFRhZxgCIAEoDBIMCgRkYXRhGAMgASgMIkYKB01lc3NhZ2USDwoHZGF0YXNldBgBIAEoCRILCgNyb3cYAiABKAkSDgoGY29sdW1uGAMgASgJEg0KBXZhbHVlGAQgASgJIkoKD01lc3NhZ2VFbnZlbG9wZRIRCgl0aW1lc3RhbXAYASABKAkSEwoLaXNFbmNyeXB0ZWQYAiABKAgSDwoHY29udGVudBgDIAEoDCJ2CgtTeW5jUmVxdWVzdBIiCghtZXNzYWdlcxgBIAMoCzIQLk1lc3NhZ2VFbnZlbG9wZRIOCgZmaWxlSWQYAiABKAkSDwoHZ3JvdXBJZBgDIAEoCRINCgVrZXlJZBgFIAEoCRINCgVzaW5jZRgGIAEoCUoECAQQBSJCCgxTeW5jUmVzcG9uc2USIgoIbWVzc2FnZXMYASADKAsyEC5NZXNzYWdlRW52ZWxvcGUSDgoGbWVya2xlGAIgASgJYgZwcm90bzM',\n);\n\n/**\n * @generated from message EncryptedData\n */\nexport type EncryptedData = Message$1<'EncryptedData'> & {\n /**\n * @generated from field: bytes iv = 1;\n */\n iv: Uint8Array;\n\n /**\n * @generated from field: bytes authTag = 2;\n */\n authTag: Uint8Array;\n\n /**\n * @generated from field: bytes data = 3;\n */\n data: Uint8Array;\n};\n\n/**\n * Describes the message EncryptedData.\n * Use `create(EncryptedDataSchema)` to create a new message.\n */\nexport const EncryptedDataSchema: GenMessage<EncryptedData> /*@__PURE__*/ =\n messageDesc(file_sync, 0);\n\n/**\n * @generated from message Message\n */\nexport type Message = Message$1<'Message'> & {\n /**\n * @generated from field: string dataset = 1;\n */\n dataset: string;\n\n /**\n * @generated from field: string row = 2;\n */\n row: string;\n\n /**\n * @generated from field: string column = 3;\n */\n column: string;\n\n /**\n * @generated from field: string value = 4;\n */\n value: string;\n};\n\n/**\n * Describes the message Message.\n * Use `create(MessageSchema)` to create a new message.\n */\nexport const MessageSchema: GenMessage<Message> /*@__PURE__*/ = messageDesc(\n file_sync,\n 1,\n);\n\n/**\n * @generated from message MessageEnvelope\n */\nexport type MessageEnvelope = Message$1<'MessageEnvelope'> & {\n /**\n * @generated from field: string timestamp = 1;\n */\n timestamp: string;\n\n /**\n * @generated from field: bool isEncrypted = 2;\n */\n isEncrypted: boolean;\n\n /**\n * @generated from field: bytes content = 3;\n */\n content: Uint8Array;\n};\n\n/**\n * Describes the message MessageEnvelope.\n * Use `create(MessageEnvelopeSchema)` to create a new message.\n */\nexport const MessageEnvelopeSchema: GenMessage<MessageEnvelope> /*@__PURE__*/ =\n messageDesc(file_sync, 2);\n\n/**\n * @generated from message SyncRequest\n */\nexport type SyncRequest = Message$1<'SyncRequest'> & {\n /**\n * @generated from field: repeated MessageEnvelope messages = 1;\n */\n messages: MessageEnvelope[];\n\n /**\n * @generated from field: string fileId = 2;\n */\n fileId: string;\n\n /**\n * @generated from field: string groupId = 3;\n */\n groupId: string;\n\n /**\n * @generated from field: string keyId = 5;\n */\n keyId: string;\n\n /**\n * @generated from field: string since = 6;\n */\n since: string;\n};\n\n/**\n * Describes the message SyncRequest.\n * Use `create(SyncRequestSchema)` to create a new message.\n */\nexport const SyncRequestSchema: GenMessage<SyncRequest> /*@__PURE__*/ =\n messageDesc(file_sync, 3);\n\n/**\n * @generated from message SyncResponse\n */\nexport type SyncResponse = Message$1<'SyncResponse'> & {\n /**\n * @generated from field: repeated MessageEnvelope messages = 1;\n */\n messages: MessageEnvelope[];\n\n /**\n * @generated from field: string merkle = 2;\n */\n merkle: string;\n};\n\n/**\n * Describes the message SyncResponse.\n * Use `create(SyncResponseSchema)` to create a new message.\n */\nexport const SyncResponseSchema: GenMessage<SyncResponse> /*@__PURE__*/ =\n messageDesc(file_sync, 4);\n","export class FileNotFound extends Error {\n constructor(params = {}) {\n super(\"File does not exist or you don't have access to it\");\n this.details = params;\n }\n}\n\nexport class GenericFileError extends Error {\n constructor(message, params = {}) {\n super(message);\n this.details = params;\n }\n}\n","import { join, resolve } from 'node:path';\n\nimport { config } from '#load-config';\n\nimport type { BrandedId } from './types';\n\nconst ID_REGEX = /^[a-zA-Z0-9_-]+$/;\n\nexport type FileId = BrandedId<'file'>;\nexport type GroupId = BrandedId<'group'>;\n\nexport function isValidFileId(id: string): id is FileId {\n return ID_REGEX.test(id);\n}\n\nexport function isValidGroupId(id: string): id is GroupId {\n return ID_REGEX.test(id);\n}\n\nexport function getPathForUserFile(fileId: FileId) {\n return join(resolve(config.get('userFiles')), `file-${fileId}.blob`);\n}\n\nexport function getPathForGroupFile(groupId: GroupId) {\n return join(resolve(config.get('userFiles')), `group-${groupId}.sqlite`);\n}\n","import { getAccountDb, isAdmin } from '#account-db';\nimport { FileNotFound, GenericFileError } from '#app-sync/errors';\nimport type { WrappedDatabase } from '#db';\nimport { isValidFileId, isValidGroupId } from '#util/paths';\nimport type { FileId, GroupId } from '#util/paths';\n\nclass FileBase {\n name: string | null | undefined;\n groupId: GroupId | null | undefined;\n encryptSalt: string | null | undefined;\n encryptTest: string | null | undefined;\n encryptKeyId: string | null | undefined;\n encryptMeta: string | null | undefined;\n syncVersion: string | null | undefined;\n deleted: boolean;\n owner: string | null | undefined;\n\n constructor(\n name: string | null | undefined,\n groupId: GroupId | null | undefined,\n encryptSalt: string | null | undefined,\n encryptTest: string | null | undefined,\n encryptKeyId: string | null | undefined,\n encryptMeta: string | null | undefined,\n syncVersion: string | null | undefined,\n deleted: number | boolean | undefined,\n owner: string | null | undefined,\n ) {\n this.name = name;\n this.groupId = groupId;\n this.encryptSalt = encryptSalt;\n this.encryptTest = encryptTest;\n this.encryptKeyId = encryptKeyId;\n this.encryptMeta = encryptMeta;\n this.syncVersion = syncVersion;\n this.deleted = typeof deleted === 'boolean' ? deleted : Boolean(deleted);\n this.owner = owner;\n }\n}\n\ntype FileConstructorArgs = {\n id: FileId;\n name: string | null;\n groupId: GroupId | null;\n encryptSalt?: string | null;\n encryptTest?: string | null;\n encryptKeyId?: string | null;\n encryptMeta: string | null;\n syncVersion: string | null;\n deleted?: number | boolean;\n owner: string | null;\n};\n\nclass File extends FileBase {\n id: FileId;\n\n constructor({\n id,\n name = null,\n groupId = null,\n encryptSalt = null,\n encryptTest = null,\n encryptKeyId = null,\n encryptMeta = null,\n syncVersion = null,\n deleted = false,\n owner = null,\n }: FileConstructorArgs) {\n super(\n name,\n groupId,\n encryptSalt,\n encryptTest,\n encryptKeyId,\n encryptMeta,\n syncVersion,\n deleted,\n owner,\n );\n this.id = id;\n }\n}\n\n/**\n * Represents a file update. Will only update the fields that are defined.\n * @class\n * @extends FileBase\n */\nclass FileUpdate extends FileBase {\n constructor({\n name = undefined,\n groupId = undefined,\n encryptSalt = undefined,\n encryptTest = undefined,\n encryptKeyId = undefined,\n encryptMeta = undefined,\n syncVersion = undefined,\n deleted = undefined,\n owner = undefined,\n }: Partial<FileConstructorArgs>) {\n super(\n name,\n groupId,\n encryptSalt,\n encryptTest,\n encryptKeyId,\n encryptMeta,\n syncVersion,\n deleted,\n owner,\n );\n }\n}\n\nconst boolToInt = (bool: boolean) => {\n return bool ? 1 : 0;\n};\n\ntype RawFile = {\n id: string;\n group_id: string | null;\n sync_version: string | null;\n name: string | null;\n encrypt_meta: string | null;\n encrypt_salt: string | null;\n encrypt_test: string | null;\n encrypt_keyid: string | null;\n deleted: number;\n owner: string | null;\n};\n\nclass FilesService {\n accountDb: WrappedDatabase;\n\n constructor(accountDb: WrappedDatabase) {\n this.accountDb = accountDb;\n }\n\n get(fileId: FileId) {\n const rawFile = this.getRaw(fileId);\n if (!rawFile || (rawFile && rawFile.deleted)) {\n throw new FileNotFound();\n }\n\n return this.validate(rawFile);\n }\n\n set(file: File) {\n const deletedInt = boolToInt(file.deleted);\n this.accountDb.mutate(\n 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta, encrypt_salt, encrypt_test, encrypt_keyid, deleted, owner) VALUES (?, ?, ?, ?, ?, ?, ?, ? ,?, ?)',\n [\n file.id,\n file.groupId,\n file.syncVersion?.toString(),\n file.name,\n file.encryptMeta,\n file.encryptSalt,\n file.encryptTest,\n file.encryptKeyId,\n deletedInt,\n file.owner,\n ],\n );\n }\n\n find({ userId, limit = 1000 }: { userId: string; limit?: number }) {\n const canSeeAll = isAdmin(userId);\n\n return (\n canSeeAll\n ? this.accountDb.all('SELECT * FROM files WHERE deleted = 0 LIMIT ?', [\n limit,\n ])\n : this.accountDb.all(\n `SELECT files.*\n FROM files\n WHERE files.owner = ? and deleted = 0\n UNION\n SELECT files.*\n FROM files\n JOIN user_access\n ON user_access.file_id = files.id\n AND user_access.user_id = ?\n WHERE files.deleted = 0 LIMIT ?`,\n [userId, userId, limit],\n )\n ).map((item: RawFile) => this.validate(item));\n }\n\n findUsersWithAccess(fileId: FileId) {\n const userAccess =\n this.accountDb.all(\n `SELECT UA.user_id as userId, users.display_name displayName, users.user_name userName\n FROM files\n JOIN user_access UA ON UA.file_id = files.id\n JOIN users on users.id = UA.user_id\n WHERE files.id = ?\n UNION ALL\n SELECT users.id, users.display_name, users.user_name\n FROM files\n JOIN users on users.id = files.owner\n WHERE files.id = ?\n `,\n [fileId, fileId],\n ) || [];\n\n return userAccess;\n }\n\n update(id: FileId, fileUpdate: Partial<File>) {\n let query = 'UPDATE files SET';\n const params = [];\n const updates = [];\n\n if (fileUpdate.name !== undefined) {\n updates.push('name = ?');\n params.push(fileUpdate.name);\n }\n if (fileUpdate.groupId !== undefined) {\n updates.push('group_id = ?');\n params.push(fileUpdate.groupId);\n }\n if (fileUpdate.encryptSalt !== undefined) {\n updates.push('encrypt_salt = ?');\n params.push(fileUpdate.encryptSalt);\n }\n if (fileUpdate.encryptTest !== undefined) {\n updates.push('encrypt_test = ?');\n params.push(fileUpdate.encryptTest);\n }\n if (fileUpdate.encryptKeyId !== undefined) {\n updates.push('encrypt_keyid = ?');\n params.push(fileUpdate.encryptKeyId);\n }\n if (fileUpdate.encryptMeta !== undefined) {\n updates.push('encrypt_meta = ?');\n params.push(fileUpdate.encryptMeta);\n }\n if (fileUpdate.syncVersion !== undefined) {\n updates.push('sync_version = ?');\n params.push(fileUpdate.syncVersion);\n }\n if (fileUpdate.deleted !== undefined) {\n updates.push('deleted = ?');\n params.push(boolToInt(fileUpdate.deleted));\n }\n\n if (updates.length > 0) {\n query += ' ' + updates.join(', ') + ' WHERE id = ?';\n params.push(id);\n\n const res = this.accountDb.mutate(query, params);\n\n if (res.changes !== 1) {\n throw new GenericFileError('Could not update File', { id });\n }\n }\n\n // Return the modified object\n const rawFile = this.getRaw(id);\n if (!rawFile) {\n throw new GenericFileError('File not found', { id });\n }\n return this.validate(rawFile);\n }\n\n getRaw(fileId: FileId): RawFile | null {\n return this.accountDb.first(`SELECT * FROM files WHERE id = ?`, [fileId]);\n }\n\n validate(rawFile: RawFile) {\n const fileId = rawFile.id;\n if (!isValidFileId(fileId)) {\n throw new GenericFileError('Invalid file ID', { fileId });\n }\n\n let groupId: GroupId | null = null;\n if (rawFile.group_id !== null) {\n if (!isValidGroupId(rawFile.group_id)) {\n throw new GenericFileError('Invalid group ID', {\n groupId: rawFile.group_id,\n });\n }\n groupId = rawFile.group_id;\n }\n\n return new File({\n id: fileId,\n name: rawFile.name,\n groupId,\n encryptSalt: rawFile.encrypt_salt,\n encryptTest: rawFile.encrypt_test,\n encryptKeyId: rawFile.encrypt_keyid,\n encryptMeta: rawFile.encrypt_meta,\n syncVersion: rawFile.sync_version,\n deleted: Boolean(rawFile.deleted),\n owner: rawFile.owner,\n });\n }\n}\n\nconst filesService = new FilesService(getAccountDb());\n\nexport { filesService, FilesService, File, FileUpdate };\n","// This is a version representing the internal format of sync\n// messages. When this changes, all sync files need to be reset. We\n// will check this version when syncing and notify the user if they\n// need to reset.\nconst SYNC_FORMAT_VERSION = 2;\n\nconst validateSyncedFile = (groupId, keyId, currentFile) => {\n if (\n currentFile.syncVersion == null ||\n currentFile.syncVersion < SYNC_FORMAT_VERSION\n ) {\n return 'file-old-version';\n }\n\n // When resetting sync state, something went wrong. There is no\n // group id and it's awaiting a file to be uploaded.\n if (currentFile.groupId == null) {\n return 'file-needs-upload';\n }\n\n // Check to make sure the uploaded file is valid and has been\n // encrypted with the same key it is registered with (this might\n // be wrong if there was an error during the key creation\n // process)\n const uploadedKeyId = currentFile.encryptMeta\n ? JSON.parse(currentFile.encryptMeta).keyId\n : null;\n if (uploadedKeyId !== currentFile.encryptKeyId) {\n return 'file-key-mismatch';\n }\n\n // The changes being synced are part of an old group, which\n // means the file has been reset. User needs to re-download.\n if (groupId !== currentFile.groupId) {\n return 'file-has-reset';\n }\n\n // The data is encrypted with a different key which is\n // unacceptable. We can't accept these changes. Reject them and\n // tell the user that they need to generate the correct key\n // (which necessitates a sync reset so they need to re-download).\n if (keyId !== currentFile.encryptKeyId) {\n return 'file-has-new-key';\n }\n\n return null;\n};\n\nconst validateUploadedFile = (groupId, keyId, currentFile) => {\n if (!currentFile) {\n // File is new, so no need to validate\n return null;\n }\n // The uploading file is part of an old group, so reject\n // it. All of its internal sync state is invalid because its\n // old. The sync state has been reset, so user needs to\n // either reset again or download from the current group.\n if (groupId !== currentFile.groupId) {\n return 'file-has-reset';\n }\n\n // The key that the file is encrypted with is different than\n // the current registered key. All data must always be\n // encrypted with the registered key for consistency. Key\n // changes always necessitate a sync reset, which means this\n // upload is trying to overwrite another reset. That might\n // be be fine, but since we definitely cannot accept a file\n // encrypted with the wrong key, we bail and suggest the\n // user download the latest file.\n if (keyId !== currentFile.encryptKeyId) {\n return 'file-has-new-key';\n }\n\n return null;\n};\n\nexport { validateSyncedFile, validateUploadedFile };\n","export default \"\\nCREATE TABLE messages_binary\\n (timestamp TEXT PRIMARY KEY,\\n is_encrypted BOOLEAN,\\n content bytea);\\n\\nCREATE TABLE messages_merkles\\n (id INTEGER PRIMARY KEY,\\n merkle TEXT);\\n\"","import { existsSync } from 'node:fs';\n\nimport {\n create,\n merkle,\n MessageEnvelopeSchema,\n Timestamp,\n} from '@actual-app/crdt';\n\nimport { openDatabase } from './db';\nimport messagesSql from './sql/messages.sql?raw';\nimport { getPathForGroupFile } from './util/paths';\n\nfunction getGroupDb(groupId) {\n const path = getPathForGroupFile(groupId);\n const needsInit = !existsSync(path);\n\n const db = openDatabase(path);\n\n if (needsInit) {\n db.exec(messagesSql);\n }\n\n return db;\n}\n\nfunction addMessages(db, messages) {\n let returnValue;\n db.transaction(() => {\n let trie = getMerkle(db);\n\n if (messages.length > 0) {\n for (const msg of messages) {\n const info = db.mutate(\n `INSERT OR IGNORE INTO messages_binary (timestamp, is_encrypted, content)\n VALUES (?, ?, ?)`,\n [msg.timestamp, msg.isEncrypted ? 1 : 0, Buffer.from(msg.content)],\n );\n\n if (info.changes > 0) {\n trie = merkle.insert(trie, Timestamp.parse(msg.timestamp));\n }\n }\n }\n\n trie = merkle.prune(trie);\n\n db.mutate(\n 'INSERT INTO messages_merkles (id, merkle) VALUES (1, ?) ON CONFLICT (id) DO UPDATE SET merkle = ?',\n [JSON.stringify(trie), JSON.stringify(trie)],\n );\n\n returnValue = trie;\n });\n\n return returnValue;\n}\n\nfunction getMerkle(db) {\n const rows = db.all('SELECT * FROM messages_merkles');\n\n if (rows.length > 0) {\n return JSON.parse(rows[0].merkle);\n } else {\n // No merkle trie exists yet (first sync of the app), so create a\n // default one.\n return {};\n }\n}\n\nexport function sync(messages, since, groupId) {\n const db = getGroupDb(groupId);\n const newMessages = db.all(\n `SELECT * FROM messages_binary\n WHERE timestamp > ?\n ORDER BY timestamp`,\n [since],\n );\n\n const trie = addMessages(db, messages);\n\n db.close();\n\n return {\n trie,\n newMessages: newMessages.map(msg =>\n create(MessageEnvelopeSchema, {\n timestamp: msg.timestamp,\n isEncrypted: msg.is_encrypted === 1,\n content: msg.content,\n }),\n ),\n };\n}\n","// @ts-strict-ignore\nimport { Buffer } from 'node:buffer';\nimport fs from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\nimport {\n create,\n fromBinary,\n SyncRequestSchema,\n SyncResponseSchema,\n toBinary,\n} from '@actual-app/crdt';\nimport type { Request, Response } from 'express';\nimport express from 'express';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { getAccountDb, isAdmin } from './account-db';\nimport { FileNotFound } from './app-sync/errors';\nimport {\n File,\n FilesService,\n FileUpdate,\n} from './app-sync/services/files-service';\nimport {\n validateSyncedFile,\n validateUploadedFile,\n} from './app-sync/validation';\nimport { config } from './load-config';\nimport * as UserService from './services/user-service';\nimport * as simpleSync from './sync-simple';\nimport {\n errorMiddleware,\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from './util/middlewares';\nimport {\n getPathForGroupFile,\n getPathForUserFile,\n isValidFileId,\n isValidGroupId,\n} from './util/paths';\nimport type { GroupId } from './util/paths';\n\nconst app = express();\napp.use(validateSessionMiddleware);\napp.use(errorMiddleware);\napp.use(requestLoggerMiddleware);\napp.use(\n express.raw({\n type: 'application/actual-sync',\n limit: `${config.get('upload.fileSizeSyncLimitMB')}mb`,\n }),\n);\napp.use(\n express.raw({\n type: 'application/encrypted-file',\n limit: `${config.get('upload.syncEncryptedFileSizeLimitMB')}mb`,\n }),\n);\napp.use(express.json({ limit: `${config.get('upload.fileSizeLimitMB')}mb` }));\n\nexport { app as handlers };\n\nconst OK_RESPONSE = { status: 'ok' };\n\nfunction boolToInt(deleted: boolean) {\n return deleted ? 1 : 0;\n}\n\nfunction generateGroupId(): GroupId {\n const id = uuidv4();\n if (!isValidGroupId(id)) {\n throw new TypeError('UUID format no longer matches expected format');\n }\n return id;\n}\n\nfunction extractSingleHeader(\n req: Request,\n res: Response,\n key: string,\n): string | null {\n const value = req.headers[key];\n if (!value) {\n return null;\n }\n if (typeof value !== 'string') {\n res.status(400).send('Duplicate headers encountered for key ' + key);\n return null;\n }\n return value;\n}\n\nconst verifyFileExists = (\n fileId: unknown,\n filesService: FilesService,\n res: Response,\n errorObject: string | Record<string, unknown>,\n) => {\n if (typeof fileId !== 'string' || !isValidFileId(fileId)) {\n res.status(400).send('invalid fileId');\n return;\n }\n\n try {\n return filesService.get(fileId);\n } catch (e) {\n if (e instanceof FileNotFound) {\n //FIXME: error code should be 404. Need to make sure frontend is ok with it.\n //TODO: put this into a middleware that checks if FileNotFound is thrown and returns 404 and same error message\n // for every FileNotFound error\n res.status(400).send(errorObject);\n return;\n }\n throw e;\n }\n};\n\nfunction requireFileOwner(file: File, userId: string) {\n const isOwner = file.owner === userId;\n const isServerAdmin = isAdmin(userId);\n if (isOwner || isServerAdmin) {\n return null;\n }\n return 'file-access-not-allowed';\n}\n\nfunction requireFileAccess(file: File, userId: string) {\n if (requireFileOwner(file, userId) === null) {\n return null;\n }\n if (UserService.countUserAccess(file.id, userId) > 0) {\n return null;\n }\n return 'file-access-not-allowed';\n}\n\napp.post('/sync', async (req, res): Promise<void> => {\n let requestPb;\n try {\n requestPb = fromBinary(SyncRequestSchema, req.body);\n } catch (e) {\n console.log('Error parsing sync request', e);\n res.status(500);\n res.send({ status: 'error', reason: 'internal-error' });\n return;\n }\n\n const fileId = requestPb.fileId || null;\n const groupId = requestPb.groupId || null;\n const keyId = requestPb.keyId || null;\n const since = requestPb.since || null;\n const messages = requestPb.messages;\n\n if (!since) {\n res.status(422).send({\n details: 'since-required',\n reason: 'unprocessable-entity',\n status: 'error',\n });\n return;\n }\n\n const filesService = new FilesService(getAccountDb());\n\n const currentFile = verifyFileExists(\n fileId,\n filesService,\n res,\n 'file-not-found',\n );\n\n if (!currentFile) {\n return;\n }\n\n const fileAccessError = requireFileAccess(currentFile, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n const errorMessage = validateSyncedFile(groupId, keyId, currentFile);\n if (errorMessage) {\n res.status(400);\n res.send(errorMessage);\n return;\n }\n\n const { trie, newMessages } = simpleSync.sync(messages, since, groupId);\n\n const responsePb = create(SyncResponseSchema, {\n merkle: JSON.stringify(trie),\n messages: newMessages,\n });\n\n res.set('Content-Type', 'application/actual-sync');\n res.set('X-ACTUAL-SYNC-METHOD', 'simple');\n res.send(Buffer.from(toBinary(SyncResponseSchema, responsePb)));\n});\n\napp.post('/user-get-key', (req, res) => {\n if (!res.locals) return;\n\n const { fileId } = req.body || {};\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(fileId, filesService, res, 'file-not-found');\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n res.send({\n status: 'ok',\n data: {\n id: file.encryptKeyId,\n salt: file.encryptSalt,\n test: file.encryptTest,\n },\n });\n});\n\napp.post('/user-create-key', (req, res) => {\n const { fileId, keyId, keySalt, testContent } = req.body || {};\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(fileId, filesService, res, 'file-not-found');\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileOwner(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n filesService.update(\n file.id,\n new FileUpdate({\n encryptSalt: keySalt,\n encryptKeyId: keyId,\n encryptTest: testContent,\n }),\n );\n\n res.send(OK_RESPONSE);\n});\n\napp.post('/reset-user-file', async (req, res) => {\n const { fileId } = req.body || {};\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(\n fileId,\n filesService,\n res,\n 'User or file not found',\n );\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileOwner(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n const groupId = file.groupId;\n\n filesService.update(file.id, new FileUpdate({ groupId: null }));\n\n if (groupId) {\n try {\n await fs.unlink(getPathForGroupFile(groupId));\n } catch {\n console.log(`Unable to delete sync data for group \"${groupId}\"`);\n }\n }\n\n res.send(OK_RESPONSE);\n});\n\napp.post('/upload-user-file', async (req, res) => {\n if (typeof req.headers['x-actual-name'] !== 'string') {\n // FIXME: Not sure how this cannot be a string when the header is\n // set.\n res.status(400).send('single x-actual-name is required');\n return;\n }\n\n const name = decodeURIComponent(req.headers['x-actual-name']);\n const fileId = req.headers['x-actual-file-id'];\n\n if (!fileId || typeof fileId !== 'string') {\n res.status(400).send('fileId is required');\n return;\n }\n if (!isValidFileId(fileId)) {\n res.status(400).send('invalid fileId');\n return;\n }\n\n let groupId = req.headers['x-actual-group-id'] || null;\n const encryptMeta = extractSingleHeader(req, res, 'x-actual-encrypt-meta');\n if (res.headersSent) return;\n const syncFormatVersion = extractSingleHeader(req, res, 'x-actual-format');\n if (res.headersSent) return;\n\n if (!!groupId && (typeof groupId !== 'string' || !isValidGroupId(groupId))) {\n res.status(400).send('invalid groupId');\n return;\n }\n\n const keyId =\n encryptMeta && typeof encryptMeta === 'string'\n ? JSON.parse(encryptMeta).keyId\n : null;\n\n const filesService = new FilesService(getAccountDb());\n let currentFile;\n\n try {\n currentFile = filesService.get(fileId);\n } catch (e) {\n if (e instanceof FileNotFound) {\n currentFile = null;\n } else {\n throw e;\n }\n }\n\n const fileAccessError = currentFile\n ? requireFileAccess(currentFile, res.locals.user_id)\n : null;\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n const errorMessage = validateUploadedFile(groupId, keyId, currentFile);\n if (errorMessage) {\n res.status(400).send(errorMessage);\n return;\n }\n\n try {\n await fs.writeFile(getPathForUserFile(fileId), req.body);\n } catch (err) {\n console.log('Error writing file', err);\n res.status(500).send({ status: 'error' });\n return;\n }\n\n if (!currentFile) {\n // it's new\n const newGroupId = generateGroupId();\n groupId = newGroupId;\n filesService.set(\n new File({\n id: fileId,\n groupId: newGroupId,\n syncVersion: syncFormatVersion,\n name,\n encryptMeta,\n owner:\n res.locals.user_id ||\n (() => {\n throw new Error('User ID is required for file creation');\n })(),\n }),\n );\n\n res.send({ status: 'ok', groupId });\n return;\n }\n\n if (!groupId) {\n // sync state was reset, create new group\n const newGroupId = generateGroupId();\n groupId = newGroupId;\n filesService.update(fileId, new FileUpdate({ groupId: newGroupId }));\n }\n\n // Regardless, update some properties\n filesService.update(\n fileId,\n new FileUpdate({\n syncVersion: syncFormatVersion,\n encryptMeta,\n name,\n }),\n );\n\n res.send({ status: 'ok', groupId });\n});\n\napp.get('/download-user-file', async (req, res) => {\n const fileId = req.headers['x-actual-file-id'];\n if (typeof fileId !== 'string') {\n // FIXME: Not sure how this cannot be a string when the header is\n // set.\n res.status(400).send('Single file ID is required');\n return;\n }\n if (!isValidFileId(fileId)) {\n res.status(400).send('invalid fileId');\n return;\n }\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(\n fileId,\n filesService,\n res,\n 'User or file not found',\n );\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n const path = getPathForUserFile(fileId);\n\n if (!path.startsWith(resolve(config.get('userFiles')))) {\n //Ensure the user doesn't try to access files outside of the user files directory\n res.status(403).send('Access denied');\n return;\n }\n\n res.setHeader('Content-Disposition', `attachment;filename=${fileId}`);\n res.sendFile(path, { dotfiles: 'allow' });\n});\n\napp.post('/update-user-filename', (req, res) => {\n const { fileId, name } = req.body || {};\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(fileId, filesService, res, 'file-not-found');\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n filesService.update(file.id, new FileUpdate({ name }));\n res.send(OK_RESPONSE);\n});\n\napp.get('/list-user-files', (req, res) => {\n const fileService = new FilesService(getAccountDb());\n const rows = fileService.find({ userId: res.locals.user_id });\n res.send({\n status: 'ok',\n data: rows.map(row => ({\n deleted: boolToInt(row.deleted),\n fileId: row.id,\n groupId: row.groupId,\n name: row.name,\n encryptKeyId: row.encryptKeyId,\n owner: row.owner,\n usersWithAccess: fileService.findUsersWithAccess(row.id).map(access => ({\n ...access,\n owner: access.userId === row.owner,\n })),\n })),\n });\n});\n\napp.get('/get-user-file-info', (req, res) => {\n const fileId = req.headers['x-actual-file-id'];\n\n // TODO: Return 422 if fileId is not provided. Need to make sure frontend can handle it\n // if (!fileId) {\n // return res.status(422).send({\n // details: 'fileId-required',\n // reason: 'unprocessable-entity',\n // status: 'error',\n // });\n // }\n\n const fileService = new FilesService(getAccountDb());\n\n const file = verifyFileExists(fileId, fileService, res, {\n status: 'error',\n reason: 'file-not-found',\n });\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n res.send({\n status: 'ok',\n data: {\n deleted: boolToInt(file.deleted), // FIXME: convert to boolean, make sure it works in the frontend\n fileId: file.id,\n groupId: file.groupId,\n name: file.name,\n encryptMeta: file.encryptMeta ? JSON.parse(file.encryptMeta) : null,\n usersWithAccess: fileService.findUsersWithAccess(file.id).map(access => ({\n ...access,\n owner: access.userId === file.owner,\n })),\n },\n });\n});\n\napp.post('/delete-user-file', (req, res) => {\n const { fileId } = req.body || {};\n\n if (!fileId) {\n res.status(422).send({\n details: 'fileId-required',\n reason: 'unprocessable-entity',\n status: 'error',\n });\n return;\n }\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(fileId, filesService, res, 'file-not-found');\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileOwner(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n filesService.update(file.id, new FileUpdate({ deleted: true }));\n\n res.send(OK_RESPONSE);\n});\n","import fs, { readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport cors from 'cors';\nimport express from 'express';\nimport rateLimit from 'express-rate-limit';\n\nimport { bootstrap } from './account-db';\nimport * as accountApp from './app-account';\nimport * as adminApp from './app-admin';\nimport * as corsApp from './app-cors-proxy';\nimport * as enableBankingApp from './app-enablebanking/app-enablebanking';\nimport * as goCardlessApp from './app-gocardless/app-gocardless';\nimport * as openidApp from './app-openid';\nimport * as pluggai from './app-pluggyai/app-pluggyai';\nimport * as secretApp from './app-secrets';\nimport * as simpleFinApp from './app-simplefin/app-simplefin';\nimport * as syncApp from './app-sync';\nimport { config } from './load-config';\n\nconst app = express();\n\nprocess.on('unhandledRejection', reason => {\n console.log('Rejection:', reason);\n});\n\napp.disable('x-powered-by');\napp.use(cors());\napp.set('trust proxy', config.get('trustedProxies'));\nif (process.env.NODE_ENV !== 'development') {\n app.use(\n rateLimit({\n windowMs: 60 * 1000,\n max: 500,\n legacyHeaders: false,\n standardHeaders: true,\n }),\n );\n}\n\napp.use(express.json({ limit: `${config.get('upload.fileSizeLimitMB')}mb` }));\n\napp.use(\n express.raw({\n type: 'application/actual-sync',\n limit: `${config.get('upload.fileSizeSyncLimitMB')}mb`,\n }),\n);\n\napp.use(\n express.raw({\n type: 'application/encrypted-file',\n limit: `${config.get('upload.syncEncryptedFileSizeLimitMB')}mb`,\n }),\n);\n\napp.use('/sync', syncApp.handlers);\napp.use('/account', accountApp.handlers);\napp.use('/gocardless', goCardlessApp.handlers);\napp.use('/simplefin', simpleFinApp.handlers);\napp.use('/pluggyai', pluggai.handlers);\napp.use('/enablebanking', enableBankingApp.handlers);\napp.use('/secret', secretApp.handlers);\n\nif (config.get('corsProxy.enabled')) {\n app.use('/cors-proxy', corsApp.handlers);\n}\n\napp.use('/admin', adminApp.handlers);\napp.use('/openid', openidApp.handlers);\n\napp.get('/mode', (req, res) => {\n res.send(config.get('mode'));\n});\n\napp.get('/info', (_req, res) => {\n function findPackageJson(startDir: string) {\n // find the nearest package.json file while traversing up the directory tree\n let currentPath = startDir;\n let directoriesSearched = 0;\n const pathRoot = resolve(currentPath, '/');\n try {\n while (currentPath !== pathRoot && directoriesSearched < 5) {\n const packageJsonPath = resolve(currentPath, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(\n readFileSync(packageJsonPath, 'utf-8'),\n );\n\n if (packageJson.name === '@actual-app/sync-server') {\n return packageJson;\n }\n }\n\n currentPath = resolve(join(currentPath, '..')); // Move up one directory\n directoriesSearched++;\n }\n } catch (error) {\n console.error('Error while searching for package.json:', error);\n }\n\n return null;\n }\n\n const dirname = resolve(fileURLToPath(import.meta.url), '../');\n const packageJson = findPackageJson(dirname);\n\n res.status(200).json({\n build: {\n name: packageJson?.name,\n description: packageJson?.description,\n version: packageJson?.version,\n },\n });\n});\n\napp.get('/health', (_req, res) => {\n res.status(200).json({ status: 'UP' });\n});\n\napp.get('/metrics', (_req, res) => {\n res.status(200).json({\n mem: process.memoryUsage(),\n uptime: process.uptime(),\n });\n});\n\n// The web frontend.\n// Dev mode proxies to Vite, which injects inline preamble scripts and uses\n// a websocket for HMR. Loosen script-src and connect-src accordingly.\n// `'unsafe-eval'` is required at runtime for the Electron app, so it is\n// kept in both branches.\nconst isDev = process.env.NODE_ENV === 'development';\nconst scriptSrc = isDev\n ? \"'self' 'unsafe-inline' 'unsafe-eval' blob:\"\n : \"'self' 'unsafe-eval' blob:\";\nconst connectSrc = isDev ? \"'self' ws: wss: http: https:\" : 'http: https:';\nconst csp = [\n \"default-src 'self' blob:\",\n \"img-src 'self' blob: data:\",\n `script-src ${scriptSrc}`,\n \"style-src 'self' 'unsafe-inline'\",\n \"font-src 'self' data:\",\n `connect-src ${connectSrc}`,\n].join('; ');\n\napp.use((req, res, next) => {\n res.set('Cross-Origin-Opener-Policy', 'same-origin');\n res.set('Cross-Origin-Embedder-Policy', 'require-corp');\n res.set('Content-Security-Policy', csp);\n next();\n});\nif (isDev) {\n console.log(\n 'Running in development mode - Proxying frontend routes to React Dev Server',\n );\n\n // Imported within Dev block to allow dev dependency in package.json (reduces package size in production)\n const httpProxyMiddleware = await import('http-proxy-middleware');\n\n app.use(\n httpProxyMiddleware.createProxyMiddleware({\n target: 'http://localhost:3001',\n changeOrigin: true,\n ws: true,\n }),\n );\n} else {\n console.log('Running in production mode - Serving static React app');\n\n app.use(express.static(config.get('webRoot'), { index: false }));\n app.get('/{*splat}', (req, res) =>\n res.sendFile('index.html', { root: config.get('webRoot') }),\n );\n}\n\nfunction parseHTTPSConfig(value: string) {\n if (value.startsWith('-----BEGIN')) {\n return value;\n }\n return fs.readFileSync(value);\n}\n\nfunction sendServerStartedMessage() {\n // Signify to any parent process that the server has started. Used in electron desktop app\n // oxlint-disable-next-line typescript/ban-ts-comment\n // @ts-ignore-error electron types\n process.parentPort?.postMessage({ type: 'server-started' });\n console.log(\n 'Listening on ' + config.get('hostname') + ':' + config.get('port') + '...',\n );\n}\n\nexport async function run() {\n const portVal = config.get('port');\n const port = typeof portVal === 'string' ? parseInt(portVal) : portVal;\n const hostname = config.get('hostname');\n const openIdConfig = config?.getProperties()?.openId;\n if (\n openIdConfig?.discoveryURL ||\n openIdConfig?.issuer?.authorization_endpoint\n ) {\n console.log('OpenID configuration found. Preparing server to use it');\n try {\n const result = await bootstrap({ openId: openIdConfig }, true);\n if ('error' in result && result.error) {\n console.log(result.error);\n } else {\n console.log('OpenID configured!');\n }\n } catch (err) {\n console.error(err);\n }\n }\n\n if (config.get('https.key') && config.get('https.cert')) {\n const https = await import('node:https');\n const httpsOptions = {\n ...config.get('https'),\n key: parseHTTPSConfig(config.get('https.key')),\n cert: parseHTTPSConfig(config.get('https.cert')),\n };\n https.createServer(httpsOptions, app).listen(port, hostname, () => {\n sendServerStartedMessage();\n });\n } else {\n app.listen(port, hostname, () => {\n sendServerStartedMessage();\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-CLRswo4E.js","names":["app","app","UserService.getOwnerCount","UserService.getAllUsers","UserService.validateRole","UserService.getUserByUsername","uuidv4","UserService.getUserById","UserService.getOwnerId","UserService.deleteUser","UserService.checkFilePermission","UserService.getFileById","UserService.getUserAccess","UserService.countUserAccess","UserService.deleteUserAccessByFileId","UserService.getAllUserAccess","app","config","#secretId","#secretKey","#token","#request","app","getDate","app","app","messagesSql","merkle.insert","merkle.prune"],"sources":["../../src/util/middlewares.ts","../../src/app-account.js","../../src/app-admin.js","../../src/app-cors-proxy.js","../../src/app-gocardless/util/handle-error.ts","../../src/services/secrets-service.js","../../src/app-enablebanking/utils/errors.ts","../../src/app-enablebanking/utils/jwt.ts","../../src/app-enablebanking/services/enablebanking-service.ts","../../src/app-enablebanking/app-enablebanking.ts","../../src/util/hash.ts","../../src/app-gocardless/errors.ts","../../src/app-gocardless/utils.ts","../../src/util/title/lower-case.js","../../src/util/title/specials.js","../../src/util/title/index.js","../../src/util/payee-name.ts","../../src/app-gocardless/banks/integration-bank.ts","../../src/app-gocardless/banks/abanca_caglesmm.ts","../../src/app-gocardless/banks/abnamro_abnanl2a.ts","../../src/app-gocardless/banks/american_express_aesudef1.ts","../../src/app-gocardless/banks/bancsabadell_bsabesbbb.ts","../../src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.ts","../../src/app-gocardless/banks/bankinter_bkbkesmm.ts","../../src/app-gocardless/banks/belfius_gkccbebb.ts","../../src/app-gocardless/banks/berliner_sparkasse_beladebexxx.ts","../../src/app-gocardless/banks/bnp_be_gebabebb.ts","../../src/app-gocardless/banks/boursobank_bousfrppxxx.ts","../../src/app-gocardless/banks/bper_retail_bpmoit22.ts","../../src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.ts","../../src/app-gocardless/banks/cbc_cregbebb.ts","../../src/app-gocardless/banks/cetelem_cetmptp1xxx.ts","../../src/app-gocardless/banks/util/escape-regexp.ts","../../src/app-gocardless/banks/commerzbank_cobadeff.ts","../../src/app-gocardless/banks/danskebank_privat.ts","../../src/app-gocardless/banks/direkt_heladef1822.ts","../../src/app-gocardless/banks/easybank_bawaatww.ts","../../src/app-gocardless/banks/entercard_swednokk.ts","../../src/app-gocardless/banks/fortuneo_ftnofrp1xxx.ts","../../src/app-gocardless/banks/hype_hyeeit22.ts","../../src/app-gocardless/banks/ing_ingbrobu.ts","../../src/app-gocardless/banks/ing_ingddeff.ts","../../src/app-gocardless/banks/ing_pl_ingbplpw.ts","../../src/app-gocardless/banks/isybank_itbbitmm.ts","../../src/app-gocardless/banks/kbc_kredbebb.ts","../../src/app-gocardless/banks/lhv_lhvbee22.ts","../../src/app-gocardless/banks/mbank_retail_brexplpw.ts","../../src/app-gocardless/banks/nationwide_naiagb21.ts","../../src/app-gocardless/banks/nbg_ethngraaxxx.ts","../../src/app-gocardless/banks/norwegian_xx_norwnok1.ts","../../src/app-gocardless/banks/raiffeisen_at_rzbaatww.ts","../../src/app-gocardless/banks/revolut_revolt21.ts","../../src/app-gocardless/banks/sandboxfinance_sfin0000.ts","../../src/app-gocardless/banks/seb_kort_bank_ab.ts","../../src/app-gocardless/banks/seb_privat.ts","../../src/app-gocardless/banks/sparnord_spnodk22.ts","../../src/app-gocardless/banks/spk_karlsruhe_karsde66.ts","../../src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.ts","../../src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.ts","../../src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.ts","../../src/app-gocardless/banks/ssk_munchen.ts","../../src/app-gocardless/banks/swedbank_habalv22.ts","../../src/app-gocardless/banks/virgin_nrnbgb22.ts","../../src/app-gocardless/bank-factory.ts","../../src/app-gocardless/services/gocardless-api.ts","../../src/app-gocardless/services/gocardless-service.ts","../../src/app-gocardless/app-gocardless.ts","../../src/app-openid.ts","../../src/app-pluggyai/pluggyai-service.js","../../src/app-pluggyai/app-pluggyai.js","../../src/app-secrets.js","../../src/app-simplefin/app-simplefin.js","../../../crdt/src/crdt/merkle.ts","../../../crdt/src/crdt/timestamp.ts","../../../crdt/src/proto/sync_pb.ts","../../src/app-sync/errors.js","../../src/util/paths.ts","../../src/app-sync/services/files-service.ts","../../src/app-sync/validation.js","../../src/sql/messages.sql?raw","../../src/sync-simple.js","../../src/app-sync.ts","../../src/app.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\nimport * as expressWinston from 'express-winston';\nimport * as winston from 'winston';\n\nimport { validateSession } from './validate-user';\n\nasync function errorMiddleware(\n err: Error,\n req: Request,\n res: Response,\n next: NextFunction,\n) {\n if (res.headersSent) {\n // If you call next() with an error after you have started writing the response\n // (for example, if you encounter an error while streaming the response\n // to the client), the Express default error handler closes\n // the connection and fails the request.\n\n // So when you add a custom error handler, you must delegate\n // to the default Express error handler, when the headers\n // have already been sent to the client\n // Source: https://expressjs.com/en/guide/error-handling.html\n return next(err);\n }\n\n console.log(`Error on endpoint %s`, {\n requestUrl: req.url,\n stacktrace: err.stack,\n });\n res.status(500).send({ status: 'error', reason: 'internal-error' });\n}\n\nconst validateSessionMiddleware = async (\n req: Request,\n res: Response,\n next: NextFunction,\n) => {\n const session = await validateSession(req, res);\n if (!session) {\n return;\n }\n\n res.locals = session;\n next();\n};\n\nconst requestLoggerMiddleware = expressWinston.logger({\n transports: [new winston.transports.Console()],\n format: winston.format.combine(\n ...(Object.prototype.hasOwnProperty.call(process.env, 'NO_COLOR')\n ? []\n : [winston.format.colorize()]),\n winston.format.timestamp(),\n winston.format.printf(args => {\n const { timestamp, level, meta } = args;\n const { res, req } = meta as { res: Response; req: Request };\n\n return `${String(timestamp)} ${String(level)}: ${req.method} ${res.statusCode} ${req.url}`;\n }),\n ),\n});\n\nexport { validateSessionMiddleware, errorMiddleware, requestLoggerMiddleware };\n","import express from 'express';\nimport rateLimit from 'express-rate-limit';\n\nimport {\n bootstrap,\n getActiveLoginMethod,\n getLoginMethod,\n getServerPrefs,\n getUserInfo,\n isAdmin,\n listLoginMethods,\n needsBootstrap,\n setServerPrefs,\n} from './account-db';\nimport { isValidRedirectUrl, loginWithOpenIdSetup } from './accounts/openid';\nimport { changePassword, loginWithPassword } from './accounts/password';\nimport { errorMiddleware, requestLoggerMiddleware } from './util/middlewares';\nimport { validateAuthHeader, validateSession } from './util/validate-user';\n\nconst app = express();\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(errorMiddleware);\napp.use(requestLoggerMiddleware);\n\nconst authRateLimiter = rateLimit({\n windowMs: 15 * 60 * 1000, // 15 minutes\n max: 5, // 5 attempts per window\n legacyHeaders: false,\n standardHeaders: true,\n skipSuccessfulRequests: true,\n message: { status: 'error', reason: 'too-many-requests' },\n});\n\nexport { app as handlers, authRateLimiter };\n\n// Non-authenticated endpoints:\n//\n// /needs-bootstrap\n// /boostrap (special endpoint for setting up the instance, cant call again)\n// /login\n\napp.get('/needs-bootstrap', (req, res) => {\n const availableLoginMethods = listLoginMethods();\n res.send({\n status: 'ok',\n data: {\n bootstrapped: !needsBootstrap(),\n loginMethod:\n availableLoginMethods.length === 1\n ? availableLoginMethods[0].method\n : getLoginMethod(),\n availableLoginMethods,\n multiuser: getActiveLoginMethod() === 'openid',\n },\n });\n});\n\napp.post('/bootstrap', authRateLimiter, async (req, res) => {\n const boot = await bootstrap(req.body);\n\n if (boot?.error) {\n res.status(400).send({ status: 'error', reason: boot?.error });\n return;\n }\n res.send({ status: 'ok', data: boot });\n});\n\napp.get('/login-methods', (req, res) => {\n const methods = listLoginMethods();\n res.send({ status: 'ok', methods });\n});\n\napp.post('/login', authRateLimiter, async (req, res) => {\n const loginMethod = getLoginMethod(req);\n console.log('Logging in via ' + loginMethod);\n let tokenRes = null;\n switch (loginMethod) {\n case 'header': {\n const headerVal = req.get('x-actual-password') || '';\n const obfuscated =\n '*'.repeat(headerVal.length) || 'No password provided.';\n console.debug('HEADER VALUE: ' + obfuscated);\n if (headerVal === '') {\n res.send({ status: 'error', reason: 'invalid-header' });\n return;\n } else {\n if (validateAuthHeader(req)) {\n tokenRes = loginWithPassword(headerVal);\n } else {\n res.send({ status: 'error', reason: 'proxy-not-trusted' });\n return;\n }\n }\n break;\n }\n case 'openid': {\n if (!isValidRedirectUrl(req.body.returnUrl)) {\n res\n .status(400)\n .send({ status: 'error', reason: 'Invalid redirect URL' });\n return;\n }\n\n const { error, url } = await loginWithOpenIdSetup(\n req.body.returnUrl,\n req.body.password,\n );\n if (error) {\n res.status(400).send({ status: 'error', reason: error });\n return;\n }\n res.send({ status: 'ok', data: { returnUrl: url } });\n return;\n }\n\n default:\n tokenRes = loginWithPassword(req.body.password);\n break;\n }\n const { error, token } = tokenRes;\n\n if (error) {\n res.status(400).send({ status: 'error', reason: error });\n return;\n }\n\n res.send({ status: 'ok', data: { token } });\n});\n\napp.post('/change-password', (req, res) => {\n const session = validateSession(req, res);\n if (!session) return;\n\n if (!isAdmin(session.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n if (session.auth_method !== 'password') {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'password-auth-not-active',\n });\n return;\n }\n\n const { error } = changePassword(req.body.password);\n\n if (error) {\n res.status(400).send({ status: 'error', reason: error });\n return;\n }\n\n res.send({ status: 'ok', data: {} });\n});\n\napp.post('/server-prefs', (req, res) => {\n const session = validateSession(req, res);\n if (!session) return;\n\n if (!isAdmin(session.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { prefs } = req.body || {};\n\n if (!prefs || typeof prefs !== 'object') {\n res.status(400).send({ status: 'error', reason: 'invalid-prefs' });\n return;\n }\n\n setServerPrefs(prefs);\n\n res.send({ status: 'ok', data: {} });\n});\n\napp.get('/validate', (req, res) => {\n const session = validateSession(req, res);\n if (session) {\n const user = getUserInfo(session.user_id);\n if (!user) {\n res.status(400).send({ status: 'error', reason: 'User not found' });\n return;\n }\n\n res.send({\n status: 'ok',\n data: {\n validated: true,\n userName: user?.user_name,\n permission: user?.role,\n userId: session?.user_id,\n displayName: user?.display_name,\n loginMethod: session?.auth_method,\n prefs: getServerPrefs(),\n },\n });\n }\n});\n","import express from 'express';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { isAdmin } from './account-db';\nimport * as UserService from './services/user-service';\nimport {\n errorMiddleware,\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from './util/middlewares';\nimport { validateSession } from './util/validate-user';\n\nconst app = express();\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(requestLoggerMiddleware);\n\nexport { app as handlers };\n\napp.get('/owner-created/', (req, res) => {\n try {\n const ownerCount = UserService.getOwnerCount();\n res.json(ownerCount > 0);\n } catch {\n res.status(500).json({ error: 'Failed to retrieve owner count' });\n }\n});\n\napp.get('/users/', validateSessionMiddleware, (req, res) => {\n const users = UserService.getAllUsers();\n res.json(\n users.map(u => ({\n ...u,\n owner: u.owner === 1,\n enabled: u.enabled === 1,\n })),\n );\n});\n\napp.post('/users', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { userName, role, displayName, enabled } = req.body || {};\n\n if (!userName || !role) {\n res.status(400).send({\n status: 'error',\n reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,\n details: `${!userName ? 'Username' : 'Role'} cannot be empty`,\n });\n return;\n }\n\n const roleIdFromDb = UserService.validateRole(role);\n if (!roleIdFromDb) {\n res.status(400).send({\n status: 'error',\n reason: 'role-does-not-exists',\n details: 'Selected role does not exist',\n });\n return;\n }\n\n const userIdInDb = UserService.getUserByUsername(userName);\n if (userIdInDb) {\n res.status(400).send({\n status: 'error',\n reason: 'user-already-exists',\n details: `User ${userName} already exists`,\n });\n return;\n }\n\n const userId = uuidv4();\n UserService.insertUser(\n userId,\n userName,\n displayName || null,\n enabled ? 1 : 0,\n );\n\n res.status(200).send({ status: 'ok', data: { id: userId } });\n});\n\napp.patch('/users', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { id, userName, role, displayName, enabled } = req.body || {};\n\n if (!userName || !role) {\n res.status(400).send({\n status: 'error',\n reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,\n details: `${!userName ? 'Username' : 'Role'} cannot be empty`,\n });\n return;\n }\n\n const roleIdFromDb = UserService.validateRole(role);\n if (!roleIdFromDb) {\n res.status(400).send({\n status: 'error',\n reason: 'role-does-not-exists',\n details: 'Selected role does not exist',\n });\n return;\n }\n\n const userIdInDb = UserService.getUserById(id);\n if (!userIdInDb) {\n res.status(400).send({\n status: 'error',\n reason: 'cannot-find-user-to-update',\n details: `Cannot find user ${userName} to update`,\n });\n return;\n }\n\n UserService.updateUserWithRole(\n userIdInDb,\n userName,\n displayName || null,\n enabled ? 1 : 0,\n role,\n );\n\n res.status(200).send({ status: 'ok', data: { id: userIdInDb } });\n});\n\napp.delete('/users', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { ids } = req.body || {};\n let totalDeleted = 0;\n ids.forEach(item => {\n const ownerId = UserService.getOwnerId();\n\n if (item === ownerId) return;\n\n UserService.deleteUserAccess(item);\n UserService.transferAllFilesFromUser(ownerId, item);\n const usersDeleted = UserService.deleteUser(item);\n totalDeleted += usersDeleted;\n });\n\n if (ids.length === totalDeleted) {\n res\n .status(200)\n .send({ status: 'ok', data: { someDeletionsFailed: false } });\n } else {\n res.status(400).send({\n status: 'error',\n reason: 'not-all-deleted',\n details: '',\n });\n }\n});\n\napp.get('/access', validateSessionMiddleware, (req, res) => {\n const fileId = req.query.fileId;\n\n const { granted } = UserService.checkFilePermission(\n fileId,\n res.locals.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return false;\n }\n\n const fileIdInDb = UserService.getFileById(fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return false;\n }\n\n const accesses = UserService.getUserAccess(\n fileId,\n res.locals.user_id,\n isAdmin(res.locals.user_id),\n );\n\n res.json(accesses);\n});\n\napp.post('/access', (req, res) => {\n const userAccess = req.body || {};\n const session = validateSession(req, res);\n\n if (!session) return;\n\n const { granted } = UserService.checkFilePermission(\n userAccess.fileId,\n session.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(session.user_id)) {\n res.status(400).send({\n status: 'error',\n reason: 'file-denied',\n details: \"You don't have permissions over this file\",\n });\n return;\n }\n\n const fileIdInDb = UserService.getFileById(userAccess.fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return;\n }\n\n if (!userAccess.userId) {\n res.status(400).send({\n status: 'error',\n reason: 'user-cant-be-empty',\n details: 'User cannot be empty',\n });\n return;\n }\n\n if (UserService.countUserAccess(userAccess.fileId, userAccess.userId) > 0) {\n res.status(400).send({\n status: 'error',\n reason: 'user-already-have-access',\n details: 'User already have access',\n });\n return;\n }\n\n UserService.addUserAccess(userAccess.userId, userAccess.fileId);\n\n res.status(200).send({ status: 'ok', data: {} });\n});\n\napp.delete('/access', (req, res) => {\n const fileId = req.query.fileId;\n const session = validateSession(req, res);\n if (!session) return;\n\n const { granted } = UserService.checkFilePermission(\n fileId,\n session.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(session.user_id)) {\n res.status(400).send({\n status: 'error',\n reason: 'file-denied',\n details: \"You don't have permissions over this file\",\n });\n return;\n }\n\n const fileIdInDb = UserService.getFileById(fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return;\n }\n\n const { ids } = req.body || {};\n const totalDeleted = UserService.deleteUserAccessByFileId(ids, fileId);\n\n if (ids.length === totalDeleted) {\n res\n .status(200)\n .send({ status: 'ok', data: { someDeletionsFailed: false } });\n } else {\n res.status(400).send({\n status: 'error',\n reason: 'not-all-deleted',\n details: '',\n });\n }\n});\n\napp.get('/access/users', validateSessionMiddleware, async (req, res) => {\n const fileId = req.query.fileId;\n\n const { granted } = UserService.checkFilePermission(\n fileId,\n res.locals.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(res.locals.user_id)) {\n res.status(400).send({\n status: 'error',\n reason: 'file-denied',\n details: \"You don't have permissions over this file\",\n });\n return;\n }\n\n const fileIdInDb = UserService.getFileById(fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return;\n }\n\n const users = UserService.getAllUserAccess(fileId);\n res.json(users);\n});\n\napp.post(\n '/access/transfer-ownership/',\n validateSessionMiddleware,\n (req, res) => {\n const newUserOwner = req.body || {};\n\n const { granted } = UserService.checkFilePermission(\n newUserOwner.fileId,\n res.locals.user_id,\n ) || {\n granted: 0,\n };\n\n if (granted === 0 && !isAdmin(res.locals.user_id)) {\n res.status(400).send({\n status: 'error',\n reason: 'file-denied',\n details: \"You don't have permissions over this file\",\n });\n return;\n }\n\n const fileIdInDb = UserService.getFileById(newUserOwner.fileId);\n if (!fileIdInDb) {\n res.status(404).send({\n status: 'error',\n reason: 'invalid-file-id',\n details: 'File not found at server',\n });\n return;\n }\n\n if (!newUserOwner.newUserId) {\n res.status(400).send({\n status: 'error',\n reason: 'user-cant-be-empty',\n details: 'Username cannot be empty',\n });\n return;\n }\n\n const newUserIdFromDb = UserService.getUserById(newUserOwner.newUserId);\n if (newUserIdFromDb === 0) {\n res.status(400).send({\n status: 'error',\n reason: 'new-user-not-found',\n details: 'New user not found',\n });\n return;\n }\n\n UserService.updateFileOwner(newUserOwner.newUserId, newUserOwner.fileId);\n\n res.status(200).send({ status: 'ok', data: {} });\n },\n);\n\napp.use(errorMiddleware);\n","import express from 'express';\nimport rateLimit from 'express-rate-limit';\nimport ipaddr from 'ipaddr.js';\n\nimport { config } from './load-config';\nimport { requestLoggerMiddleware } from './util/middlewares';\nimport { validateSession } from './util/validate-user';\n\nconst app = express();\n\napp.use(express.json());\napp.use(requestLoggerMiddleware);\napp.use(\n rateLimit({\n windowMs: 60 * 1000,\n max: 25,\n legacyHeaders: false,\n standardHeaders: true,\n }),\n);\n\n// Cache for the allowlist to avoid fetching it on every request\nlet allowlistedRepos = [];\nlet lastAllowlistFetch = 0;\nconst ALLOWLIST_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n// Export cache clearing function for testing\nexport const clearAllowlistCache = () => {\n allowlistedRepos = [];\n lastAllowlistFetch = 0;\n};\n\nasync function fetchAllowlist() {\n const now = Date.now();\n if (\n now - lastAllowlistFetch < ALLOWLIST_CACHE_TTL &&\n allowlistedRepos.length > 0\n ) {\n return allowlistedRepos;\n }\n\n try {\n const response = await fetch(\n 'https://raw.githubusercontent.com/actualbudget/plugin-store/refs/heads/main/plugins.json',\n );\n if (!response.ok) {\n throw new Error(`Failed to fetch allowlist: ${response.status}`);\n }\n const plugins = await response.json();\n allowlistedRepos = plugins.map(plugin => plugin.url);\n lastAllowlistFetch = now;\n console.log('Updated plugin allowlist:', allowlistedRepos);\n return allowlistedRepos;\n } catch (error) {\n console.error('Failed to fetch plugin allowlist:', error);\n // Return empty array if fetch fails to be safe\n allowlistedRepos = [];\n return allowlistedRepos;\n }\n}\n\n/**\n * Return true only if the URL is on an allowlist and not a local/private address.\n */\nfunction isUrlAllowed(targetUrl) {\n try {\n const url = new URL(targetUrl);\n const hostname = url.hostname;\n\n // Block private/local IP addresses\n if (ipaddr.isValid(hostname)) {\n const ip = ipaddr.parse(hostname);\n if (\n [\n 'private',\n 'loopback',\n 'linkLocal',\n 'uniqueLocal',\n 'unspecified',\n ].includes(ip.range())\n ) {\n console.warn(`Blocked request to private/localhost IP: ${hostname}`);\n return false;\n }\n }\n\n // Always allow the specific plugin-store URL\n if (\n targetUrl ===\n 'https://raw.githubusercontent.com/actualbudget/plugin-store/refs/heads/main/plugins.json'\n ) {\n return true;\n }\n\n // Check against allowlisted repositories\n for (const repoUrl of allowlistedRepos) {\n try {\n const { pathname } = new URL(repoUrl);\n const [, repoOwner, repoName] = pathname.split('/');\n\n if (\n targetUrl === repoUrl ||\n targetUrl.startsWith(repoUrl + '/') ||\n (hostname === 'api.github.com' &&\n url.pathname.startsWith(`/repos/${repoOwner}/${repoName}`)) ||\n (hostname === 'raw.githubusercontent.com' &&\n url.pathname.startsWith(`/${repoOwner}/${repoName}/`)) ||\n (hostname === 'github.com' &&\n url.pathname.startsWith(`/${repoOwner}/${repoName}/releases/`))\n ) {\n return true;\n }\n } catch (e) {\n console.warn(\n 'Invalid repository URL in allowlist:',\n repoUrl,\n e.message,\n );\n }\n }\n\n return false;\n } catch (e) {\n console.warn('Invalid target URL:', targetUrl, e.message);\n return false;\n }\n}\n\napp.use('/', async (req, res) => {\n // CORS preflight\n if (req.method === 'OPTIONS') {\n res.set('Access-Control-Allow-Origin', '*');\n res.set('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type, X-Actual-Token');\n res.set('Access-Control-Max-Age', '600');\n return res.status(204).end();\n }\n\n const targetUrlString = req.query.url;\n\n if (!targetUrlString) {\n return res.status(400).json({ error: 'Missing url parameter' });\n }\n\n // Validate session/token\n const session = await validateSession(req, res);\n if (!session) {\n return; // validateSession already sent the response\n }\n\n let url;\n try {\n url = new URL(targetUrlString);\n } catch {\n return res.status(400).json({ error: 'Invalid url parameter' });\n }\n\n // Fetch the latest allowlist\n try {\n await fetchAllowlist();\n } catch (error) {\n console.error('Failed to fetch allowlist:', error);\n return res.status(403).json({\n error: 'URL not allowed',\n message: 'Unable to verify allowlist',\n });\n }\n\n // Check if the URL is allowed\n if (!isUrlAllowed(url.href)) {\n console.warn('Blocked request to unauthorized URL:', url.href);\n return res.status(403).json({\n error: 'URL not allowed',\n message:\n 'Only allowlisted plugin repositories are allowed (localhost only in development)',\n });\n }\n\n try {\n const { method = 'GET', headers: customHeaders = {} } = req.body || {};\n\n if (typeof method !== 'string') {\n return res.status(400).json({ error: 'Invalid method parameter' });\n }\n const methodNormalized = method.toUpperCase();\n if (!['GET', 'HEAD'].includes(methodNormalized)) {\n return res.status(405).json({ error: 'Method not allowed' });\n }\n\n const requestHeaders = {\n ...req.headers,\n ...customHeaders,\n host: url.host,\n };\n\n // Remove headers that shouldn't be forwarded\n delete requestHeaders['x-actual-token'];\n delete requestHeaders['content-length'];\n delete requestHeaders['cookie'];\n delete requestHeaders['cookie2'];\n\n // Add GitHub authentication if token is configured and request is to GitHub\n const githubToken = config.get('github.token');\n if (\n githubToken &&\n (url.hostname === 'api.github.com' ||\n url.hostname === 'raw.githubusercontent.com' ||\n (url.hostname === 'github.com' && url.pathname.includes('/releases/')))\n ) {\n requestHeaders['Authorization'] = `Bearer ${githubToken}`;\n requestHeaders['User-Agent'] = 'Actual-Budget-Plugin-System';\n console.log(\n `Using GitHub authentication for request to: ${url.hostname}`,\n );\n }\n\n const response = await fetch(url.href, {\n method: methodNormalized,\n headers: requestHeaders,\n });\n\n const contentType =\n response.headers.get('content-type') || 'application/octet-stream';\n\n res.set('Access-Control-Allow-Origin', '*');\n res.status(response.status);\n\n // Try to detect if this might be JSON content based on URL or content\n const urlString = url.toString().toLowerCase();\n const isLikelyJson =\n contentType?.includes('application/json') ||\n urlString.includes('.json') ||\n urlString.includes('/manifest') ||\n urlString.includes('manifest.json') ||\n urlString.includes('package.json');\n\n if (isLikelyJson) {\n // For JSON responses, return the actual content\n res.set('Content-Type', 'application/json');\n const text = await response.text();\n try {\n res.json(JSON.parse(text));\n } catch {\n // If it's not valid JSON, treat as text\n res.set('Content-Type', contentType || 'text/plain');\n res.send(text);\n }\n } else if (contentType?.includes('text/')) {\n // For text responses, return as plain text\n res.set('Content-Type', contentType);\n const text = await response.text();\n res.send(text);\n } else {\n // For actual binary responses, return as JSON format\n res.set('Content-Type', 'application/json');\n const buffer = await response.arrayBuffer();\n const binaryData = {\n data: Array.from(new Uint8Array(buffer)),\n contentType,\n isBinary: true,\n };\n res.json(binaryData);\n }\n } catch (err) {\n res\n .status(500)\n .json({ error: 'Error proxying request', details: err.message });\n }\n});\n\nexport { app as handlers };\n","import type { Request, Response } from 'express';\n\nexport function handleError(\n func: (req: Request, res: Response) => Promise<unknown>,\n) {\n return (req: Request, res: Response) => {\n func(req, res).catch(err => {\n console.log('Error', req.originalUrl, err.message || String(err));\n res.send({\n status: 'ok',\n data: {\n error_code: 'INTERNAL_ERROR',\n error_type: err.message ? err.message : 'internal-error',\n },\n });\n });\n };\n}\n","import createDebug from 'debug';\n\nimport { getAccountDb } from '#account-db';\n\n/**\n * An enum of valid secret names.\n * @readonly\n * @enum {string}\n */\nexport const SecretName = {\n gocardless_secretId: 'gocardless_secretId',\n gocardless_secretKey: 'gocardless_secretKey',\n simplefin_token: 'simplefin_token',\n simplefin_accessKey: 'simplefin_accessKey',\n pluggyai_clientId: 'pluggyai_clientId',\n pluggyai_clientSecret: 'pluggyai_clientSecret',\n pluggyai_itemIds: 'pluggyai_itemIds',\n enablebanking_applicationId: 'enablebanking_applicationId',\n enablebanking_secretKey: 'enablebanking_secretKey',\n};\n\nclass SecretsDb {\n constructor() {\n this.debug = createDebug('actual:secrets-db');\n this.db = null;\n }\n\n open() {\n return getAccountDb();\n }\n\n set(name, value) {\n if (!this.db) {\n this.db = this.open();\n }\n\n this.debug(`setting secret '${name}' to '${value}'`);\n const result = this.db.mutate(\n `INSERT OR REPLACE INTO secrets (name, value) VALUES (?,?)`,\n [name, value],\n );\n return result;\n }\n\n get(name) {\n if (!this.db) {\n this.db = this.open();\n }\n\n this.debug(`getting secret '${name}'`);\n const result = this.db.first(`SELECT value FROM secrets WHERE name =?`, [\n name,\n ]);\n return result;\n }\n}\n\nconst secretsDb = new SecretsDb();\nconst _cachedSecrets = new Map();\n/**\n * A service for managing secrets stored in `secretsDb`.\n */\nexport const secretsService = {\n /**\n * Retrieves the value of a secret by name.\n * @param {SecretName} name - The name of the secret to retrieve.\n * @returns {string|null} The value of the secret, or null if the secret does not exist.\n */\n get: name => {\n return _cachedSecrets.get(name) ?? secretsDb.get(name)?.value ?? null;\n },\n\n /**\n * Sets the value of a secret by name.\n * @param {SecretName} name - The name of the secret to set.\n * @param {string} value - The value to set for the secret.\n * @returns {Object}\n */\n set: (name, value) => {\n const result = secretsDb.set(name, value);\n\n if (result.changes === 1) {\n _cachedSecrets.set(name, value);\n }\n return result;\n },\n\n /**\n * Determines whether a secret with the given name exists.\n * @param {SecretName} name - The name of the secret to check for existence.\n * @returns {boolean} True if a secret with the given name exists, false otherwise.\n */\n exists: name => {\n return Boolean(secretsService.get(name));\n },\n};\n","import createDebug from 'debug';\n\nconst debug = createDebug('actual:enable-banking:errors');\n\nexport class EnableBankingError extends Error {\n error_type: string;\n error_code: string;\n\n constructor(error_type: string, error_code: string, message?: string) {\n super(message || `Enable Banking error: ${error_type} - ${error_code}`);\n this.name = 'EnableBankingError';\n this.error_type = error_type;\n this.error_code = error_code;\n }\n}\n\nexport function handleEnableBankingError(\n statusCode: number,\n body: unknown,\n): EnableBankingError {\n const bodyStr =\n typeof body === 'string' ? body : JSON.stringify(body ?? 'unknown');\n debug('Enable Banking API error: status=%d body=%s', statusCode, bodyStr);\n\n const parsed: Record<string, unknown> =\n typeof body === 'object' && body !== null\n ? Object.fromEntries(Object.entries(body))\n : {};\n const message = typeof parsed.message === 'string' ? parsed.message : bodyStr;\n const errorType = typeof parsed.error === 'string' ? parsed.error : 'UNKNOWN';\n\n if (statusCode === 401 || statusCode === 403) {\n return new EnableBankingError(message, 'INVALID_ACCESS_TOKEN', message);\n }\n\n if (statusCode === 429) {\n return new EnableBankingError(message, 'RATE_LIMIT_EXCEEDED', message);\n }\n\n if (statusCode === 404) {\n return new EnableBankingError(message, 'NOT_FOUND', message);\n }\n\n if (statusCode >= 400 && statusCode < 500) {\n // Check for closed/expired session errors (case-insensitive)\n const lowerErrorType = (errorType || '').toLowerCase();\n const lowerMessage = (message || '').toLowerCase();\n if (\n lowerErrorType === 'closed_session' ||\n lowerErrorType === 'expired_session' ||\n lowerMessage.includes('session') ||\n lowerMessage.includes('expired')\n ) {\n return new EnableBankingError(message, 'INVALID_ACCESS_TOKEN', message);\n }\n return new EnableBankingError(message, 'INVALID_INPUT', message);\n }\n\n return new EnableBankingError(message, 'INTERNAL_ERROR', message);\n}\n","import { sign } from 'jws';\nimport type { Algorithm } from 'jws';\n\ntype Header = { typ: string; alg: Algorithm; kid: string };\n\ntype JWTPayload = {\n iss: string;\n aud: string;\n iat: number;\n exp: number;\n};\n\nfunction getJWTHeader(applicationId: string): Header {\n return { typ: 'JWT', alg: 'RS256', kid: applicationId };\n}\n\nfunction getJWTBody(exp = 3600): JWTPayload {\n const timestamp = Math.floor(Date.now() / 1000);\n return {\n iss: 'enablebanking.com',\n aud: 'api.enablebanking.com',\n iat: timestamp,\n exp: timestamp + exp,\n };\n}\n\nexport function getJWT(\n applicationId: string,\n secretKey: string,\n exp = 3600,\n): string {\n return sign({\n header: getJWTHeader(applicationId),\n payload: getJWTBody(exp),\n secret: secretKey,\n });\n}\n","import createDebug from 'debug';\n\nimport {\n EnableBankingError,\n handleEnableBankingError,\n} from '#app-enablebanking/utils/errors';\nimport { getJWT } from '#app-enablebanking/utils/jwt';\nimport { SecretName, secretsService } from '#services/secrets-service';\n\nconst debug = createDebug('actual:enable-banking:service');\n\nconst BASE_URL = 'https://api.enablebanking.com';\n\n// --- Type definitions ---\n\nexport type EnableBankingTransaction = {\n entry_reference?: string;\n transaction_id?: string;\n transaction_amount: { currency: string; amount: string };\n creditor?: { name?: string };\n debtor?: { name?: string };\n credit_debit_indicator?: 'CRDT' | 'DBIT';\n status?: 'BOOK' | 'PDNG';\n booking_date?: string;\n value_date?: string;\n transaction_date?: string;\n remittance_information?: string[];\n};\n\ntype EnableBankingBalance = {\n balance_amount: { currency: string; amount: string };\n balance_type: string;\n reference_date?: string;\n};\n\nexport type EnableBankingSessionAccount = {\n account_id?: { iban?: string };\n account_servicer?: { bic_fi?: string; name?: string };\n name?: string;\n currency?: string;\n uid: string;\n};\n\nexport type EnableBankingSession = {\n session_id: string;\n accounts: EnableBankingSessionAccount[];\n aspsp?: { name?: string; country?: string };\n};\n\ntype EnableBankingAspsp = {\n name: string;\n country: string;\n [key: string]: unknown;\n};\n\ntype EnableBankingAuthResponse = {\n url: string;\n authorization_id: string;\n};\n\ntype BankSyncTransaction = EnableBankingTransaction & {\n transactionId: string;\n date: string;\n bookingDate: string;\n valueDate?: string;\n transactionAmount: { amount: string; currency: string };\n payeeName: string;\n notes?: string;\n remittanceInformationUnstructured?: string;\n booked: boolean;\n};\n\ntype BankSyncBalance = {\n balanceAmount: { amount: number; currency: string };\n balanceType: string;\n referenceDate?: string;\n};\n\ntype NormalizedAccount = {\n account_id: string;\n name: string;\n institution: string;\n currency?: string;\n iban?: string;\n};\n\n// --- PSU headers ---\n\nexport type PsuHeaders = {\n 'Psu-Ip-Address'?: string;\n 'Psu-User-Agent'?: string;\n};\n\n// --- Helper functions ---\n\nfunction getCredentials(): { applicationId: string; secretKey: string } {\n const applicationId = secretsService.get(\n SecretName.enablebanking_applicationId,\n );\n const secretKey = secretsService.get(SecretName.enablebanking_secretKey);\n\n if (!applicationId || !secretKey) {\n throw new EnableBankingError(\n 'INVALID_INPUT',\n 'NOT_CONFIGURED',\n 'Enable Banking is not configured',\n );\n }\n\n return { applicationId, secretKey };\n}\n\nfunction getAuthorizationHeader(): string {\n const { applicationId, secretKey } = getCredentials();\n const token = getJWT(applicationId, secretKey);\n return `Bearer ${token}`;\n}\n\nconst REQUEST_TIMEOUT_MS = 30_000; // 30 seconds\n\nasync function request<T>(\n method: string,\n path: string,\n body?: unknown,\n authHeaderOverride?: string,\n psuHeaders?: PsuHeaders,\n): Promise<T> {\n const url = `${BASE_URL}${path}`;\n debug('%s %s', method, url);\n\n const headers: Record<string, string> = {\n Authorization: authHeaderOverride ?? getAuthorizationHeader(),\n 'Content-Type': 'application/json',\n };\n\n // Forward PSU headers to signal the end-user is online.\n // This exempts the request from background data-fetch rate limits\n // that many ASPSPs enforce (e.g. 4 requests/day).\n if (psuHeaders) {\n for (const [key, value] of Object.entries(psuHeaders)) {\n if (value) {\n headers[key] = value;\n }\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n const options: RequestInit = { method, headers, signal: controller.signal };\n if (body !== undefined) {\n options.body = JSON.stringify(body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, options);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new EnableBankingError(\n 'TIMED_OUT',\n 'TIMED_OUT',\n 'Request timed out',\n );\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n\n if (!response.ok) {\n let responseBody: unknown;\n try {\n responseBody = await response.json();\n } catch {\n responseBody = await response.text().catch(() => 'unknown');\n }\n throw handleEnableBankingError(response.status, responseBody);\n }\n\n // eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- generic API wrapper, type is validated by caller\n return (await response.json()) as T;\n}\n\n// --- Normalization functions ---\n\n// SEPA / ISO 20022 structured remittance prefixes (e.g. `EREF+invoice-42`).\n// They are metadata for clearing systems, not user-facing text, so we strip\n// them from the front of each remittance line. The list is an allowlist of\n// known prefixes rather than a catch-all `[A-Z]{3,}\\+` so we don't accidentally\n// strip merchant tokens like `BMW+` or `USB+` that legitimately start a\n// description.\nconst SEPA_PREFIX_RE =\n /^(?:EREF|KREF|MREF|CRED|DBTR|CDTR|SVWZ|SVCL|PURP|RTRN|REJT|REFE|SDVA|INDA|NTAV|ULTC|ULTD|ULTB|ABWA|ABWE|IBAN|BIC|COAM|OAMT|REMI|SQTP|ROC)\\+/;\n\nfunction stripSepaPrefix(s: string): string {\n return s.replace(SEPA_PREFIX_RE, '').trim();\n}\n\nfunction cleanRemittanceArray(arr: string[]): string[] {\n return arr.map(stripSepaPrefix).filter(Boolean);\n}\n\nexport function normalizeTransaction(\n tx: EnableBankingTransaction,\n): BankSyncTransaction {\n const transactionId = tx.entry_reference || tx.transaction_id || '';\n const bookingDate =\n tx.booking_date || tx.value_date || tx.transaction_date || '';\n const valueDate = tx.value_date;\n\n let payeeName = '';\n if (tx.credit_debit_indicator === 'CRDT' && tx.debtor?.name) {\n payeeName = tx.debtor.name;\n } else if (tx.credit_debit_indicator === 'DBIT' && tx.creditor?.name) {\n payeeName = tx.creditor.name;\n } else if (tx.creditor?.name) {\n payeeName = tx.creditor.name;\n } else if (tx.debtor?.name) {\n payeeName = tx.debtor.name;\n } else if (\n tx.remittance_information &&\n tx.remittance_information.length > 0\n ) {\n const cleanedFallback = cleanRemittanceArray(tx.remittance_information);\n if (cleanedFallback.length > 0) {\n payeeName = cleanedFallback[0];\n }\n }\n\n const cleanedAll = tx.remittance_information\n ? cleanRemittanceArray(tx.remittance_information)\n : [];\n const remittanceInformationUnstructured =\n cleanedAll.length > 0 ? cleanedAll.join(' ') : undefined;\n\n // Normalize amount based on credit/debit indicator.\n // When indicator is present, strip existing sign and apply the correct one.\n // When absent, preserve the original sign from the bank.\n const trimmedAmount = tx.transaction_amount.amount.trim();\n let signedAmount: string;\n if (tx.credit_debit_indicator === 'DBIT') {\n signedAmount = '-' + trimmedAmount.replace(/^[+-]/, '');\n } else if (tx.credit_debit_indicator === 'CRDT') {\n signedAmount = trimmedAmount.replace(/^[+-]/, '');\n } else {\n signedAmount = trimmedAmount;\n }\n\n return {\n ...tx,\n transactionId,\n date: bookingDate,\n bookingDate,\n valueDate,\n transactionAmount: {\n amount: signedAmount,\n currency: tx.transaction_amount.currency,\n },\n payeeName,\n notes: remittanceInformationUnstructured,\n remittanceInformationUnstructured,\n booked: tx.status !== 'PDNG',\n };\n}\n\nexport function normalizeBalance(bal: EnableBankingBalance): BankSyncBalance {\n const amount = Math.round(parseFloat(bal.balance_amount.amount) * 100);\n return {\n balanceAmount: {\n amount,\n currency: bal.balance_amount.currency,\n },\n balanceType: bal.balance_type,\n referenceDate: bal.reference_date,\n };\n}\n\nexport function normalizeAccount(\n account: EnableBankingSessionAccount,\n aspsp?: { name?: string },\n): NormalizedAccount {\n return {\n account_id: account.uid,\n name: account.name || account.account_id?.iban || account.uid,\n institution: aspsp?.name || account.account_servicer?.name || 'Unknown',\n currency: account.currency,\n iban: account.account_id?.iban,\n };\n}\n\n// --- Service ---\n\nexport const enableBankingService = {\n isConfigured(): boolean {\n const applicationId = secretsService.get(\n SecretName.enablebanking_applicationId,\n );\n const secretKey = secretsService.get(SecretName.enablebanking_secretKey);\n return !!(applicationId && secretKey);\n },\n\n async validateCredentials(\n applicationId: string,\n secretKey: string,\n ): Promise<unknown> {\n const token = getJWT(applicationId, secretKey);\n return request<unknown>(\n 'GET',\n '/application',\n undefined,\n `Bearer ${token}`,\n );\n },\n\n async getApplication(): Promise<unknown> {\n return request<unknown>('GET', '/application');\n },\n\n async getAspsps(country?: string): Promise<EnableBankingAspsp[]> {\n const query = country ? `?country=${encodeURIComponent(country)}` : '';\n return request<EnableBankingAspsp[]>('GET', `/aspsps${query}`);\n },\n\n async startAuth(\n aspsp: { name: string; country: string },\n redirectUrl: string,\n state: string,\n maxConsentValidity?: number,\n ): Promise<EnableBankingAuthResponse> {\n const DEFAULT_CONSENT_DAYS = 90;\n const defaultMs = DEFAULT_CONSENT_DAYS * 24 * 60 * 60 * 1000;\n\n // Respect the ASPSP's maximum_consent_validity (in seconds) if provided,\n // capping at our default of 90 days.\n const consentMs =\n maxConsentValidity != null && maxConsentValidity > 0\n ? Math.min(maxConsentValidity * 1000, defaultMs)\n : defaultMs;\n\n const validUntil = new Date(Date.now() + consentMs);\n\n return request<EnableBankingAuthResponse>('POST', '/auth', {\n aspsp: { name: aspsp.name, country: aspsp.country },\n redirect_url: redirectUrl,\n state,\n access: {\n valid_until: validUntil.toISOString(),\n },\n });\n },\n\n async createSession(code: string): Promise<EnableBankingSession> {\n return request<EnableBankingSession>('POST', '/sessions', { code });\n },\n\n async getSession(sessionId: string): Promise<EnableBankingSession> {\n return request<EnableBankingSession>(\n 'GET',\n `/sessions/${encodeURIComponent(sessionId)}`,\n );\n },\n\n async getBalances(\n accountUid: string,\n psuHeaders?: PsuHeaders,\n ): Promise<{ balances: EnableBankingBalance[] }> {\n return request<{ balances: EnableBankingBalance[] }>(\n 'GET',\n `/accounts/${encodeURIComponent(accountUid)}/balances`,\n undefined,\n undefined,\n psuHeaders,\n );\n },\n\n async getTransactions(\n accountUid: string,\n dateFrom: string,\n dateTo: string,\n continuationKey?: string,\n psuHeaders?: PsuHeaders,\n ): Promise<{\n transactions: EnableBankingTransaction[];\n continuation_key?: string;\n }> {\n let path = `/accounts/${encodeURIComponent(accountUid)}/transactions?date_from=${encodeURIComponent(dateFrom)}&date_to=${encodeURIComponent(dateTo)}`;\n if (continuationKey) {\n path += `&continuation_key=${encodeURIComponent(continuationKey)}`;\n }\n return request<{\n transactions: EnableBankingTransaction[];\n continuation_key?: string;\n }>('GET', path, undefined, undefined, psuHeaders);\n },\n\n async getAllTransactions(\n accountUid: string,\n dateFrom: string,\n dateTo: string,\n psuHeaders?: PsuHeaders,\n ): Promise<EnableBankingTransaction[]> {\n const allTransactions: EnableBankingTransaction[] = [];\n let continuationKey: string | undefined;\n const maxIterations = 100;\n let iteration = 0;\n\n do {\n const result = await enableBankingService.getTransactions(\n accountUid,\n dateFrom,\n dateTo,\n continuationKey,\n psuHeaders,\n );\n allTransactions.push(...result.transactions);\n\n if (\n result.continuation_key &&\n result.continuation_key === continuationKey\n ) {\n break;\n }\n\n continuationKey = result.continuation_key;\n iteration++;\n } while (continuationKey && iteration < maxIterations);\n\n return allTransactions;\n },\n};\n","import createDebug from 'debug';\nimport type { Request, Response } from 'express';\nimport express from 'express';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { handleError } from '#app-gocardless/util/handle-error';\nimport { SecretName, secretsService } from '#services/secrets-service';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from '#util/middlewares';\n\nimport type {\n EnableBankingSession,\n PsuHeaders,\n} from './services/enablebanking-service';\nimport {\n enableBankingService,\n normalizeAccount,\n normalizeBalance,\n normalizeTransaction,\n} from './services/enablebanking-service';\nimport { EnableBankingError } from './utils/errors';\n\nconst debug = createDebug('actual:enable-banking:app');\n\nconst app = express();\nexport { app as handlers };\napp.use(requestLoggerMiddleware);\napp.use(express.json());\n\n// --- Shared helpers ---\n\nfunction extractPsuHeaders(req: Request): PsuHeaders {\n const ip = req.ip;\n const ua =\n typeof req.headers['user-agent'] === 'string'\n ? req.headers['user-agent']\n : undefined;\n\n const headers: PsuHeaders = {};\n if (ip) headers['Psu-Ip-Address'] = ip;\n if (ua) headers['Psu-User-Agent'] = ua;\n return headers;\n}\n\nasync function buildSessionResult(\n session: EnableBankingSession,\n psuHeaders?: PsuHeaders,\n) {\n const accountsWithBalances = await Promise.all(\n session.accounts.map(async account => {\n const normalized = normalizeAccount(account, session.aspsp);\n\n let balances: ReturnType<typeof normalizeBalance>[] = [];\n try {\n const balanceResult = await enableBankingService.getBalances(\n account.uid,\n psuHeaders,\n );\n balances = balanceResult.balances.map(normalizeBalance);\n } catch (err) {\n debug('Failed to fetch balances for account %s: %s', account.uid, err);\n }\n\n const preferredBalance =\n balances.find(b => b.balanceType === 'CLAV') ?? balances[0];\n\n return {\n ...normalized,\n balance: preferredBalance ? preferredBalance.balanceAmount.amount : 0,\n balances,\n };\n }),\n );\n\n return {\n session_id: session.session_id,\n accounts: accountsWithBalances,\n aspsp: session.aspsp,\n };\n}\n\n// Auth callback from bank redirect — must be before validateSessionMiddleware\n// since the bank redirects here directly (no auth token available)\napp.get('/auth_callback', async (req: Request, res: Response) => {\n const code = typeof req.query.code === 'string' ? req.query.code : undefined;\n const state =\n typeof req.query.state === 'string' ? req.query.state : undefined;\n\n if (!code) {\n res\n .status(400)\n .send(\n '<html><body><p>Authorization failed: missing code.</p></body></html>',\n );\n return;\n }\n\n if (!state) {\n res\n .status(400)\n .send(\n '<html><body><p>Authorization failed: missing state parameter.</p></body></html>',\n );\n return;\n }\n\n try {\n const session = await enableBankingService.createSession(code);\n debug(\n 'Callback session created: %s with %d accounts',\n session.session_id,\n session.accounts.length,\n );\n\n const result = await buildSessionResult(session, extractPsuHeaders(req));\n\n // Always cache the result so retries within TTL can read it\n completedAuths.set(state, result);\n setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n const pending = pendingAuths.get(state);\n if (pending) {\n pending.resolve(result);\n cleanupPendingAuth(state);\n }\n\n res.send(\n '<html><body><p>Authorization successful. This window will close.</p>' +\n '<script>setTimeout(function(){window.close()},1000)</script></body></html>',\n );\n } catch (error) {\n const errorResult = {\n error: error instanceof Error ? error.message : 'unknown error',\n };\n\n completedAuths.set(state, errorResult);\n setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n const pending = pendingAuths.get(state);\n if (pending) {\n pending.reject(error);\n cleanupPendingAuth(state);\n }\n\n debug('Callback auth error: %s', error);\n res\n .status(500)\n .send(\n '<html><body><p>Authorization failed. You can close this window and try again.</p></body></html>',\n );\n }\n});\n\napp.use(validateSessionMiddleware);\n\n// --- Poll/complete-auth coordination ---\n\ntype PendingAuth = {\n id: string;\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n// NOTE: These in-memory maps make the auth handoff process-local.\n// Multi-instance deployments require sticky routing so the same instance\n// handles both the callback and client poll for a given state.\nconst pendingAuths = new Map<string, PendingAuth>();\nconst completedAuths = new Map<string, unknown>();\nlet nextWaiterId = 0;\n\nconst POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\nconst COMPLETED_AUTH_TTL_MS = 30 * 1000; // 30 seconds\n\nfunction cleanupPendingAuth(state: string, waiterId?: string) {\n const entry = pendingAuths.get(state);\n if (entry && (waiterId == null || entry.id === waiterId)) {\n clearTimeout(entry.timer);\n pendingAuths.delete(state);\n }\n}\n\n// --- Routes ---\n\napp.post(\n '/status',\n handleError(async (req: Request, res: Response) => {\n const configured = enableBankingService.isConfigured();\n\n res.send({\n status: 'ok',\n data: {\n configured,\n },\n });\n }),\n);\n\napp.post(\n '/configure',\n handleError(async (req: Request, res: Response) => {\n const { applicationId, secretKey } = req.body || {};\n\n if (!applicationId || !secretKey) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing applicationId or secretKey',\n },\n });\n return;\n }\n\n // Validate credentials before persisting to avoid exposing\n // transient bad creds to concurrent requests\n try {\n const appInfo = await enableBankingService.validateCredentials(\n applicationId,\n secretKey,\n );\n debug('Enable Banking application validated: %o', appInfo);\n } catch (error) {\n debug('Enable Banking configuration validation failed: %s', error);\n res.send({\n status: 'ok',\n data: {\n error_code: 'CONFIGURATION_FAILED',\n error_type: error instanceof Error ? error.message : 'unknown error',\n },\n });\n return;\n }\n\n // Only persist after successful validation\n secretsService.set(SecretName.enablebanking_applicationId, applicationId);\n secretsService.set(SecretName.enablebanking_secretKey, secretKey);\n\n res.send({\n status: 'ok',\n data: {\n configured: true,\n },\n });\n }),\n);\n\napp.post(\n '/aspsps',\n handleError(async (req: Request, res: Response) => {\n const { country } = req.body || {};\n\n try {\n const aspsps = await enableBankingService.getAspsps(country);\n\n res.send({\n status: 'ok',\n data: aspsps,\n });\n } catch (error) {\n res.send({\n status: 'ok',\n data: {\n error: error instanceof Error ? error.message : 'unknown error',\n },\n });\n }\n }),\n);\n\napp.post(\n '/start-auth',\n handleError(async (req: Request, res: Response) => {\n const { aspsp, redirectUrl, maxConsentValidity } = req.body || {};\n\n if (!aspsp || !redirectUrl) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing aspsp or redirectUrl',\n },\n });\n return;\n }\n\n const state = uuidv4();\n\n try {\n const authResponse = await enableBankingService.startAuth(\n aspsp,\n redirectUrl,\n state,\n typeof maxConsentValidity === 'number' ? maxConsentValidity : undefined,\n );\n\n res.send({\n status: 'ok',\n data: {\n url: authResponse.url,\n state,\n },\n });\n } catch (error) {\n res.send({\n status: 'ok',\n data: {\n error: error instanceof Error ? error.message : 'unknown error',\n },\n });\n }\n }),\n);\n\napp.post(\n '/complete-auth',\n handleError(async (req: Request, res: Response) => {\n const { code, state } = req.body || {};\n\n if (!code) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing code',\n },\n });\n return;\n }\n\n try {\n const session = await enableBankingService.createSession(code);\n debug(\n 'Session created: %s with %d accounts',\n session.session_id,\n session.accounts.length,\n );\n\n const result = await buildSessionResult(session, extractPsuHeaders(req));\n\n // Always cache so retries within TTL can read the result\n if (state) {\n completedAuths.set(state, result);\n setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n const pending = pendingAuths.get(state);\n if (pending) {\n pending.resolve(result);\n cleanupPendingAuth(state);\n }\n }\n\n res.send({\n status: 'ok',\n data: result,\n });\n } catch (error) {\n const errorResult = {\n error: error instanceof Error ? error.message : 'unknown error',\n };\n\n if (state) {\n completedAuths.set(state, errorResult);\n setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n const pending = pendingAuths.get(state);\n if (pending) {\n pending.reject(error);\n cleanupPendingAuth(state);\n }\n }\n\n res.send({\n status: 'ok',\n data: errorResult,\n });\n }\n }),\n);\n\napp.post(\n '/poll-auth',\n handleError(async (req: Request, res: Response) => {\n const { state } = req.body || {};\n\n if (!state) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing state',\n },\n });\n return;\n }\n\n const waiterId = String(++nextWaiterId);\n let hasClientDisconnected = false;\n\n try {\n // If complete-auth already fired before poll-auth, return immediately\n if (completedAuths.has(state)) {\n const result = completedAuths.get(state);\n completedAuths.delete(state);\n res.send({ status: 'ok', data: result });\n return;\n }\n\n const result = await new Promise((resolve, reject) => {\n // Clean up any existing waiter for this state\n const existing = pendingAuths.get(state);\n if (existing) {\n clearTimeout(existing.timer);\n existing.reject(new Error('Poll superseded'));\n }\n\n let settled = false;\n const safeResolve = (value: unknown) => {\n if (settled) return;\n settled = true;\n resolve(value);\n };\n const safeReject = (reason: unknown) => {\n if (settled) return;\n settled = true;\n reject(reason);\n };\n\n const timer = setTimeout(() => {\n cleanupPendingAuth(state, waiterId);\n safeReject(new Error('Polling timed out'));\n }, POLL_TIMEOUT_MS);\n\n pendingAuths.set(state, {\n id: waiterId,\n resolve: safeResolve,\n reject: safeReject,\n timer,\n });\n\n // Clean up if client disconnects before resolution\n res.on('close', () => {\n if (!res.writableFinished && !settled) {\n hasClientDisconnected = true;\n cleanupPendingAuth(state, waiterId);\n safeReject(new Error('Client disconnected'));\n }\n });\n });\n\n if (hasClientDisconnected || res.destroyed || res.writableEnded) {\n return;\n }\n\n res.send({\n status: 'ok',\n data: result,\n });\n } catch (error) {\n cleanupPendingAuth(state, waiterId);\n if (hasClientDisconnected || res.destroyed || res.writableEnded) {\n return;\n }\n res.send({\n status: 'ok',\n data: {\n error: error instanceof Error ? error.message : 'unknown error',\n },\n });\n }\n }),\n);\n\napp.post(\n '/transactions',\n handleError(async (req: Request, res: Response) => {\n const { accountId, startDate } = req.body || {};\n\n if (!accountId || !startDate) {\n res.send({\n status: 'ok',\n data: {\n error_code: 'INVALID_INPUT',\n error_type: 'Missing accountId or startDate',\n },\n });\n return;\n }\n\n const psuHeaders = extractPsuHeaders(req);\n\n try {\n const dateTo = new Date().toISOString().split('T')[0];\n const dateFrom =\n typeof startDate === 'string'\n ? startDate\n : new Date(startDate).toISOString().split('T')[0];\n\n // Fetch balances\n const balanceResult = await enableBankingService.getBalances(\n accountId,\n psuHeaders,\n );\n const balances = balanceResult.balances.map(normalizeBalance);\n\n // Determine starting balance, preferring CLAV balance type\n let startingBalance = 0;\n if (balances.length > 0) {\n const preferredBalance =\n balances.find(b => b.balanceType === 'CLAV') ?? balances[0];\n startingBalance = preferredBalance.balanceAmount.amount;\n }\n\n // Fetch all paginated transactions\n const rawTransactions = await enableBankingService.getAllTransactions(\n accountId,\n dateFrom,\n dateTo,\n psuHeaders,\n );\n\n const all: ReturnType<typeof normalizeTransaction>[] = [];\n const booked: ReturnType<typeof normalizeTransaction>[] = [];\n const pending: ReturnType<typeof normalizeTransaction>[] = [];\n\n for (const tx of rawTransactions) {\n const normalized = normalizeTransaction(tx);\n all.push(normalized);\n if (normalized.booked) {\n booked.push(normalized);\n } else {\n pending.push(normalized);\n }\n }\n\n res.send({\n status: 'ok',\n data: {\n transactions: {\n all,\n booked,\n pending,\n },\n balances,\n startingBalance,\n },\n });\n } catch (error) {\n debug('Error fetching transactions: %s', error);\n\n // Return structured error codes so the client can show\n // appropriate UI (e.g. re-auth prompt for expired sessions)\n if (error instanceof EnableBankingError) {\n if (error.error_code === 'INVALID_ACCESS_TOKEN') {\n res.send({\n status: 'ok',\n data: {\n error_type: 'ITEM_ERROR',\n error_code: 'ITEM_LOGIN_REQUIRED',\n },\n });\n return;\n }\n\n // The bank-sync wire format expects `error_type` to be a broad\n // machine-readable category (matched by AccountSyncCheck's switch),\n // not the human message we now keep on `EnableBankingError.error_type`.\n const wireErrorType =\n error.error_code === 'NOT_FOUND' ? 'INVALID_INPUT' : error.error_code;\n\n res.send({\n status: 'ok',\n data: {\n error_type: wireErrorType,\n error_code: error.error_code,\n },\n });\n return;\n }\n\n res.send({\n status: 'ok',\n data: {\n error_type: 'INTERNAL_ERROR',\n error_code: 'INTERNAL_ERROR',\n },\n });\n }\n }),\n);\n","import crypto from 'crypto';\n\nexport function sha256String(str: string) {\n return crypto.createHash('sha256').update(str).digest('base64');\n}\n","import type { GoCardlessRequisitionId } from './gocardless-node.types';\n\nexport class RequisitionNotLinked extends Error {\n details: unknown;\n\n constructor(params: unknown = {}) {\n super('Requisition not linked yet');\n this.details = params;\n }\n}\n\nexport class AccountNotLinkedToRequisition extends Error {\n details: {\n accountId: string;\n requisitionId: GoCardlessRequisitionId;\n };\n\n constructor(accountId: string, requisitionId: GoCardlessRequisitionId) {\n super('Provided account id is not linked to given requisition');\n this.details = { accountId, requisitionId };\n }\n}\n\nexport class GenericGoCardlessError extends Error {\n details: unknown;\n\n constructor(data: unknown = {}) {\n super('GoCardless returned error');\n this.details = data;\n }\n}\n\nexport class GoCardlessClientError extends Error {\n details: unknown;\n\n constructor(message: string, details: unknown) {\n super(message);\n this.details = details;\n }\n}\n\nexport class InvalidInputDataError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Invalid provided parameters', response);\n }\n}\n\nexport class InvalidGoCardlessTokenError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Token is invalid or expired', response);\n }\n}\n\nexport class AccessDeniedError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('IP address access denied', response);\n }\n}\n\nexport class NotFoundError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Resource not found', response);\n }\n}\n\nexport class ResourceSuspended extends GoCardlessClientError {\n constructor(response: unknown) {\n super(\n 'Resource was suspended due to numerous errors that occurred while accessing it',\n response,\n );\n }\n}\n\nexport class RateLimitError extends GoCardlessClientError {\n constructor(response: unknown) {\n super(\n 'Daily request limit set by the Institution has been exceeded',\n response,\n );\n }\n}\n\nexport class UnknownError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Request to Institution returned an error', response);\n }\n}\n\nexport class ServiceError extends GoCardlessClientError {\n constructor(response: unknown) {\n super('Institution service unavailable', response);\n }\n}\n","import type { Transaction } from './gocardless-node.types';\n\nexport const printIban = (account: { iban?: string | null }): string => {\n if (account.iban) {\n return '(XXX ' + account.iban.slice(-4) + ')';\n } else {\n return '';\n }\n};\n\nconst compareDates = (\n a: string | number | Date | undefined,\n b: string | number | Date | undefined,\n): number => {\n if (a == null && b == null) {\n return 0;\n } else if (a == null) {\n return 1;\n } else if (b == null) {\n return -1;\n }\n\n return +new Date(a) - +new Date(b);\n};\n\nconst compareFunctions: ((a: Transaction, b: Transaction) => number)[] = [\n (a, b) => compareDates(a.bookingDate, b.bookingDate),\n (a, b) => compareDates(a.bookingDateTime, b.bookingDateTime),\n (a, b) => compareDates(a.valueDate, b.valueDate),\n (a, b) => compareDates(a.valueDateTime, b.valueDateTime),\n];\n\nexport const sortByBookingDateOrValueDate = <T extends Transaction>(\n transactions: T[] = [],\n): T[] =>\n transactions.sort((a, b) => {\n for (const sortFunction of compareFunctions) {\n const result = sortFunction(b, a);\n if (result !== 0) {\n return result;\n }\n }\n return 0;\n });\n\nexport const amountToInteger = (n: string | number): number =>\n Math.round(Number(n) * 100);\n","const conjunctions = [\n 'for', //\n 'and',\n 'nor',\n 'but',\n 'or',\n 'yet',\n 'so',\n];\n\nconst articles = [\n 'a', //\n 'an',\n 'the',\n];\n\nconst prepositions = [\n 'aboard',\n 'about',\n 'above',\n 'across',\n 'after',\n 'against',\n 'along',\n 'amid',\n 'among',\n 'anti',\n 'around',\n 'as',\n 'at',\n 'before',\n 'behind',\n 'below',\n 'beneath',\n 'beside',\n 'besides',\n 'between',\n 'beyond',\n 'but',\n 'by',\n 'concerning',\n 'considering',\n 'despite',\n 'down',\n 'during',\n 'except',\n 'excepting',\n 'excluding',\n 'following',\n 'for',\n 'from',\n 'in',\n 'inside',\n 'into',\n 'like',\n 'minus',\n 'near',\n 'of',\n 'off',\n 'on',\n 'onto',\n 'opposite',\n 'over',\n 'past',\n 'per',\n 'plus',\n 'regarding',\n 'round',\n 'save',\n 'since',\n 'than',\n 'through',\n 'to',\n 'toward',\n 'towards',\n 'under',\n 'underneath',\n 'unlike',\n 'until',\n 'up',\n 'upon',\n 'versus',\n 'via',\n 'with',\n 'within',\n 'without',\n];\n\nexport const lowerCaseSet = new Set([\n ...conjunctions,\n ...articles,\n ...prepositions,\n]);\n","export const specials = [\n 'CLI',\n 'API',\n 'HTTP',\n 'HTTPS',\n 'JSX',\n 'DNS',\n 'URL',\n 'CI',\n 'CDN',\n 'GitHub',\n 'CSS',\n 'JS',\n 'JavaScript',\n 'TypeScript',\n 'HTML',\n 'WordPress',\n 'JavaScript',\n 'Next.js',\n 'Node.js',\n];\n","// Utilities\nimport { lowerCaseSet } from './lower-case';\nimport { specials } from './specials';\n\nconst character =\n '[0-9\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58-\\u0C59\\u0C60-\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0-\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E87-\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA-\\u0EAB\\u0EAD-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]';\nconst regex = new RegExp(\n `(?:(?:(\\\\s?(?:^|[.\\\\(\\\\)!?;:\"-])\\\\s*)(${character}))|(${character}))(${character}*[’']*${character}*)`,\n 'g',\n);\n\nconst convertToRegExp = specials =>\n specials.map(s => [new RegExp(`\\\\b${s}\\\\b`, 'gi'), s]);\n\nfunction parseMatch(match) {\n const firstCharacter = match[0];\n\n // test first character\n if (/\\s/.test(firstCharacter)) {\n // if whitespace - trim and return\n return match.substr(1);\n }\n if (/[()]/.test(firstCharacter)) {\n // if parens - this shouldn't be replaced\n return null;\n }\n\n return match;\n}\n\nexport function title(str, options = { special: undefined }) {\n str = str\n .toLowerCase()\n .replace(regex, (m, lead = '', forced, lower, rest) => {\n const parsedMatch = parseMatch(m);\n if (!parsedMatch) {\n return m;\n }\n if (!forced) {\n const fullLower = lower + rest;\n\n if (lowerCaseSet.has(fullLower)) {\n return parsedMatch;\n }\n }\n\n return lead + (lower || forced).toUpperCase() + rest;\n });\n\n const customSpecials = options.special || [];\n const replace = [...specials, ...customSpecials];\n const replaceRegExp = convertToRegExp(replace);\n\n replaceRegExp.forEach(([pattern, s]) => {\n str = str.replace(pattern, s);\n });\n\n return str;\n}\n","import type { Transaction } from '#app-gocardless/gocardless-node.types';\n\nimport { title } from './title/index';\n\nfunction formatPayeeIban(iban: string) {\n return '(' + iban.slice(0, 4) + ' XXX ' + iban.slice(-4) + ')';\n}\n\nexport const formatPayeeName = (trans: Transaction) => {\n const amount = Number(trans.transactionAmount.amount);\n const nameParts = [];\n\n // get the correct name and account fields for the transaction amount\n let name;\n let account;\n if (amount > 0 || Object.is(amount, 0)) {\n name = trans.debtorName;\n account = trans.debtorAccount;\n } else {\n name = trans.creditorName;\n account = trans.creditorAccount;\n }\n\n // use the correct name field if it was found\n // if not, use whatever we can find\n\n // if the primary name option is set, prevent the account from falling back\n account = name ? account : trans.debtorAccount || trans.creditorAccount;\n\n name =\n name ||\n trans.debtorName ||\n trans.creditorName ||\n trans.remittanceInformationUnstructured ||\n (trans.remittanceInformationUnstructuredArray || []).join(', ') ||\n trans.additionalInformation;\n\n if (name) {\n nameParts.push(title(name));\n }\n\n if (typeof account === 'object' && account && account.iban) {\n nameParts.push(formatPayeeIban(account.iban));\n }\n\n return nameParts.join(' ');\n};\n","import * as d from 'date-fns';\n\nimport {\n amountToInteger,\n printIban,\n sortByBookingDateOrValueDate,\n} from '#app-gocardless/utils';\nimport { formatPayeeName } from '#util/payee-name';\n\nimport type { IBank } from './bank.interface';\n\nconst SORTED_BALANCE_TYPE_LIST = [\n 'closingBooked',\n 'expected',\n 'forwardAvailable',\n 'interimAvailable',\n 'interimBooked',\n 'nonInvoiced',\n 'openingBooked',\n];\n\nexport default {\n institutionIds: ['IntegrationBank'],\n\n normalizeAccount(account) {\n return {\n account_id: account.id,\n institution: account.institution,\n mask: (account?.iban || '0000').slice(-4),\n iban: account?.iban || null,\n name: [\n account.name ?? account.displayName ?? account.product,\n printIban(account),\n account.currency,\n ]\n .filter(Boolean)\n .join(' '),\n official_name: account.product ?? `integration-${account.institution_id}`,\n type: 'checking',\n };\n },\n\n normalizeTransaction(transaction, _booked, editedTransaction?) {\n const trans = editedTransaction ?? transaction;\n\n const date =\n trans.date ||\n transaction.bookingDate ||\n transaction.bookingDateTime ||\n transaction.valueDate ||\n transaction.valueDateTime;\n\n // If we couldn't find a valid date field we filter out this transaction\n // and hope that we will import it again once the bank has processed the\n // transaction further.\n if (!date) {\n return null;\n }\n\n const notes =\n trans.notes ??\n trans.remittanceInformationUnstructured ??\n trans.remittanceInformationUnstructuredArray?.join(' ') ??\n '';\n\n transaction.remittanceInformationUnstructuredArrayString =\n transaction.remittanceInformationUnstructuredArray?.join(',');\n transaction.remittanceInformationStructuredArrayString =\n transaction.remittanceInformationStructuredArray?.join(',');\n\n return {\n ...transaction,\n payeeName: trans.payeeName ?? formatPayeeName(trans),\n date: d.format(d.parseISO(date), 'yyyy-MM-dd'),\n notes,\n };\n },\n\n sortTransactions(transactions = []) {\n return sortByBookingDateOrValueDate(transactions);\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances\n .filter(item => SORTED_BALANCE_TYPE_LIST.includes(item.balanceType))\n .sort(\n (a, b) =>\n SORTED_BALANCE_TYPE_LIST.indexOf(a.balanceType) -\n SORTED_BALANCE_TYPE_LIST.indexOf(b.balanceType),\n )[0];\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'ABANCA_CAGLESMM',\n 'ABANCA_CAGLPTPL',\n 'ABANCA_CORP_CAGLPTPL',\n ],\n\n // Abanca transactions doesn't get the creditorName/debtorName properly\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.creditorName = transaction.remittanceInformationStructured;\n editedTrans.debtorName = transaction.remittanceInformationStructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ABNAMRO_ABNANL2A'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const infoArray = transaction.remittanceInformationUnstructuredArray ?? [];\n\n // There is no remittanceInformationUnstructured, so we'll make it\n editedTrans.remittanceInformationUnstructured = infoArray.join(', ');\n\n // Remove clutter to extract the payee from remittanceInformationUnstructured ...\n // ... when not otherwise provided.\n const payeeName = infoArray\n .map(el => el.match(/^(?:.*\\*)?(.+),PAS\\d+$/))\n .find(match => match)?.[1];\n\n editedTrans.debtorName = transaction.debtorName || payeeName;\n editedTrans.creditorName = transaction.creditorName || payeeName;\n\n editedTrans.date = (transaction.valueDateTime ?? '').slice(0, 10);\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n sortTransactions(transactions = []) {\n return transactions.sort(\n (a, b) =>\n +new Date(b.valueDateTime ?? '') - +new Date(a.valueDateTime ?? ''),\n );\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n if (sortedTransactions.length) {\n const oldestTransaction =\n sortedTransactions[sortedTransactions.length - 1];\n const oldestKnownBalance = amountToInteger(\n oldestTransaction.balanceAfterTransaction?.balanceAmount.amount || 0,\n );\n const oldestTransactionAmount = amountToInteger(\n oldestTransaction.transactionAmount.amount,\n );\n\n return oldestKnownBalance - oldestTransactionAmount;\n } else {\n return amountToInteger(\n balances.find(balance => 'interimBooked' === balance.balanceType)\n ?.balanceAmount.amount || 0,\n );\n }\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['AMERICAN_EXPRESS_AESUDEF1'],\n\n normalizeAccount(account) {\n return {\n ...Fallback.normalizeAccount(account),\n // The `iban` field for these American Express cards is actually a masked\n // version of the PAN. No IBAN is provided.\n mask: account.iban.slice(-5),\n iban: null,\n name: [account.details, `(${account.iban.slice(-5)})`].join(' '),\n official_name: account.details ?? '',\n };\n },\n\n /**\n * For AMERICAN_EXPRESS_AESUDEF1 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use the non-standard `information` balance type\n * which is the only one provided for American Express.\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'information' === balance.balanceType.toString(),\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BANCSABADELL_BSABESBB'],\n\n // Sabadell transactions don't get the creditorName/debtorName properly\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const amount = transaction.transactionAmount.amount;\n\n // The amount is negative for outgoing transactions, positive for incoming transactions.\n const isCreditorPayee = Number.parseFloat(amount) < 0;\n\n const payeeName = (transaction.remittanceInformationUnstructuredArray ?? [])\n .join(' ')\n .trim();\n\n // The payee name is the creditor name for outgoing transactions and the debtor name for incoming transactions.\n editedTrans.creditorName = isCreditorPayee ? payeeName : undefined;\n editedTrans.debtorName = isCreditorPayee ? undefined : payeeName;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BANK_OF_IRELAND_B365_BOFIIE2D'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured = fixupPayee(\n transaction.remittanceInformationUnstructured ?? '',\n );\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n\nfunction fixupPayee(payee: string) {\n let fixedPayee = payee;\n\n // remove all duplicate whitespace\n fixedPayee = fixedPayee.replace(/\\s+/g, ' ').trim();\n\n // remove date prefix\n fixedPayee = fixedPayee.replace(/^(POS)?(C)?[0-9]{1,2}\\w{3}/, '').trim();\n\n // remove direct debit postfix\n fixedPayee = fixedPayee.replace(/sepa dd$/i, '').trim();\n\n // remove bank transfer prefix\n fixedPayee = fixedPayee.replace(/^365 online/i, '').trim();\n\n // remove curve card prefix\n fixedPayee = fixedPayee.replace(/^CRV\\*/, '').trim();\n\n return fixedPayee;\n}\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BANKINTER_BKBKESMM'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured = (\n transaction.remittanceInformationUnstructured ?? ''\n )\n .replaceAll(/\\/Txt\\/(\\w\\|)?/gi, '')\n .replaceAll(';', ' ');\n\n editedTrans.debtorName = transaction.debtorName?.replaceAll(';', ' ');\n editedTrans.creditorName =\n transaction.creditorName?.replaceAll(';', ' ') ??\n editedTrans.remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BELFIUS_GKCCBEBB'],\n\n // The problem is that we have transaction with duplicated transaction ids.\n // This is not expected and the nordigen api has a work-around for some backs\n // They will set an internalTransactionId which is unique\n normalizeTransaction(transaction, booked) {\n transaction.transactionId = transaction.internalTransactionId;\n\n return Fallback.normalizeTransaction(transaction, booked);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BERLINER_SPARKASSE_BELADEBEXXX'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let remittanceInformationUnstructured;\n\n if (transaction.remittanceInformationUnstructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured;\n } else if (transaction.remittanceInformationStructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructured;\n } else if (\n (transaction.remittanceInformationStructuredArray?.length ?? 0) > 0\n ) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructuredArray?.join(' ');\n }\n\n if (transaction.additionalInformation) {\n remittanceInformationUnstructured +=\n ' ' + transaction.additionalInformation;\n }\n\n const usefulCreditorName =\n transaction.ultimateCreditor ||\n transaction.creditorName ||\n transaction.debtorName;\n\n editedTrans.creditorName = usefulCreditorName;\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n /**\n * For SANDBOXFINANCE_SFIN0000 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'FINTRO_BE_GEBABEBB',\n 'HELLO_BE_GEBABEBB',\n 'BNP_BE_GEBABEBB',\n ],\n\n /** BNP_BE_GEBABEBB provides a lot of useful information via the 'additionalField'\n * There does not seem to be a specification of this field, but the following information is contained in its subfields:\n * - for pending transactions: the 'atmPosName'\n * - for booked transactions: the 'narrative'.\n * This narrative subfield is most useful as it contains information required to identify the transaction,\n * especially in case of debit card or instant payment transactions.\n * Do note that the narrative subfield ALSO contains the remittance information if any.\n * The goal of the normalization is to place any relevant information of the additionalInformation\n * field in the remittanceInformationUnstructuredArray field.\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Extract the creditor name to fill it in with information from the\n // additionalInformation field in case it's not yet defined.\n let creditorName = transaction.creditorName;\n\n if (transaction.additionalInformation) {\n const additionalInformationObject: Record<string, string> = {};\n const additionalInfoRegex = /(, )?([^:]+): ((\\[.*?\\])|([^,]*))/g;\n const matches =\n transaction.additionalInformation.matchAll(additionalInfoRegex);\n if (matches) {\n let creditorNameFromNarrative; // Possible value for creditorName\n for (const match of matches) {\n const key = match[2].trim();\n let value = (match[4] || match[5]).trim();\n if (key === 'narrative') {\n // Set narrativeName to the first element in the \"narrative\" array.\n const first_value = value.matchAll(/'(.+?)'/g)?.next().value;\n creditorNameFromNarrative = first_value\n ? first_value[1].trim()\n : undefined;\n }\n // Remove square brackets and single quotes and commas\n value = value.replace(/[[\\]',]/g, '');\n additionalInformationObject[key] = value;\n }\n // Keep existing unstructuredArray and add atmPosName and narrative\n editedTrans.remittanceInformationUnstructuredArray = [\n ...(transaction.remittanceInformationUnstructuredArray ?? []),\n additionalInformationObject?.atmPosName,\n additionalInformationObject?.narrative,\n ].filter(Boolean) as string[];\n\n // If the creditor name doesn't exist in the original transactions,\n // set it to the atmPosName or narrativeName if they exist; otherwise\n // leave empty and let the default rules handle it.\n creditorName =\n creditorName ??\n additionalInformationObject?.atmPosName ??\n creditorNameFromNarrative ??\n null;\n }\n }\n\n editedTrans.creditorName = creditorName;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { title } from '#util/title';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nconst regexCard =\n /^CARTE (?<date>\\d{2}\\/\\d{2}\\/\\d{2}) (?<payeeName>.+?)( \\d+)?( CB\\*\\d{4})?$/;\nconst regexAtmWithdrawal =\n /^RETRAIT DAB (?<date>\\d{2}\\/\\d{2}\\/\\d{2}) (?<locationName>.+?) CB\\*\\d{4,}/;\nconst regexTransfer = /^VIR /;\nconst regexInstantTransfer = /^VIR INST /;\nconst regexSepa = /^(PRLV|VIR) SEPA /;\nconst regexLoan = /^ECH PRET:/;\nconst regexCreditNote =\n /^AVOIR (?<date>\\d{2}\\/\\d{2}\\/\\d{2}) (?<payeeName>.+?) CB\\*\\d{4,}/;\n\nexport default {\n ...Fallback,\n\n institutionIds: ['BOURSORAMA_BOUSFRPP'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructuredArray = (\n transaction.remittanceInformationUnstructuredArray ?? []\n )\n // Remove the localisation with backslashes that are sometimes present\n .map(line => line.replace(/\\\\.+/g, ''))\n // Remove an unwanted line that pollutes the remittance information\n .filter(line => line.startsWith('Réf : ') === false);\n\n const infoArray = editedTrans.remittanceInformationUnstructuredArray;\n\n let match: string | undefined;\n\n // Transactions can have their identifier in any line, as the order of lines is not guaranteed.\n // This is why we check all lines for specific patterns.\n if ((match = infoArray.find(line => regexCard.test(line)))) {\n // Card transaction\n const cardMatch = match.match(regexCard);\n editedTrans.payeeName = title(cardMatch?.groups?.payeeName ?? '');\n editedTrans.notes = `Carte ${cardMatch?.groups?.date}`;\n if (infoArray.length > 1) {\n editedTrans.notes += ' ' + infoArray.filter(l => l !== match).join(' ');\n }\n } else if ((match = infoArray.find(line => regexLoan.test(line)))) {\n // Loan\n editedTrans.payeeName = 'Prêt bancaire';\n editedTrans.notes = match;\n } else if (\n (match = infoArray.find(line => regexAtmWithdrawal.test(line)))\n ) {\n // ATM withdrawal\n const atmMatch = match.match(regexAtmWithdrawal);\n editedTrans.payeeName = 'Retrait DAB';\n editedTrans.notes = `Retrait ${atmMatch?.groups?.date} ${atmMatch?.groups?.locationName}`;\n if (infoArray.length > 1) {\n editedTrans.notes += ' ' + infoArray.filter(l => l !== match).join(' ');\n }\n } else if ((match = infoArray.find(line => regexCreditNote.test(line)))) {\n // Credit note (refund)\n const creditMatch = match.match(regexCreditNote);\n editedTrans.payeeName = title(creditMatch?.groups?.payeeName ?? '');\n editedTrans.notes = `Avoir ${creditMatch?.groups?.date}`;\n if (infoArray.length > 1) {\n editedTrans.notes += ' ' + infoArray.filter(l => l !== match).join(' ');\n }\n } else if (\n (match = infoArray.find(line => regexInstantTransfer.test(line)))\n ) {\n // Instant transfer\n editedTrans.payeeName = title(match.replace(regexInstantTransfer, ''));\n editedTrans.notes = infoArray.filter(l => l !== match).join(' ');\n } else if ((match = infoArray.find(line => regexSepa.test(line)))) {\n // SEPA transfer\n editedTrans.payeeName = title(match.replace(regexSepa, ''));\n editedTrans.notes = infoArray.filter(l => l !== match).join(' ');\n } else if ((match = infoArray.find(line => regexTransfer.test(line)))) {\n // Other transfer\n // Must be evaluated after the other transfers as they're more specific\n // (here VIR only)\n const infoArrayWithoutLine = infoArray.filter(l => l !== match);\n editedTrans.payeeName = title(infoArrayWithoutLine.join(' '));\n editedTrans.notes = match.replace(regexTransfer, '');\n } else {\n // Unknown transaction type\n editedTrans.payeeName = title(infoArray[0].replace(/ \\d+$/, ''));\n editedTrans.notes = infoArray.slice(1).join(' ');\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","/**\n * Normalize BPER Retail BPMOIT22 transactions by extracting a friendly payee\n * while keeping the raw description in the notes field.\n */\n\nimport type { Transaction } from '#app-gocardless/gocardless-node.types';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nconst CARD_PAYMENT_PREFIX = 'PAGAMENTO SU CIRCUITO INTERNAZIONALE';\nconst CARD_PAYMENT_SUFFIX = 'Operazione carta';\nconst BONIFICO_PREFIX = 'BONIFICO';\nconst BONIFICO_ESTERI_PREFIX = 'BONIFICI ESTERI';\nconst SDD_PREFIX = 'ADDEBITO SDD';\nconst BOLLETTINO_MARKER = 'CREDITORE:';\n\nconst BONIFICO_ORIGINATOR_REGEX =\n /o\\/c:\\s*([A-Z0-9\\s.'/&-]+?)(?:ABI|BIC|IBAN|a favore di|Num|EUR|$)/i;\nconst SDD_PAYEE_REGEX = /ADDEBITO SDD\\s+([A-Z0-9\\s.'/&-]+?)(?:N:|ID:|$)/i;\nconst BOLLETTINO_PAYEE_REGEX = /CREDITORE:\\s*([A-Z0-9\\s.'/&-]+)/i;\n\n// Extract payee for card transactions\nfunction parseCardPayee(description: string) {\n const [beforeSuffix] = description.split(CARD_PAYMENT_SUFFIX);\n\n return beforeSuffix.replace(CARD_PAYMENT_PREFIX, '').trim();\n}\n\n// Extract originator for bonifico (domestic/foreign transfers)\nfunction parseBonificoOriginator(description: string) {\n const match = description.match(BONIFICO_ORIGINATOR_REGEX);\n\n return match ? match[1].trim() : '';\n}\n\n// Extract creditor for SDD direct debits\nfunction parseSddPayee(description: string) {\n const match = description.match(SDD_PAYEE_REGEX);\n\n return match ? match[1].trim() : '';\n}\n\n// Extract creditor for bollettini / utilities\nfunction parseBollettinoPayee(description: string) {\n const match = description.match(BOLLETTINO_PAYEE_REGEX);\n\n return match ? match[1].trim() : '';\n}\n\nfunction setPayee(editedTransaction: Transaction, payee: string) {\n if (!payee) {\n return;\n }\n\n editedTransaction.creditorName = payee;\n editedTransaction.debtorName = payee;\n}\n\nconst BperRetailBank = {\n ...Fallback,\n\n institutionIds: ['BPER_RETAIL_BPMOIT22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n const description = (\n transaction.remittanceInformationUnstructured || ''\n ).trim();\n\n if (description) {\n editedTrans.remittanceInformationUnstructured = description;\n }\n\n let payee = '';\n\n if (description.startsWith(CARD_PAYMENT_PREFIX)) {\n payee = parseCardPayee(description);\n } else if (\n description.startsWith(BONIFICO_PREFIX) ||\n description.startsWith(BONIFICO_ESTERI_PREFIX)\n ) {\n payee = parseBonificoOriginator(description);\n } else if (description.startsWith(SDD_PREFIX)) {\n payee = parseSddPayee(description);\n } else if (description.includes(BOLLETTINO_MARKER)) {\n payee = parseBollettinoPayee(description);\n }\n\n setPayee(editedTrans, payee);\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n\nexport default BperRetailBank;\n","/**\n * Extracts the payee name from the unstructured remittance information string based on pattern detection.\n *\n * This function scans the `remittanceInformationUnstructured` string for the presence of\n * any of the specified patterns and removes the substring from the position of the last\n * occurrence of the most relevant pattern. If no patterns are found, it returns the original string.\n *\n * @param remittanceInformationUnstructured - The unstructured remittance information from which to extract the payee name.\n * @param patterns - An array of patterns to look for within the remittance information.\n * These patterns are used to identify and remove unwanted parts of the remittance information.\n * @returns The extracted payee name, cleaned of any matched patterns, or the original\n * remittance information if no patterns are found.\n *\n * @example\n * const remittanceInfo = 'John Doe Paiement Maestro par Carte de débit CBC 05-09-2024 à 15.43 heures 6703 19XX XXXX X...';\n * const patterns = ['Paiement', 'Domiciliation', 'Transfert', 'Ordre permanent'];\n * const payeeName = extractPayeeNameFromRemittanceInfo(remittanceInfo, patterns); // --> 'John Doe'\n */\nexport function extractPayeeNameFromRemittanceInfo(\n remittanceInformationUnstructured: string,\n patterns: string[],\n): string {\n if (!remittanceInformationUnstructured || !patterns.length) {\n return remittanceInformationUnstructured;\n }\n\n const indexForRemoval = patterns.reduce((maxIndex, pattern) => {\n const index = remittanceInformationUnstructured.lastIndexOf(pattern);\n return index > maxIndex ? index : maxIndex;\n }, -1);\n\n return indexForRemoval > -1\n ? remittanceInformationUnstructured.substring(0, indexForRemoval).trim()\n : remittanceInformationUnstructured;\n}\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\nimport { extractPayeeNameFromRemittanceInfo } from './util/extract-payeeName-from-remittanceInfo';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['CBC_CREGBEBB'],\n\n /**\n * For negative amounts, the only payee information we have is returned in\n * remittanceInformationUnstructured.\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n if (Number(transaction.transactionAmount.amount) > 0) {\n editedTrans.payeeName =\n transaction.debtorName ||\n transaction.remittanceInformationUnstructured ||\n 'undefined';\n } else {\n editedTrans.payeeName =\n transaction.creditorName ||\n extractPayeeNameFromRemittanceInfo(\n transaction.remittanceInformationUnstructured ?? '',\n ['Paiement', 'Domiciliation', 'Transfert', 'Ordre permanent'],\n ) ||\n 'undefined';\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['CETELEM_CETMPTP1XXX'],\n\n /**\n * Sign of transaction amount needs to be flipped for Cetelem Black credit cards\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n transaction.transactionAmount = {\n // Flip transaction amount sign\n ...transaction.transactionAmount,\n amount: (-parseFloat(transaction.transactionAmount.amount)).toString(),\n };\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","// escape special characters in the string to create a valid regular expression\nexport function escapeRegExp(string: string): string {\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\nimport { escapeRegExp } from './util/escape-regexp';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['COMMERZBANK_COBADEFF'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // remittanceInformationUnstructured is limited to 140 chars thus ...\n // ... missing information form remittanceInformationUnstructuredArray ...\n // ... so we recreate it.\n editedTrans.remittanceInformationUnstructured = (\n transaction.remittanceInformationUnstructuredArray ?? []\n ).join(' ');\n\n // The limitations of remittanceInformationUnstructuredArray ...\n // ... can result in split keywords. We fix these. Other ...\n // ... splits will need to be fixed by user with rules.\n const keywords = [\n 'End-to-End-Ref.:',\n 'Mandatsref:',\n 'Gläubiger-ID:',\n 'SEPA-BASISLASTSCHRIFT',\n 'Kartenzahlung',\n 'Dauerauftrag',\n ];\n keywords.forEach(keyword => {\n editedTrans.remittanceInformationUnstructured = (\n editedTrans.remittanceInformationUnstructured ?? ''\n ).replace(\n // There can be spaces in keywords\n RegExp(keyword.split('').join('\\\\s*'), 'gi'),\n ', ' + keyword + ' ',\n );\n });\n\n // Clean up remittanceInformation, deduplicate payee (removing slashes ...\n // ... that are added to the remittanceInformation field), and ...\n // ... remove clutter like \"End-to-End-Ref.: NOTPROVIDED\"\n const payee = escapeRegExp(\n transaction.creditorName || transaction.debtorName || '',\n );\n editedTrans.remittanceInformationUnstructured =\n editedTrans.remittanceInformationUnstructured\n .replace(/\\s*(,)?\\s+/g, '$1 ')\n .replace(RegExp(payee.split(' ').join('(/*| )'), 'gi'), ' ')\n .replace(', End-to-End-Ref.: NOTPROVIDED', '')\n .trim();\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n // TODO: Add other Danske Bank BICs?\n // https://danskeci.com/ci/transaction-banking/bank-identifier-code\n institutionIds: ['DANSKEBANK_DABADKKK', 'DANSKEBANK_DABANO22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n /**\n * Danske Bank appends the EndToEndID: NOTPROVIDED to\n * remittanceInformationUnstructured, cluttering the data.\n *\n * We clean thais up by removing any instances of this string from all transactions.\n *\n */\n editedTrans.remittanceInformationUnstructured = (\n transaction.remittanceInformationUnstructured ?? ''\n ).replace('\\nEndToEndID: NOTPROVIDED', '');\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => balance.balanceType === 'interimAvailable',\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['DIREKT_HELADEF1822'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ??\n transaction.remittanceInformationStructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { Transaction } from '#app-gocardless/gocardless-node.types';\nimport { formatPayeeName } from '#util/payee-name';\nimport { title } from '#util/title';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['EASYBANK_BAWAATWW'],\n\n // If date is same, sort by transactionId\n sortTransactions: (transactions = []) =>\n transactions.sort((a, b) => {\n const diff =\n +new Date(b.valueDate || b.bookingDate || '') -\n +new Date(a.valueDate || a.bookingDate || '');\n if (diff !== 0) return diff;\n return parseInt(b.transactionId ?? '') - parseInt(a.transactionId ?? '');\n }),\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let payeeName = formatPayeeName(transaction);\n if (!payeeName) payeeName = extractPayeeName(transaction);\n editedTrans.payeeName = payeeName;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n\n// extracts the payee name from the remittanceInformationStructured\nfunction extractPayeeName(transaction: Transaction) {\n const structured = transaction.remittanceInformationStructured ?? '';\n // The payee name is betweeen the transaction timestamp (11.07. 11:36) and the location, that starts with \\\\\n const regex = /\\d{2}\\.\\d{2}\\. \\d{2}:\\d{2}(.*)\\\\\\\\/;\n const matches = structured.match(regex);\n if (matches && matches.length > 1 && matches[1]) {\n return title(matches[1]);\n } else {\n // As a fallback if still no payee is found, the whole information is used\n return structured;\n }\n}\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ENTERCARD_SWEDNOKK'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // GoCardless's Entercard integration returns forex transactions with the\n // foreign amount in `transactionAmount`, but at least the amount actually\n // billed to the account is now available in\n // `remittanceInformationUnstructured`.\n const remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ?? '';\n if (remittanceInformationUnstructured.startsWith('billingAmount: ')) {\n transaction.transactionAmount = {\n amount: remittanceInformationUnstructured.substring(15),\n currency: 'SEK',\n };\n }\n\n editedTrans.date = transaction.valueDate;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(balances[0]?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['FORTUNEO_FTNOFRP1XXX'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Most of the information from the transaction is in the remittanceInformationUnstructuredArray field.\n // We extract the creditor and debtor names from this field.\n // The remittanceInformationUnstructuredArray field usually contain keywords like \"Vir\" for\n // bank transfers or \"Carte 03/06\" for card payments, as well as the date.\n // We remove these keywords to get a cleaner payee name.\n const keywordsToRemove = [\n 'VIR INST',\n 'VIR',\n 'PRLV',\n 'ANN CARTE',\n 'CARTE \\\\d{2}\\\\/\\\\d{2}',\n ];\n\n const details = (\n transaction.remittanceInformationUnstructuredArray ?? []\n ).join(' ');\n const amount = transaction.transactionAmount.amount;\n\n const regex = new RegExp(keywordsToRemove.join('|'), 'g');\n const payeeName = details.replace(regex, '').trim();\n\n // The amount is negative for outgoing transactions, positive for incoming transactions.\n const isCreditorPayee = parseFloat(amount) < 0;\n\n // The payee name is the creditor name for outgoing transactions and the debtor name for incoming transactions.\n editedTrans.creditorName = isCreditorPayee ? payeeName : undefined;\n editedTrans.debtorName = isCreditorPayee ? undefined : payeeName;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['HYPE_HYEEIT22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n /** Online card payments - identified by \"crd\" transaction code\n * always start with PAGAMENTO PRESSO + <payee name>\n */\n if (transaction.proprietaryBankTransactionCode === 'crd') {\n // remove PAGAMENTO PRESSO and set payee name\n editedTrans.debtorName =\n transaction.remittanceInformationUnstructured?.slice(\n 'PAGAMENTO PRESSO '.length,\n );\n }\n /**\n * In-app money transfers (p2p) and bank transfers (bon) have remittance info structure like\n * DENARO (INVIATO/RICEVUTO) (A/DA) {payee_name} - {payment_info} (p2p)\n * HAI (INVIATO/RICEVUTO) UN BONIFICO (A/DA) {payee_name} - {payment_info} (bon)\n */\n if (\n transaction.proprietaryBankTransactionCode === 'p2p' ||\n transaction.proprietaryBankTransactionCode === 'bon'\n ) {\n // keep only {payment_info} portion of remittance info\n // NOTE: if {payee_name} contains dashes (unlikely / impossible?), this probably gets bugged!\n const remittance = transaction.remittanceInformationUnstructured ?? '';\n const idx = remittance.indexOf(' - ');\n editedTrans.remittanceInformationUnstructured =\n idx === -1 ? remittance : remittance.slice(idx + 3).trim();\n }\n /**\n * CONVERT ESCAPED UNICODE TO CODEPOINTS\n * p2p payments allow user to write arbitrary unicode strings as messages\n * gocardless reports unicode codepoints as \\Uxxxx\n * so it groups them in 4bytes bundles\n * the code below assumes this is always the case\n */\n if (transaction.proprietaryBankTransactionCode === 'p2p') {\n let str = transaction.remittanceInformationUnstructured ?? '';\n let idx = str.indexOf('\\\\U');\n let start_idx = idx;\n let codepoints = [];\n while (idx !== -1) {\n codepoints.push(parseInt(str.slice(idx + 2, idx + 6), 16));\n const next_idx = str.indexOf('\\\\U', idx + 6);\n if (next_idx === idx + 6) {\n idx = next_idx;\n continue;\n }\n str =\n str.slice(0, start_idx) +\n String.fromCodePoint(...codepoints) +\n str.slice(idx + 6);\n codepoints = [];\n idx = str.indexOf('\\\\U'); // slight inefficiency?\n start_idx = idx;\n }\n editedTrans.remittanceInformationUnstructured = str;\n }\n\n editedTrans.date = transaction.valueDate || transaction.bookingDate;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ING_INGBROBU'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n //Merchant transactions all have the same transactionId of 'NOTPROVIDED'.\n //For booked transactions, this can be set to the internalTransactionId\n //For pending transactions, this needs to be removed for them to show up in Actual\n\n //For deduplication to work better, payeeName needs to be standardized\n //and converted from a pending transaction form (\"payeeName\":\"Card no: xxxxxxxxxxxx1111\"') to a booked transaction form (\"payeeName\":\"Card no: Xxxx Xxxx Xxxx 1111\")\n if (transaction.transactionId === 'NOTPROVIDED') {\n //Some corner case transactions only have the `proprietaryBankTransactionCode` field, this need to be copied to `remittanceInformationUnstructured`\n if (\n transaction.proprietaryBankTransactionCode &&\n !transaction.remittanceInformationUnstructured\n ) {\n editedTrans.remittanceInformationUnstructured =\n transaction.proprietaryBankTransactionCode;\n }\n\n if (booked) {\n transaction.transactionId = transaction.internalTransactionId;\n if (\n transaction.remittanceInformationUnstructured &&\n transaction.remittanceInformationUnstructured\n .toLowerCase()\n .includes('card no:')\n ) {\n editedTrans.creditorName =\n transaction.remittanceInformationUnstructured.split(',')[0];\n //Catch all case for other types of payees\n } else {\n editedTrans.creditorName =\n transaction.remittanceInformationUnstructured;\n }\n } else {\n transaction.transactionId = undefined;\n\n if (\n transaction.remittanceInformationUnstructured &&\n transaction.remittanceInformationUnstructured\n .toLowerCase()\n .includes('card no:')\n ) {\n editedTrans.creditorName =\n transaction.remittanceInformationUnstructured.replace(\n /x{4}/g,\n 'Xxxx ',\n );\n //Catch all case for other types of payees\n } else {\n editedTrans.creditorName =\n transaction.remittanceInformationUnstructured;\n }\n //Remove remittanceInformationUnstructured from pending transactions, so the `notes` field remains empty (there is no merchant information)\n //Once booked, the right `notes` (containing the merchant) will be populated\n editedTrans.remittanceInformationUnstructured = undefined;\n }\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ING_INGDDEFF'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const remittanceInformationMatch = /remittanceinformation:(.*)$/.exec(\n transaction.remittanceInformationUnstructured ?? '',\n );\n\n editedTrans.remittanceInformationUnstructured = remittanceInformationMatch\n ? remittanceInformationMatch[1]\n : transaction.remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n sortTransactions(transactions = []) {\n return transactions.sort((a, b) => {\n const diff =\n +new Date(b.valueDate || b.bookingDate || '') -\n +new Date(a.valueDate || a.bookingDate || '');\n if (diff) return diff;\n const idA = parseInt(a.transactionId ?? '');\n const idB = parseInt(b.transactionId ?? '');\n if (!isNaN(idA) && !isNaN(idB)) return idB - idA;\n return 0;\n });\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimBooked' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ING_PL_INGBPLPW'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.date = transaction.valueDate;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n sortTransactions(transactions = []) {\n return transactions.sort((a, b) => {\n return (\n Number((b.transactionId ?? '').substr(2)) -\n Number((a.transactionId ?? '').substr(2))\n );\n });\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n if (sortedTransactions.length) {\n const oldestTransaction =\n sortedTransactions[sortedTransactions.length - 1];\n const oldestKnownBalance = amountToInteger(\n oldestTransaction.balanceAfterTransaction?.balanceAmount.amount || 0,\n );\n const oldestTransactionAmount = amountToInteger(\n oldestTransaction.transactionAmount.amount,\n );\n\n return oldestKnownBalance - oldestTransactionAmount;\n } else {\n return amountToInteger(\n balances.find(balance => 'interimBooked' === balance.balanceType)\n ?.balanceAmount.amount || 0,\n );\n }\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['ISYBANK_ITBBITMM'],\n\n // It has been reported that valueDate is more accurate than booking date\n // when it is provided\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.date = transaction.valueDate ?? transaction.bookingDate;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\nimport { extractPayeeNameFromRemittanceInfo } from './util/extract-payeeName-from-remittanceInfo';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['KBC_KREDBEBB', 'KBC_BRUSSELS_KREDBEBB'],\n\n /**\n * For negative amounts, the only payee information we have is returned in\n * remittanceInformationUnstructured.\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n if (Number(transaction.transactionAmount.amount) > 0) {\n editedTrans.payeeName =\n transaction.debtorName ||\n transaction.remittanceInformationUnstructured ||\n 'undefined';\n } else {\n editedTrans.payeeName =\n transaction.creditorName ||\n extractPayeeNameFromRemittanceInfo(\n transaction.remittanceInformationUnstructured ?? '',\n ['Betaling met', 'Domiciliëring', 'Overschrijving'],\n );\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import * as d from 'date-fns';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['LHV_LHVBEE22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // extract bookingDate and creditorName for card transactions, e.g.\n // (..1234) 2025-01-02 09:32 CrustumOU\\Poordi 3\\Tallinn\\10156 ESTEST\n // bookingDate: 2025-01-02\n // creditorName: CrustumOU\n const cardTxRegex =\n /^\\(\\.\\.(\\d{4})\\) (\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}) (.+)$/g;\n const cardTxMatch = cardTxRegex.exec(\n transaction?.remittanceInformationUnstructured ?? '',\n );\n\n if (cardTxMatch) {\n const extractedDate = d.parse(cardTxMatch[2], 'yyyy-MM-dd', new Date());\n\n editedTrans.payeeName = cardTxMatch[4].split('\\\\')[0].trim();\n\n if (booked && d.isValid(extractedDate)) {\n editedTrans.date = d.format(extractedDate, 'yyyy-MM-dd');\n }\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['MBANK_RETAIL_BREXPLPW'],\n\n /**\n * When requesting transaction details for MBANK_RETAIL_BREXPLPW\n * using gocardless API, it seems that bookingDate and valueDate are swapped.\n * valueDate will always come before bookingDate, so as a simple fix,\n * I have overwritten integration-bank.normalizeTransaction() here,\n * swapped dates back (by giving valueDate higher priority) and\n * called parent method with edited transaction as argument\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const date =\n transaction.valueDate ||\n transaction.valueDateTime ||\n transaction.bookingDate ||\n transaction.bookingDateTime;\n\n editedTrans.date = date;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n sortTransactions(transactions = []) {\n return transactions.sort(\n (a, b) => Number(b.transactionId) - Number(a.transactionId),\n );\n },\n\n /**\n * For MBANK_RETAIL_BREXPLPW we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimBooked' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['NATIONWIDE_NAIAGB21'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Nationwide can sometimes return pending transactions with a date\n // representing the latest a transaction could be booked. This stops\n // actual's deduplication logic from working as it only checks 7 days\n // ahead/behind and the transactionID from Nationwide changes when a\n // transaction is booked\n if (!booked) {\n const useDate = new Date(\n Math.min(\n new Date(transaction.bookingDate ?? '').getTime(),\n new Date().getTime(),\n ),\n );\n editedTrans.date = useDate.toISOString().slice(0, 10);\n }\n\n // Nationwide also occasionally returns erroneous transaction_ids\n // that are malformed and can even change after import. This will ignore\n // these ids and unset them. When a correct ID is returned then it will\n // update via the deduplication logic\n const debitCreditRegex = /^00(DEB|CRED)IT.+$/;\n const validLengths = [\n 40, // Nationwide credit cards\n 32, // Nationwide current accounts\n ];\n\n if (\n transaction.transactionId?.match(debitCreditRegex) ||\n !validLengths.includes(transaction.transactionId?.length ?? -1)\n ) {\n transaction.transactionId = undefined;\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['NBG_ETHNGRAAXXX'],\n\n /**\n * Fixes for the pending transactions:\n * - Corrects amount to negative (nbg erroneously omits the minus sign in pending transactions)\n * - Removes prefix 'ΑΓΟΡΑ' from remittance information to align with the booked transaction (necessary for fuzzy matching to work)\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n if (\n !transaction.transactionId &&\n (transaction.remittanceInformationUnstructured ?? '').startsWith('ΑΓΟΡΑ ')\n ) {\n transaction.transactionAmount = {\n amount: '-' + transaction.transactionAmount.amount,\n currency: transaction.transactionAmount.currency,\n };\n editedTrans.remittanceInformationUnstructured = (\n transaction.remittanceInformationUnstructured ?? ''\n ).substring(6);\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n /**\n * For NBG_ETHNGRAAXXX we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'NORWEGIAN_NO_NORWNOK1',\n 'NORWEGIAN_SE_NORWNOK1',\n 'NORWEGIAN_DE_NORWNOK1',\n 'NORWEGIAN_DK_NORWNOK1',\n 'NORWEGIAN_ES_NORWNOK1',\n 'NORWEGIAN_FI_NORWNOK1',\n ],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n if (booked) {\n editedTrans.date = transaction.bookingDate;\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n }\n\n /**\n * For pending transactions there are two possibilities:\n *\n * - Either a `valueDate` was set, in which case it corresponds to when the\n * transaction actually occurred, or\n * - There is no date field, in which case we try to parse the correct date\n * out of the `remittanceInformationStructured` field.\n *\n * If neither case succeeds then we return `null` causing this transaction\n * to be filtered out for now, and hopefully we'll be able to import it\n * once the bank has processed it further.\n */\n if (transaction.valueDate !== undefined) {\n editedTrans.date = transaction.valueDate;\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n }\n\n if (transaction.remittanceInformationStructured) {\n const remittanceInfoRegex = / (\\d{4}-\\d{2}-\\d{2}) /;\n const matches =\n transaction.remittanceInformationStructured.match(remittanceInfoRegex);\n if (matches) {\n editedTrans.date = matches[1];\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n }\n }\n\n return null;\n },\n\n /**\n * For NORWEGIAN_XX_NORWNOK1 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `expected` balance type because it\n * corresponds to the current running balance, whereas `interimAvailable`\n * holds the remaining credit limit.\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'expected' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { Transaction } from '#app-gocardless/gocardless-node.types';\nimport { formatPayeeName } from '#util/payee-name';\nimport { title } from '#util/title';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['RAIFFEISEN_AT_RZBAATWW'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let payeeName = formatPayeeName(transaction);\n if (!payeeName) {\n payeeName = extractPayeeName(transaction);\n }\n editedTrans.payeeName = payeeName;\n\n // avoid empty notes if payee is set but no information in unstructured information\n // if no structured or unstructured information is provided, return the endToEndId instead\n editedTrans.remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ??\n transaction.remittanceInformationStructured ??\n transaction.endToEndId;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n\n// extracts the payee name from the remittanceInformationStructured\nfunction extractPayeeName(transaction: Transaction) {\n const structured = transaction.remittanceInformationStructured ?? '';\n // The payee name is at the beginning and has a max length of 12 characters\n // (if structured information is actually structured ...).\n const regex = /(.{12}) \\d{4} .* \\d{2}\\.\\d{2}\\. \\d{2}:\\d{2}/;\n const matches = structured.match(regex);\n if (matches && matches.length > 1 && matches[1]) {\n const name = title(matches[1]);\n // These transactions never contained creditor information in my tests, thus no\n // attempt to add the IBAN to the name...\n return name;\n } else {\n // As a fallback if still no payee is found, the whole information is used\n return structured;\n }\n}\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['REVOLUT_REVOLT21'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const infoArray = transaction.remittanceInformationUnstructuredArray ?? [];\n\n if (infoArray[0]?.startsWith('Bizum payment from: ')) {\n editedTrans.payeeName = infoArray[0].replace('Bizum payment from: ', '');\n editedTrans.remittanceInformationUnstructured = infoArray[1];\n }\n\n if (infoArray[0]?.startsWith('Bizum payment to: ')) {\n editedTrans.remittanceInformationUnstructured = infoArray[1];\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SANDBOXFINANCE_SFIN0000'],\n\n /**\n * For SANDBOXFINANCE_SFIN0000 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'SEB_KORT_AB_NO_SKHSFI21',\n 'SEB_KORT_AB_SE_SKHSFI21',\n 'SEB_CARD_ESSESESS',\n 'NORDIC_CHOICE_CLUB_NO_SKHSFI21',\n 'NORDIC_CHOICE_CLUB_SE_SKHSFI21',\n 'EUROCARD_SE_SKHSFI21',\n 'EUROCARD_DK_SKHSFI21',\n 'EUROCARD_FI_SKHSFI21',\n 'EUROCARD_NO_SKHSFI21',\n 'GLOBECARD_DK_SKHSFI21',\n 'GLOBECARD_NO_SKHSFI21',\n 'OPEL_MASTERCARD_SKHSFI21',\n 'SAAB_MASTERCARD_SKHSFI21',\n 'SAS_MASTERCARD_NO_SKHSFI21',\n 'SAS_MASTERCARD_SE_SKHSFI21',\n 'SAS_MASTERCARD_FI_SKHSFI21',\n 'SAS_MASTERCARD_DK_SKHSFI21',\n 'SJ_PRIO_MASTERCARD_SKHSFI21',\n 'CIRCLE_K_MASTERCARD_NO_SKHSFI21',\n 'CIRCLE_K_MASTERCARD_SE_SKHSFI21',\n 'CIRCLE_K_MASTERCARD_DK_SKHSFI21',\n 'WALLET_SKHSFI21',\n 'INGO_MASTERCARD_SKHSFI21',\n 'SCANDIC_SKHSFI21',\n ],\n\n /**\n * Sign of transaction amount needs to be flipped for SEB credit cards\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Creditor name is stored in additionInformation for SEB\n editedTrans.creditorName = transaction.additionalInformation;\n transaction.transactionAmount = {\n // Flip transaction amount sign\n amount: (-parseFloat(transaction.transactionAmount.amount)).toString(),\n currency: transaction.transactionAmount.currency,\n };\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n /**\n * For SEB_KORT_AB_NO_SKHSFI21 and SEB_KORT_AB_SE_SKHSFI21 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `expected` and `nonInvoiced` balance types because it\n * corresponds to the current running balance, whereas `interimAvailable`\n * holds the remaining credit limit.\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'expected' === balance.balanceType,\n );\n\n const nonInvoiced = balances.find(\n balance => 'nonInvoiced' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n -amountToInteger(currentBalance?.balanceAmount.amount || 0) +\n amountToInteger(nonInvoiced?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SEB_ESSESESS_PRIVATE'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // Creditor name is stored in additionInformation for SEB\n editedTrans.creditorName = transaction.additionalInformation;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimBooked' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: [\n 'SPARNORD_SPNODK22',\n 'LAGERNES_BANK_LAPNDKK1',\n 'ANDELSKASSEN_FALLESKASSEN_FAELDKK1',\n ],\n\n /**\n * Banks on the BEC backend only give information regarding the transaction in additionalInformation\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured =\n transaction.additionalInformation;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SPK_KARLSRUHE_KARSDE66XXX'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let remittanceInformationUnstructured;\n\n if (transaction.remittanceInformationUnstructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured;\n } else if (transaction.remittanceInformationStructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructured;\n } else if (\n (transaction.remittanceInformationStructuredArray?.length ?? 0) > 0\n ) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructuredArray?.join(' ');\n }\n\n if (transaction.additionalInformation) {\n remittanceInformationUnstructured +=\n ' ' + transaction.additionalInformation;\n }\n\n const usefulCreditorName =\n transaction.ultimateCreditor ||\n transaction.creditorName ||\n transaction.debtorName;\n\n editedTrans.creditorName = usefulCreditorName;\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n\n /**\n * For SANDBOXFINANCE_SFIN0000 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SPK_MARBURG_BIEDENKOPF_HELADEF1MAR'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n let remittanceInformationUnstructured;\n\n if (transaction.remittanceInformationUnstructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured;\n } else if (transaction.remittanceInformationStructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructured;\n } else if (\n (transaction.remittanceInformationStructuredArray?.length ?? 0) > 0\n ) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructuredArray?.join(' ');\n }\n\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SPK_WORMS_ALZEY_RIED_MALADE51WOR'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n editedTrans.remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ??\n transaction.remittanceInformationStructured ??\n transaction.remittanceInformationStructuredArray?.join(' ');\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SSK_DUSSELDORF_DUSSDEDDXXX'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n // If the transaction is not booked yet by the bank, don't import it.\n // Reason being that the transaction doesn't have the information yet\n // to make the payee and notes field be of any use. It's filled with\n // a placeholder text and wouldn't be corrected on the next sync.\n if (!booked) {\n console.debug(\n 'Skipping unbooked transaction:',\n transaction.transactionId,\n );\n return null;\n }\n\n // Prioritize unstructured information, falling back to structured formats\n let remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured ??\n transaction.remittanceInformationStructured ??\n transaction.remittanceInformationStructuredArray?.join(' ');\n\n if (transaction.additionalInformation) {\n remittanceInformationUnstructured = [\n remittanceInformationUnstructured,\n transaction.additionalInformation,\n ]\n .filter(Boolean)\n .join(' ');\n }\n\n const usefulCreditorName =\n transaction.ultimateCreditor ||\n transaction.creditorName ||\n transaction.debtorName;\n\n editedTrans.creditorName = usefulCreditorName;\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","/**\n * Credit for this code goes to Nebukadneza at https://github.com/Nebukadneza\n */\nimport { amountToInteger } from '#app-gocardless/utils';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n institutionIds: ['SSK_MUNCHEN_SSKMDEMMXXX'],\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n let remittanceInformationUnstructured;\n if (transaction.remittanceInformationUnstructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationUnstructured;\n } else if (transaction.remittanceInformationStructured) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructured;\n } else if (\n (transaction.remittanceInformationStructuredArray?.length ?? 0) > 0\n ) {\n remittanceInformationUnstructured =\n transaction.remittanceInformationStructuredArray?.join(' ');\n }\n if (transaction.additionalInformation) {\n remittanceInformationUnstructured +=\n ' ' + transaction.additionalInformation;\n }\n const usefulCreditorName =\n transaction.ultimateCreditor ||\n transaction.creditorName ||\n transaction.debtorName;\n editedTrans.creditorName = usefulCreditorName;\n editedTrans.remittanceInformationUnstructured =\n remittanceInformationUnstructured;\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n /**\n * For SANDBOXFINANCE_SFIN0000 we don't know what balance was\n * after each transaction so we have to calculate it by getting\n * current balance from the account and subtract all the transactions\n *\n * As a current balance we use `interimBooked` balance type because\n * it includes transaction placed during current day\n */\n calculateStartingBalance(sortedTransactions = [], balances = []) {\n const currentBalance = balances.find(\n balance => 'interimAvailable' === balance.balanceType,\n );\n return sortedTransactions.reduce(\n (total, trans) => {\n return total - amountToInteger(trans.transactionAmount.amount);\n },\n amountToInteger(currentBalance?.balanceAmount.amount || 0),\n );\n },\n} satisfies IBank;\n","import * as d from 'date-fns';\n\nimport type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['SWEDBANK_HABALV22'],\n\n /**\n * The actual transaction date for card transactions is only available in the remittanceInformationUnstructured field when the transaction is booked.\n */\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const isCardTransaction =\n transaction.remittanceInformationUnstructured?.startsWith('PIRKUMS');\n\n if (isCardTransaction) {\n if (!booked && !transaction.creditorName) {\n const creditorNameMatch =\n transaction.remittanceInformationUnstructured?.match(\n /PIRKUMS [\\d*]+ \\d{2}\\.\\d{2}\\.\\d{2} \\d{2}:\\d{2} [\\d.]+ \\w{3} \\(\\d+\\) (.+)/,\n );\n\n if (creditorNameMatch) {\n editedTrans.creditorName = creditorNameMatch[1];\n }\n }\n\n const dateMatch = transaction.remittanceInformationUnstructured?.match(\n /PIRKUMS [\\d*]+ (\\d{2}\\.\\d{2}\\.\\d{4})/,\n );\n\n if (dateMatch) {\n const extractedDate = d\n .parse(dateMatch[1], 'dd.MM.yyyy', new Date())\n .toISOString();\n\n editedTrans.date = extractedDate;\n }\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './bank.interface';\nimport Fallback from './integration-bank';\n\nexport default {\n ...Fallback,\n\n institutionIds: ['VIRGIN_NRNBGB22'],\n\n normalizeTransaction(transaction, booked) {\n const editedTrans = { ...transaction };\n\n const transferPrefixes = ['MOB', 'FPS'];\n const methodRegex = /^(Card|WLT)\\s\\d+/;\n\n const parts = (transaction.remittanceInformationUnstructured ?? '').split(\n ', ',\n );\n\n if (transferPrefixes.includes(parts[0])) {\n // Transfer remittance information begins with either \"MOB\" or \"FPS\"\n // the second field contains the payee and the third contains the\n // reference\n\n editedTrans.creditorName = parts[1];\n editedTrans.debtorName = parts[1];\n editedTrans.remittanceInformationUnstructured = parts[2];\n } else if (parts[0].match(methodRegex)) {\n // The payee is prefixed with the payment method, eg \"Card 11, {payee}\"\n\n editedTrans.creditorName = parts[1];\n editedTrans.debtorName = parts[1];\n } else {\n // Simple payee name\n\n editedTrans.creditorName = transaction.remittanceInformationUnstructured;\n editedTrans.debtorName = transaction.remittanceInformationUnstructured;\n }\n\n return Fallback.normalizeTransaction(transaction, booked, editedTrans);\n },\n} satisfies IBank;\n","import type { IBank } from './banks/bank.interface';\nimport IntegrationBank from './banks/integration-bank';\n\n// Filename convention: <name>_<bic>.{ts,js} (skips bank.interface,\n// integration-bank, and any other helper without an underscore).\nconst bankModules = import.meta.glob<{ default: IBank }>(\n './banks/*_*.{ts,js}',\n {\n eager: true,\n },\n);\n\nexport const banks: IBank[] = Object.values(bankModules).map(m => m.default);\n\nexport function BankFactory(institutionId: string): IBank {\n return (\n banks.find(b => b.institutionIds.includes(institutionId)) || IntegrationBank\n );\n}\n","import type {\n GoCardlessAccountDetails,\n GoCardlessAccountId,\n GoCardlessAccountMetadata,\n GoCardlessAgreementId,\n GoCardlessInstitutionId,\n GoCardlessRequisitionId,\n Institution,\n Requisition,\n} from '#app-gocardless/gocardless-node.types';\nimport type {\n GetBalances,\n GetTransactionsResponse,\n} from '#app-gocardless/gocardless.types';\n\nconst BASE_URL = 'https://bankaccountdata.gocardless.com/api/v2';\nconst ALLOWED_ORIGIN = new URL(BASE_URL).origin;\n\nexport type TokenResponse = {\n access: string;\n refresh: string;\n access_expires: number;\n refresh_expires: number;\n};\n\ntype AgreementResponse = {\n id: GoCardlessAgreementId;\n created: string;\n max_historical_days: number;\n access_valid_for_days: number;\n access_scope: string[];\n accepted: string | null;\n institution_id: GoCardlessInstitutionId;\n};\n\nexport type AccountDetailsResponse = {\n account: GoCardlessAccountDetails;\n};\n\nexport class GoCardlessApiError extends Error {\n response: {\n status: number;\n headers: Record<string, string>;\n data?: unknown;\n };\n\n constructor(\n message: string,\n status: number,\n headers: Record<string, string>,\n ) {\n super(message);\n this.response = { status, headers };\n }\n}\n\nexport class GoCardlessApi {\n #secretId: string | null;\n #secretKey: string | null;\n #token: string | null = null;\n\n constructor({\n secretId,\n secretKey,\n }: {\n secretId: string | null;\n secretKey: string | null;\n }) {\n this.#secretId = secretId;\n this.#secretKey = secretKey;\n }\n\n get secretId(): string | null {\n return this.#secretId;\n }\n\n get secretKey(): string | null {\n return this.#secretKey;\n }\n\n get token(): string | null {\n return this.#token;\n }\n\n set token(value: string | null) {\n this.#token = value;\n }\n\n async #request<T>(\n endpoint: string,\n {\n method = 'GET',\n body,\n }: {\n method?: 'GET' | 'POST' | 'DELETE';\n body?: Record<string, unknown>;\n } = {},\n ): Promise<T> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n 'Content-Type': 'application/json',\n };\n\n if (this.#token) {\n headers.Authorization = `Bearer ${this.#token}`;\n }\n\n const url = new URL(`${BASE_URL}${endpoint}`);\n if (url.origin !== ALLOWED_ORIGIN || !url.pathname.startsWith('/api/v2/')) {\n throw new Error(`Invalid GoCardless API endpoint: ${endpoint}`);\n }\n\n const response = await fetch(url, {\n method,\n headers,\n signal: AbortSignal.timeout(20000),\n ...(body\n ? {\n body: JSON.stringify(\n Object.fromEntries(\n Object.entries(body).filter(([, v]) => v != null),\n ),\n ),\n }\n : {}),\n });\n\n if (!response.ok) {\n const error = new GoCardlessApiError(\n `GoCardless API error: ${response.status}`,\n response.status,\n Object.fromEntries(response.headers.entries()),\n );\n try {\n error.response.data = await response.json();\n } catch {}\n console.log(\n `GoCardless ${method} ${endpoint} ${response.status}`,\n error.response.data ? JSON.stringify(error.response.data) : '(no body)',\n );\n throw error;\n }\n\n return response.json() as Promise<T>;\n }\n\n async generateToken(): Promise<TokenResponse> {\n const data = await this.#request<TokenResponse>('/token/new/', {\n method: 'POST',\n body: {\n secret_id: this.#secretId,\n secret_key: this.#secretKey,\n },\n });\n this.#token = data.access;\n return data;\n }\n\n async exchangeToken({\n refreshToken,\n }: {\n refreshToken: string;\n }): Promise<TokenResponse> {\n const data = await this.#request<TokenResponse>('/token/refresh/', {\n method: 'POST',\n body: { refresh: refreshToken },\n });\n this.#token = data.access;\n return data;\n }\n\n async getInstitutions({\n country,\n }: {\n country: string;\n }): Promise<Institution[]> {\n return this.#request<Institution[]>(`/institutions/?country=${country}`);\n }\n\n async getInstitutionById(id: GoCardlessInstitutionId): Promise<Institution> {\n return this.#request<Institution>(`/institutions/${id}/`);\n }\n\n async createRequisition({\n redirectUrl,\n institutionId,\n agreement,\n userLanguage,\n reference,\n ssn,\n redirectImmediate,\n accountSelection,\n }: {\n redirectUrl: string;\n institutionId: GoCardlessInstitutionId;\n agreement: GoCardlessAgreementId;\n userLanguage: string;\n reference: string | null;\n ssn: string | null;\n redirectImmediate: boolean;\n accountSelection: boolean;\n }): Promise<Requisition> {\n return this.#request<Requisition>('/requisitions/', {\n method: 'POST',\n body: {\n redirect: redirectUrl,\n institution_id: institutionId,\n agreement,\n user_language: userLanguage,\n reference,\n ssn,\n redirect_immediate: redirectImmediate,\n account_selection: accountSelection,\n },\n });\n }\n\n async getRequisitionById(\n requisitionId: GoCardlessRequisitionId,\n ): Promise<Requisition> {\n return this.#request<Requisition>(`/requisitions/${requisitionId}/`);\n }\n\n async deleteRequisition(\n requisitionId: GoCardlessRequisitionId,\n ): Promise<{ summary: string; detail: string }> {\n return this.#request(`/requisitions/${requisitionId}/`, {\n method: 'DELETE',\n });\n }\n\n async createAgreement({\n institutionId,\n maxHistoricalDays = 90,\n accessValidForDays = 90,\n accessScope = ['balances', 'details', 'transactions'],\n }: {\n institutionId: GoCardlessInstitutionId;\n maxHistoricalDays?: number;\n accessValidForDays?: number;\n accessScope?: string[];\n }): Promise<AgreementResponse> {\n return this.#request<AgreementResponse>('/agreements/enduser/', {\n method: 'POST',\n body: {\n institution_id: institutionId,\n max_historical_days: maxHistoricalDays,\n access_valid_for_days: accessValidForDays,\n access_scope: accessScope,\n },\n });\n }\n\n async getAccountMetadata(\n accountId: GoCardlessAccountId,\n ): Promise<GoCardlessAccountMetadata> {\n return this.#request<GoCardlessAccountMetadata>(`/accounts/${accountId}/`);\n }\n\n async getAccountDetails(\n accountId: GoCardlessAccountId,\n ): Promise<AccountDetailsResponse> {\n return this.#request<AccountDetailsResponse>(\n `/accounts/${accountId}/details/`,\n );\n }\n\n async getAccountBalances(\n accountId: GoCardlessAccountId,\n ): Promise<GetBalances> {\n return this.#request<GetBalances>(`/accounts/${accountId}/balances/`);\n }\n\n async getAccountTransactions({\n accountId,\n dateFrom,\n dateTo,\n }: {\n accountId: GoCardlessAccountId;\n dateFrom?: string;\n dateTo?: string;\n }): Promise<GetTransactionsResponse> {\n const params = new URLSearchParams();\n if (dateFrom) params.set('date_from', dateFrom);\n if (dateTo) params.set('date_to', dateTo);\n const query = params.toString();\n return this.#request<GetTransactionsResponse>(\n `/accounts/${accountId}/transactions/${query ? `?${query}` : ''}`,\n );\n }\n\n async initSession({\n redirectUrl,\n institutionId,\n maxHistoricalDays = 90,\n accessValidForDays = 90,\n userLanguage = 'en',\n referenceId = null,\n ssn = null,\n redirectImmediate = false,\n accountSelection = false,\n }: {\n redirectUrl: string;\n institutionId: GoCardlessInstitutionId;\n maxHistoricalDays?: number | string;\n accessValidForDays?: number | string;\n userLanguage?: string;\n referenceId?: string | null;\n ssn?: string | null;\n redirectImmediate?: boolean;\n accountSelection?: boolean;\n }): Promise<Requisition> {\n const agreement = await this.createAgreement({\n institutionId,\n maxHistoricalDays: Number(maxHistoricalDays),\n accessValidForDays: Number(accessValidForDays),\n });\n\n return this.createRequisition({\n redirectUrl,\n institutionId,\n agreement: agreement.id,\n userLanguage,\n reference: referenceId,\n ssn,\n redirectImmediate,\n accountSelection,\n });\n }\n}\n","import { v4 as uuidv4 } from 'uuid';\n\nimport { BankFactory } from '#app-gocardless/bank-factory';\nimport type { IBank } from '#app-gocardless/banks/bank.interface';\nimport {\n AccessDeniedError,\n AccountNotLinkedToRequisition,\n GenericGoCardlessError,\n InvalidGoCardlessTokenError,\n InvalidInputDataError,\n NotFoundError,\n RateLimitError,\n RequisitionNotLinked,\n ResourceSuspended,\n ServiceError,\n UnknownError,\n} from '#app-gocardless/errors';\nimport type {\n Balance,\n GoCardlessAccountId,\n GoCardlessAccountMetadata,\n GoCardlessInstitutionId,\n GoCardlessRequisitionId,\n Institution,\n Requisition,\n Transaction,\n} from '#app-gocardless/gocardless-node.types';\nimport type {\n CreateRequisitionParams,\n DetailedAccount,\n DetailedAccountWithInstitution,\n GetBalances,\n GetTransactionsParams,\n GetTransactionsResponse,\n NormalizedAccountDetails,\n TransactionWithBookedStatus,\n} from '#app-gocardless/gocardless.types';\nimport { SecretName, secretsService } from '#services/secrets-service';\n\nimport type { AccountDetailsResponse, TokenResponse } from './gocardless-api';\nimport { GoCardlessApi, GoCardlessApiError } from './gocardless-api';\n\nconst clients = new Map<string, GoCardlessApi>();\n\nconst getGocardlessClient = (): GoCardlessApi => {\n const secrets = {\n secretId: secretsService.get(SecretName.gocardless_secretId),\n secretKey: secretsService.get(SecretName.gocardless_secretKey),\n };\n\n const hash = JSON.stringify(secrets);\n\n let client = clients.get(hash);\n if (!client) {\n client = new GoCardlessApi(secrets);\n clients.set(hash, client);\n }\n\n return client;\n};\n\nexport const handleGoCardlessError = (error: unknown): never => {\n const status =\n error instanceof GoCardlessApiError ? error.response.status : undefined;\n\n switch (status) {\n case 400:\n throw new InvalidInputDataError(error);\n case 401:\n throw new InvalidGoCardlessTokenError(error);\n case 403:\n throw new AccessDeniedError(error);\n case 404:\n throw new NotFoundError(error);\n case 409:\n throw new ResourceSuspended(error);\n case 429:\n throw new RateLimitError(error);\n case 500:\n throw new UnknownError(error);\n case 503:\n throw new ServiceError(error);\n default:\n throw new GenericGoCardlessError(error);\n }\n};\n\nexport const goCardlessService = {\n isConfigured: (): boolean => {\n return !!(\n getGocardlessClient().secretId && getGocardlessClient().secretKey\n );\n },\n\n setToken: async (): Promise<void> => {\n const isExpiredJwtToken = (token: string | null): boolean => {\n if (!token) return true;\n try {\n const payload = JSON.parse(\n Buffer.from(token.split('.')[1], 'base64url').toString(),\n );\n const clockTimestamp = Math.floor(Date.now() / 1000);\n return clockTimestamp >= payload.exp;\n } catch {\n return true;\n }\n };\n\n if (isExpiredJwtToken(getGocardlessClient().token)) {\n await client.generateToken().catch(handleGoCardlessError);\n }\n },\n\n getLinkedRequisition: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<Requisition> => {\n const requisition = await goCardlessService.getRequisition(requisitionId);\n\n const { status } = requisition;\n\n // Continue only if status of requisition is \"LN\" which\n // means the account has been successfully linked to the requisition\n if (status !== 'LN') {\n throw new RequisitionNotLinked({ requisitionStatus: status });\n }\n\n return requisition;\n },\n\n getRequisitionWithAccounts: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<{\n requisition: Requisition;\n accounts: NormalizedAccountDetails[];\n }> => {\n const requisition =\n await goCardlessService.getLinkedRequisition(requisitionId);\n\n console.log('GoCardless requisition linked:', {\n institutionId: requisition.institution_id,\n requisitionId,\n agreementId: requisition.agreement,\n accountIds: requisition.accounts,\n });\n\n const institutionIdSet = new Set<GoCardlessInstitutionId>();\n const detailedAccounts = await Promise.all(\n requisition.accounts.map(async (accountId: GoCardlessAccountId) => {\n const account = await goCardlessService.getDetailedAccount(accountId);\n institutionIdSet.add(account.institution_id);\n return account;\n }),\n );\n\n const institutions = await Promise.all(\n Array.from(institutionIdSet).map(\n async (institutionId: GoCardlessInstitutionId) => {\n return await goCardlessService.getInstitution(institutionId);\n },\n ),\n );\n\n const extendedAccounts =\n await goCardlessService.extendAccountsAboutInstitutions({\n accounts: detailedAccounts,\n institutions,\n });\n\n const normalizedAccounts = extendedAccounts.map(account => {\n const bank: IBank = BankFactory(account.institution_id);\n return bank.normalizeAccount(account);\n });\n\n return { requisition, accounts: normalizedAccounts };\n },\n\n getTransactionsWithBalance: async (\n requisitionId: GoCardlessRequisitionId,\n accountId: GoCardlessAccountId,\n startDate: string | undefined,\n endDate: string | undefined,\n ): Promise<{\n balances: Balance[];\n institutionId: GoCardlessInstitutionId;\n startingBalance: number;\n transactions: {\n booked: Transaction[];\n pending: Transaction[];\n all: TransactionWithBookedStatus[];\n };\n }> => {\n const { institution_id, accounts: accountIds } =\n await goCardlessService.getLinkedRequisition(requisitionId);\n\n if (!accountIds.includes(accountId)) {\n throw new AccountNotLinkedToRequisition(accountId, requisitionId);\n }\n\n const [normalizedTransactions, accountBalance] = await Promise.all([\n goCardlessService.getNormalizedTransactions(\n requisitionId,\n accountId,\n startDate,\n endDate,\n ),\n goCardlessService.getBalances(accountId),\n ]);\n\n const transactions = normalizedTransactions.transactions;\n\n const bank: IBank = BankFactory(institution_id);\n\n const startingBalance = bank.calculateStartingBalance(\n transactions.booked,\n accountBalance.balances,\n );\n\n return {\n balances: accountBalance.balances,\n institutionId: institution_id,\n startingBalance,\n transactions,\n };\n },\n\n getNormalizedTransactions: async (\n requisitionId: GoCardlessRequisitionId,\n accountId: GoCardlessAccountId,\n startDate: string | undefined,\n endDate: string | undefined,\n ): Promise<{\n institutionId: GoCardlessInstitutionId;\n transactions: {\n booked: Transaction[];\n pending: Transaction[];\n all: TransactionWithBookedStatus[];\n };\n }> => {\n const { institution_id, accounts: accountIds } =\n await goCardlessService.getLinkedRequisition(requisitionId);\n\n if (!accountIds.includes(accountId)) {\n throw new AccountNotLinkedToRequisition(accountId, requisitionId);\n }\n\n const transactions = await goCardlessService.getTransactions({\n institutionId: institution_id,\n accountId,\n startDate,\n endDate,\n });\n\n const bank: IBank = BankFactory(institution_id);\n const sortedBookedTransactions = bank.sortTransactions(\n transactions.transactions.booked,\n );\n const sortedPendingTransactions = bank.sortTransactions(\n transactions.transactions.pending,\n );\n const allTransactions: TransactionWithBookedStatus[] =\n sortedBookedTransactions.map(t => ({\n ...t,\n booked: true,\n }));\n sortedPendingTransactions.forEach(t =>\n allTransactions.push({ ...t, booked: false }),\n );\n const sortedAllTransactions = bank.sortTransactions(allTransactions);\n\n return {\n institutionId: institution_id,\n transactions: {\n booked: sortedBookedTransactions,\n pending: sortedPendingTransactions,\n all: sortedAllTransactions,\n },\n };\n },\n\n createRequisition: async ({\n institutionId,\n host,\n }: CreateRequisitionParams): Promise<{\n link: string;\n requisitionId: GoCardlessRequisitionId;\n }> => {\n await goCardlessService.setToken();\n\n const institution = await goCardlessService.getInstitution(institutionId);\n const accountSelection =\n institution.supported_features?.includes('account_selection') ?? false;\n const separateContinuousHistoryConsent =\n institution.supported_features?.includes(\n 'separate_continuous_history_consent',\n ) ?? false;\n\n const body = {\n redirectUrl: host + '/gocardless/link',\n institutionId,\n referenceId: uuidv4(),\n accessValidForDays: institution.max_access_valid_for_days,\n maxHistoricalDays: separateContinuousHistoryConsent\n ? 90\n : institution.transaction_total_days,\n userLanguage: 'en',\n ssn: null,\n redirectImmediate: false,\n accountSelection,\n };\n\n console.log('GoCardless requisition request:', {\n institutionId,\n accessValidForDays: body.accessValidForDays,\n maxHistoricalDays: body.maxHistoricalDays,\n transactionTotalDays: institution.transaction_total_days,\n separateContinuousHistoryConsent,\n accountSelection,\n supportedFeatures: institution.supported_features,\n });\n\n const response = await client.initSession(body).catch(async () => {\n console.log('Failed to link using:');\n console.log(body);\n console.log(\n 'Falling back to accessValidForDays = 90 ' +\n 'and maxHistoricalDays = 89',\n );\n\n return await client\n .initSession({\n ...body,\n accessValidForDays: 90,\n maxHistoricalDays: 89,\n })\n .catch(handleGoCardlessError);\n });\n\n const { link, id: requisitionId } = response;\n\n console.log('GoCardless requisition created:', {\n institutionId,\n requisitionId,\n agreementId: response.agreement,\n });\n\n return {\n link,\n requisitionId,\n };\n },\n\n deleteRequisition: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<{ summary: string; detail: string }> => {\n await goCardlessService.getRequisition(requisitionId);\n return client.deleteRequisition(requisitionId).catch(handleGoCardlessError);\n },\n\n getRequisition: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<Requisition> => {\n await goCardlessService.setToken();\n return client\n .getRequisitionById(requisitionId)\n .catch(handleGoCardlessError);\n },\n\n getDetailedAccount: async (\n accountId: GoCardlessAccountId,\n ): Promise<DetailedAccount> => {\n const [detailedAccount, metadataAccount] = await Promise.all([\n client.getDetails(accountId),\n client.getMetadata(accountId),\n ]).catch(handleGoCardlessError);\n\n const accountDetails = detailedAccount.account ?? {};\n const metadata = metadataAccount ?? {};\n\n // Some banks provide additional data in both fields, but can do yucky things like have an empty\n // string in one place but not the other. We'll fix this by merging the two objects, but preferring truthy values\n // from the metadata object over the details object.\n const truthyMetadata = Object.fromEntries(\n Object.entries(metadata).filter(([, v]) => v),\n );\n return {\n ...accountDetails,\n ...truthyMetadata,\n } as unknown as DetailedAccount;\n },\n\n getAccountMetadata: async (\n accountId: GoCardlessAccountId,\n ): Promise<GoCardlessAccountMetadata> =>\n client.getMetadata(accountId).catch(handleGoCardlessError),\n\n getInstitutions: async (country: string): Promise<Institution[]> =>\n client.getInstitutions(country).catch(handleGoCardlessError),\n\n getInstitution: async (\n institutionId: GoCardlessInstitutionId,\n ): Promise<Institution> =>\n client.getInstitutionById(institutionId).catch(handleGoCardlessError),\n\n extendAccountsAboutInstitutions: async ({\n accounts,\n institutions,\n }: {\n accounts: DetailedAccount[];\n institutions: Institution[];\n }): Promise<DetailedAccountWithInstitution[]> => {\n const institutionsById = institutions.reduce<Record<string, Institution>>(\n (acc, institution) => {\n acc[institution.id] = institution;\n return acc;\n },\n {},\n );\n\n return accounts.map(account => {\n const institution = institutionsById[account.institution_id] ?? null;\n return {\n ...account,\n institution,\n };\n });\n },\n\n getTransactions: async ({\n institutionId,\n accountId,\n startDate,\n endDate,\n }: GetTransactionsParams): Promise<GetTransactionsResponse> => {\n const response = await client\n .getTransactions({\n accountId,\n dateFrom: startDate,\n dateTo: endDate,\n })\n .catch(handleGoCardlessError);\n\n const bank: IBank = BankFactory(institutionId);\n response.transactions.booked = response.transactions.booked\n .map(transaction => bank.normalizeTransaction(transaction, true))\n .filter(t => t != null);\n response.transactions.pending = response.transactions.pending\n .map(transaction => bank.normalizeTransaction(transaction, false))\n .filter(t => t != null);\n\n return response;\n },\n\n getBalances: async (accountId: GoCardlessAccountId): Promise<GetBalances> =>\n client.getBalances(accountId).catch(handleGoCardlessError),\n};\n\n// All GoCardless API calls go through this object so tests can mock it easily.\nexport const client = {\n getBalances: async (accountId: GoCardlessAccountId): Promise<GetBalances> =>\n await getGocardlessClient().getAccountBalances(accountId),\n getTransactions: async ({\n accountId,\n dateFrom,\n dateTo,\n }: {\n accountId: GoCardlessAccountId;\n dateFrom?: string;\n dateTo?: string;\n }): Promise<GetTransactionsResponse> =>\n await getGocardlessClient().getAccountTransactions({\n accountId,\n dateFrom,\n dateTo,\n }),\n getInstitutions: async (country: string): Promise<Institution[]> =>\n await getGocardlessClient().getInstitutions({ country }),\n getInstitutionById: async (\n institutionId: GoCardlessInstitutionId,\n ): Promise<Institution> =>\n await getGocardlessClient().getInstitutionById(institutionId),\n getDetails: async (\n accountId: GoCardlessAccountId,\n ): Promise<AccountDetailsResponse> =>\n await getGocardlessClient().getAccountDetails(accountId),\n getMetadata: async (\n accountId: GoCardlessAccountId,\n ): Promise<GoCardlessAccountMetadata> =>\n await getGocardlessClient().getAccountMetadata(accountId),\n getRequisitionById: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<Requisition> =>\n await getGocardlessClient().getRequisitionById(requisitionId),\n deleteRequisition: async (\n requisitionId: GoCardlessRequisitionId,\n ): Promise<{ summary: string; detail: string }> =>\n await getGocardlessClient().deleteRequisition(requisitionId),\n initSession: async ({\n redirectUrl,\n institutionId,\n referenceId,\n accessValidForDays,\n maxHistoricalDays,\n userLanguage,\n ssn,\n redirectImmediate,\n accountSelection,\n }: {\n redirectUrl: string;\n institutionId: GoCardlessInstitutionId;\n referenceId: string | null;\n accessValidForDays: number | string;\n maxHistoricalDays: number | string;\n userLanguage: string;\n ssn: string | null;\n redirectImmediate: boolean;\n accountSelection: boolean;\n }): Promise<Requisition> =>\n await getGocardlessClient().initSession({\n redirectUrl,\n institutionId,\n referenceId,\n accessValidForDays,\n maxHistoricalDays,\n userLanguage,\n ssn,\n redirectImmediate,\n accountSelection,\n }),\n generateToken: async (): Promise<TokenResponse> =>\n await getGocardlessClient().generateToken(),\n exchangeToken: async ({\n refreshToken,\n }: {\n refreshToken: string;\n }): Promise<TokenResponse> =>\n await getGocardlessClient().exchangeToken({ refreshToken }),\n};\n","import path from 'path';\n\nimport express from 'express';\n\nimport { sha256String } from '#util/hash';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from '#util/middlewares';\n\nimport {\n AccountNotLinkedToRequisition,\n GenericGoCardlessError,\n GoCardlessClientError,\n RateLimitError,\n RequisitionNotLinked,\n} from './errors';\nimport type {\n GoCardlessAccountId,\n GoCardlessInstitutionId,\n GoCardlessRequisitionId,\n} from './gocardless-node.types';\nimport { goCardlessService } from './services/gocardless-service';\nimport { handleError } from './util/handle-error';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction validateOrigin(origin: string | undefined) {\n let url;\n try {\n url = new URL(origin ?? '');\n } catch {\n throw new Error('Invalid Origin header');\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error('Invalid Origin header');\n }\n return url.origin;\n}\n\nconst SAFE_ID = /^[a-zA-Z0-9_-]+$/;\nfunction sanitizeId<T extends string = string>(id: unknown): T {\n if (typeof id !== 'string' || !SAFE_ID.test(id)) {\n throw new Error(`Invalid GoCardless identifier: ${String(id)}`);\n }\n return id as T;\n}\n\nconst app = express();\napp.use(requestLoggerMiddleware);\n\napp.get('/link', function (req, res) {\n res.sendFile('link.html', { root: path.resolve('./src/app-gocardless') });\n});\n\nexport { app as handlers };\napp.use(express.json());\napp.use(validateSessionMiddleware);\n\napp.post('/status', async (req, res) => {\n res.send({\n status: 'ok',\n data: {\n configured: goCardlessService.isConfigured(),\n },\n });\n});\n\napp.post(\n '/create-web-token',\n handleError(async (req, res) => {\n const { institutionId: rawInstitutionId } = req.body || {};\n const institutionId = sanitizeId<GoCardlessInstitutionId>(rawInstitutionId);\n const host = validateOrigin(req.headers.origin);\n\n const { link, requisitionId } = await goCardlessService.createRequisition({\n institutionId,\n host,\n });\n\n res.send({\n status: 'ok',\n data: {\n link,\n requisitionId,\n },\n });\n }),\n);\n\napp.post(\n '/get-accounts',\n handleError(async (req, res) => {\n const requisitionId = sanitizeId<GoCardlessRequisitionId>(\n (req.body || {}).requisitionId,\n );\n\n try {\n const { requisition, accounts } =\n await goCardlessService.getRequisitionWithAccounts(requisitionId);\n\n res.send({\n status: 'ok',\n data: {\n ...requisition,\n accounts: await Promise.all(\n accounts.map(async account =>\n account?.iban\n ? { ...account, iban: sha256String(account.iban) }\n : account,\n ),\n ),\n },\n });\n } catch (error) {\n if (error instanceof RequisitionNotLinked) {\n res.send({\n status: 'ok',\n requisitionStatus: isRecord(error.details)\n ? error.details.requisitionStatus\n : undefined,\n });\n } else {\n throw error;\n }\n }\n }),\n);\n\napp.post(\n '/get-banks',\n handleError(async (req, res) => {\n const { country: rawCountry, showDemo = false } = req.body || {};\n const country = sanitizeId(rawCountry);\n\n await goCardlessService.setToken();\n const data = await goCardlessService.getInstitutions(country);\n\n res.send({\n status: 'ok',\n data: showDemo\n ? [\n {\n id: 'SANDBOXFINANCE_SFIN0000',\n name: 'DEMO bank (used for testing bank-sync)',\n },\n ...data,\n ]\n : data,\n });\n }),\n);\n\napp.post(\n '/remove-account',\n handleError(async (req, res) => {\n const requisitionId = sanitizeId<GoCardlessRequisitionId>(\n (req.body || {}).requisitionId,\n );\n\n const data = await goCardlessService.deleteRequisition(requisitionId);\n if (data.summary === 'Requisition deleted') {\n res.send({\n status: 'ok',\n data,\n });\n } else {\n res.send({\n status: 'error',\n data: {\n data,\n reason: 'Can not delete requisition',\n },\n });\n }\n }),\n);\n\napp.post(\n '/transactions',\n handleError(async (req, res) => {\n const {\n requisitionId: rawRequisitionId,\n startDate,\n endDate,\n accountId: rawAccountId,\n includeBalance = true,\n } = req.body || {};\n const requisitionId = sanitizeId<GoCardlessRequisitionId>(rawRequisitionId);\n const accountId = sanitizeId<GoCardlessAccountId>(rawAccountId);\n\n try {\n if (includeBalance) {\n const {\n balances,\n institutionId,\n startingBalance,\n transactions: { booked, pending, all },\n } = await goCardlessService.getTransactionsWithBalance(\n requisitionId,\n accountId,\n startDate,\n endDate,\n );\n\n res.send({\n status: 'ok',\n data: {\n balances,\n institutionId,\n startingBalance,\n transactions: {\n booked,\n pending,\n all,\n },\n },\n });\n } else {\n const {\n institutionId,\n transactions: { booked, pending, all },\n } = await goCardlessService.getNormalizedTransactions(\n requisitionId,\n accountId,\n startDate,\n endDate,\n );\n\n res.send({\n status: 'ok',\n data: {\n institutionId,\n transactions: {\n booked,\n pending,\n all,\n },\n },\n });\n }\n } catch (error) {\n const errorDetails =\n error instanceof RequisitionNotLinked ||\n error instanceof GenericGoCardlessError ||\n error instanceof GoCardlessClientError ||\n error instanceof AccountNotLinkedToRequisition\n ? error.details\n : undefined;\n\n const responseHeaders =\n isRecord(errorDetails) && isRecord(errorDetails.response)\n ? errorDetails.response.headers\n : undefined;\n\n const rateLimitHeaders = isRecord(responseHeaders)\n ? Object.fromEntries(\n Object.entries(responseHeaders).filter(([key]) =>\n key.startsWith('x-ratelimit-'),\n ),\n )\n : {};\n\n const errorMessage =\n error instanceof Error && error.message ? error.message : String(error);\n\n const sendErrorResponse = (data: Record<string, unknown>) =>\n res.send({\n status: 'ok',\n data: { ...data, details: errorDetails, rateLimitHeaders },\n });\n\n switch (true) {\n case error instanceof RequisitionNotLinked:\n sendErrorResponse({\n error_type: 'ITEM_ERROR',\n error_code: 'ITEM_LOGIN_REQUIRED',\n status: 'expired',\n reason:\n 'Access to account has expired as set in End User Agreement',\n });\n break;\n case error instanceof AccountNotLinkedToRequisition:\n sendErrorResponse({\n error_type: 'INVALID_INPUT',\n error_code: 'INVALID_ACCESS_TOKEN',\n status: 'rejected',\n reason: 'Account not linked with this requisition',\n });\n break;\n case error instanceof RateLimitError:\n sendErrorResponse({\n error_type: 'RATE_LIMIT_EXCEEDED',\n error_code: 'NORDIGEN_ERROR',\n status: 'rejected',\n reason: 'Rate limit exceeded',\n });\n break;\n case error instanceof GenericGoCardlessError:\n console.log('Something went wrong', errorMessage);\n sendErrorResponse({\n error_type: 'SYNC_ERROR',\n error_code: 'NORDIGEN_ERROR',\n });\n break;\n default:\n console.log('Something went wrong', errorMessage);\n sendErrorResponse({\n error_type: 'UNKNOWN',\n error_code: 'UNKNOWN',\n reason: 'Something went wrong',\n });\n break;\n }\n }\n }),\n);\n","import express from 'express';\nimport rateLimit from 'express-rate-limit';\n\nimport { disableOpenID, enableOpenID, isAdmin } from './account-db';\nimport { isValidRedirectUrl, loginWithOpenIdFinalize } from './accounts/openid';\nimport { checkPassword } from './accounts/password';\nimport * as UserService from './services/user-service';\nimport {\n errorMiddleware,\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from './util/middlewares';\n\nconst app = express();\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(requestLoggerMiddleware);\n\nconst openIdConfigRateLimiter = rateLimit({\n windowMs: 15 * 60 * 1000,\n max: 5,\n legacyHeaders: false,\n standardHeaders: true,\n message: { status: 'error', reason: 'too-many-requests' },\n});\n\nexport { app as handlers, openIdConfigRateLimiter };\n\napp.post('/enable', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { error } = (await enableOpenID(req.body)) || {};\n\n if (error) {\n res.status(500).send({ status: 'error', reason: error });\n return;\n }\n res.send({ status: 'ok' });\n});\n\napp.post('/disable', validateSessionMiddleware, async (req, res) => {\n if (!isAdmin(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'forbidden',\n details: 'permission-not-found',\n });\n return;\n }\n\n const { error } = (await disableOpenID(req.body)) || {};\n\n if (error) {\n res.status(401).send({ status: 'error', reason: error });\n return;\n }\n res.send({ status: 'ok' });\n});\n\napp.post('/config', openIdConfigRateLimiter, async (req, res) => {\n const ownerCount = UserService.getOwnerCount();\n\n if (ownerCount > 0) {\n res.status(400).send({ status: 'error', reason: 'already-bootstraped' });\n return;\n }\n\n if (!checkPassword(req.body.password)) {\n res.status(400).send({ status: 'error', reason: 'invalid-password' });\n return;\n }\n\n const auth = UserService.getOpenIDConfig();\n\n if (!auth) {\n res\n .status(500)\n .send({ status: 'error', reason: 'OpenID configuration not found' });\n return;\n }\n\n try {\n const openIdConfig = JSON.parse(auth.extra_data);\n res.send({ status: 'ok', data: { openId: openIdConfig } });\n } catch {\n res\n .status(500)\n .send({ status: 'error', reason: 'Invalid OpenID configuration' });\n }\n});\n\napp.get('/callback', async (req, res) => {\n const { error, url } = await loginWithOpenIdFinalize(req.query);\n\n if (error) {\n res.status(400).send({ status: 'error', reason: error });\n return;\n }\n\n if (!isValidRedirectUrl(url)) {\n res.status(400).send({ status: 'error', reason: 'Invalid redirect URL' });\n return;\n }\n\n res.redirect(url);\n});\n\napp.use(errorMiddleware);\n","import { PluggyClient } from 'pluggy-sdk';\n\nimport { SecretName, secretsService } from '#services/secrets-service';\n\nlet pluggyClient = null;\n\nfunction getPluggyClient() {\n if (!pluggyClient) {\n const clientId = secretsService.get(SecretName.pluggyai_clientId);\n const clientSecret = secretsService.get(SecretName.pluggyai_clientSecret);\n\n pluggyClient = new PluggyClient({\n clientId,\n clientSecret,\n });\n }\n\n return pluggyClient;\n}\n\nexport const pluggyaiService = {\n isConfigured: () => {\n return !!(\n secretsService.get(SecretName.pluggyai_clientId) &&\n secretsService.get(SecretName.pluggyai_clientSecret) &&\n secretsService.get(SecretName.pluggyai_itemIds)\n );\n },\n\n getAccountsByItemId: async itemId => {\n try {\n const client = getPluggyClient();\n const { results, total, ...rest } = await client.fetchAccounts(itemId);\n return {\n results,\n total,\n ...rest,\n hasError: false,\n errors: {},\n };\n } catch (error) {\n console.error(`Error fetching accounts: ${error.message}`);\n throw error;\n }\n },\n getAccountById: async accountId => {\n try {\n const client = getPluggyClient();\n const account = await client.fetchAccount(accountId);\n return {\n ...account,\n hasError: false,\n errors: {},\n };\n } catch (error) {\n console.error(`Error fetching account: ${error.message}`);\n throw error;\n }\n },\n\n getTransactionsByAccountId: async (accountId, startDate, pageSize, page) => {\n try {\n const client = getPluggyClient();\n\n const account = await pluggyaiService.getAccountById(accountId);\n\n // the sandbox data doesn't move the dates automatically so the\n // transactions are often older than 90 days. The owner on one of the\n // sandbox accounts is set to John Doe so in these cases we'll ignore\n // the start date.\n const sandboxAccount = account.owner === 'John Doe';\n\n if (sandboxAccount) startDate = '2000-01-01';\n\n const transactions = await client.fetchTransactions(accountId, {\n from: startDate,\n pageSize,\n page,\n });\n\n if (sandboxAccount) {\n transactions.results = transactions.results.map(t => ({\n ...t,\n sandbox: true,\n }));\n }\n\n return {\n ...transactions,\n hasError: false,\n errors: {},\n };\n } catch (error) {\n console.error(`Error fetching transactions: ${error.message}`);\n throw error;\n }\n },\n getTransactions: async (accountId, startDate) => {\n let transactions = [];\n let result = await pluggyaiService.getTransactionsByAccountId(\n accountId,\n startDate,\n 500,\n 1,\n );\n transactions = transactions.concat(result.results);\n const totalPages = result.totalPages;\n while (result.page !== totalPages) {\n result = await pluggyaiService.getTransactionsByAccountId(\n accountId,\n startDate,\n 500,\n result.page + 1,\n );\n transactions = transactions.concat(result.results);\n }\n\n return transactions;\n },\n};\n","import express from 'express';\n\nimport { handleError } from '#app-gocardless/util/handle-error';\nimport { SecretName, secretsService } from '#services/secrets-service';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from '#util/middlewares';\n\nimport { pluggyaiService } from './pluggyai-service';\n\nconst app = express();\nexport { app as handlers };\napp.use(requestLoggerMiddleware);\napp.use(express.json());\napp.use(validateSessionMiddleware);\n\napp.post(\n '/status',\n handleError(async (req, res) => {\n const clientId = secretsService.get(SecretName.pluggyai_clientId);\n const configured = clientId != null;\n\n res.send({\n status: 'ok',\n data: {\n configured,\n },\n });\n }),\n);\n\napp.post(\n '/accounts',\n handleError(async (req, res) => {\n try {\n const itemIds = secretsService\n .get(SecretName.pluggyai_itemIds)\n .split(',')\n .map(item => item.trim());\n\n let accounts = [];\n\n for (const item of itemIds) {\n const partial = await pluggyaiService.getAccountsByItemId(item);\n accounts = accounts.concat(partial.results);\n }\n\n res.send({\n status: 'ok',\n data: {\n accounts,\n },\n });\n } catch (error) {\n res.send({\n status: 'ok',\n data: {\n error: error.message,\n },\n });\n }\n }),\n);\n\napp.post(\n '/transactions',\n handleError(async (req, res) => {\n const { accountId, startDate } = req.body || {};\n\n try {\n const transactions = await pluggyaiService.getTransactions(\n accountId,\n startDate,\n );\n\n const account = await pluggyaiService.getAccountById(accountId);\n\n let startingBalance = parseInt(\n Math.round(account.balance * 100).toString(),\n );\n if (account.type === 'CREDIT') {\n startingBalance = -startingBalance;\n }\n const date = getDate(new Date(account.updatedAt));\n\n const balances = [\n {\n balanceAmount: {\n amount: startingBalance,\n currency: account.currencyCode,\n },\n balanceType: 'expected',\n referenceDate: date,\n },\n ];\n\n const all = [];\n const booked = [];\n const pending = [];\n\n for (const trans of transactions) {\n const newTrans = {};\n\n newTrans.booked = !(trans.status === 'PENDING');\n\n const transactionDate = new Date(trans.date);\n\n if (transactionDate < startDate && !trans.sandbox) {\n continue;\n }\n\n newTrans.date = getDate(transactionDate);\n newTrans.payeeName = getPayeeName(trans);\n newTrans.notes = trans.descriptionRaw || trans.description;\n\n if (account.type === 'CREDIT') {\n if (trans.amountInAccountCurrency) {\n trans.amountInAccountCurrency *= -1;\n }\n\n trans.amount *= -1;\n }\n\n let amountInCurrency = trans.amountInAccountCurrency ?? trans.amount;\n amountInCurrency = Math.round(amountInCurrency * 100) / 100;\n\n newTrans.transactionAmount = {\n amount: amountInCurrency,\n currency: trans.currencyCode,\n };\n\n newTrans.transactionId = trans.id;\n newTrans.sortOrder = transactionDate.getTime();\n\n delete trans.amount;\n\n const finalTrans = { ...flattenObject(trans), ...newTrans };\n if (newTrans.booked) {\n booked.push(finalTrans);\n } else {\n pending.push(finalTrans);\n }\n all.push(finalTrans);\n }\n\n const sortFunction = (a, b) => b.sortOrder - a.sortOrder;\n\n const bookedSorted = booked.sort(sortFunction);\n const pendingSorted = pending.sort(sortFunction);\n const allSorted = all.sort(sortFunction);\n\n res.send({\n status: 'ok',\n data: {\n balances,\n startingBalance,\n transactions: {\n all: allSorted,\n booked: bookedSorted,\n pending: pendingSorted,\n },\n },\n });\n } catch (error) {\n res.send({\n status: 'ok',\n data: {\n error: error.message,\n },\n });\n }\n return;\n }),\n);\n\nfunction getDate(date) {\n return date.toISOString().split('T')[0];\n}\n\nfunction flattenObject(obj, prefix = '') {\n const result = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const newKey = prefix ? `${prefix}.${key}` : key;\n\n if (value === null) {\n continue;\n }\n\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n Object.assign(result, flattenObject(value, newKey));\n } else {\n result[newKey] = value;\n }\n }\n\n return result;\n}\n\nfunction getPayeeName(trans) {\n if (trans.merchant && (trans.merchant.name || trans.merchant.businessName)) {\n return trans.merchant.name || trans.merchant.businessName || '';\n }\n\n if (trans.paymentData) {\n const { receiver, payer } = trans.paymentData;\n\n if (trans.type === 'DEBIT' && receiver) {\n return receiver.name || receiver.documentNumber?.value || '';\n }\n\n if (trans.type === 'CREDIT' && payer) {\n return payer.name || payer.documentNumber?.value || '';\n }\n }\n\n return '';\n}\n","import express from 'express';\n\nimport { getActiveLoginMethod, isAdmin } from './account-db';\nimport { SecretName, secretsService } from './services/secrets-service';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from './util/middlewares';\n\nconst app = express();\n\nexport { app as handlers };\napp.use(express.json());\napp.use(requestLoggerMiddleware);\napp.use(validateSessionMiddleware);\n\n// In OpenID mode the secrets store is admin-managed; non-admins must be\n// blocked from both reads and writes, otherwise they can enumerate which\n// integrations are configured.\nfunction canManageSecrets(userId) {\n return getActiveLoginMethod() !== 'openid' || isAdmin(userId);\n}\n\napp.post('/', async (req, res) => {\n if (!canManageSecrets(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'not-admin',\n details: 'You have to be admin to set secrets',\n });\n return;\n }\n\n const { name, value } = req.body || {};\n\n if (!(name in SecretName)) {\n res.status(400).send({\n status: 'error',\n reason: 'invalid-secret-name',\n details: 'Unknown secret name',\n });\n return;\n }\n\n secretsService.set(name, value);\n\n res.status(200).send({ status: 'ok' });\n});\n\napp.get('/:name', async (req, res) => {\n if (!canManageSecrets(res.locals.user_id)) {\n res.status(403).send({\n status: 'error',\n reason: 'not-admin',\n details: 'You have to be admin to read secrets',\n });\n return;\n }\n\n const name = req.params.name;\n if (!(name in SecretName)) {\n res.status(404).send('key not found');\n return;\n }\n\n if (secretsService.exists(name)) {\n res.sendStatus(204);\n } else {\n res.status(404).send('key not found');\n }\n});\n","import https from 'https';\n\nimport express from 'express';\n\nimport { handleError } from '#app-gocardless/util/handle-error';\nimport { SecretName, secretsService } from '#services/secrets-service';\nimport {\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from '#util/middlewares';\n\nconst app = express();\nexport { app as handlers };\napp.use(requestLoggerMiddleware);\napp.use(express.json());\napp.use(validateSessionMiddleware);\n\napp.post(\n '/status',\n handleError(async (req, res) => {\n const token = secretsService.get(SecretName.simplefin_token);\n const configured = token != null && token !== 'Forbidden';\n\n res.send({\n status: 'ok',\n data: {\n configured,\n },\n });\n }),\n);\n\napp.post(\n '/accounts',\n handleError(async (req, res) => {\n let accessKey = secretsService.get(SecretName.simplefin_accessKey);\n\n try {\n if (accessKey == null || accessKey === 'Forbidden') {\n const token = secretsService.get(SecretName.simplefin_token);\n if (token == null || token === 'Forbidden') {\n throw new Error('No token');\n } else {\n accessKey = await getAccessKey(token);\n secretsService.set(SecretName.simplefin_accessKey, accessKey);\n if (accessKey == null || accessKey === 'Forbidden') {\n throw new Error('No access key');\n }\n }\n }\n } catch {\n invalidToken(res);\n return;\n }\n\n try {\n const accounts = await getAccounts(accessKey, null, null, null, true);\n\n res.send({\n status: 'ok',\n data: {\n accounts: accounts.accounts,\n },\n });\n } catch (e) {\n serverDown(e, res);\n return;\n }\n }),\n);\n\napp.post(\n '/transactions',\n handleError(async (req, res) => {\n const { accountId, startDate } = req.body || {};\n\n const accessKey = secretsService.get(SecretName.simplefin_accessKey);\n\n if (accessKey == null || accessKey === 'Forbidden') {\n invalidToken(res);\n return;\n }\n\n if (Array.isArray(accountId) !== Array.isArray(startDate)) {\n console.log({ accountId, startDate });\n throw new Error(\n 'accountId and startDate must either both be arrays or both be strings',\n );\n }\n if (Array.isArray(accountId) && accountId.length !== startDate.length) {\n console.log({ accountId, startDate });\n throw new Error('accountId and startDate arrays must be the same length');\n }\n\n const earliestStartDate = Array.isArray(startDate)\n ? startDate.reduce((a, b) => (a < b ? a : b))\n : startDate;\n let results;\n try {\n results = await getTransactions(\n accessKey,\n Array.isArray(accountId) ? accountId : [accountId],\n new Date(earliestStartDate),\n );\n } catch (e) {\n if (e.message === 'Forbidden') {\n invalidToken(res);\n } else {\n serverDown(e, res);\n }\n return;\n }\n\n let response = {};\n if (Array.isArray(accountId)) {\n for (let i = 0; i < accountId.length; i++) {\n const id = accountId[i];\n response[id] = getAccountResponse(results, id, new Date(startDate[i]));\n }\n } else {\n response = getAccountResponse(results, accountId, new Date(startDate));\n }\n\n if (results.hasError) {\n res.send({\n status: 'ok',\n data: !Array.isArray(accountId)\n ? results.errors[accountId][0]\n : {\n ...response,\n errors: results.errors,\n },\n });\n return;\n }\n\n res.send({\n status: 'ok',\n data: response,\n });\n }),\n);\n\nfunction logAccountError(results, accountId, data) {\n const errors = results.errors[accountId] || [];\n errors.push(data);\n results.errors[accountId] = errors;\n results.hasError = true;\n}\n\nfunction getAccountResponse(results, accountId, startDate) {\n const account =\n !results?.accounts || results.accounts.find(a => a.id === accountId);\n if (!account) {\n console.log(\n `The account \"${accountId}\" was not found. Here were the accounts returned:`,\n );\n if (results?.accounts) {\n results.accounts.forEach(a => console.log(`${a.id} - ${a.org.name}`));\n }\n logAccountError(results, accountId, {\n error_type: 'ACCOUNT_MISSING',\n error_code: 'ACCOUNT_MISSING',\n reason: `The account \"${accountId}\" was not found. Try unlinking and relinking the account.`,\n });\n return;\n }\n\n const needsAttention = results.sferrors.find(e =>\n e.startsWith(`Connection to ${account.org.name} may need attention`),\n );\n if (needsAttention) {\n logAccountError(results, accountId, {\n error_type: 'ACCOUNT_NEEDS_ATTENTION',\n error_code: 'ACCOUNT_NEEDS_ATTENTION',\n reason:\n 'The account needs your attention at <a href=\"https://bridge.simplefin.org/auth/login\">SimpleFIN</a>.',\n });\n }\n\n const startingBalance = parseInt(account.balance.replace('.', ''));\n const date = getDate(new Date(account['balance-date'] * 1000));\n\n const balances = [\n {\n balanceAmount: {\n amount: account.balance,\n currency: account.currency,\n },\n balanceType: 'expected',\n referenceDate: date,\n },\n {\n balanceAmount: {\n amount: account.balance,\n currency: account.currency,\n },\n balanceType: 'interimAvailable',\n referenceDate: date,\n },\n ];\n\n const all = [];\n const booked = [];\n const pending = [];\n\n for (const trans of account.transactions) {\n const newTrans = {};\n\n let dateToUse = 0;\n\n if (trans.pending ?? trans.posted === 0) {\n newTrans.booked = false;\n dateToUse = trans.transacted_at;\n } else {\n newTrans.booked = true;\n dateToUse = trans.posted;\n }\n\n const transactionDate = new Date(dateToUse * 1000);\n\n if (transactionDate < startDate) {\n continue;\n }\n\n newTrans.sortOrder = dateToUse;\n newTrans.date = getDate(transactionDate);\n newTrans.payeeName = trans.payee;\n newTrans.notes = trans.description;\n newTrans.transactionAmount = { amount: trans.amount, currency: 'USD' };\n newTrans.transactionId = trans.id;\n newTrans.valueDate = newTrans.bookingDate;\n\n if (trans.transacted_at) {\n newTrans.transactedDate = getDate(new Date(trans.transacted_at * 1000));\n }\n\n if (trans.posted) {\n newTrans.postedDate = getDate(new Date(trans.posted * 1000));\n }\n\n if (newTrans.booked) {\n booked.push(newTrans);\n } else {\n pending.push(newTrans);\n }\n all.push(newTrans);\n }\n\n const sortFunction = (a, b) => b.sortOrder - a.sortOrder;\n\n const bookedSorted = booked.sort(sortFunction);\n const pendingSorted = pending.sort(sortFunction);\n const allSorted = all.sort(sortFunction);\n\n return {\n balances,\n startingBalance,\n transactions: {\n all: allSorted,\n booked: bookedSorted,\n pending: pendingSorted,\n },\n };\n}\n\nfunction invalidToken(res) {\n res.send({\n status: 'ok',\n data: {\n error_type: 'INVALID_ACCESS_TOKEN',\n error_code: 'INVALID_ACCESS_TOKEN',\n status: 'rejected',\n reason:\n 'Invalid SimpleFIN access token. Reset the token and re-link any broken accounts.',\n },\n });\n}\n\nfunction serverDown(e, res) {\n console.log(e);\n res.send({\n status: 'ok',\n data: {\n error_type: 'SERVER_DOWN',\n error_code: 'SERVER_DOWN',\n status: 'rejected',\n reason: 'There was an error communicating with SimpleFIN.',\n },\n });\n}\n\nfunction parseAccessKey(accessKey) {\n let scheme = null;\n let rest = null;\n let auth = null;\n let username = null;\n let password = null;\n let baseUrl = null;\n if (!accessKey || !accessKey.match(/^.*\\/\\/.*:.*@.*$/)) {\n console.log('Invalid SimpleFIN access key');\n throw new Error(`Invalid access key`);\n }\n [scheme, rest] = accessKey.split('//');\n [auth, rest] = rest.split('@');\n [username, password] = auth.split(':');\n baseUrl = `${scheme}//${rest}`;\n return {\n baseUrl,\n username,\n password,\n };\n}\n\nasync function getAccessKey(base64Token) {\n const token = Buffer.from(base64Token, 'base64').toString();\n const options = {\n method: 'POST',\n port: 443,\n headers: { 'Content-Length': 0 },\n };\n return new Promise((resolve, reject) => {\n const req = https.request(new URL(token), options, res => {\n res.on('data', d => {\n resolve(d.toString());\n });\n });\n req.on('error', e => {\n reject(e);\n });\n req.end();\n });\n}\n\nasync function getTransactions(accessKey, accounts, startDate, endDate) {\n const now = new Date();\n startDate = startDate || new Date(now.getFullYear(), now.getMonth(), 1);\n endDate = endDate || new Date(now.getFullYear(), now.getMonth() + 1, 1);\n console.log(`${getDate(startDate)} - ${getDate(endDate)}`);\n return await getAccounts(accessKey, accounts, startDate, endDate);\n}\n\nfunction getDate(date) {\n return date.toISOString().split('T')[0];\n}\n\nfunction normalizeDate(date) {\n return (date.valueOf() - date.getTimezoneOffset() * 60 * 1000) / 1000;\n}\n\nasync function getAccounts(\n accessKey,\n accounts,\n startDate,\n endDate,\n noTransactions = false,\n) {\n const sfin = parseAccessKey(accessKey);\n\n const headers = {\n Authorization: `Basic ${Buffer.from(\n `${sfin.username}:${sfin.password}`,\n ).toString('base64')}`,\n };\n\n const params = new URLSearchParams();\n if (!noTransactions) {\n if (startDate) {\n params.append('start-date', normalizeDate(startDate));\n }\n if (endDate) {\n params.append('end-date', normalizeDate(endDate));\n }\n params.append('pending', '1');\n } else {\n params.append('balances-only', '1');\n }\n\n if (accounts) {\n for (const id of accounts) {\n params.append('account', id);\n }\n }\n\n const url = new URL(`${sfin.baseUrl}/accounts`);\n url.search = params.toString();\n\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers,\n redirect: 'follow',\n });\n\n if (response.status === 403) {\n throw new Error('Forbidden');\n }\n\n const text = await response.text();\n try {\n const results = JSON.parse(text);\n results.sferrors = results.errors;\n results.hasError = false;\n results.errors = {};\n return results;\n } catch (e) {\n console.log(`Error parsing JSON response: ${text}`);\n throw e;\n }\n}\n","// TODO: Ok, several problems:\n//\n// * If nothing matches between two merkle trees, we should fallback\n// * to the last window instead the front one (use 0 instead of the\n// * key)\n//\n// * Need to check to make sure if account exists when handling\n// * transaction changes in syncing\n\nimport type { Timestamp } from './timestamp';\n\n/**\n * Represents a node within a trinary radix trie.\n */\nexport type TrieNode = {\n '0'?: TrieNode;\n '1'?: TrieNode;\n '2'?: TrieNode;\n hash?: number;\n};\n\ntype NumberTrieNodeKey = keyof Omit<TrieNode, 'hash'>;\n\nexport function emptyTrie(): TrieNode {\n return { hash: 0 };\n}\n\nfunction isNumberTrieNodeKey(input: string): input is NumberTrieNodeKey {\n return ['0', '1', '2'].includes(input);\n}\n\nexport function getKeys(trie: TrieNode): NumberTrieNodeKey[] {\n return Object.keys(trie).filter(isNumberTrieNodeKey);\n}\n\nexport function keyToTimestamp(key: string): number {\n // 16 is the length of the base 3 value of the current time in\n // minutes. Ensure it's padded to create the full value\n const fullkey = key + '0'.repeat(16 - key.length);\n\n // Parse the base 3 representation\n return parseInt(fullkey, 3) * 1000 * 60;\n}\n\n/**\n * Mutates `trie` to insert a node at `timestamp`\n */\nexport function insert(trie: TrieNode, timestamp: Timestamp) {\n const hash = timestamp.hash();\n const key = Number(Math.floor(timestamp.millis() / 1000 / 60)).toString(3);\n\n trie = Object.assign({}, trie, { hash: (trie.hash || 0) ^ hash });\n return insertKey(trie, key, hash);\n}\n\nfunction insertKey(trie: TrieNode, key: string, hash: number): TrieNode {\n if (key.length === 0) {\n return trie;\n }\n const c = key[0];\n const t = isNumberTrieNodeKey(c) ? trie[c] : undefined;\n const n = t || {};\n return Object.assign({}, trie, {\n [c]: Object.assign({}, n, insertKey(n, key.slice(1), hash), {\n hash: (n.hash || 0) ^ hash,\n }),\n });\n}\n\nexport function build(timestamps: Timestamp[]) {\n const trie = emptyTrie();\n for (const timestamp of timestamps) {\n insert(trie, timestamp);\n }\n return trie;\n}\n\nexport function diff(trie1: TrieNode, trie2: TrieNode): number | null {\n if (trie1.hash === trie2.hash) {\n return null;\n }\n\n let node1 = trie1;\n let node2 = trie2;\n let k = '';\n\n // This loop will eventually stop when it traverses down to find\n // where the hashes differ, or otherwise when there are no leaves\n // left (this shouldn't happen, if that's the case the hash check at\n // the top of this function should pass)\n while (true) {\n const keyset = new Set([...getKeys(node1), ...getKeys(node2)]);\n const keys = [...keyset.values()];\n keys.sort((a, b) => a.localeCompare(b));\n\n let diffkey: null | '0' | '1' | '2' = null;\n\n // Traverse down the trie through keys that aren't the same. We\n // traverse down the keys in order. Stop in two cases: either one\n // of the nodes doesn't have the key, or a different key isn't\n // found. For the former case, we have to that because pruning is\n // lossy. We don't know if we've pruned off a changed key so we\n // can't traverse down anymore. For the latter case, it means two\n // things: either we've hit the bottom of the tree, or the changed\n // key has been pruned off. In the latter case we have a \"partial\"\n // key and will fill the rest with 0s. Note that if multiple older\n // messages were added into one trie, it's possible we will\n // generate a time that only encompasses *some* of the those\n // messages. Pruning is lossy, and we traverse down the left-most\n // changed time that we know of, because of pruning it might take\n // multiple passes to sync up a trie.\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n\n const next1 = node1[key];\n const next2 = node2[key];\n\n if (!next1 || !next2) {\n break;\n }\n\n if (next1.hash !== next2.hash) {\n diffkey = key;\n break;\n }\n }\n\n if (!diffkey) {\n return keyToTimestamp(k);\n }\n\n k += diffkey;\n node1 = node1[diffkey] || emptyTrie();\n node2 = node2[diffkey] || emptyTrie();\n }\n\n // oxlint-disable-next-line no-unreachable\n return null;\n}\n\nexport function prune(trie: TrieNode, n = 2): TrieNode {\n // Do nothing if empty\n if (!trie.hash) {\n return trie;\n }\n\n const keys = getKeys(trie);\n keys.sort((a, b) => a.localeCompare(b));\n\n const next: TrieNode = { hash: trie.hash };\n\n // Prune child nodes.\n for (const k of keys.slice(-n)) {\n const node = trie[k];\n\n if (!node) {\n throw new Error(`TrieNode for key ${k} could not be found`);\n }\n\n next[k] = prune(node, n);\n }\n\n return next;\n}\n\nexport function debug(trie: TrieNode, k = '', indent = 0): string {\n const str =\n ' '.repeat(indent) +\n (k !== '' ? `k: ${k} ` : '') +\n `hash: ${trie.hash || '(empty)'}\\n`;\n return (\n str +\n getKeys(trie)\n .map(key => {\n const node = trie[key];\n if (!node) return '';\n return debug(node, key, indent + 2);\n })\n .join('')\n );\n}\n","import murmurhash from 'murmurhash';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport type { TrieNode } from './merkle';\n\n/**\n * Hybrid Unique Logical Clock (HULC) timestamp generator\n *\n * Globally-unique, monotonic timestamps are generated from the\n * combination of the unreliable system time, a counter, and an\n * identifier for the current node (instance, machine, process, etc.).\n * These timestamps can accommodate clock stuttering (duplicate values),\n * regression, and node differences within the configured maximum drift.\n *\n * In order to generate timestamps locally or for transmission to another\n * node, use the send() method. For global causality, timestamps must\n * be included in each message processed by the system. Whenever a\n * message is received, its timestamp must be passed to the recv()\n * method.\n *\n * Timestamps serialize into a 46-character collatable string\n * example: 2015-04-24T22:23:42.123Z-1000-0123456789ABCDEF\n * example: 2015-04-24T22:23:42.123Z-1000-A219E7A71CC18912\n *\n * The 64-bit hybrid clock is based on the HLC specification,\n * http://www.cse.buffalo.edu/tech-reports/2014-04.pdf\n */\n\nexport type Clock = {\n timestamp: MutableTimestamp;\n merkle: TrieNode;\n};\n\n// A mutable global clock\nlet clock: Clock;\n\nexport function setClock(clock_: Clock): void {\n clock = clock_;\n}\n\nexport function getClock(): Clock {\n return clock;\n}\n\nexport function makeClock(timestamp: Timestamp, merkle: TrieNode = {}) {\n return { timestamp: MutableTimestamp.from(timestamp), merkle };\n}\n\nexport function serializeClock(clock: Clock): string {\n return JSON.stringify({\n timestamp: clock.timestamp.toString(),\n merkle: clock.merkle,\n });\n}\n\nexport function deserializeClock(clock: string): Clock {\n let data;\n try {\n data = JSON.parse(clock);\n } catch {\n data = {\n timestamp: '1970-01-01T00:00:00.000Z-0000-' + makeClientId(),\n merkle: {},\n };\n }\n\n const ts = Timestamp.parse(data.timestamp);\n\n if (!ts) {\n throw new Timestamp.InvalidError(data.timestamp);\n }\n\n return {\n timestamp: MutableTimestamp.from(ts),\n merkle: data.merkle,\n };\n}\n\nexport function makeClientId() {\n return uuidv4().replace(/-/g, '').slice(-16);\n}\n\nconst config = {\n // Allow 5 minutes of clock drift\n maxDrift: 5 * 60 * 1000,\n};\n\nconst MAX_COUNTER = parseInt('0xFFFF');\nconst MAX_NODE_LENGTH = 16;\n\n/**\n * timestamp instance class\n */\nexport class Timestamp {\n _state: { millis: number; counter: number; node: string };\n\n constructor(millis: number, counter: number, node: string) {\n this._state = {\n millis,\n counter,\n node,\n };\n }\n\n valueOf() {\n return this.toString();\n }\n\n toString() {\n return [\n new Date(this.millis()).toISOString(),\n ('0000' + this.counter().toString(16).toUpperCase()).slice(-4),\n ('0000000000000000' + this.node()).slice(-16),\n ].join('-');\n }\n\n millis() {\n return this._state.millis;\n }\n\n counter() {\n return this._state.counter;\n }\n\n node() {\n return this._state.node;\n }\n\n hash() {\n return murmurhash.v3(this.toString());\n }\n\n // Timestamp generator initialization\n // * sets the node ID to an arbitrary value\n // * useful for mocking/unit testing\n static init(options: { maxDrift?: number; node?: string } = {}) {\n if (options.maxDrift) {\n config.maxDrift = options.maxDrift;\n }\n\n setClock(\n makeClock(\n new Timestamp(\n 0,\n 0,\n options.node\n ? ('0000000000000000' + options.node).toString().slice(-16)\n : '',\n ),\n ),\n );\n }\n\n /**\n * maximum timestamp\n */\n\n static max = Timestamp.parse(\n '9999-12-31T23:59:59.999Z-FFFF-FFFFFFFFFFFFFFFF',\n )!;\n\n /**\n * timestamp parsing\n * converts a fixed-length string timestamp to the structured value\n */\n static parse(timestamp: string | Timestamp): Timestamp | null {\n if (timestamp instanceof Timestamp) {\n return timestamp;\n }\n if (typeof timestamp === 'string') {\n const parts = timestamp.split('-');\n if (parts && parts.length === 5) {\n const millis = Date.parse(parts.slice(0, 3).join('-')).valueOf();\n const counter = parseInt(parts[3], 16);\n const node = parts[4];\n if (\n !isNaN(millis) &&\n millis >= 0 &&\n !isNaN(counter) &&\n counter <= MAX_COUNTER &&\n typeof node === 'string' &&\n node.length <= MAX_NODE_LENGTH\n ) {\n return new Timestamp(millis, counter, node);\n }\n }\n }\n return null;\n }\n\n /**\n * Timestamp send. Generates a unique, monotonic timestamp suitable\n * for transmission to another system in string format\n */\n static send(): Timestamp | null {\n if (!clock) {\n return null;\n }\n\n // retrieve the local wall time\n const phys = Date.now();\n\n // unpack the clock.timestamp logical time and counter\n const lOld = clock.timestamp.millis();\n const cOld = clock.timestamp.counter();\n\n // calculate the next logical time and counter\n // * ensure that the logical time never goes backward\n // * increment the counter if phys time does not advance\n const lNew = Math.max(lOld, phys);\n const cNew = lOld === lNew ? cOld + 1 : 0;\n\n // check the result for drift and counter overflow\n if (lNew - phys > config.maxDrift) {\n throw new Timestamp.ClockDriftError(lNew, phys, config.maxDrift);\n }\n if (cNew > MAX_COUNTER) {\n throw new Timestamp.OverflowError();\n }\n\n // repack the logical time/counter\n clock.timestamp.setMillis(lNew);\n clock.timestamp.setCounter(cNew);\n\n return new Timestamp(\n clock.timestamp.millis(),\n clock.timestamp.counter(),\n clock.timestamp.node(),\n );\n }\n\n // Timestamp receive. Parses and merges a timestamp from a remote\n // system with the local timeglobal uniqueness and monotonicity are\n // preserved\n static recv(msg: Timestamp): Timestamp | null {\n if (!clock) {\n return null;\n }\n\n // retrieve the local wall time\n const phys = Date.now();\n\n // unpack the message wall time/counter\n const lMsg = msg.millis();\n const cMsg = msg.counter();\n\n // assert the node id and remote clock drift\n // if (msg.node() === clock.timestamp.node()) {\n // throw new Timestamp.DuplicateNodeError(clock.timestamp.node());\n // }\n if (lMsg - phys > config.maxDrift) {\n throw new Timestamp.ClockDriftError();\n }\n\n // unpack the clock.timestamp logical time and counter\n const lOld = clock.timestamp.millis();\n const cOld = clock.timestamp.counter();\n\n // calculate the next logical time and counter\n // . ensure that the logical time never goes backward\n // . if all logical clocks are equal, increment the max counter\n // . if max = old > message, increment local counter\n // . if max = messsage > old, increment message counter\n // . otherwise, clocks are monotonic, reset counter\n const lNew = Math.max(Math.max(lOld, phys), lMsg);\n const cNew =\n lNew === lOld && lNew === lMsg\n ? Math.max(cOld, cMsg) + 1\n : lNew === lOld\n ? cOld + 1\n : lNew === lMsg\n ? cMsg + 1\n : 0;\n\n // check the result for drift and counter overflow\n if (lNew - phys > config.maxDrift) {\n throw new Timestamp.ClockDriftError();\n }\n if (cNew > MAX_COUNTER) {\n throw new Timestamp.OverflowError();\n }\n\n // repack the logical time/counter\n clock.timestamp.setMillis(lNew);\n clock.timestamp.setCounter(cNew);\n\n return new Timestamp(\n clock.timestamp.millis(),\n clock.timestamp.counter(),\n clock.timestamp.node(),\n );\n }\n\n /**\n * zero/minimum timestamp\n */\n\n static zero = Timestamp.parse(\n '1970-01-01T00:00:00.000Z-0000-0000000000000000',\n )!;\n\n static since = (isoString: string) => isoString + '-0000-0000000000000000';\n\n /**\n * error classes\n */\n static DuplicateNodeError = class DuplicateNodeError extends Error {\n constructor(node: string) {\n super('duplicate node identifier ' + node);\n this.name = 'DuplicateNodeError';\n }\n };\n\n static ClockDriftError = class ClockDriftError extends Error {\n constructor(...args: unknown[]) {\n super(['maximum clock drift exceeded', ...args.map(String)].join(' '));\n this.name = 'ClockDriftError';\n }\n };\n\n static OverflowError = class OverflowError extends Error {\n constructor() {\n super('timestamp counter overflow');\n this.name = 'OverflowError';\n }\n };\n\n static InvalidError = class InvalidError extends Error {\n constructor(...args: unknown[]) {\n super(['timestamp is not valid'].concat(args.map(String)).join(' '));\n this.name = 'InvalidError';\n }\n };\n}\n\nclass MutableTimestamp extends Timestamp {\n static from(timestamp: Timestamp) {\n return new MutableTimestamp(\n timestamp.millis(),\n timestamp.counter(),\n timestamp.node(),\n );\n }\n\n setMillis(n: number) {\n this._state.millis = n;\n }\n\n setCounter(n: number) {\n this._state.counter = n;\n }\n\n setNode(n: string) {\n this._state.node = n;\n }\n}\n","// @generated by protoc-gen-es v2.11.0 with parameter \"target=ts\"\n// @generated from file sync.proto (syntax proto3)\n/* eslint-disable */\n\nimport type { Message as Message$1 } from '@bufbuild/protobuf';\nimport type { GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2';\nimport { fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2';\n\n/**\n * Describes the file sync.proto.\n */\nexport const file_sync: GenFile /*@__PURE__*/ = fileDesc(\n 'CgpzeW5jLnByb3RvIjoKDUVuY3J5cHRlZERhdGESCgoCaXYYASABKAwSDwoHYXV0aFRhZxgCIAEoDBIMCgRkYXRhGAMgASgMIkYKB01lc3NhZ2USDwoHZGF0YXNldBgBIAEoCRILCgNyb3cYAiABKAkSDgoGY29sdW1uGAMgASgJEg0KBXZhbHVlGAQgASgJIkoKD01lc3NhZ2VFbnZlbG9wZRIRCgl0aW1lc3RhbXAYASABKAkSEwoLaXNFbmNyeXB0ZWQYAiABKAgSDwoHY29udGVudBgDIAEoDCJ2CgtTeW5jUmVxdWVzdBIiCghtZXNzYWdlcxgBIAMoCzIQLk1lc3NhZ2VFbnZlbG9wZRIOCgZmaWxlSWQYAiABKAkSDwoHZ3JvdXBJZBgDIAEoCRINCgVrZXlJZBgFIAEoCRINCgVzaW5jZRgGIAEoCUoECAQQBSJCCgxTeW5jUmVzcG9uc2USIgoIbWVzc2FnZXMYASADKAsyEC5NZXNzYWdlRW52ZWxvcGUSDgoGbWVya2xlGAIgASgJYgZwcm90bzM',\n);\n\n/**\n * @generated from message EncryptedData\n */\nexport type EncryptedData = Message$1<'EncryptedData'> & {\n /**\n * @generated from field: bytes iv = 1;\n */\n iv: Uint8Array;\n\n /**\n * @generated from field: bytes authTag = 2;\n */\n authTag: Uint8Array;\n\n /**\n * @generated from field: bytes data = 3;\n */\n data: Uint8Array;\n};\n\n/**\n * Describes the message EncryptedData.\n * Use `create(EncryptedDataSchema)` to create a new message.\n */\nexport const EncryptedDataSchema: GenMessage<EncryptedData> /*@__PURE__*/ =\n messageDesc(file_sync, 0);\n\n/**\n * @generated from message Message\n */\nexport type Message = Message$1<'Message'> & {\n /**\n * @generated from field: string dataset = 1;\n */\n dataset: string;\n\n /**\n * @generated from field: string row = 2;\n */\n row: string;\n\n /**\n * @generated from field: string column = 3;\n */\n column: string;\n\n /**\n * @generated from field: string value = 4;\n */\n value: string;\n};\n\n/**\n * Describes the message Message.\n * Use `create(MessageSchema)` to create a new message.\n */\nexport const MessageSchema: GenMessage<Message> /*@__PURE__*/ = messageDesc(\n file_sync,\n 1,\n);\n\n/**\n * @generated from message MessageEnvelope\n */\nexport type MessageEnvelope = Message$1<'MessageEnvelope'> & {\n /**\n * @generated from field: string timestamp = 1;\n */\n timestamp: string;\n\n /**\n * @generated from field: bool isEncrypted = 2;\n */\n isEncrypted: boolean;\n\n /**\n * @generated from field: bytes content = 3;\n */\n content: Uint8Array;\n};\n\n/**\n * Describes the message MessageEnvelope.\n * Use `create(MessageEnvelopeSchema)` to create a new message.\n */\nexport const MessageEnvelopeSchema: GenMessage<MessageEnvelope> /*@__PURE__*/ =\n messageDesc(file_sync, 2);\n\n/**\n * @generated from message SyncRequest\n */\nexport type SyncRequest = Message$1<'SyncRequest'> & {\n /**\n * @generated from field: repeated MessageEnvelope messages = 1;\n */\n messages: MessageEnvelope[];\n\n /**\n * @generated from field: string fileId = 2;\n */\n fileId: string;\n\n /**\n * @generated from field: string groupId = 3;\n */\n groupId: string;\n\n /**\n * @generated from field: string keyId = 5;\n */\n keyId: string;\n\n /**\n * @generated from field: string since = 6;\n */\n since: string;\n};\n\n/**\n * Describes the message SyncRequest.\n * Use `create(SyncRequestSchema)` to create a new message.\n */\nexport const SyncRequestSchema: GenMessage<SyncRequest> /*@__PURE__*/ =\n messageDesc(file_sync, 3);\n\n/**\n * @generated from message SyncResponse\n */\nexport type SyncResponse = Message$1<'SyncResponse'> & {\n /**\n * @generated from field: repeated MessageEnvelope messages = 1;\n */\n messages: MessageEnvelope[];\n\n /**\n * @generated from field: string merkle = 2;\n */\n merkle: string;\n};\n\n/**\n * Describes the message SyncResponse.\n * Use `create(SyncResponseSchema)` to create a new message.\n */\nexport const SyncResponseSchema: GenMessage<SyncResponse> /*@__PURE__*/ =\n messageDesc(file_sync, 4);\n","export class FileNotFound extends Error {\n constructor(params = {}) {\n super(\"File does not exist or you don't have access to it\");\n this.details = params;\n }\n}\n\nexport class GenericFileError extends Error {\n constructor(message, params = {}) {\n super(message);\n this.details = params;\n }\n}\n","import { join, resolve } from 'node:path';\n\nimport { config } from '#load-config';\n\nimport type { BrandedId } from './types';\n\nconst ID_REGEX = /^[a-zA-Z0-9_-]+$/;\n\nexport type FileId = BrandedId<'file'>;\nexport type GroupId = BrandedId<'group'>;\n\nexport function isValidFileId(id: string): id is FileId {\n return ID_REGEX.test(id);\n}\n\nexport function isValidGroupId(id: string): id is GroupId {\n return ID_REGEX.test(id);\n}\n\nexport function getPathForUserFile(fileId: FileId) {\n return join(resolve(config.get('userFiles')), `file-${fileId}.blob`);\n}\n\nexport function getPathForGroupFile(groupId: GroupId) {\n return join(resolve(config.get('userFiles')), `group-${groupId}.sqlite`);\n}\n","import { getAccountDb, isAdmin } from '#account-db';\nimport { FileNotFound, GenericFileError } from '#app-sync/errors';\nimport type { WrappedDatabase } from '#db';\nimport { isValidFileId, isValidGroupId } from '#util/paths';\nimport type { FileId, GroupId } from '#util/paths';\n\nclass FileBase {\n name: string | null | undefined;\n groupId: GroupId | null | undefined;\n encryptSalt: string | null | undefined;\n encryptTest: string | null | undefined;\n encryptKeyId: string | null | undefined;\n encryptMeta: string | null | undefined;\n syncVersion: string | null | undefined;\n deleted: boolean;\n owner: string | null | undefined;\n\n constructor(\n name: string | null | undefined,\n groupId: GroupId | null | undefined,\n encryptSalt: string | null | undefined,\n encryptTest: string | null | undefined,\n encryptKeyId: string | null | undefined,\n encryptMeta: string | null | undefined,\n syncVersion: string | null | undefined,\n deleted: number | boolean | undefined,\n owner: string | null | undefined,\n ) {\n this.name = name;\n this.groupId = groupId;\n this.encryptSalt = encryptSalt;\n this.encryptTest = encryptTest;\n this.encryptKeyId = encryptKeyId;\n this.encryptMeta = encryptMeta;\n this.syncVersion = syncVersion;\n this.deleted = typeof deleted === 'boolean' ? deleted : Boolean(deleted);\n this.owner = owner;\n }\n}\n\ntype FileConstructorArgs = {\n id: FileId;\n name: string | null;\n groupId: GroupId | null;\n encryptSalt?: string | null;\n encryptTest?: string | null;\n encryptKeyId?: string | null;\n encryptMeta: string | null;\n syncVersion: string | null;\n deleted?: number | boolean;\n owner: string | null;\n};\n\nclass File extends FileBase {\n id: FileId;\n\n constructor({\n id,\n name = null,\n groupId = null,\n encryptSalt = null,\n encryptTest = null,\n encryptKeyId = null,\n encryptMeta = null,\n syncVersion = null,\n deleted = false,\n owner = null,\n }: FileConstructorArgs) {\n super(\n name,\n groupId,\n encryptSalt,\n encryptTest,\n encryptKeyId,\n encryptMeta,\n syncVersion,\n deleted,\n owner,\n );\n this.id = id;\n }\n}\n\n/**\n * Represents a file update. Will only update the fields that are defined.\n * @class\n * @extends FileBase\n */\nclass FileUpdate extends FileBase {\n constructor({\n name = undefined,\n groupId = undefined,\n encryptSalt = undefined,\n encryptTest = undefined,\n encryptKeyId = undefined,\n encryptMeta = undefined,\n syncVersion = undefined,\n deleted = undefined,\n owner = undefined,\n }: Partial<FileConstructorArgs>) {\n super(\n name,\n groupId,\n encryptSalt,\n encryptTest,\n encryptKeyId,\n encryptMeta,\n syncVersion,\n deleted,\n owner,\n );\n }\n}\n\nconst boolToInt = (bool: boolean) => {\n return bool ? 1 : 0;\n};\n\ntype RawFile = {\n id: string;\n group_id: string | null;\n sync_version: string | null;\n name: string | null;\n encrypt_meta: string | null;\n encrypt_salt: string | null;\n encrypt_test: string | null;\n encrypt_keyid: string | null;\n deleted: number;\n owner: string | null;\n};\n\nclass FilesService {\n accountDb: WrappedDatabase;\n\n constructor(accountDb: WrappedDatabase) {\n this.accountDb = accountDb;\n }\n\n get(fileId: FileId) {\n const rawFile = this.getRaw(fileId);\n if (!rawFile || (rawFile && rawFile.deleted)) {\n throw new FileNotFound();\n }\n\n return this.validate(rawFile);\n }\n\n set(file: File) {\n const deletedInt = boolToInt(file.deleted);\n this.accountDb.mutate(\n 'INSERT INTO files (id, group_id, sync_version, name, encrypt_meta, encrypt_salt, encrypt_test, encrypt_keyid, deleted, owner) VALUES (?, ?, ?, ?, ?, ?, ?, ? ,?, ?)',\n [\n file.id,\n file.groupId,\n file.syncVersion?.toString(),\n file.name,\n file.encryptMeta,\n file.encryptSalt,\n file.encryptTest,\n file.encryptKeyId,\n deletedInt,\n file.owner,\n ],\n );\n }\n\n find({ userId, limit = 1000 }: { userId: string; limit?: number }) {\n const canSeeAll = isAdmin(userId);\n\n return (\n canSeeAll\n ? this.accountDb.all('SELECT * FROM files WHERE deleted = 0 LIMIT ?', [\n limit,\n ])\n : this.accountDb.all(\n `SELECT files.*\n FROM files\n WHERE files.owner = ? and deleted = 0\n UNION\n SELECT files.*\n FROM files\n JOIN user_access\n ON user_access.file_id = files.id\n AND user_access.user_id = ?\n WHERE files.deleted = 0 LIMIT ?`,\n [userId, userId, limit],\n )\n ).map((item: RawFile) => this.validate(item));\n }\n\n findUsersWithAccess(fileId: FileId) {\n const userAccess =\n this.accountDb.all(\n `SELECT UA.user_id as userId, users.display_name displayName, users.user_name userName\n FROM files\n JOIN user_access UA ON UA.file_id = files.id\n JOIN users on users.id = UA.user_id\n WHERE files.id = ?\n UNION ALL\n SELECT users.id, users.display_name, users.user_name\n FROM files\n JOIN users on users.id = files.owner\n WHERE files.id = ?\n `,\n [fileId, fileId],\n ) || [];\n\n return userAccess;\n }\n\n update(id: FileId, fileUpdate: Partial<File>) {\n let query = 'UPDATE files SET';\n const params = [];\n const updates = [];\n\n if (fileUpdate.name !== undefined) {\n updates.push('name = ?');\n params.push(fileUpdate.name);\n }\n if (fileUpdate.groupId !== undefined) {\n updates.push('group_id = ?');\n params.push(fileUpdate.groupId);\n }\n if (fileUpdate.encryptSalt !== undefined) {\n updates.push('encrypt_salt = ?');\n params.push(fileUpdate.encryptSalt);\n }\n if (fileUpdate.encryptTest !== undefined) {\n updates.push('encrypt_test = ?');\n params.push(fileUpdate.encryptTest);\n }\n if (fileUpdate.encryptKeyId !== undefined) {\n updates.push('encrypt_keyid = ?');\n params.push(fileUpdate.encryptKeyId);\n }\n if (fileUpdate.encryptMeta !== undefined) {\n updates.push('encrypt_meta = ?');\n params.push(fileUpdate.encryptMeta);\n }\n if (fileUpdate.syncVersion !== undefined) {\n updates.push('sync_version = ?');\n params.push(fileUpdate.syncVersion);\n }\n if (fileUpdate.deleted !== undefined) {\n updates.push('deleted = ?');\n params.push(boolToInt(fileUpdate.deleted));\n }\n\n if (updates.length > 0) {\n query += ' ' + updates.join(', ') + ' WHERE id = ?';\n params.push(id);\n\n const res = this.accountDb.mutate(query, params);\n\n if (res.changes !== 1) {\n throw new GenericFileError('Could not update File', { id });\n }\n }\n\n // Return the modified object\n const rawFile = this.getRaw(id);\n if (!rawFile) {\n throw new GenericFileError('File not found', { id });\n }\n return this.validate(rawFile);\n }\n\n getRaw(fileId: FileId): RawFile | null {\n return this.accountDb.first(`SELECT * FROM files WHERE id = ?`, [fileId]);\n }\n\n validate(rawFile: RawFile) {\n const fileId = rawFile.id;\n if (!isValidFileId(fileId)) {\n throw new GenericFileError('Invalid file ID', { fileId });\n }\n\n let groupId: GroupId | null = null;\n if (rawFile.group_id !== null) {\n if (!isValidGroupId(rawFile.group_id)) {\n throw new GenericFileError('Invalid group ID', {\n groupId: rawFile.group_id,\n });\n }\n groupId = rawFile.group_id;\n }\n\n return new File({\n id: fileId,\n name: rawFile.name,\n groupId,\n encryptSalt: rawFile.encrypt_salt,\n encryptTest: rawFile.encrypt_test,\n encryptKeyId: rawFile.encrypt_keyid,\n encryptMeta: rawFile.encrypt_meta,\n syncVersion: rawFile.sync_version,\n deleted: Boolean(rawFile.deleted),\n owner: rawFile.owner,\n });\n }\n}\n\nconst filesService = new FilesService(getAccountDb());\n\nexport { filesService, FilesService, File, FileUpdate };\n","// This is a version representing the internal format of sync\n// messages. When this changes, all sync files need to be reset. We\n// will check this version when syncing and notify the user if they\n// need to reset.\nconst SYNC_FORMAT_VERSION = 2;\n\nconst validateSyncedFile = (groupId, keyId, currentFile) => {\n if (\n currentFile.syncVersion == null ||\n currentFile.syncVersion < SYNC_FORMAT_VERSION\n ) {\n return 'file-old-version';\n }\n\n // When resetting sync state, something went wrong. There is no\n // group id and it's awaiting a file to be uploaded.\n if (currentFile.groupId == null) {\n return 'file-needs-upload';\n }\n\n // Check to make sure the uploaded file is valid and has been\n // encrypted with the same key it is registered with (this might\n // be wrong if there was an error during the key creation\n // process)\n const uploadedKeyId = currentFile.encryptMeta\n ? JSON.parse(currentFile.encryptMeta).keyId\n : null;\n if (uploadedKeyId !== currentFile.encryptKeyId) {\n return 'file-key-mismatch';\n }\n\n // The changes being synced are part of an old group, which\n // means the file has been reset. User needs to re-download.\n if (groupId !== currentFile.groupId) {\n return 'file-has-reset';\n }\n\n // The data is encrypted with a different key which is\n // unacceptable. We can't accept these changes. Reject them and\n // tell the user that they need to generate the correct key\n // (which necessitates a sync reset so they need to re-download).\n if (keyId !== currentFile.encryptKeyId) {\n return 'file-has-new-key';\n }\n\n return null;\n};\n\nconst validateUploadedFile = (groupId, keyId, currentFile) => {\n if (!currentFile) {\n // File is new, so no need to validate\n return null;\n }\n // The uploading file is part of an old group, so reject\n // it. All of its internal sync state is invalid because its\n // old. The sync state has been reset, so user needs to\n // either reset again or download from the current group.\n if (groupId !== currentFile.groupId) {\n return 'file-has-reset';\n }\n\n // The key that the file is encrypted with is different than\n // the current registered key. All data must always be\n // encrypted with the registered key for consistency. Key\n // changes always necessitate a sync reset, which means this\n // upload is trying to overwrite another reset. That might\n // be be fine, but since we definitely cannot accept a file\n // encrypted with the wrong key, we bail and suggest the\n // user download the latest file.\n if (keyId !== currentFile.encryptKeyId) {\n return 'file-has-new-key';\n }\n\n return null;\n};\n\nexport { validateSyncedFile, validateUploadedFile };\n","export default \"\\nCREATE TABLE messages_binary\\n (timestamp TEXT PRIMARY KEY,\\n is_encrypted BOOLEAN,\\n content bytea);\\n\\nCREATE TABLE messages_merkles\\n (id INTEGER PRIMARY KEY,\\n merkle TEXT);\\n\"","import { existsSync } from 'node:fs';\n\nimport {\n create,\n merkle,\n MessageEnvelopeSchema,\n Timestamp,\n} from '@actual-app/crdt';\n\nimport { openDatabase } from './db';\nimport messagesSql from './sql/messages.sql?raw';\nimport { getPathForGroupFile } from './util/paths';\n\nfunction getGroupDb(groupId) {\n const path = getPathForGroupFile(groupId);\n const needsInit = !existsSync(path);\n\n const db = openDatabase(path);\n\n if (needsInit) {\n db.exec(messagesSql);\n }\n\n return db;\n}\n\nfunction addMessages(db, messages) {\n let returnValue;\n db.transaction(() => {\n let trie = getMerkle(db);\n\n if (messages.length > 0) {\n for (const msg of messages) {\n const info = db.mutate(\n `INSERT OR IGNORE INTO messages_binary (timestamp, is_encrypted, content)\n VALUES (?, ?, ?)`,\n [msg.timestamp, msg.isEncrypted ? 1 : 0, Buffer.from(msg.content)],\n );\n\n if (info.changes > 0) {\n trie = merkle.insert(trie, Timestamp.parse(msg.timestamp));\n }\n }\n }\n\n trie = merkle.prune(trie);\n\n db.mutate(\n 'INSERT INTO messages_merkles (id, merkle) VALUES (1, ?) ON CONFLICT (id) DO UPDATE SET merkle = ?',\n [JSON.stringify(trie), JSON.stringify(trie)],\n );\n\n returnValue = trie;\n });\n\n return returnValue;\n}\n\nfunction getMerkle(db) {\n const rows = db.all('SELECT * FROM messages_merkles');\n\n if (rows.length > 0) {\n return JSON.parse(rows[0].merkle);\n } else {\n // No merkle trie exists yet (first sync of the app), so create a\n // default one.\n return {};\n }\n}\n\nexport function sync(messages, since, groupId) {\n const db = getGroupDb(groupId);\n const newMessages = db.all(\n `SELECT * FROM messages_binary\n WHERE timestamp > ?\n ORDER BY timestamp`,\n [since],\n );\n\n const trie = addMessages(db, messages);\n\n db.close();\n\n return {\n trie,\n newMessages: newMessages.map(msg =>\n create(MessageEnvelopeSchema, {\n timestamp: msg.timestamp,\n isEncrypted: msg.is_encrypted === 1,\n content: msg.content,\n }),\n ),\n };\n}\n","// @ts-strict-ignore\nimport { Buffer } from 'node:buffer';\nimport fs from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\nimport {\n create,\n fromBinary,\n SyncRequestSchema,\n SyncResponseSchema,\n toBinary,\n} from '@actual-app/crdt';\nimport type { Request, Response } from 'express';\nimport express from 'express';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { getAccountDb, isAdmin } from './account-db';\nimport { FileNotFound } from './app-sync/errors';\nimport {\n File,\n FilesService,\n FileUpdate,\n} from './app-sync/services/files-service';\nimport {\n validateSyncedFile,\n validateUploadedFile,\n} from './app-sync/validation';\nimport { config } from './load-config';\nimport * as UserService from './services/user-service';\nimport * as simpleSync from './sync-simple';\nimport {\n errorMiddleware,\n requestLoggerMiddleware,\n validateSessionMiddleware,\n} from './util/middlewares';\nimport {\n getPathForGroupFile,\n getPathForUserFile,\n isValidFileId,\n isValidGroupId,\n} from './util/paths';\nimport type { GroupId } from './util/paths';\n\nconst app = express();\napp.use(validateSessionMiddleware);\napp.use(errorMiddleware);\napp.use(requestLoggerMiddleware);\napp.use(\n express.raw({\n type: 'application/actual-sync',\n limit: `${config.get('upload.fileSizeSyncLimitMB')}mb`,\n }),\n);\napp.use(\n express.raw({\n type: 'application/encrypted-file',\n limit: `${config.get('upload.syncEncryptedFileSizeLimitMB')}mb`,\n }),\n);\napp.use(express.json({ limit: `${config.get('upload.fileSizeLimitMB')}mb` }));\n\nexport { app as handlers };\n\nconst OK_RESPONSE = { status: 'ok' };\n\nfunction boolToInt(deleted: boolean) {\n return deleted ? 1 : 0;\n}\n\nfunction generateGroupId(): GroupId {\n const id = uuidv4();\n if (!isValidGroupId(id)) {\n throw new TypeError('UUID format no longer matches expected format');\n }\n return id;\n}\n\nfunction extractSingleHeader(\n req: Request,\n res: Response,\n key: string,\n): string | null {\n const value = req.headers[key];\n if (!value) {\n return null;\n }\n if (typeof value !== 'string') {\n res.status(400).send('Duplicate headers encountered for key ' + key);\n return null;\n }\n return value;\n}\n\nconst verifyFileExists = (\n fileId: unknown,\n filesService: FilesService,\n res: Response,\n errorObject: string | Record<string, unknown>,\n) => {\n if (typeof fileId !== 'string' || !isValidFileId(fileId)) {\n res.status(400).send('invalid fileId');\n return;\n }\n\n try {\n return filesService.get(fileId);\n } catch (e) {\n if (e instanceof FileNotFound) {\n //FIXME: error code should be 404. Need to make sure frontend is ok with it.\n //TODO: put this into a middleware that checks if FileNotFound is thrown and returns 404 and same error message\n // for every FileNotFound error\n res.status(400).send(errorObject);\n return;\n }\n throw e;\n }\n};\n\nfunction requireFileAccess(file: File, userId: string) {\n const isOwner = file.owner === userId;\n const isServerAdmin = isAdmin(userId);\n if (isOwner || isServerAdmin) {\n return null;\n }\n if (UserService.countUserAccess(file.id, userId) > 0) {\n return null;\n }\n return 'file-access-not-allowed';\n}\n\napp.post('/sync', async (req, res): Promise<void> => {\n let requestPb;\n try {\n requestPb = fromBinary(SyncRequestSchema, req.body);\n } catch (e) {\n console.log('Error parsing sync request', e);\n res.status(500);\n res.send({ status: 'error', reason: 'internal-error' });\n return;\n }\n\n const fileId = requestPb.fileId || null;\n const groupId = requestPb.groupId || null;\n const keyId = requestPb.keyId || null;\n const since = requestPb.since || null;\n const messages = requestPb.messages;\n\n if (!since) {\n res.status(422).send({\n details: 'since-required',\n reason: 'unprocessable-entity',\n status: 'error',\n });\n return;\n }\n\n const filesService = new FilesService(getAccountDb());\n\n const currentFile = verifyFileExists(\n fileId,\n filesService,\n res,\n 'file-not-found',\n );\n\n if (!currentFile) {\n return;\n }\n\n const fileAccessError = requireFileAccess(currentFile, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n const errorMessage = validateSyncedFile(groupId, keyId, currentFile);\n if (errorMessage) {\n res.status(400);\n res.send(errorMessage);\n return;\n }\n\n const { trie, newMessages } = simpleSync.sync(messages, since, groupId);\n\n const responsePb = create(SyncResponseSchema, {\n merkle: JSON.stringify(trie),\n messages: newMessages,\n });\n\n res.set('Content-Type', 'application/actual-sync');\n res.set('X-ACTUAL-SYNC-METHOD', 'simple');\n res.send(Buffer.from(toBinary(SyncResponseSchema, responsePb)));\n});\n\napp.post('/user-get-key', (req, res) => {\n if (!res.locals) return;\n\n const { fileId } = req.body || {};\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(fileId, filesService, res, 'file-not-found');\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n res.send({\n status: 'ok',\n data: {\n id: file.encryptKeyId,\n salt: file.encryptSalt,\n test: file.encryptTest,\n },\n });\n});\n\napp.post('/user-create-key', (req, res) => {\n const { fileId, keyId, keySalt, testContent } = req.body || {};\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(fileId, filesService, res, 'file-not-found');\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n filesService.update(\n file.id,\n new FileUpdate({\n encryptSalt: keySalt,\n encryptKeyId: keyId,\n encryptTest: testContent,\n }),\n );\n\n res.send(OK_RESPONSE);\n});\n\napp.post('/reset-user-file', async (req, res) => {\n const { fileId } = req.body || {};\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(\n fileId,\n filesService,\n res,\n 'User or file not found',\n );\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n const groupId = file.groupId;\n\n filesService.update(file.id, new FileUpdate({ groupId: null }));\n\n if (groupId) {\n try {\n await fs.unlink(getPathForGroupFile(groupId));\n } catch {\n console.log(`Unable to delete sync data for group \"${groupId}\"`);\n }\n }\n\n res.send(OK_RESPONSE);\n});\n\napp.post('/upload-user-file', async (req, res) => {\n if (typeof req.headers['x-actual-name'] !== 'string') {\n // FIXME: Not sure how this cannot be a string when the header is\n // set.\n res.status(400).send('single x-actual-name is required');\n return;\n }\n\n const name = decodeURIComponent(req.headers['x-actual-name']);\n const fileId = req.headers['x-actual-file-id'];\n\n if (!fileId || typeof fileId !== 'string') {\n res.status(400).send('fileId is required');\n return;\n }\n if (!isValidFileId(fileId)) {\n res.status(400).send('invalid fileId');\n return;\n }\n\n let groupId = req.headers['x-actual-group-id'] || null;\n const encryptMeta = extractSingleHeader(req, res, 'x-actual-encrypt-meta');\n if (res.headersSent) return;\n const syncFormatVersion = extractSingleHeader(req, res, 'x-actual-format');\n if (res.headersSent) return;\n\n if (!!groupId && (typeof groupId !== 'string' || !isValidGroupId(groupId))) {\n res.status(400).send('invalid groupId');\n return;\n }\n\n const keyId =\n encryptMeta && typeof encryptMeta === 'string'\n ? JSON.parse(encryptMeta).keyId\n : null;\n\n const filesService = new FilesService(getAccountDb());\n let currentFile;\n\n try {\n currentFile = filesService.get(fileId);\n } catch (e) {\n if (e instanceof FileNotFound) {\n currentFile = null;\n } else {\n throw e;\n }\n }\n\n const fileAccessError = currentFile\n ? requireFileAccess(currentFile, res.locals.user_id)\n : null;\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n const errorMessage = validateUploadedFile(groupId, keyId, currentFile);\n if (errorMessage) {\n res.status(400).send(errorMessage);\n return;\n }\n\n try {\n await fs.writeFile(getPathForUserFile(fileId), req.body);\n } catch (err) {\n console.log('Error writing file', err);\n res.status(500).send({ status: 'error' });\n return;\n }\n\n if (!currentFile) {\n // it's new\n const newGroupId = generateGroupId();\n groupId = newGroupId;\n filesService.set(\n new File({\n id: fileId,\n groupId: newGroupId,\n syncVersion: syncFormatVersion,\n name,\n encryptMeta,\n owner:\n res.locals.user_id ||\n (() => {\n throw new Error('User ID is required for file creation');\n })(),\n }),\n );\n\n res.send({ status: 'ok', groupId });\n return;\n }\n\n if (!groupId) {\n // sync state was reset, create new group\n const newGroupId = generateGroupId();\n groupId = newGroupId;\n filesService.update(fileId, new FileUpdate({ groupId: newGroupId }));\n }\n\n // Regardless, update some properties\n filesService.update(\n fileId,\n new FileUpdate({\n syncVersion: syncFormatVersion,\n encryptMeta,\n name,\n }),\n );\n\n res.send({ status: 'ok', groupId });\n});\n\napp.get('/download-user-file', async (req, res) => {\n const fileId = req.headers['x-actual-file-id'];\n if (typeof fileId !== 'string') {\n // FIXME: Not sure how this cannot be a string when the header is\n // set.\n res.status(400).send('Single file ID is required');\n return;\n }\n if (!isValidFileId(fileId)) {\n res.status(400).send('invalid fileId');\n return;\n }\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(\n fileId,\n filesService,\n res,\n 'User or file not found',\n );\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n const path = getPathForUserFile(fileId);\n\n if (!path.startsWith(resolve(config.get('userFiles')))) {\n //Ensure the user doesn't try to access files outside of the user files directory\n res.status(403).send('Access denied');\n return;\n }\n\n res.setHeader('Content-Disposition', `attachment;filename=${fileId}`);\n res.sendFile(path, { dotfiles: 'allow' });\n});\n\napp.post('/update-user-filename', (req, res) => {\n const { fileId, name } = req.body || {};\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(fileId, filesService, res, 'file-not-found');\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n filesService.update(file.id, new FileUpdate({ name }));\n res.send(OK_RESPONSE);\n});\n\napp.get('/list-user-files', (req, res) => {\n const fileService = new FilesService(getAccountDb());\n const rows = fileService.find({ userId: res.locals.user_id });\n res.send({\n status: 'ok',\n data: rows.map(row => ({\n deleted: boolToInt(row.deleted),\n fileId: row.id,\n groupId: row.groupId,\n name: row.name,\n encryptKeyId: row.encryptKeyId,\n owner: row.owner,\n usersWithAccess: fileService.findUsersWithAccess(row.id).map(access => ({\n ...access,\n owner: access.userId === row.owner,\n })),\n })),\n });\n});\n\napp.get('/get-user-file-info', (req, res) => {\n const fileId = req.headers['x-actual-file-id'];\n\n // TODO: Return 422 if fileId is not provided. Need to make sure frontend can handle it\n // if (!fileId) {\n // return res.status(422).send({\n // details: 'fileId-required',\n // reason: 'unprocessable-entity',\n // status: 'error',\n // });\n // }\n\n const fileService = new FilesService(getAccountDb());\n\n const file = verifyFileExists(fileId, fileService, res, {\n status: 'error',\n reason: 'file-not-found',\n });\n\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n res.send({\n status: 'ok',\n data: {\n deleted: boolToInt(file.deleted), // FIXME: convert to boolean, make sure it works in the frontend\n fileId: file.id,\n groupId: file.groupId,\n name: file.name,\n encryptMeta: file.encryptMeta ? JSON.parse(file.encryptMeta) : null,\n usersWithAccess: fileService.findUsersWithAccess(file.id).map(access => ({\n ...access,\n owner: access.userId === file.owner,\n })),\n },\n });\n});\n\napp.post('/delete-user-file', (req, res) => {\n const { fileId } = req.body || {};\n\n if (!fileId) {\n res.status(422).send({\n details: 'fileId-required',\n reason: 'unprocessable-entity',\n status: 'error',\n });\n return;\n }\n\n const filesService = new FilesService(getAccountDb());\n const file = verifyFileExists(fileId, filesService, res, 'file-not-found');\n if (!file) {\n return;\n }\n\n const fileAccessError = requireFileAccess(file, res.locals.user_id);\n if (fileAccessError) {\n res.status(403);\n res.send(fileAccessError);\n return;\n }\n\n filesService.update(file.id, new FileUpdate({ deleted: true }));\n\n res.send(OK_RESPONSE);\n});\n","import fs, { readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport cors from 'cors';\nimport express from 'express';\nimport rateLimit from 'express-rate-limit';\n\nimport { bootstrap } from './account-db';\nimport * as accountApp from './app-account';\nimport * as adminApp from './app-admin';\nimport * as corsApp from './app-cors-proxy';\nimport * as enableBankingApp from './app-enablebanking/app-enablebanking';\nimport * as goCardlessApp from './app-gocardless/app-gocardless';\nimport * as openidApp from './app-openid';\nimport * as pluggai from './app-pluggyai/app-pluggyai';\nimport * as secretApp from './app-secrets';\nimport * as simpleFinApp from './app-simplefin/app-simplefin';\nimport * as syncApp from './app-sync';\nimport { config } from './load-config';\n\nconst app = express();\n\nprocess.on('unhandledRejection', reason => {\n console.log('Rejection:', reason);\n});\n\napp.disable('x-powered-by');\napp.use(cors());\napp.set('trust proxy', config.get('trustedProxies'));\nif (process.env.NODE_ENV !== 'development') {\n app.use(\n rateLimit({\n windowMs: 60 * 1000,\n max: 500,\n legacyHeaders: false,\n standardHeaders: true,\n }),\n );\n}\n\napp.use(express.json({ limit: `${config.get('upload.fileSizeLimitMB')}mb` }));\n\napp.use(\n express.raw({\n type: 'application/actual-sync',\n limit: `${config.get('upload.fileSizeSyncLimitMB')}mb`,\n }),\n);\n\napp.use(\n express.raw({\n type: 'application/encrypted-file',\n limit: `${config.get('upload.syncEncryptedFileSizeLimitMB')}mb`,\n }),\n);\n\napp.use('/sync', syncApp.handlers);\napp.use('/account', accountApp.handlers);\napp.use('/gocardless', goCardlessApp.handlers);\napp.use('/simplefin', simpleFinApp.handlers);\napp.use('/pluggyai', pluggai.handlers);\napp.use('/enablebanking', enableBankingApp.handlers);\napp.use('/secret', secretApp.handlers);\n\nif (config.get('corsProxy.enabled')) {\n app.use('/cors-proxy', corsApp.handlers);\n}\n\napp.use('/admin', adminApp.handlers);\napp.use('/openid', openidApp.handlers);\n\napp.get('/mode', (req, res) => {\n res.send(config.get('mode'));\n});\n\napp.get('/info', (_req, res) => {\n function findPackageJson(startDir: string) {\n // find the nearest package.json file while traversing up the directory tree\n let currentPath = startDir;\n let directoriesSearched = 0;\n const pathRoot = resolve(currentPath, '/');\n try {\n while (currentPath !== pathRoot && directoriesSearched < 5) {\n const packageJsonPath = resolve(currentPath, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(\n readFileSync(packageJsonPath, 'utf-8'),\n );\n\n if (packageJson.name === '@actual-app/sync-server') {\n return packageJson;\n }\n }\n\n currentPath = resolve(join(currentPath, '..')); // Move up one directory\n directoriesSearched++;\n }\n } catch (error) {\n console.error('Error while searching for package.json:', error);\n }\n\n return null;\n }\n\n const dirname = resolve(fileURLToPath(import.meta.url), '../');\n const packageJson = findPackageJson(dirname);\n\n res.status(200).json({\n build: {\n name: packageJson?.name,\n description: packageJson?.description,\n version: packageJson?.version,\n },\n });\n});\n\napp.get('/health', (_req, res) => {\n res.status(200).json({ status: 'UP' });\n});\n\napp.get('/metrics', (_req, res) => {\n res.status(200).json({\n mem: process.memoryUsage(),\n uptime: process.uptime(),\n });\n});\n\n// The web frontend.\n// Dev mode proxies to Vite, which injects inline preamble scripts and uses\n// a websocket for HMR. Loosen script-src and connect-src accordingly.\n// `'unsafe-eval'` is required at runtime for the Electron app, so it is\n// kept in both branches.\nconst isDev = process.env.NODE_ENV === 'development';\nconst scriptSrc = isDev\n ? \"'self' 'unsafe-inline' 'unsafe-eval' blob:\"\n : \"'self' 'unsafe-eval' blob:\";\nconst connectSrc = isDev ? \"'self' ws: wss: http: https:\" : 'http: https:';\nconst csp = [\n \"default-src 'self' blob:\",\n \"img-src 'self' blob: data:\",\n `script-src ${scriptSrc}`,\n \"style-src 'self' 'unsafe-inline'\",\n \"font-src 'self' data:\",\n `connect-src ${connectSrc}`,\n].join('; ');\n\napp.use((req, res, next) => {\n res.set('Cross-Origin-Opener-Policy', 'same-origin');\n res.set('Cross-Origin-Embedder-Policy', 'require-corp');\n res.set('Content-Security-Policy', csp);\n next();\n});\nif (isDev) {\n console.log(\n 'Running in development mode - Proxying frontend routes to React Dev Server',\n );\n\n // Imported within Dev block to allow dev dependency in package.json (reduces package size in production)\n const httpProxyMiddleware = await import('http-proxy-middleware');\n\n app.use(\n httpProxyMiddleware.createProxyMiddleware({\n target: 'http://localhost:3001',\n changeOrigin: true,\n ws: true,\n }),\n );\n} else {\n console.log('Running in production mode - Serving static React app');\n\n app.use(express.static(config.get('webRoot'), { index: false }));\n app.get('/{*splat}', (req, res) =>\n res.sendFile('index.html', { root: config.get('webRoot') }),\n );\n}\n\nfunction parseHTTPSConfig(value: string) {\n if (value.startsWith('-----BEGIN')) {\n return value;\n }\n return fs.readFileSync(value);\n}\n\nfunction sendServerStartedMessage() {\n // Signify to any parent process that the server has started. Used in electron desktop app\n // oxlint-disable-next-line typescript/ban-ts-comment\n // @ts-ignore-error electron types\n process.parentPort?.postMessage({ type: 'server-started' });\n console.log(\n 'Listening on ' + config.get('hostname') + ':' + config.get('port') + '...',\n );\n}\n\nexport async function run() {\n const portVal = config.get('port');\n const port = typeof portVal === 'string' ? parseInt(portVal) : portVal;\n const hostname = config.get('hostname');\n const openIdConfig = config?.getProperties()?.openId;\n if (\n openIdConfig?.discoveryURL ||\n openIdConfig?.issuer?.authorization_endpoint\n ) {\n console.log('OpenID configuration found. Preparing server to use it');\n try {\n const result = await bootstrap({ openId: openIdConfig }, true);\n if ('error' in result && result.error) {\n console.log(result.error);\n } else {\n console.log('OpenID configured!');\n }\n } catch (err) {\n console.error(err);\n }\n }\n\n if (config.get('https.key') && config.get('https.cert')) {\n const https = await import('node:https');\n const httpsOptions = {\n ...config.get('https'),\n key: parseHTTPSConfig(config.get('https.key')),\n cert: parseHTTPSConfig(config.get('https.cert')),\n };\n https.createServer(httpsOptions, app).listen(port, hostname, () => {\n sendServerStartedMessage();\n });\n } else {\n app.listen(port, hostname, () => {\n sendServerStartedMessage();\n });\n }\n}\n"],"mappings":"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"}
|