@coderoftime/fints 0.1.0 → 0.2.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.
- package/CHANGELOG.md +142 -1
- package/README.md +127 -37
- package/dist/client.d.ts +48 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +199 -0
- package/dist/client.js.map +1 -0
- package/dist/core/account.d.ts +35 -7
- package/dist/core/account.d.ts.map +1 -1
- package/dist/core/account.js +84 -13
- package/dist/core/account.js.map +1 -1
- package/dist/core/amount.d.ts +2 -1
- package/dist/core/amount.d.ts.map +1 -1
- package/dist/core/amount.js +4 -1
- package/dist/core/amount.js.map +1 -1
- package/dist/core/balance.d.ts +1 -0
- package/dist/core/balance.d.ts.map +1 -1
- package/dist/core/balance.js +6 -5
- package/dist/core/balance.js.map +1 -1
- package/dist/core/bank-info.d.ts +1 -1
- package/dist/core/bank-info.js +1 -1
- package/dist/core/client.d.ts +50 -15
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/config.d.ts +28 -16
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +66 -26
- package/dist/core/config.js.map +1 -1
- package/dist/core/errors.d.ts +85 -23
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +112 -47
- package/dist/core/errors.js.map +1 -1
- package/dist/core/feedback.d.ts +4 -1
- package/dist/core/feedback.d.ts.map +1 -1
- package/dist/core/feedback.js +4 -1
- package/dist/core/feedback.js.map +1 -1
- package/dist/core/transaction.d.ts +6 -1
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +9 -4
- package/dist/core/transaction.js.map +1 -1
- package/dist/core/transport.d.ts +33 -6
- package/dist/core/transport.d.ts.map +1 -1
- package/dist/core/transport.js +77 -20
- package/dist/core/transport.js.map +1 -1
- package/dist/formats/camt.d.ts +24 -4
- package/dist/formats/camt.d.ts.map +1 -1
- package/dist/formats/camt.js +124 -41
- package/dist/formats/camt.js.map +1 -1
- package/dist/formats/mt940.d.ts +15 -3
- package/dist/formats/mt940.d.ts.map +1 -1
- package/dist/formats/mt940.js +161 -82
- package/dist/formats/mt940.js.map +1 -1
- package/dist/formats/readers.d.ts +6 -2
- package/dist/formats/readers.d.ts.map +1 -1
- package/dist/formats/readers.js +24 -29
- package/dist/formats/readers.js.map +1 -1
- package/dist/formats/record-budget.d.ts +7 -0
- package/dist/formats/record-budget.d.ts.map +1 -0
- package/dist/formats/record-budget.js +17 -0
- package/dist/formats/record-budget.js.map +1 -0
- package/dist/formats/sepa-purpose.d.ts +6 -0
- package/dist/formats/sepa-purpose.d.ts.map +1 -1
- package/dist/formats/sepa-purpose.js +23 -2
- package/dist/formats/sepa-purpose.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/session/app-approval.d.ts +11 -5
- package/dist/session/app-approval.d.ts.map +1 -1
- package/dist/session/app-approval.js +23 -12
- package/dist/session/app-approval.js.map +1 -1
- package/dist/session/dialog-runner.d.ts +7 -10
- package/dist/session/dialog-runner.d.ts.map +1 -1
- package/dist/session/dialog-runner.js +15 -16
- package/dist/session/dialog-runner.js.map +1 -1
- package/dist/session/login-guard.d.ts +54 -49
- package/dist/session/login-guard.d.ts.map +1 -1
- package/dist/session/login-guard.js +156 -84
- package/dist/session/login-guard.js.map +1 -1
- package/dist/session/sepa-accounts.d.ts +20 -14
- package/dist/session/sepa-accounts.d.ts.map +1 -1
- package/dist/session/sepa-accounts.js +62 -46
- package/dist/session/sepa-accounts.js.map +1 -1
- package/dist/session/session-setup.d.ts +33 -25
- package/dist/session/session-setup.d.ts.map +1 -1
- package/dist/session/session-setup.js +87 -65
- package/dist/session/session-setup.js.map +1 -1
- package/dist/session/session-state.d.ts +9 -6
- package/dist/session/session-state.d.ts.map +1 -1
- package/dist/session/session-state.js +52 -17
- package/dist/session/session-state.js.map +1 -1
- package/dist/session/signed-dialog.d.ts +77 -0
- package/dist/session/signed-dialog.d.ts.map +1 -0
- package/dist/session/signed-dialog.js +170 -0
- package/dist/session/signed-dialog.js.map +1 -0
- package/dist/session/tan-selection.d.ts +26 -18
- package/dist/session/tan-selection.d.ts.map +1 -1
- package/dist/session/tan-selection.js +44 -33
- package/dist/session/tan-selection.js.map +1 -1
- package/dist/session/user-prompts.d.ts +8 -4
- package/dist/session/user-prompts.d.ts.map +1 -1
- package/dist/session/user-prompts.js +34 -14
- package/dist/session/user-prompts.js.map +1 -1
- package/dist/tan/challenge.d.ts +34 -13
- package/dist/tan/challenge.d.ts.map +1 -1
- package/dist/tan/challenge.js +25 -26
- package/dist/tan/challenge.js.map +1 -1
- package/dist/tan/flicker.d.ts +7 -1
- package/dist/tan/flicker.d.ts.map +1 -1
- package/dist/tan/flicker.js +40 -6
- package/dist/tan/flicker.js.map +1 -1
- package/dist/tan/tan-image.d.ts +3 -2
- package/dist/tan/tan-image.d.ts.map +1 -1
- package/dist/tan/tan-image.js +7 -5
- package/dist/tan/tan-image.js.map +1 -1
- package/dist/tan/tan-models.d.ts +5 -1
- package/dist/tan/tan-models.d.ts.map +1 -1
- package/dist/tan/tan-models.js +10 -5
- package/dist/tan/tan-models.js.map +1 -1
- package/dist/util/async.d.ts +0 -2
- package/dist/util/async.d.ts.map +1 -1
- package/dist/util/async.js +0 -4
- package/dist/util/async.js.map +1 -1
- package/dist/util/bytes.d.ts +2 -3
- package/dist/util/bytes.d.ts.map +1 -1
- package/dist/util/bytes.js +12 -20
- package/dist/util/bytes.js.map +1 -1
- package/dist/util/call-context.d.ts.map +1 -1
- package/dist/util/call-context.js +3 -2
- package/dist/util/call-context.js.map +1 -1
- package/dist/util/dates.d.ts +9 -16
- package/dist/util/dates.d.ts.map +1 -1
- package/dist/util/dates.js +39 -19
- package/dist/util/dates.js.map +1 -1
- package/dist/util/errors.d.ts +9 -1
- package/dist/util/errors.d.ts.map +1 -1
- package/dist/util/errors.js +12 -3
- package/dist/util/errors.js.map +1 -1
- package/dist/util/json.d.ts +4 -0
- package/dist/util/json.d.ts.map +1 -1
- package/dist/util/json.js +18 -0
- package/dist/util/json.js.map +1 -1
- package/dist/util/text.d.ts +21 -4
- package/dist/util/text.d.ts.map +1 -1
- package/dist/util/text.js +42 -13
- package/dist/util/text.js.map +1 -1
- package/dist/util/xml.d.ts +4 -3
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/util/xml.js +37 -26
- package/dist/util/xml.js.map +1 -1
- package/dist/v3/account-extension.d.ts.map +1 -1
- package/dist/v3/account-extension.js +7 -10
- package/dist/v3/account-extension.js.map +1 -1
- package/dist/v3/bank-connection.d.ts +34 -0
- package/dist/v3/bank-connection.d.ts.map +1 -0
- package/dist/v3/bank-connection.js +58 -0
- package/dist/v3/bank-connection.js.map +1 -0
- package/dist/v3/dialog.d.ts +80 -46
- package/dist/v3/dialog.d.ts.map +1 -1
- package/dist/v3/dialog.js +162 -59
- package/dist/v3/dialog.js.map +1 -1
- package/dist/v3/message.d.ts +17 -4
- package/dist/v3/message.d.ts.map +1 -1
- package/dist/v3/message.js +62 -18
- package/dist/v3/message.js.map +1 -1
- package/dist/v3/parameter-store.d.ts +75 -0
- package/dist/v3/parameter-store.d.ts.map +1 -0
- package/dist/v3/parameter-store.js +143 -0
- package/dist/v3/parameter-store.js.map +1 -0
- package/dist/v3/parameters.d.ts +38 -12
- package/dist/v3/parameters.d.ts.map +1 -1
- package/dist/v3/parameters.js +91 -37
- package/dist/v3/parameters.js.map +1 -1
- package/dist/v3/protocol.d.ts +43 -50
- package/dist/v3/protocol.d.ts.map +1 -1
- package/dist/v3/protocol.js +58 -208
- package/dist/v3/protocol.js.map +1 -1
- package/dist/v3/queries.d.ts +51 -0
- package/dist/v3/queries.d.ts.map +1 -0
- package/dist/v3/queries.js +120 -0
- package/dist/v3/queries.js.map +1 -0
- package/dist/v3/segments.d.ts +29 -12
- package/dist/v3/segments.d.ts.map +1 -1
- package/dist/v3/segments.js +65 -23
- package/dist/v3/segments.js.map +1 -1
- package/dist/v3/syntax.d.ts +65 -14
- package/dist/v3/syntax.d.ts.map +1 -1
- package/dist/v3/syntax.js +167 -101
- package/dist/v3/syntax.js.map +1 -1
- package/package.json +10 -6
- package/src/client.ts +252 -0
- package/src/core/account.ts +86 -13
- package/src/core/amount.ts +3 -1
- package/src/core/balance.ts +6 -5
- package/src/core/bank-info.ts +1 -1
- package/src/core/client.ts +52 -15
- package/src/core/config.ts +69 -28
- package/src/core/errors.ts +122 -48
- package/src/core/feedback.ts +4 -1
- package/src/core/transaction.ts +9 -4
- package/src/core/transport.ts +98 -23
- package/src/formats/camt.ts +136 -39
- package/src/formats/mt940.ts +167 -81
- package/src/formats/readers.ts +24 -31
- package/src/formats/record-budget.ts +18 -0
- package/src/formats/sepa-purpose.ts +21 -2
- package/src/index.ts +4 -2
- package/src/session/app-approval.ts +51 -38
- package/src/session/dialog-runner.ts +31 -28
- package/src/session/login-guard.ts +174 -88
- package/src/session/sepa-accounts.ts +63 -45
- package/src/session/session-setup.ts +90 -71
- package/src/session/session-state.ts +56 -22
- package/src/session/signed-dialog.ts +192 -0
- package/src/session/tan-selection.ts +50 -39
- package/src/session/user-prompts.ts +35 -16
- package/src/tan/challenge.ts +40 -31
- package/src/tan/flicker.ts +44 -6
- package/src/tan/tan-image.ts +7 -5
- package/src/tan/tan-models.ts +10 -5
- package/src/util/async.ts +0 -5
- package/src/util/bytes.ts +13 -19
- package/src/util/call-context.ts +3 -2
- package/src/util/dates.ts +41 -24
- package/src/util/errors.ts +14 -3
- package/src/util/json.ts +16 -0
- package/src/util/text.ts +42 -12
- package/src/util/xml.ts +37 -26
- package/src/v3/account-extension.ts +7 -12
- package/src/v3/bank-connection.ts +67 -0
- package/src/v3/dialog.ts +175 -91
- package/src/v3/message.ts +69 -17
- package/src/v3/parameter-store.ts +160 -0
- package/src/v3/parameters.ts +110 -37
- package/src/v3/protocol.ts +70 -253
- package/src/v3/queries.ts +190 -0
- package/src/v3/segments.ts +65 -22
- package/src/v3/syntax.ts +153 -91
- package/dist/session/bank-connection.d.ts +0 -28
- package/dist/session/bank-connection.d.ts.map +0 -1
- package/dist/session/bank-connection.js +0 -48
- package/dist/session/bank-connection.js.map +0 -1
- package/dist/session/parameter-data.d.ts +0 -83
- package/dist/session/parameter-data.d.ts.map +0 -1
- package/dist/session/parameter-data.js +0 -68
- package/dist/session/parameter-data.js.map +0 -1
- package/dist/session/pin-tan-client.d.ts +0 -54
- package/dist/session/pin-tan-client.d.ts.map +0 -1
- package/dist/session/pin-tan-client.js +0 -153
- package/dist/session/pin-tan-client.js.map +0 -1
- package/dist/session/pin-tan-dialog.d.ts +0 -71
- package/dist/session/pin-tan-dialog.d.ts.map +0 -1
- package/dist/session/pin-tan-dialog.js +0 -124
- package/dist/session/pin-tan-dialog.js.map +0 -1
- package/dist/session/protocol.d.ts +0 -209
- package/dist/session/protocol.d.ts.map +0 -1
- package/dist/session/protocol.js +0 -75
- package/dist/session/protocol.js.map +0 -1
- package/dist/session/response-handler.d.ts +0 -48
- package/dist/session/response-handler.d.ts.map +0 -1
- package/dist/session/response-handler.js +0 -121
- package/dist/session/response-handler.js.map +0 -1
- package/dist/v3/client.d.ts +0 -24
- package/dist/v3/client.d.ts.map +0 -1
- package/dist/v3/client.js +0 -16
- package/dist/v3/client.js.map +0 -1
- package/src/session/bank-connection.ts +0 -51
- package/src/session/parameter-data.ts +0 -137
- package/src/session/pin-tan-client.ts +0 -186
- package/src/session/pin-tan-dialog.ts +0 -160
- package/src/session/protocol.ts +0 -311
- package/src/session/response-handler.ts +0 -130
- package/src/v3/client.ts +0 -29
package/src/client.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { Account } from './core/account.js';
|
|
2
|
+
import type { Balance } from './core/balance.js';
|
|
3
|
+
import type { BankInfo } from './core/bank-info.js';
|
|
4
|
+
import type { FinTsClient, FinTsClientOptions, TransactionQuery } from './core/client.js';
|
|
5
|
+
import { FinTsConfig, requirePin, type FinTsConfigInit } from './core/config.js';
|
|
6
|
+
import { FinTsStateError, FinTsUnsupportedError, type FinTsLoginRefusedError } from './core/errors.js';
|
|
7
|
+
import type { Transaction } from './core/transaction.js';
|
|
8
|
+
import { DialogRunner } from './session/dialog-runner.js';
|
|
9
|
+
import { LoginGuard } from './session/login-guard.js';
|
|
10
|
+
import { SepaAccounts } from './session/sepa-accounts.js';
|
|
11
|
+
import { SessionSetup } from './session/session-setup.js';
|
|
12
|
+
import { restoreSession, saveSession, type SessionState } from './session/session-state.js';
|
|
13
|
+
import { TanSelection } from './session/tan-selection.js';
|
|
14
|
+
import { UserPrompts } from './session/user-prompts.js';
|
|
15
|
+
import { TanMedium, type TanMethod } from './tan/tan-models.js';
|
|
16
|
+
import { SerialExecutor } from './util/async.js';
|
|
17
|
+
import { CallContext } from './util/call-context.js';
|
|
18
|
+
import { calendarDay, notAfterToday } from './util/dates.js';
|
|
19
|
+
import { invalidArgument } from './util/errors.js';
|
|
20
|
+
import { ParameterStore } from './v3/parameter-store.js';
|
|
21
|
+
import { FinTs3Protocol } from './v3/protocol.js';
|
|
22
|
+
import { balanceQuery, tanMediaQuery, transactionsQuery } from './v3/queries.js';
|
|
23
|
+
|
|
24
|
+
/** What `FinTs3Client` takes. */
|
|
25
|
+
export interface FinTs3ClientInit {
|
|
26
|
+
/** Where and as whom to log in: a `FinTsConfig`, or what its constructor takes. */
|
|
27
|
+
config: FinTsConfig | FinTsConfigInit;
|
|
28
|
+
/**
|
|
29
|
+
* The user's PIN. The client keeps it in memory only, never logs it, and forgets it once it is closed or the bank has
|
|
30
|
+
* refused the login. It is out of reach of `JSON.stringify` and Node.js's `util.inspect` of the client, but not of a
|
|
31
|
+
* debugger or the browser's DevTools: never log the client. A client whose `options.state` holds a refused login
|
|
32
|
+
* does not send it (`FinTsClient.loginRefusal`).
|
|
33
|
+
*/
|
|
34
|
+
pin: string;
|
|
35
|
+
/** How the client reaches the user and the bank. */
|
|
36
|
+
options: FinTsClientOptions;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A token of the client call running now, so that a call from inside its callbacks is recognized. */
|
|
40
|
+
const runningCalls = new CallContext<object>();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The calendar day of `bound`, the `name` bound of a `TransactionQuery`: the bank takes dates without time of day.
|
|
44
|
+
* Throws `TypeError` for anything but a valid `Date`, such as an Invalid Date, which would go out as `0NaNNaNNaN`.
|
|
45
|
+
*/
|
|
46
|
+
function requestedDay(bound: unknown, name: string): Date | undefined {
|
|
47
|
+
if (bound === undefined) return undefined;
|
|
48
|
+
if (!(bound instanceof Date) || Number.isNaN(bound.getTime())) throw invalidArgument('Not a valid Date', name);
|
|
49
|
+
return calendarDay(bound);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** FinTS 3.0 PIN/TAN client, the version German banks use. */
|
|
53
|
+
export class FinTs3Client implements FinTsClient {
|
|
54
|
+
// A call such as `getBalance` runs through these parts:
|
|
55
|
+
// 1. `DialogRunner.open` reuses the session's dialog or logs in, through `SessionSetup.remedied`, which logs in once
|
|
56
|
+
// more for each refusal it can take care of.
|
|
57
|
+
// 2. `SessionSetup.prepare` first settles what is open: the bank parameter data, the synchronization, and through
|
|
58
|
+
// `TanSelection` the TAN method and medium.
|
|
59
|
+
// 3. `SignedDialog.login` sends the login and completes the strong authentication the bank asks for, a TAN or an app
|
|
60
|
+
// approval (`AppApprovalWait`).
|
|
61
|
+
// 4. `DialogRunner.query` sends the order in that dialog, completes its approval the same way, and fetches all pages.
|
|
62
|
+
//
|
|
63
|
+
// `FinTs3Protocol` builds, signs and posts the messages of each `FinTs3Dialog`, the queries build the orders and read
|
|
64
|
+
// the answers, and the `ParameterStore` keeps the bank's parameter data and IDs. `SepaAccounts` completes IBAN and
|
|
65
|
+
// BIC, and `UserPrompts` is everything the app is told or asked. The `LoginGuard` holds the PIN: the protocol takes it
|
|
66
|
+
// from the guard for every message it signs, and `SignedDialog` has the guard judge every answer, so that the PIN does
|
|
67
|
+
// not go out after a refusal.
|
|
68
|
+
//
|
|
69
|
+
// The fields are ECMAScript private, so that neither `util.inspect` nor `JSON.stringify` of a client reaches the PIN
|
|
70
|
+
// or the account data behind them.
|
|
71
|
+
|
|
72
|
+
readonly #prompts: UserPrompts;
|
|
73
|
+
readonly #guard: LoginGuard;
|
|
74
|
+
readonly #parameters: ParameterStore;
|
|
75
|
+
readonly #protocol: FinTs3Protocol;
|
|
76
|
+
readonly #sessionState: SessionState;
|
|
77
|
+
readonly #tans: TanSelection;
|
|
78
|
+
readonly #setup: SessionSetup;
|
|
79
|
+
readonly #dialogs: DialogRunner;
|
|
80
|
+
readonly #sepa: SepaAccounts;
|
|
81
|
+
readonly #queue = new SerialExecutor();
|
|
82
|
+
#closed: Promise<void> | undefined;
|
|
83
|
+
|
|
84
|
+
/** The call running now, which a call from inside its callbacks would wait for. */
|
|
85
|
+
#runningCall: object | undefined;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A client with the session restored from `FinTsClientOptions.state` where it was saved for this user and bank.
|
|
89
|
+
*
|
|
90
|
+
* Throws `TypeError` if the configuration is invalid, as the constructor of `FinTsConfig` tells, or the PIN is empty,
|
|
91
|
+
* begins or ends with a blank, or has characters FinTS 3.0 cannot send: it uses ISO 8859-1, so `€` and most
|
|
92
|
+
* non-Latin letters are not possible.
|
|
93
|
+
*/
|
|
94
|
+
constructor(init: FinTs3ClientInit) {
|
|
95
|
+
const config = init.config instanceof FinTsConfig ? init.config : new FinTsConfig(init.config);
|
|
96
|
+
this.#prompts = new UserPrompts(init.options);
|
|
97
|
+
this.#guard = new LoginGuard(requirePin(init.pin));
|
|
98
|
+
this.#parameters = new ParameterStore(config);
|
|
99
|
+
this.#protocol = new FinTs3Protocol(this.#parameters, this.#guard, init.options.transport, this.#prompts.logger);
|
|
100
|
+
this.#sessionState = restoreSession(init.options.state, this.#parameters, this.#guard);
|
|
101
|
+
this.#tans = new TanSelection(this.#protocol, this.#sessionState, this.#prompts);
|
|
102
|
+
const session = {
|
|
103
|
+
parameters: this.#parameters,
|
|
104
|
+
state: this.#sessionState,
|
|
105
|
+
prompts: this.#prompts,
|
|
106
|
+
guard: this.#guard,
|
|
107
|
+
};
|
|
108
|
+
this.#setup = new SessionSetup(session, this.#protocol, this.#tans);
|
|
109
|
+
this.#dialogs = new DialogRunner(this.#setup);
|
|
110
|
+
this.#sepa = new SepaAccounts(this.#parameters, this.#sessionState, this.#dialogs);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get state(): string {
|
|
114
|
+
return saveSession(this.#parameters, this.#guard, this.#sessionState);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get loginRefusal(): FinTsLoginRefusedError | undefined {
|
|
118
|
+
return this.#guard.refusal;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get selectedTanMethod(): string | undefined {
|
|
122
|
+
return this.#sessionState.tanMethod;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get selectedTanMedium(): string | undefined {
|
|
126
|
+
return this.#sessionState.tanMedium;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
selectTanMethod(securityFunction: string, tanMedium?: string): Promise<void> {
|
|
130
|
+
return this.#serialized(async () => {
|
|
131
|
+
await this.#dialogs.end();
|
|
132
|
+
this.#tans.select(securityFunction, tanMedium);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
clearLoginRefusal(pin: string): Promise<void> {
|
|
137
|
+
return this.#serialized(async () => {
|
|
138
|
+
const sendable = requirePin(pin);
|
|
139
|
+
// The dialog ends with the PIN it was opened with.
|
|
140
|
+
await this.#dialogs.end();
|
|
141
|
+
this.#guard.replacePin(sendable);
|
|
142
|
+
this.#tans.forgetMedium();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getBankInfo(): Promise<BankInfo> {
|
|
147
|
+
return this.#serialized(() => this.#setup.remedied(() => this.#setup.bankInfo()));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getTanMethods(): Promise<TanMethod[]> {
|
|
151
|
+
return this.#serialized(() => this.#setup.remedied(() => this.#setup.methods()));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getTanMedia(): Promise<TanMedium[]> {
|
|
155
|
+
return this.#serialized(async () => {
|
|
156
|
+
// The session keeps the list of a lookup that found no medium; the app gets media of its own.
|
|
157
|
+
const lookedUp = (await this.#setup.remedied(() => this.#setup.prepare())) ?? this.#tans.lookedUpMedia;
|
|
158
|
+
return (
|
|
159
|
+
lookedUp?.map((m) => new TanMedium(m)) ??
|
|
160
|
+
(await this.#dialogs.query(() => {
|
|
161
|
+
const query = tanMediaQuery(this.#parameters);
|
|
162
|
+
if (query === undefined) throw new FinTsUnsupportedError('The bank does not list TAN media');
|
|
163
|
+
return query;
|
|
164
|
+
}))
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getAccounts(): Promise<Account[]> {
|
|
170
|
+
return this.#serialized(() => this.#sepa.completedAccounts());
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getBalance(account: Account): Promise<Balance> {
|
|
174
|
+
return this.#serialized(async () => {
|
|
175
|
+
const completed = await this.#sepa.complete(account);
|
|
176
|
+
// The dialog start delivers the user parameter data, which may carry the complete balance: then the client must
|
|
177
|
+
// not ask for it separately (Formals E.3.1.1, F.3.2).
|
|
178
|
+
await this.#dialogs.open();
|
|
179
|
+
return (
|
|
180
|
+
this.#parameters.userParameters?.completeBalance(completed) ??
|
|
181
|
+
(await this.#dialogs.query(() => balanceQuery(this.#parameters, completed)))
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async getTransactions(account: Account, query: TransactionQuery = {}): Promise<Transaction[]> {
|
|
187
|
+
// Checked before the call waits for others; an async method, so that every argument error is a rejection.
|
|
188
|
+
const firstDay = requestedDay(query.from, 'from');
|
|
189
|
+
const lastDay = requestedDay(query.to, 'to');
|
|
190
|
+
if (firstDay !== undefined && lastDay !== undefined && firstDay.getTime() > lastDay.getTime()) {
|
|
191
|
+
throw invalidArgument('`from` lies on a day after `to`', 'from');
|
|
192
|
+
}
|
|
193
|
+
return this.#serialized(async () => {
|
|
194
|
+
const completed = await this.#sepa.complete(account);
|
|
195
|
+
const transactions = await this.#dialogs.query(() =>
|
|
196
|
+
transactionsQuery(
|
|
197
|
+
this.#parameters,
|
|
198
|
+
completed,
|
|
199
|
+
query.format ?? 'auto',
|
|
200
|
+
notAfterToday(firstDay),
|
|
201
|
+
notAfterToday(lastDay),
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
// A stable sort, so that transactions of the same day keep the bank's order.
|
|
205
|
+
return transactions.sort(
|
|
206
|
+
(a, b) => (a.bookingDate ?? a.valueDate).getTime() - (b.bookingDate ?? b.valueDate).getTime(),
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
close(): Promise<void> {
|
|
212
|
+
this.#closed ??= this.#closeNow();
|
|
213
|
+
return this.#closed;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#closeNow(): Promise<void> {
|
|
217
|
+
this.#prompts.close();
|
|
218
|
+
this.#protocol.beginClose();
|
|
219
|
+
return this.#queue.run(() =>
|
|
220
|
+
this.#prompts.reportingCallbackErrors(async () => {
|
|
221
|
+
try {
|
|
222
|
+
await this.#dialogs.end();
|
|
223
|
+
} finally {
|
|
224
|
+
this.#guard.close();
|
|
225
|
+
this.#protocol.close();
|
|
226
|
+
}
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#serialized<T>(action: () => Promise<T>): Promise<T> {
|
|
232
|
+
const caller = runningCalls.current;
|
|
233
|
+
if (caller !== undefined && caller === this.#runningCall) {
|
|
234
|
+
// The call would wait behind the running one, which waits for the callback: neither would ever end.
|
|
235
|
+
return Promise.reject(new FinTsStateError('The FinTS client was called from inside one of its callbacks'));
|
|
236
|
+
}
|
|
237
|
+
return this.#queue.run(async () => {
|
|
238
|
+
const call = {};
|
|
239
|
+
this.#runningCall = call;
|
|
240
|
+
try {
|
|
241
|
+
return await runningCalls.run(call, () =>
|
|
242
|
+
this.#guard.runCall(() => {
|
|
243
|
+
this.#prompts.checkOpen();
|
|
244
|
+
return this.#prompts.reportingCallbackErrors(action);
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
} finally {
|
|
248
|
+
this.#runningCall = undefined;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
package/src/core/account.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { copyDate } from '../util/dates.js';
|
|
1
2
|
import { ImmutableMap } from '../util/immutable-map.js';
|
|
2
|
-
import { maskedId } from '../util/text.js';
|
|
3
|
+
import { maskedId, nonBlank } from '../util/text.js';
|
|
3
4
|
import { defaultCountryCode } from './config.js';
|
|
4
5
|
|
|
5
6
|
/** Account kind derived from the FinTS account type code. */
|
|
@@ -40,7 +41,7 @@ const categoriesByTens: readonly AccountCategory[] = [
|
|
|
40
41
|
];
|
|
41
42
|
|
|
42
43
|
/** The category of the FinTS account type `code`, which is `undefined` if the bank states none. */
|
|
43
|
-
|
|
44
|
+
function accountCategoryOf(code: number | undefined): AccountCategory {
|
|
44
45
|
if (code === undefined || code < 1) return 'unknown';
|
|
45
46
|
return categoriesByTens[Math.floor(code / 10)] ?? 'other';
|
|
46
47
|
}
|
|
@@ -63,7 +64,15 @@ export interface AccountInit {
|
|
|
63
64
|
dataStatus?: AccountDataStatus | undefined;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* A bank account as reported by the user parameter data or the SEPA account query.
|
|
69
|
+
*
|
|
70
|
+
* An account the app builds itself, such as from a stored IBAN, stands for the one of `getAccounts` it `matches`:
|
|
71
|
+
* queries take the details the bank states of that one, and the transactions the bank permits for it. One the bank
|
|
72
|
+
* names nowhere goes out with its account number, sub-account, IBAN, BIC, bank and country code and currency only, and
|
|
73
|
+
* is refused with `FinTsUnsupportedError` where the bank's lists of permitted transactions are complete. Queries never
|
|
74
|
+
* take the permitted transactions from the app's account.
|
|
75
|
+
*/
|
|
67
76
|
export class Account {
|
|
68
77
|
/** National account number. */
|
|
69
78
|
readonly accountNumber: string | undefined;
|
|
@@ -77,10 +86,13 @@ export class Account {
|
|
|
77
86
|
/** Numeric country code of the bank, `280` for Germany. */
|
|
78
87
|
readonly countryCode: string;
|
|
79
88
|
|
|
80
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* International bank account number, in its electronic format: without blanks and in capitals, however
|
|
91
|
+
* `AccountInit.iban` wrote it, such as in groups of four as printed.
|
|
92
|
+
*/
|
|
81
93
|
readonly iban: string | undefined;
|
|
82
94
|
|
|
83
|
-
/** Bank identifier code of the bank holding the account. */
|
|
95
|
+
/** Bank identifier code of the bank holding the account, without blanks and in capitals. */
|
|
84
96
|
readonly bic: string | undefined;
|
|
85
97
|
|
|
86
98
|
/** Customer ID the account is assigned to; may differ from the login name. */
|
|
@@ -101,8 +113,8 @@ export class Account {
|
|
|
101
113
|
/**
|
|
102
114
|
* The transactions the bank lists as permitted for this account, by their segment IDs, such as `HKSAL`.
|
|
103
115
|
*
|
|
104
|
-
* The client applies them itself: `getBalance` and `getTransactions`
|
|
105
|
-
*
|
|
116
|
+
* The client applies them itself: `getBalance` and `getTransactions` do not send a query they rule out, and fail with
|
|
117
|
+
* `FinTsUnsupportedError`.
|
|
106
118
|
*/
|
|
107
119
|
readonly allowedTransactions: readonly string[];
|
|
108
120
|
|
|
@@ -115,13 +127,14 @@ export class Account {
|
|
|
115
127
|
/** What the bank tells about the data it holds for the account, where it does. */
|
|
116
128
|
readonly dataStatus: AccountDataStatus | undefined;
|
|
117
129
|
|
|
130
|
+
/** Takes copies of the permitted transactions and the data status of `init`, which no other account shares. */
|
|
118
131
|
constructor(init: AccountInit) {
|
|
119
132
|
this.accountNumber = init.accountNumber;
|
|
120
133
|
this.subAccount = init.subAccount;
|
|
121
134
|
this.bankCode = init.bankCode;
|
|
122
135
|
this.countryCode = init.countryCode ?? defaultCountryCode;
|
|
123
|
-
this.iban = init.iban;
|
|
124
|
-
this.bic = init.bic;
|
|
136
|
+
this.iban = electronicFormat(init.iban);
|
|
137
|
+
this.bic = electronicFormat(init.bic);
|
|
125
138
|
this.customerId = init.customerId;
|
|
126
139
|
this.currency = init.currency;
|
|
127
140
|
this.ownerName = init.ownerName;
|
|
@@ -129,7 +142,7 @@ export class Account {
|
|
|
129
142
|
this.typeCode = init.typeCode;
|
|
130
143
|
this.allowedTransactions = Object.freeze([...(init.allowedTransactions ?? [])]);
|
|
131
144
|
this.allowedTransactionsComplete = init.allowedTransactionsComplete ?? false;
|
|
132
|
-
this.dataStatus = init.dataStatus;
|
|
145
|
+
this.dataStatus = init.dataStatus && new AccountDataStatus(init.dataStatus);
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
/** The kind of account, derived from `typeCode`. */
|
|
@@ -146,10 +159,10 @@ export class Account {
|
|
|
146
159
|
*/
|
|
147
160
|
matches(other: Account): boolean {
|
|
148
161
|
if (this.iban !== undefined && other.iban !== undefined) return this.iban === other.iban;
|
|
149
|
-
const number = this
|
|
162
|
+
const number = nationalNumber(this);
|
|
150
163
|
return (
|
|
151
164
|
number !== undefined &&
|
|
152
|
-
number === other
|
|
165
|
+
number === nationalNumber(other) &&
|
|
153
166
|
(this.subAccount ?? '') === (other.subAccount ?? '') &&
|
|
154
167
|
sameOrUnstated(this.bankCode, other.bankCode) &&
|
|
155
168
|
sameOrUnstated(this.countryCode, other.countryCode)
|
|
@@ -190,10 +203,69 @@ export class Account {
|
|
|
190
203
|
}
|
|
191
204
|
}
|
|
192
205
|
|
|
206
|
+
/**
|
|
207
|
+
* An IBAN or BIC in the form the bank sends and compares, without blanks and in capitals; `undefined` for blanks only.
|
|
208
|
+
*/
|
|
209
|
+
function electronicFormat(code: string | undefined): string | undefined {
|
|
210
|
+
return nonBlank(code?.replace(/\s/g, '').toUpperCase());
|
|
211
|
+
}
|
|
212
|
+
|
|
193
213
|
function sameOrUnstated(a: string, b: string): boolean {
|
|
194
214
|
return a === '' || b === '' || a === b;
|
|
195
215
|
}
|
|
196
216
|
|
|
217
|
+
/** The account number as `Account.matches` compares it: without leading zeros. */
|
|
218
|
+
function nationalNumber(account: Account): string | undefined {
|
|
219
|
+
return account.accountNumber?.replace(/^0+/, '');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Accounts by IBAN and by national account, to find the first one an account `matches` without comparing it with each:
|
|
224
|
+
* a bank may list tens of thousands.
|
|
225
|
+
*
|
|
226
|
+
* Bank and country codes count as they are, where `matches` takes an empty one for any: the accounts a bank lists state
|
|
227
|
+
* both.
|
|
228
|
+
*/
|
|
229
|
+
export class AccountIndex {
|
|
230
|
+
private readonly byIban = new Map<string, number>();
|
|
231
|
+
private readonly byNational = new Map<string, number>();
|
|
232
|
+
|
|
233
|
+
/** Those without IBAN only: an account with IBAN matches one with IBAN by that alone. */
|
|
234
|
+
private readonly byNationalWithoutIban = new Map<string, number>();
|
|
235
|
+
|
|
236
|
+
constructor(private readonly accounts: readonly Account[]) {
|
|
237
|
+
accounts.forEach((account, i) => {
|
|
238
|
+
const national = nationalKey(account);
|
|
239
|
+
if (account.iban !== undefined) keepFirst(this.byIban, account.iban, i);
|
|
240
|
+
if (national === undefined) return;
|
|
241
|
+
keepFirst(this.byNational, national, i);
|
|
242
|
+
if (account.iban === undefined) keepFirst(this.byNationalWithoutIban, national, i);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** The first of the accounts that `account` matches. */
|
|
247
|
+
find(account: Account): Account | undefined {
|
|
248
|
+
const national = nationalKey(account);
|
|
249
|
+
const byNational = account.iban === undefined ? this.byNational : this.byNationalWithoutIban;
|
|
250
|
+
const found = [
|
|
251
|
+
account.iban === undefined ? undefined : this.byIban.get(account.iban),
|
|
252
|
+
national === undefined ? undefined : byNational.get(national),
|
|
253
|
+
].filter((i) => i !== undefined);
|
|
254
|
+
return found.length === 0 ? undefined : this.accounts[Math.min(...found)];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function nationalKey(account: Account): string | undefined {
|
|
259
|
+
const number = nationalNumber(account);
|
|
260
|
+
return number === undefined
|
|
261
|
+
? undefined
|
|
262
|
+
: JSON.stringify([number, account.subAccount ?? '', account.bankCode, account.countryCode]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function keepFirst(index: Map<string, number>, key: string, position: number): void {
|
|
266
|
+
if (!index.has(key)) index.set(key, position);
|
|
267
|
+
}
|
|
268
|
+
|
|
197
269
|
/**
|
|
198
270
|
* What the bank tells about the data it holds for an account, so that an app asks only for what has changed (FinTS 3.0
|
|
199
271
|
* user parameter data, "Erweiterung, kontobezogen", Formals E.3.1 and F).
|
|
@@ -226,6 +298,7 @@ export class AccountDataStatus {
|
|
|
226
298
|
/** Whether the balances the transactions carry are the current ones. */
|
|
227
299
|
readonly transactionBalancesCurrent: boolean;
|
|
228
300
|
|
|
301
|
+
/** Takes copies of the inventory and the date of `init`, which no other status shares. */
|
|
229
302
|
constructor(init: {
|
|
230
303
|
inventory?: ReadonlyMap<string, string>;
|
|
231
304
|
lastTransactions?: string | undefined;
|
|
@@ -234,7 +307,7 @@ export class AccountDataStatus {
|
|
|
234
307
|
}) {
|
|
235
308
|
this.inventory = new ImmutableMap(init.inventory ?? []);
|
|
236
309
|
this.lastTransactions = init.lastTransactions;
|
|
237
|
-
this.nextTransactionsAt = init.nextTransactionsAt;
|
|
310
|
+
this.nextTransactionsAt = copyDate(init.nextTransactionsAt);
|
|
238
311
|
this.transactionBalancesCurrent = init.transactionBalancesCurrent ?? false;
|
|
239
312
|
}
|
|
240
313
|
|
package/src/core/amount.ts
CHANGED
|
@@ -200,9 +200,11 @@ export class Amount {
|
|
|
200
200
|
/**
|
|
201
201
|
* `amount` as a credit (unchanged) or a debit (negated), by the bank's credit/debit `mark`.
|
|
202
202
|
*
|
|
203
|
-
* Throws `FormatError` for any other `mark`, `undefined` included: a missing mark must not be read as a credit.
|
|
203
|
+
* Throws `FormatError` for any other `mark`, `undefined` included: a missing mark must not be read as a credit. It does
|
|
204
|
+
* as well for a negative `amount`, whose sign the mark would turn: the mark alone gives the direction.
|
|
204
205
|
*/
|
|
205
206
|
export function withMark(amount: Amount, mark: string | undefined, credit: string, debit: string): Amount {
|
|
207
|
+
if (amount.isNegative) throw new FormatError('Negative amount with a credit/debit mark', amount.toString());
|
|
206
208
|
if (mark === credit) return amount;
|
|
207
209
|
if (mark === debit) return amount.negate();
|
|
208
210
|
throw new FormatError(`Credit/debit mark is neither "${credit}" nor "${debit}"`, mark);
|
package/src/core/balance.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatIsoDate } from '../util/dates.js';
|
|
1
|
+
import { copyDate, formatIsoDate } from '../util/dates.js';
|
|
2
2
|
import type { Account } from './account.js';
|
|
3
3
|
import type { Amount } from './amount.js';
|
|
4
4
|
|
|
@@ -59,18 +59,19 @@ export class Balance {
|
|
|
59
59
|
/** The bank's name for the account, for example `Girokonto`. */
|
|
60
60
|
readonly productName: string | undefined;
|
|
61
61
|
|
|
62
|
+
/** Takes copies of the dates of `init`: each balance has its own, which no other one shares. */
|
|
62
63
|
constructor(init: BalanceInit) {
|
|
63
64
|
this.account = init.account;
|
|
64
65
|
this.booked = init.booked;
|
|
65
|
-
this.bookedAsOf = init.bookedAsOf;
|
|
66
|
-
this.bookedAsOfTime = init.bookedAsOfTime;
|
|
66
|
+
this.bookedAsOf = copyDate(init.bookedAsOf);
|
|
67
|
+
this.bookedAsOfTime = copyDate(init.bookedAsOfTime);
|
|
67
68
|
this.pending = init.pending;
|
|
68
69
|
this.creditLine = init.creditLine;
|
|
69
70
|
this.available = init.available;
|
|
70
71
|
this.used = init.used;
|
|
71
72
|
this.overdraft = init.overdraft;
|
|
72
|
-
this.bookingDate = init.bookingDate;
|
|
73
|
-
this.bookingTime = init.bookingTime;
|
|
73
|
+
this.bookingDate = copyDate(init.bookingDate);
|
|
74
|
+
this.bookingTime = copyDate(init.bookingTime);
|
|
74
75
|
this.productName = init.productName;
|
|
75
76
|
}
|
|
76
77
|
|
package/src/core/bank-info.ts
CHANGED
|
@@ -26,7 +26,7 @@ export class BankInfo {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Time in seconds after which the bank will likely end a dialog without messages, `0` for no limit (HIBPA, Formals
|
|
29
|
-
* C.9). The client starts a new dialog for a call after that time.
|
|
29
|
+
* C.9). The client starts a new dialog for a call after that time, or after 5 minutes where the bank states none.
|
|
30
30
|
*/
|
|
31
31
|
readonly maxTimeout: number | undefined;
|
|
32
32
|
|
package/src/core/client.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { TanMedium, TanMethod } from '../tan/tan-models.js';
|
|
|
3
3
|
import type { Account } from './account.js';
|
|
4
4
|
import type { Balance } from './balance.js';
|
|
5
5
|
import type { BankInfo, BankMessage } from './bank-info.js';
|
|
6
|
+
import type { FinTsLoginRefusedError } from './errors.js';
|
|
6
7
|
import type { FinTsFeedback } from './feedback.js';
|
|
7
8
|
import type { Transaction, TransactionFormat } from './transaction.js';
|
|
8
9
|
import type { FinTsTransport } from './transport.js';
|
|
@@ -49,9 +50,15 @@ export interface FinTsClientOptions {
|
|
|
49
50
|
logger?: MessageLogger;
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
|
-
* The `FinTsClient.state` of a previous session, which skips the bank setup steps.
|
|
53
|
+
* The `FinTsClient.state` of a previous session, which skips the bank setup steps. A refused login in it keeps the
|
|
54
|
+
* client from sending the PIN (`FinTsClient.loginRefusal`).
|
|
53
55
|
*
|
|
54
|
-
* A state
|
|
56
|
+
* A state belongs to one client at a time: restore the clients of one bank user one after another, each from the
|
|
57
|
+
* state the one before saved, never two at once from the same state. The state saved last wins, and could lift a
|
|
58
|
+
* refused login the other client saved.
|
|
59
|
+
*
|
|
60
|
+
* A state saved for another user or bank is ignored, and of one saved for another server of the bank, such as before
|
|
61
|
+
* the bank moved its FinTS URL, only the refused login holds.
|
|
55
62
|
*/
|
|
56
63
|
state?: string | undefined;
|
|
57
64
|
|
|
@@ -76,22 +83,33 @@ export interface TransactionQuery {
|
|
|
76
83
|
/**
|
|
77
84
|
* Read access to a bank account over FinTS.
|
|
78
85
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
86
|
+
* A call logs in where it needs to, which sends the PIN and may ask the callbacks for a TAN method, a TAN medium and an
|
|
87
|
+
* approval. Calls share one bank dialog while it is open, so that the approval of its login serves them all. The next
|
|
88
|
+
* call logs in anew, which may ask for another approval, once the dialog has idled longer than `BankInfo.maxTimeout`,
|
|
89
|
+
* after `selectTanMethod` and `clearLoginRefusal`, once the bank has ended the dialog, and after a message failed with
|
|
90
|
+
* another error than the bank's (`FinTsBankError`), such as a cancelled approval or a broken connection. Calls run one
|
|
91
|
+
* after another; call `close` when done.
|
|
81
92
|
*
|
|
82
93
|
* A callback in `FinTsClientOptions` must not wait for another call of its client, which would wait for the running
|
|
83
|
-
* call. On Node.js, Deno and Bun such a call fails with `FinTsStateError`; elsewhere it
|
|
84
|
-
* from anywhere.
|
|
94
|
+
* call. On Node.js, Deno 2.1 and Bun 1.2.6 or later such a call fails with `FinTsStateError`; elsewhere it waits
|
|
95
|
+
* forever. `close` works from anywhere.
|
|
85
96
|
*/
|
|
86
97
|
export interface FinTsClient {
|
|
87
98
|
/**
|
|
88
99
|
* Serialized session data: bank and user parameters, customer system ID, the TAN methods the user may use, the chosen
|
|
89
|
-
* TAN method and medium,
|
|
100
|
+
* TAN method and medium, the SEPA account list, and a refused login (`loginRefusal`).
|
|
90
101
|
*
|
|
91
|
-
* Contains no PIN or TAN, but account details, so store it encrypted.
|
|
102
|
+
* Contains no PIN or TAN, but account details, so store it encrypted. It holds what this client knows, and replaces
|
|
103
|
+
* the state it was restored from: one client at a time per state (`FinTsClientOptions.state`).
|
|
92
104
|
*/
|
|
93
105
|
readonly state: string;
|
|
94
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The refused login that keeps the client from sending the PIN again (`FinTsLoginRefusedError`); `undefined` while
|
|
109
|
+
* there is none. It is kept in `state` until `clearLoginRefusal`.
|
|
110
|
+
*/
|
|
111
|
+
readonly loginRefusal: FinTsLoginRefusedError | undefined;
|
|
112
|
+
|
|
95
113
|
/** Security function code of the TAN method in use, for example `942`; `undefined` before the first login. */
|
|
96
114
|
readonly selectedTanMethod: string | undefined;
|
|
97
115
|
|
|
@@ -106,10 +124,25 @@ export interface FinTsClient {
|
|
|
106
124
|
*/
|
|
107
125
|
selectTanMethod(securityFunction: string, tanMedium?: string): Promise<void>;
|
|
108
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Lifts `loginRefusal` and signs with `pin` from now on. Ends the current dialog, so that the next call logs in with
|
|
129
|
+
* it, and forgets the TAN medium, which the bank may have refused, such as that of a phone the user has replaced: where
|
|
130
|
+
* the TAN method needs one, the next call looks the media up anew and asks the `tanMediumSelector`.
|
|
131
|
+
*
|
|
132
|
+
* Pass a PIN the user has entered after the refusal, never a stored one unasked: banks lock the access after a few
|
|
133
|
+
* failed logins. Rejects with `TypeError` for a PIN the client cannot send, as its constructor throws, and with the
|
|
134
|
+
* refusal where it is a `FinTsLoginDataChangedError`: only a new client with the new login data can log in.
|
|
135
|
+
*/
|
|
136
|
+
clearLoginRefusal(pin: string): Promise<void>;
|
|
137
|
+
|
|
109
138
|
/** General information about the bank, from the bank parameter data. */
|
|
110
139
|
getBankInfo(): Promise<BankInfo>;
|
|
111
140
|
|
|
112
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* TAN procedures the bank allows for this user, which the synchronization reports. Where the session has no customer
|
|
143
|
+
* system ID yet, as without a saved state, this call logs in for it: it may ask `tanMethodSelector` where the bank
|
|
144
|
+
* refuses the one-step method, and `tanCallback` for an approval.
|
|
145
|
+
*/
|
|
113
146
|
getTanMethods(): Promise<TanMethod[]>;
|
|
114
147
|
|
|
115
148
|
/** TAN devices registered for this user. */
|
|
@@ -124,7 +157,8 @@ export interface FinTsClient {
|
|
|
124
157
|
* Where the user parameter data carry the complete balance (account extension), possibly from the saved `state`, it
|
|
125
158
|
* comes from there without a query; `Balance.bookedAsOf` tells the day it states.
|
|
126
159
|
*
|
|
127
|
-
*
|
|
160
|
+
* Rejects with `FinTsUnsupportedError` where the parameter data rule the query out, without sending it; the login,
|
|
161
|
+
* which delivers them, may have gone out.
|
|
128
162
|
*/
|
|
129
163
|
getBalance(account: Account): Promise<Balance>;
|
|
130
164
|
|
|
@@ -133,17 +167,20 @@ export interface FinTsClient {
|
|
|
133
167
|
* the same day in the bank's order.
|
|
134
168
|
*
|
|
135
169
|
* Only the local calendar day counts, and a day in the future counts as today. Banks typically ask for a TAN when
|
|
136
|
-
* `from` is more than 90 days ago; see also `Account.dataStatus`.
|
|
137
|
-
* `to
|
|
170
|
+
* `from` is more than 90 days ago; see also `Account.dataStatus`. Rejects with `TypeError`, before it waits for other
|
|
171
|
+
* calls, if `from` or `to` is no valid `Date` or `from` lies on a day after `to`, and with `FinTsUnsupportedError`,
|
|
172
|
+
* without sending the query, where the parameter data rule it out; the login may have gone out.
|
|
138
173
|
*/
|
|
139
174
|
getTransactions(account: Account, query?: TransactionQuery): Promise<Transaction[]>;
|
|
140
175
|
|
|
141
176
|
/**
|
|
142
|
-
* Ends the bank dialog and
|
|
177
|
+
* Ends the bank dialog, releases the connection and forgets the PIN.
|
|
143
178
|
*
|
|
144
179
|
* A running call stops at its next step, also while it waits for a TAN, an app approval or a selection, and fails with
|
|
145
|
-
* `FinTsStateError`, as do all later calls. A request already on its way is
|
|
146
|
-
*
|
|
180
|
+
* `FinTsStateError`, as do all later calls. A request already on its way is aborted where the transport supports it,
|
|
181
|
+
* as `HttpTransport` does: the call fails with `FinTsTransportError` then, as after a broken connection, and an aborted
|
|
182
|
+
* login counts as one whose answer was lost. Another transport is awaited, at most for its timeout; after the
|
|
183
|
+
* request, the call sends nothing but the end of its dialogs.
|
|
147
184
|
*/
|
|
148
185
|
close(): Promise<void>;
|
|
149
186
|
}
|