Files
accounted/lib/notices/predicates.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

72 lines
2.6 KiB
TypeScript

/**
* Pure notice predicates: no queries, no side effects, no server-only
* imports. Client pages ('use client') import from THIS file; the server
* detection layer (lib/notices/categories.ts, which pulls in server-only
* dependencies) builds on the same functions and re-exports them, so every
* surface runs one shared decision.
*/
export interface ExpiringBankConnection {
id: string
bank_name: string
days_left: number
}
/**
* The canonical "consent expiring soon" day-math over already-fetched
* bank_connections rows: within (0, 14] days. Used by the
* bank_connection_expiring notice AND by the Hem page's Att göra Bevaka
* row, so the two surfaces can never disagree on the threshold.
*/
export function expiringBankConnectionsFrom(
rows: { id: string; bank_name: string | null; consent_expires: string | null }[],
now: Date = new Date(),
): ExpiringBankConnection[] {
const nowMs = now.getTime()
const result: ExpiringBankConnection[] = []
for (const row of rows) {
if (!row.consent_expires) continue
const daysLeft = Math.ceil(
(new Date(row.consent_expires).getTime() - nowMs) / (1000 * 60 * 60 * 24),
)
if (daysLeft > 0 && daysLeft <= 14) {
result.push({ id: row.id, bank_name: row.bank_name ?? '', days_left: daysLeft })
}
}
return result
}
/** The skatteverket extension's /status response shape (subset we decide on). */
export interface SkvStatusLike {
connected?: boolean
disabled?: boolean
needsReconsent?: boolean
expired?: boolean
canRefresh?: boolean
}
/**
* The canonical "Skatteverket needs reconnect" predicate over a fetched
* /status shape. A connection needs reconnecting when it exists, is not
* env-disabled, and either was flagged needs_reconsent by a cron/API call or
* has an expired access token with nothing left to refresh with.
*/
export function skvStatusNeedsReconnect(s: SkvStatusLike): boolean {
return Boolean(s.connected && !s.disabled && (s.needsReconsent || (s.expired && !s.canRefresh)))
}
/**
* The canonical "this auth failure means reconnect" predicate over a failed
* skatteverket API response. 401 covers several distinct auth states (see
* handleSkvError in the skatteverket extension): only NOT_CONNECTED means "no
* connection exists"; the rest (SESSION_EXPIRED, MISSING_SCOPE, TOKEN_REVOKED,
* TOKEN_CORRUPTED, ...) fire while a stored connection exists and mean the
* user must reconnect with BankID.
*/
export function skvAuthErrorNeedsReconnect(
status: number,
code: string | null | undefined,
): boolean {
return status === 401 && code !== 'NOT_CONNECTED'
}