@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.
Files changed (272) hide show
  1. package/CHANGELOG.md +142 -1
  2. package/README.md +127 -37
  3. package/dist/client.d.ts +48 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +199 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/core/account.d.ts +35 -7
  8. package/dist/core/account.d.ts.map +1 -1
  9. package/dist/core/account.js +84 -13
  10. package/dist/core/account.js.map +1 -1
  11. package/dist/core/amount.d.ts +2 -1
  12. package/dist/core/amount.d.ts.map +1 -1
  13. package/dist/core/amount.js +4 -1
  14. package/dist/core/amount.js.map +1 -1
  15. package/dist/core/balance.d.ts +1 -0
  16. package/dist/core/balance.d.ts.map +1 -1
  17. package/dist/core/balance.js +6 -5
  18. package/dist/core/balance.js.map +1 -1
  19. package/dist/core/bank-info.d.ts +1 -1
  20. package/dist/core/bank-info.js +1 -1
  21. package/dist/core/client.d.ts +50 -15
  22. package/dist/core/client.d.ts.map +1 -1
  23. package/dist/core/config.d.ts +28 -16
  24. package/dist/core/config.d.ts.map +1 -1
  25. package/dist/core/config.js +66 -26
  26. package/dist/core/config.js.map +1 -1
  27. package/dist/core/errors.d.ts +85 -23
  28. package/dist/core/errors.d.ts.map +1 -1
  29. package/dist/core/errors.js +112 -47
  30. package/dist/core/errors.js.map +1 -1
  31. package/dist/core/feedback.d.ts +4 -1
  32. package/dist/core/feedback.d.ts.map +1 -1
  33. package/dist/core/feedback.js +4 -1
  34. package/dist/core/feedback.js.map +1 -1
  35. package/dist/core/transaction.d.ts +6 -1
  36. package/dist/core/transaction.d.ts.map +1 -1
  37. package/dist/core/transaction.js +9 -4
  38. package/dist/core/transaction.js.map +1 -1
  39. package/dist/core/transport.d.ts +33 -6
  40. package/dist/core/transport.d.ts.map +1 -1
  41. package/dist/core/transport.js +77 -20
  42. package/dist/core/transport.js.map +1 -1
  43. package/dist/formats/camt.d.ts +24 -4
  44. package/dist/formats/camt.d.ts.map +1 -1
  45. package/dist/formats/camt.js +124 -41
  46. package/dist/formats/camt.js.map +1 -1
  47. package/dist/formats/mt940.d.ts +15 -3
  48. package/dist/formats/mt940.d.ts.map +1 -1
  49. package/dist/formats/mt940.js +161 -82
  50. package/dist/formats/mt940.js.map +1 -1
  51. package/dist/formats/readers.d.ts +6 -2
  52. package/dist/formats/readers.d.ts.map +1 -1
  53. package/dist/formats/readers.js +24 -29
  54. package/dist/formats/readers.js.map +1 -1
  55. package/dist/formats/record-budget.d.ts +7 -0
  56. package/dist/formats/record-budget.d.ts.map +1 -0
  57. package/dist/formats/record-budget.js +17 -0
  58. package/dist/formats/record-budget.js.map +1 -0
  59. package/dist/formats/sepa-purpose.d.ts +6 -0
  60. package/dist/formats/sepa-purpose.d.ts.map +1 -1
  61. package/dist/formats/sepa-purpose.js +23 -2
  62. package/dist/formats/sepa-purpose.js.map +1 -1
  63. package/dist/index.d.ts +3 -3
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2 -2
  66. package/dist/index.js.map +1 -1
  67. package/dist/session/app-approval.d.ts +11 -5
  68. package/dist/session/app-approval.d.ts.map +1 -1
  69. package/dist/session/app-approval.js +23 -12
  70. package/dist/session/app-approval.js.map +1 -1
  71. package/dist/session/dialog-runner.d.ts +7 -10
  72. package/dist/session/dialog-runner.d.ts.map +1 -1
  73. package/dist/session/dialog-runner.js +15 -16
  74. package/dist/session/dialog-runner.js.map +1 -1
  75. package/dist/session/login-guard.d.ts +54 -49
  76. package/dist/session/login-guard.d.ts.map +1 -1
  77. package/dist/session/login-guard.js +156 -84
  78. package/dist/session/login-guard.js.map +1 -1
  79. package/dist/session/sepa-accounts.d.ts +20 -14
  80. package/dist/session/sepa-accounts.d.ts.map +1 -1
  81. package/dist/session/sepa-accounts.js +62 -46
  82. package/dist/session/sepa-accounts.js.map +1 -1
  83. package/dist/session/session-setup.d.ts +33 -25
  84. package/dist/session/session-setup.d.ts.map +1 -1
  85. package/dist/session/session-setup.js +87 -65
  86. package/dist/session/session-setup.js.map +1 -1
  87. package/dist/session/session-state.d.ts +9 -6
  88. package/dist/session/session-state.d.ts.map +1 -1
  89. package/dist/session/session-state.js +52 -17
  90. package/dist/session/session-state.js.map +1 -1
  91. package/dist/session/signed-dialog.d.ts +77 -0
  92. package/dist/session/signed-dialog.d.ts.map +1 -0
  93. package/dist/session/signed-dialog.js +170 -0
  94. package/dist/session/signed-dialog.js.map +1 -0
  95. package/dist/session/tan-selection.d.ts +26 -18
  96. package/dist/session/tan-selection.d.ts.map +1 -1
  97. package/dist/session/tan-selection.js +44 -33
  98. package/dist/session/tan-selection.js.map +1 -1
  99. package/dist/session/user-prompts.d.ts +8 -4
  100. package/dist/session/user-prompts.d.ts.map +1 -1
  101. package/dist/session/user-prompts.js +34 -14
  102. package/dist/session/user-prompts.js.map +1 -1
  103. package/dist/tan/challenge.d.ts +34 -13
  104. package/dist/tan/challenge.d.ts.map +1 -1
  105. package/dist/tan/challenge.js +25 -26
  106. package/dist/tan/challenge.js.map +1 -1
  107. package/dist/tan/flicker.d.ts +7 -1
  108. package/dist/tan/flicker.d.ts.map +1 -1
  109. package/dist/tan/flicker.js +40 -6
  110. package/dist/tan/flicker.js.map +1 -1
  111. package/dist/tan/tan-image.d.ts +3 -2
  112. package/dist/tan/tan-image.d.ts.map +1 -1
  113. package/dist/tan/tan-image.js +7 -5
  114. package/dist/tan/tan-image.js.map +1 -1
  115. package/dist/tan/tan-models.d.ts +5 -1
  116. package/dist/tan/tan-models.d.ts.map +1 -1
  117. package/dist/tan/tan-models.js +10 -5
  118. package/dist/tan/tan-models.js.map +1 -1
  119. package/dist/util/async.d.ts +0 -2
  120. package/dist/util/async.d.ts.map +1 -1
  121. package/dist/util/async.js +0 -4
  122. package/dist/util/async.js.map +1 -1
  123. package/dist/util/bytes.d.ts +2 -3
  124. package/dist/util/bytes.d.ts.map +1 -1
  125. package/dist/util/bytes.js +12 -20
  126. package/dist/util/bytes.js.map +1 -1
  127. package/dist/util/call-context.d.ts.map +1 -1
  128. package/dist/util/call-context.js +3 -2
  129. package/dist/util/call-context.js.map +1 -1
  130. package/dist/util/dates.d.ts +9 -16
  131. package/dist/util/dates.d.ts.map +1 -1
  132. package/dist/util/dates.js +39 -19
  133. package/dist/util/dates.js.map +1 -1
  134. package/dist/util/errors.d.ts +9 -1
  135. package/dist/util/errors.d.ts.map +1 -1
  136. package/dist/util/errors.js +12 -3
  137. package/dist/util/errors.js.map +1 -1
  138. package/dist/util/json.d.ts +4 -0
  139. package/dist/util/json.d.ts.map +1 -1
  140. package/dist/util/json.js +18 -0
  141. package/dist/util/json.js.map +1 -1
  142. package/dist/util/text.d.ts +21 -4
  143. package/dist/util/text.d.ts.map +1 -1
  144. package/dist/util/text.js +42 -13
  145. package/dist/util/text.js.map +1 -1
  146. package/dist/util/xml.d.ts +4 -3
  147. package/dist/util/xml.d.ts.map +1 -1
  148. package/dist/util/xml.js +37 -26
  149. package/dist/util/xml.js.map +1 -1
  150. package/dist/v3/account-extension.d.ts.map +1 -1
  151. package/dist/v3/account-extension.js +7 -10
  152. package/dist/v3/account-extension.js.map +1 -1
  153. package/dist/v3/bank-connection.d.ts +34 -0
  154. package/dist/v3/bank-connection.d.ts.map +1 -0
  155. package/dist/v3/bank-connection.js +58 -0
  156. package/dist/v3/bank-connection.js.map +1 -0
  157. package/dist/v3/dialog.d.ts +80 -46
  158. package/dist/v3/dialog.d.ts.map +1 -1
  159. package/dist/v3/dialog.js +162 -59
  160. package/dist/v3/dialog.js.map +1 -1
  161. package/dist/v3/message.d.ts +17 -4
  162. package/dist/v3/message.d.ts.map +1 -1
  163. package/dist/v3/message.js +62 -18
  164. package/dist/v3/message.js.map +1 -1
  165. package/dist/v3/parameter-store.d.ts +75 -0
  166. package/dist/v3/parameter-store.d.ts.map +1 -0
  167. package/dist/v3/parameter-store.js +143 -0
  168. package/dist/v3/parameter-store.js.map +1 -0
  169. package/dist/v3/parameters.d.ts +38 -12
  170. package/dist/v3/parameters.d.ts.map +1 -1
  171. package/dist/v3/parameters.js +91 -37
  172. package/dist/v3/parameters.js.map +1 -1
  173. package/dist/v3/protocol.d.ts +43 -50
  174. package/dist/v3/protocol.d.ts.map +1 -1
  175. package/dist/v3/protocol.js +58 -208
  176. package/dist/v3/protocol.js.map +1 -1
  177. package/dist/v3/queries.d.ts +51 -0
  178. package/dist/v3/queries.d.ts.map +1 -0
  179. package/dist/v3/queries.js +120 -0
  180. package/dist/v3/queries.js.map +1 -0
  181. package/dist/v3/segments.d.ts +29 -12
  182. package/dist/v3/segments.d.ts.map +1 -1
  183. package/dist/v3/segments.js +65 -23
  184. package/dist/v3/segments.js.map +1 -1
  185. package/dist/v3/syntax.d.ts +65 -14
  186. package/dist/v3/syntax.d.ts.map +1 -1
  187. package/dist/v3/syntax.js +167 -101
  188. package/dist/v3/syntax.js.map +1 -1
  189. package/package.json +10 -6
  190. package/src/client.ts +252 -0
  191. package/src/core/account.ts +86 -13
  192. package/src/core/amount.ts +3 -1
  193. package/src/core/balance.ts +6 -5
  194. package/src/core/bank-info.ts +1 -1
  195. package/src/core/client.ts +52 -15
  196. package/src/core/config.ts +69 -28
  197. package/src/core/errors.ts +122 -48
  198. package/src/core/feedback.ts +4 -1
  199. package/src/core/transaction.ts +9 -4
  200. package/src/core/transport.ts +98 -23
  201. package/src/formats/camt.ts +136 -39
  202. package/src/formats/mt940.ts +167 -81
  203. package/src/formats/readers.ts +24 -31
  204. package/src/formats/record-budget.ts +18 -0
  205. package/src/formats/sepa-purpose.ts +21 -2
  206. package/src/index.ts +4 -2
  207. package/src/session/app-approval.ts +51 -38
  208. package/src/session/dialog-runner.ts +31 -28
  209. package/src/session/login-guard.ts +174 -88
  210. package/src/session/sepa-accounts.ts +63 -45
  211. package/src/session/session-setup.ts +90 -71
  212. package/src/session/session-state.ts +56 -22
  213. package/src/session/signed-dialog.ts +192 -0
  214. package/src/session/tan-selection.ts +50 -39
  215. package/src/session/user-prompts.ts +35 -16
  216. package/src/tan/challenge.ts +40 -31
  217. package/src/tan/flicker.ts +44 -6
  218. package/src/tan/tan-image.ts +7 -5
  219. package/src/tan/tan-models.ts +10 -5
  220. package/src/util/async.ts +0 -5
  221. package/src/util/bytes.ts +13 -19
  222. package/src/util/call-context.ts +3 -2
  223. package/src/util/dates.ts +41 -24
  224. package/src/util/errors.ts +14 -3
  225. package/src/util/json.ts +16 -0
  226. package/src/util/text.ts +42 -12
  227. package/src/util/xml.ts +37 -26
  228. package/src/v3/account-extension.ts +7 -12
  229. package/src/v3/bank-connection.ts +67 -0
  230. package/src/v3/dialog.ts +175 -91
  231. package/src/v3/message.ts +69 -17
  232. package/src/v3/parameter-store.ts +160 -0
  233. package/src/v3/parameters.ts +110 -37
  234. package/src/v3/protocol.ts +70 -253
  235. package/src/v3/queries.ts +190 -0
  236. package/src/v3/segments.ts +65 -22
  237. package/src/v3/syntax.ts +153 -91
  238. package/dist/session/bank-connection.d.ts +0 -28
  239. package/dist/session/bank-connection.d.ts.map +0 -1
  240. package/dist/session/bank-connection.js +0 -48
  241. package/dist/session/bank-connection.js.map +0 -1
  242. package/dist/session/parameter-data.d.ts +0 -83
  243. package/dist/session/parameter-data.d.ts.map +0 -1
  244. package/dist/session/parameter-data.js +0 -68
  245. package/dist/session/parameter-data.js.map +0 -1
  246. package/dist/session/pin-tan-client.d.ts +0 -54
  247. package/dist/session/pin-tan-client.d.ts.map +0 -1
  248. package/dist/session/pin-tan-client.js +0 -153
  249. package/dist/session/pin-tan-client.js.map +0 -1
  250. package/dist/session/pin-tan-dialog.d.ts +0 -71
  251. package/dist/session/pin-tan-dialog.d.ts.map +0 -1
  252. package/dist/session/pin-tan-dialog.js +0 -124
  253. package/dist/session/pin-tan-dialog.js.map +0 -1
  254. package/dist/session/protocol.d.ts +0 -209
  255. package/dist/session/protocol.d.ts.map +0 -1
  256. package/dist/session/protocol.js +0 -75
  257. package/dist/session/protocol.js.map +0 -1
  258. package/dist/session/response-handler.d.ts +0 -48
  259. package/dist/session/response-handler.d.ts.map +0 -1
  260. package/dist/session/response-handler.js +0 -121
  261. package/dist/session/response-handler.js.map +0 -1
  262. package/dist/v3/client.d.ts +0 -24
  263. package/dist/v3/client.d.ts.map +0 -1
  264. package/dist/v3/client.js +0 -16
  265. package/dist/v3/client.js.map +0 -1
  266. package/src/session/bank-connection.ts +0 -51
  267. package/src/session/parameter-data.ts +0 -137
  268. package/src/session/pin-tan-client.ts +0 -186
  269. package/src/session/pin-tan-dialog.ts +0 -160
  270. package/src/session/protocol.ts +0 -311
  271. package/src/session/response-handler.ts +0 -130
  272. package/src/v3/client.ts +0 -29
package/CHANGELOG.md CHANGED
@@ -1,3 +1,142 @@
1
+ ## 0.2.0
2
+
3
+ ### Breaking
4
+
5
+ - Keep a `FinTs3Client` out of Vue's reactive state (`ref`, `reactive`, Pinia): its fields are ECMAScript private now,
6
+ which Vue's proxies cannot reach, so every call fails with a `TypeError`. Use a plain variable, a `shallowRef` or
7
+ `markRaw`.
8
+ - `FinTsTransport.post` takes an `AbortSignal` as fourth argument, which the client aborts on `close`: pass it to
9
+ `fetch`. Code that calls `post` itself must pass one; `HttpTransport.post` takes it optionally.
10
+ - A custom transport reports failures as `FinTsTransportError`, with `statusCode` for an HTTP error. The client turns
11
+ any other error, another `FinTsError` included, into a `FinTsTransportError` that keeps only its message and that of
12
+ its cause, and `FinTsTransportError` takes and carries no `cause` any more.
13
+ - A refused login is a `FinTsLoginRefusedError`, which `FinTsPinError` and `FinTsLoginDataChangedError` now extend, also
14
+ where the bank ends the dialog in answer to the login (it was `FinTsDialogAbortedError`). The client sends the PIN no
15
+ more then: see Added for `loginRefusal` and `clearLoginRefusal`.
16
+ - `FinTsTanSetupError` replaces `FinTsUnsupportedError` where the client has no TAN method or medium to log in with: let
17
+ the user choose a medium or another method (`selectTanMethod`) rather than hide the feature.
18
+ - `FinTsError` takes the message only, as `Error` does, and `FinTsBankError` takes no `name`: a subclass declares
19
+ `override name = '…'`.
20
+ - `FinTsConfig` throws a `TypeError` for what the bank would refuse: a URL with user name or password, a country code of
21
+ other than three digits (280 for Germany, not 276), a German bank code of other than eight, and login details outside
22
+ ISO 8859-1. It trims the login details and the product, and `config.url` is a new copy at each access.
23
+ - `HttpTransport` throws a `TypeError` for a `timeoutMs` that is no positive number up to 2147483647, and for a
24
+ `maxResponseBytes` that is no positive number.
25
+ - A TAN longer than `TanMethod.maxTanLength`, with anything but the digits 0 to 9 for a `numericTan` method, or with
26
+ characters outside ISO 8859-1 fails the call with a `TypeError` before it is sent. Full-width digits count as digits
27
+ (NFKC).
28
+ - A TAN callback answer that is no `TanAnswer`, such as the `undefined` of a callback that returns nothing, fails the
29
+ call with a `TypeError`.
30
+ - Bank data are read strictly, failing `getTransactions` with `FinTsProtocolError` and the parsers with `FormatError`:
31
+ an MT940 statement that does not balance, states a balance, its account or its number twice, or holds an unreadable
32
+ balance, and MT940 text without fields; in camt, an unreadable date, a `DtTm` without time, and a document without
33
+ statements.
34
+ - A negative amount next to a credit/debit mark is malformed: in camt and HISAL it fails the call, and it voids the
35
+ account extension of the user parameter data; a camt debit of `-5.00` used to read as a credit.
36
+ - `TanImage.tryParse` takes PNG, JPEG, GIF, WebP and BMP only, no SVG, and `TanImage.mimeType` is in lower case.
37
+ `FlickerCode.parse` rejects a code that would not render as the bank sent it, so `TanChallenge.flickerCode` is
38
+ `undefined` for it.
39
+ - `Account` keeps IBAN and BIC without blanks and in capitals. `Account.allowedTransactions` counts segment IDs only,
40
+ such as `HKCAZ`, no versioned names such as `HKCAZ_1`.
41
+ - `FinTsFeedback.parameters`, and with them the new login data of `FinTsLoginDataChangedError`, and
42
+ `TanChallenge.reference` are read one character per byte (ISO 8859-1), as the client sends them back.
43
+
44
+ ### Security
45
+
46
+ - The client keeps the PIN in a private field, out of reach of `JSON.stringify` and Node.js's `util.inspect`, though not
47
+ of a debugger or the browser's DevTools: never log the client. It forgets the PIN once closed or refused.
48
+ - A refused login is kept in `state`, as the bank's return codes, so that a client restored from it, also one for
49
+ another URL of the bank, does not send the PIN. Any error in the answer to a login refuses it, and so does a wrong PIN
50
+ reported later, at the dialog end included. Run the clients of one user one after another: the state saved last wins.
51
+ - After two logins in a row whose answer was lost, timed out or could not be read, which the bank may have counted, the
52
+ client refuses the login itself, with a `FinTsLoginRefusedError` without return codes, also across restored clients.
53
+ - A call sends at most three logins the bank refuses: another TAN method and a new customer system ID count once per
54
+ call each, and a refused one-step login is repeated with another method only where the bank names the method (9075, or
55
+ a 3920 next to no error but 9050 or 9800).
56
+ - New login data (3072) fail the call as soon as an answer to the login brings them, before any approval and also next
57
+ to errors; `clearLoginRefusal` cannot lift them.
58
+ - `close` stops a running call at its next message and aborts the request on its way; only the ends of its dialogs go
59
+ out after it.
60
+ - Error messages keep the PIN and account data out: a transport's error passes on its message only, without `cause`;
61
+ `FormatError` and `FinTsProtocolError` leave out the data; a URL with credentials is rejected before `fetch` puts it
62
+ in an error.
63
+ - Messages go to the bank's URL only: `HttpTransport` rejects the answer to a redirect its `fetch` followed or from
64
+ another URL, and the transport gets a copy of the checked URL for each message.
65
+ - An answer must be one complete message, with the number and, once assigned, the dialog ID of the message sent: one cut
66
+ short, broken in its framing, joined to another or answering another message fails with `FinTsProtocolError`. The
67
+ first answer of a dialog cannot be matched, so a relay must never mix up answers.
68
+ - Parameter data come only from the answers to a dialog's opening; the anonymous dialog's serve until the user's arrive,
69
+ and those of another user, of a rejected customer system ID (9391) or of a state from an earlier version or the Dart
70
+ package are fetched anew.
71
+ - A balance (HISAL) and camt transactions (HICAZ) must be for the account asked for, or the call fails with
72
+ `FinTsProtocolError`. MT940 is not checked, since banks write the `:25:` line in many ways.
73
+ - An account the app builds itself goes out as the bank knows it and is checked against the transactions the bank
74
+ permits for it; one the bank names nowhere fails with `FinTsUnsupportedError` where those lists are complete
75
+ (UPD-Verwendung 0).
76
+ - Hostile answers no longer exhaust the memory or hold up the thread: past 200,000 data elements per answer, 128 MiB or
77
+ 200,000 statements and transactions per query, or XML of 256 levels or a million elements and attributes, a call fails
78
+ with `FinTsProtocolError`, and `Mt940.parse` and `Camt.parse` with `FormatError`; reading takes linear time.
79
+ - An MT940 statement must balance, and name its account and number once, so that a payer cannot add a transaction or an
80
+ account with a purpose line that starts like a field, such as `:61:` or `:25:`.
81
+ - A SEPA keyword in the text the payer or payee writes (`SVWZ+`, `ABWA+`, `ABWE+`) is part of that text, and one that
82
+ occurs twice has no value: such text replaced the creditor ID, mandate reference or IBAN the bank stated.
83
+ - The creditor ID of a camt transaction is the creditor's (`Cdtr`) only, and the `EREF`, `MREF` and `CRED` of
84
+ `purposeCodes` come from their elements only, never from the remittance text.
85
+ - A flicker code that would not render as the bank sent it is refused rather than shown altered on the TAN generator,
86
+ and so is a data element length of 0 or above 63. A TAN image of type SVG, which may carry scripts, is refused.
87
+ - The docs say to show challenge text as text, never as HTML (`innerHTML`, `v-html`): it is the bank's text, or a
88
+ relay's.
89
+ - The README relay serves the signed-in sessions of the app only, limits their requests, forwards to the bank of the
90
+ session only, refuses redirects and passes the signal on: update relays built from it.
91
+
92
+ ### Fixed
93
+
94
+ - The README relay reports the relay's HTTP status as `FinTsTransportError.statusCode`, and its server passes the bank's
95
+ on. Without it, a bank that fails on the anonymous announcement of strong authentication never delivered its parameter
96
+ data.
97
+ - The README names the minimum Deno (2.1) and Bun (1.2.6): with older ones a call from inside a callback waits forever
98
+ instead of failing with `FinTsStateError`.
99
+ - The docs say when a call logs in again, which may ask for another approval, that `getTanMethods` may log in, that
100
+ `FinTsUnsupportedError` may follow a login, and the client's own limits of an app approval: 60 status requests where
101
+ the bank states none, and 10 minutes.
102
+ - The PIN's length limits apply to the login only: a synchronization whose first parameter data ruled out the PIN the
103
+ bank had accepted failed its own dialog end.
104
+ - An answer to a TAN or an app approval that asks for an approval once more (0030, 3955) fails with `FinTsProtocolError`
105
+ instead of reading as done, and only the first page of a query may ask for an approval.
106
+ - While the client polls for an app approval, new instructions of the bank replace the open prompt, whose
107
+ `TanChallenge.resolved` settles; a settled prompt is no longer kept until `close`.
108
+ - `getTanMedia` asks the bank unless the same call has just looked the media up; the list of a lookup that found no
109
+ active medium is handed out by every call; a TAN method switched back to has its media looked up anew;
110
+ `clearLoginRefusal` forgets the TAN medium, which the bank may have refused.
111
+ - `getTransactions` rejects a `from` or `to` that is no valid `Date` with a `TypeError`, before it waits for other
112
+ calls; an Invalid Date went out as `0NaNNaNNaN`.
113
+ - Values the bank issued go back as it sent them: dialog ID, customer system ID, TAN order reference, position to
114
+ continue at and TAN medium name, such as one in UTF-8 that fits ISO 8859-1, which the bank did not know otherwise.
115
+ - A SEPA account list the bank answers by ending the dialog counts as rejected, and an empty one is not saved, so that
116
+ neither costs every client another login.
117
+ - `HttpTransport` calls its `fetch` as a plain function (a browser's native one failed with `Illegal invocation`),
118
+ checks the size of an answer without a stream body, and reads the body of node-fetch.
119
+ - MT940: `@@` separates the lines wherever the first line holds it; each page is decoded on its own; a booking date lies
120
+ in the year that places it within its statement; only `?` separates the subfields of `:86:`.
121
+ - camt: a batch booking is split only into details that add up to it, in its currency; details count in `NtryDtls` and
122
+ purpose lines in `RmtInf` only; a `DtTm` counts on its German day; purpose lines cut at 35 characters keep their
123
+ blanks; `&constructor;` is an unknown entity.
124
+ - Models keep copies of the `Date`s they are given, so that changing one in place changes no other, nor what a later
125
+ `getAccounts`, `getBalance` or `getTanMedia` returns. A complete balance of the account extension states the
126
+ `productName`.
127
+ - An account of the user parameter data whose national account leaves out the bank code keeps its country code.
128
+ - `state` encodes the parameter data once, as the bank sent them, and bank text is decoded in bulk: both held up the
129
+ thread with large data.
130
+ - `FinTs3Client` extends no internal class, so that its declarations name no internal types.
131
+
132
+ ### Added
133
+
134
+ - `FinTsClient.loginRefusal`, the refused login that keeps the client from sending the PIN, and
135
+ `clearLoginRefusal(pin)`, which lifts it with a PIN the user has entered anew; `FinTsLoginRefusedError`.
136
+ - `FinTsTanSetupError`, for a missing TAN method or medium.
137
+ - `Camt.parse`, which gives each statement of a camt document as a `CamtStatement` with its account.
138
+ - `close` aborts a request on its way, through the signal of `FinTsTransport.post`; `HttpTransport` stops it.
139
+
1
140
  ## 0.1.0
2
141
 
3
142
  First release: a TypeScript port of the FinTS 3.0 client of the Dart package `fints`.
@@ -7,5 +146,7 @@ First release: a TypeScript port of the FinTS 3.0 client of the Dart package `fi
7
146
  parameters; TAN method and medium selection, chipTAN flicker codes and photoTAN images.
8
147
  - Bank parameter data, customer system ID and the chosen TAN method in an exportable state string, in the layout of the
9
148
  Dart package.
10
- - A client never sends its PIN again once the bank has refused a login.
149
+ - A client never sends its PIN again once the bank has refused a login, but where the bank names a reason the client
150
+ takes care of in the same call: another TAN method (3920, or 9075 after the one-step method) or a new customer system
151
+ ID (9391).
11
152
  - Runs wherever `fetch`, Web Crypto and `TextDecoder` are available; no runtime dependencies.
package/README.md CHANGED
@@ -2,25 +2,25 @@
2
2
 
3
3
  A FinTS 3.0 client for German banks, written in TypeScript, without runtime dependencies. It reads accounts, balances and transactions with PIN/TAN, including PSD2 strong customer authentication and app approval (pushTAN, SecureGo plus and the like).
4
4
 
5
- It is a port of the Dart package [`fints`](https://pub.dev/packages/fints) and behaves the same: same session logic, same safeguards, same tests. FinTS 4.1, which hardly any bank offers, is not part of the port.
5
+ It is a port of the Dart package [`fints`](https://pub.dev/packages/fints), with stricter safeguards (see [Differences from the Dart package](#differences-from-the-dart-package)). FinTS 4.1, which hardly any bank offers, is not part of the port.
6
6
 
7
7
  ## Features
8
8
 
9
9
  - **PIN/TAN** with the one-step method (`999`) and two-step methods of process variant 2 (`HKTAN` process 4, then 2 or `S`).
10
10
  - **Strong customer authentication** at login and per transaction where the bank asks for it, including exemptions (`3076`).
11
- - **App approval** (decoupled), polled by the bank's own parameters; the client stops when the app is closed or the user cancels.
12
- - **TAN media** lookup and selection, **chipTAN flicker codes** (HHD 1.3 and 1.4, as frames to animate) and **photoTAN/QR images** (as `data:` URLs).
11
+ - **App approval** (decoupled), polled as the bank's parameters say, for 10 minutes at most; the client stops when the app is closed or the user cancels.
12
+ - **TAN media** lookup and selection, **chipTAN flicker codes** (HHD 1.3 and 1.4, as frames to animate) and **photoTAN/QR images** (PNG, JPEG, GIF, WebP or BMP, as `data:` URLs for an `<img>`).
13
13
  - **Balances** (`HKSAL` 6–8) and **transactions** as camt.052 (`HKCAZ`) or MT940/MT942 (`HKKAZ` 6–7), with automatic paging.
14
14
  - **IBAN and BIC** from the SEPA account list (`HKSPA`), and the account extension of the user parameter data.
15
- - **Safe by default**: the PIN is never sent again after the bank refused a login, so a retrying app cannot lock the access; only what the bank's parameter data permit is sent; PIN and TAN are masked in every log.
16
- - **Saved sessions**: bank and user parameters, customer system ID and the chosen TAN method in one state string, which spares the setup steps at the next start.
17
- - Parsers for MT940 (German structured `:86:` fields, SEPA keywords) and camt.052/053.
15
+ - **Safe by default**: after a refused login the PIN is not sent again until the user enters it anew, also by a client restored from the saved state (see [Refused logins](#refused-logins)); only what the bank's parameter data permit is sent; PIN and TAN are masked in every log.
16
+ - **Saved sessions**: bank and user parameters, customer system ID, the chosen TAN method and a refused login in one state string, which spares the setup steps at the next start.
17
+ - Parsers for MT940 (German structured `:86:` fields, SEPA keywords) and camt.052/053/054, statement by statement with the account each is for (`Mt940.parse`, `Camt.parse`), or as one list of transactions (`parseTransactions`).
18
18
 
19
19
  ## Requirements
20
20
 
21
21
  1. **A FinTS product registration.** German banks only accept registered products. Register for free with the Deutsche Kreditwirtschaft at <https://www.fints.org/de/hersteller/produktregistrierung> and pass the ID as `productId`. Keep it out of your repository.
22
22
  2. **Your bank's FinTS URL and bank code (BLZ)**, for example from the DK institute list.
23
- 3. **A runtime with `fetch`, Web Crypto and `TextDecoder`**: Node.js 22.12 or later, Deno, Bun, or a browser behind a server of your own (see [Using it with Vue](#using-it-with-vue)). The type declarations need TypeScript 5.6 or later.
23
+ 3. **A runtime with `fetch`, Web Crypto and `TextDecoder`**: Node.js from 22.12, Deno from 2.1, Bun from 1.2.6, or a browser behind a server of your own (see [Using it with Vue](#using-it-with-vue)). The type declarations need TypeScript 5.6 or later.
24
24
 
25
25
  ## Usage
26
26
 
@@ -44,11 +44,13 @@ const client = new FinTs3Client({
44
44
  onWarning: (warning) => showNotice(warning.code, warning.text), // banks want code and text shown
45
45
  tanCallback: async (challenge) => {
46
46
  if (challenge.isDecoupled) {
47
- // Close the prompt when challenge.resolved settles; offer "approved" only if challenge.manualConfirmation.
47
+ // Close the prompt when challenge.resolved settles; the bank's new instructions come with the next call.
48
+ // Offer "approved" only if challenge.manualConfirmation.
48
49
  const confirmed = await showApprovalPrompt(challenge);
49
50
  return confirmed ? TanAnswer.approved : TanAnswer.cancelled;
50
51
  }
51
- // Show challenge.plainMessage, challenge.image?.toDataUrl() or challenge.flickerCode?.frames().
52
+ // Show challenge.plainMessage as text (textContent, never innerHTML), challenge.image?.toDataUrl() in an
53
+ // <img>, or challenge.flickerCode?.frames().
52
54
  const tan = await askForTan(challenge);
53
55
  return tan === undefined ? TanAnswer.cancelled : TanAnswer.tan(tan);
54
56
  },
@@ -65,7 +67,15 @@ try {
65
67
  }
66
68
  ```
67
69
 
68
- All calls share one bank dialog, so the user approves once per session. Calls run one after another; a call from inside a callback would wait for itself, so on Node.js, Deno and Bun it fails with `FinTsStateError` (in browsers it is not detected).
70
+ 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 approval; so does `getTanMethods` of a client without saved state. Calls share one bank dialog while it is open, so the approval of its login serves them all. The next call logs in anew, which may ask for another approval, once the dialog has idled longer than the bank allows (`BankInfo.maxTimeout`, 5 minutes where it states none), after `selectTanMethod` and `clearLoginRefusal`, once the bank has ended the dialog, and after a message failed with another error than a `FinTsBankError`, such as a cancelled or timed-out approval, a failing `tanCallback`, or a lost or unreadable answer. A query may need an approval of its own, such as one for transactions older than 90 days; a bank that asks again for a later page fails the call with `FinTsProtocolError`.
71
+
72
+ Calls run one after another. A callback must not wait for another call of its client, which would wait for the running call: on Node.js, Deno and Bun such a call fails with `FinTsStateError`, in browsers it waits forever.
73
+
74
+ A saved state belongs to one client at a time. Run the clients of one bank user one after another, each restored from the state the one before saved, for example behind a lock or a queue per user, never two at once from the same state, such as for two browser tabs: the state saved last wins, and could lift a refused login the other client saved.
75
+
76
+ Never log the client: `JSON.stringify` and Node.js's `console.log` leave out the PIN, which the client keeps in a private field until it is closed or the login refused, but a debugger and the browser's DevTools show it.
77
+
78
+ An account the app builds itself, such as `new Account({ bankCode, iban })`, stands for the account of `getAccounts` it matches by IBAN or account number, whose details and permitted transactions the queries take. One the bank names nowhere goes out with what identifies it only, and fails with `FinTsUnsupportedError` where the bank's lists of permitted transactions are complete.
69
79
 
70
80
  Calendar dates, such as `Transaction.bookingDate`, are `Date`s at local midnight, of which only the day counts. Moments, such as `Balance.bookedAsOfTime`, are exact; the bank states them in German time.
71
81
 
@@ -75,29 +85,59 @@ Calendar dates, such as `Transaction.bookingDate`, are `Date`s at local midnight
75
85
 
76
86
  There are three ways to set it up:
77
87
 
78
- 1. **The client runs on your server** (Nuxt server routes, Express, …). The Vue app calls your own API; the server keeps one `FinTs3Client` per session and passes the TAN challenges to the browser, for example with a pending request the browser answers. This keeps the bank traffic and the product ID on the server.
88
+ 1. **The client runs on your server** (Nuxt server routes, Express, …). The Vue app calls your own API; the server keeps one `FinTs3Client` per session and passes the TAN challenges to the browser, for example with a pending request the browser answers. Two sessions of one bank user share the saved state, so run their clients one after another (see [Usage](#usage)). This keeps the bank traffic and the product ID on the server.
79
89
  2. **The client runs in the browser, through a relay.** The library runs in the Vue app, so TAN prompts are plain local UI, and a small endpoint of yours forwards each message to the bank. Pass a transport that posts to it:
80
90
 
81
91
  ```ts
82
- import type { FinTsTransport } from '@coderoftime/fints';
92
+ import { FinTsTransportError, type FinTsTransport } from '@coderoftime/fints';
83
93
 
84
94
  const relay: FinTsTransport = {
85
- async post(url, body, contentType) {
95
+ async post(url, body, contentType, signal) {
86
96
  const response = await fetch('/api/fints-relay', {
87
97
  method: 'POST',
88
98
  headers: { 'Content-Type': contentType, 'X-FinTS-URL': url.href },
89
- body,
99
+ body: body.slice(), // a copy on an ArrayBuffer of its own, the only kind TypeScript's fetch types take
100
+ signal, // aborted when the client is closed
101
+ redirect: 'error', // never send the PIN on to another address
102
+ cache: 'no-store',
90
103
  });
91
- if (!response.ok) throw new Error(`Relay: HTTP ${response.status}`);
104
+ if (!response.ok) {
105
+ // The bank's HTTP status, which the relay passes on and the client acts on (FinTsTransport.post).
106
+ throw new FinTsTransportError(`Relay: HTTP ${response.status}`, { statusCode: response.status });
107
+ }
92
108
  return new Uint8Array(await response.arrayBuffer());
93
109
  },
94
110
  close() {},
95
111
  };
96
112
  ```
97
113
 
98
- The relay must forward only to the bank URLs of the DK institute list, never to any URL a caller names, or it is an open proxy. The product ID ends up in the page; anyone can read it there.
114
+ The relay forwards with `HttpTransport`, which times out, limits the size of the answer and follows no redirect, and passes the bank's HTTP status on. A sketch with the web-standard `Request` and `Response`:
115
+
116
+ ```ts
117
+ import { FinTsTransportError, HttpTransport } from '@coderoftime/fints';
118
+
119
+ const bank = new HttpTransport();
120
+
121
+ // Behind the app's login and a rate limit per session; bankUrl is the bank the user chose, as URL.href writes it.
122
+ export async function relay(request: Request, session: { bankUrl: string }): Promise<Response> {
123
+ if (request.headers.get('X-FinTS-URL') !== session.bankUrl) return new Response(null, { status: 403 });
124
+ try {
125
+ const body = new Uint8Array(await request.arrayBuffer());
126
+ const answer = await bank.post(new URL(session.bankUrl), body, 'text/plain', request.signal);
127
+ return new Response(answer.slice(), { headers: { 'Content-Type': 'text/plain', 'Cache-Control': 'no-store' } });
128
+ } catch (error) {
129
+ // The bank's HTTP status, which the client acts on; 502 where the bank did not answer.
130
+ const status = error instanceof FinTsTransportError ? error.statusCode : undefined;
131
+ return new Response(null, { status: status ?? 502 });
132
+ }
133
+ }
134
+ ```
99
135
 
100
- 3. **The app is not a web page**: Electron, Tauri or Capacitor can send requests without CORS. Pass a `FinTsTransport` that uses the platform's HTTP client, or `HttpTransport` where `fetch` is not bound by CORS.
136
+ Anyone who can call the relay can send logins to the banks it forwards to, from your server's address, which a bank may then block for all your users. So serve it to the signed-in sessions of your app only, limit the requests of each session, and forward only to the bank the user chose for the session, from a list of known https URLs such as the DK institute list. The body holds PIN and TAN: never log it, on either side, nor put it in an error message, which the client passes on. And never mix up the answers of two sessions: the client cannot tell the answer to the first message of a dialog from that of another user. The product ID ends up in the page; anyone can read it there.
137
+
138
+ 3. **The app is not a web page**: Electron, Tauri or Capacitor can send requests without CORS. Pass a `FinTsTransport` that uses the platform's HTTP client without following redirects, or `HttpTransport` where `fetch` is not bound by CORS.
139
+
140
+ Keep a client in the Vue app out of reactive state (`ref`, `reactive`, a Pinia store): Vue wraps it in a proxy there, which cannot reach the client's private fields, so every call fails with a `TypeError`. A plain variable, a `shallowRef` or `markRaw(client)` keeps the client as it is.
101
141
 
102
142
  A TAN prompt in Vue fits the callback as a promise the dialog resolves:
103
143
 
@@ -110,48 +150,98 @@ export const pending = ref<{ challenge: TanChallenge; answer: (a: TanAnswer) =>
110
150
  export function tanCallback(challenge: TanChallenge): Promise<TanAnswer> {
111
151
  return new Promise((answer) => {
112
152
  pending.value = { challenge, answer };
113
- void challenge.resolved?.then(() => (pending.value = undefined)); // the bank confirmed the app approval
153
+ void challenge.resolved?.then(() => (pending.value = undefined)); // the client needs no answer any more
114
154
  });
115
155
  }
116
156
  // The dialog component shows pending.value.challenge and calls pending.value.answer(TanAnswer.tan(input)),
117
157
  // TanAnswer.approved or TanAnswer.cancelled, then clears pending.value.
118
158
  ```
119
159
 
160
+ The dialog shows the challenge's text with `{{ }}`, which Vue escapes, never with `v-html`: `plainMessage` and `message`, like any text of the bank, are no markup to trust, and as HTML they could run scripts in the page where the user enters the PIN. `white-space: pre-line` keeps the line breaks of `plainMessage`:
161
+
162
+ ```vue
163
+ <div v-if="pending">
164
+ <p style="white-space: pre-line">{{ pending.challenge.plainMessage }}</p>
165
+ <img v-if="pending.challenge.image" :src="pending.challenge.image.toDataUrl()" alt="TAN image" />
166
+ </div>
167
+ ```
168
+
120
169
  ## Errors
121
170
 
122
- | Error | Meaning |
123
- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
124
- | `FinTsPinError` | Wrong or blocked PIN. The client sends no PIN any more; create a new client to try again. |
125
- | `FinTsTanError` | Wrong, expired or used TAN. The session stays usable. |
126
- | `FinTsTanCancelledError` | The user cancelled the TAN or the app approval. |
127
- | `FinTsDecoupledTimeoutError` | The app approval did not come within the bank's limit. |
128
- | `FinTsLoginDataChangedError` | The bank issued new login data (`3072`); `loginName` tells it. |
129
- | `FinTsDialogAbortedError` | The bank ended the dialog; the next call logs in anew. |
130
- | `FinTsBankError` | Any other rejection; `feedback` holds the return codes. After a refused login, later calls fail with the same error without contacting the bank. |
131
- | `FinTsUnsupportedError` | The bank or the user's parameter data do not offer the transaction; nothing was sent. |
132
- | `FinTsProtocolError` | The bank's answer could not be read. |
133
- | `FinTsTransportError` | No connection, an HTTP error or a timeout. |
134
- | `FinTsStateError` | The client was closed, or called from inside one of its callbacks. |
135
- | `TypeError` | An invalid argument, such as an empty PIN or a period that ends before it starts; the message never contains the PIN. |
136
-
137
- The client never sends a message larger than the bank's maximum (`BankInfo.maxMessageSize`, in kilobytes), and never repeats a call by itself: the bank's answer cannot tell whether an order went through.
171
+ | Error | Meaning |
172
+ | ---------------------------- | --------------------------------------------------------------------------------------------------------------------- |
173
+ | `FinTsLoginRefusedError` | The bank refused the login. The client sends the PIN no more, see [Refused logins](#refused-logins). |
174
+ | `FinTsPinError` | Wrong or blocked PIN, a `FinTsLoginRefusedError`. |
175
+ | `FinTsTanError` | Wrong, expired or used TAN. The session stays usable. |
176
+ | `FinTsTanCancelledError` | The user cancelled the TAN or the app approval. |
177
+ | `FinTsDecoupledTimeoutError` | The app approval did not come within the requests the bank allows (60 where it states none) or 10 minutes. |
178
+ | `FinTsLoginDataChangedError` | The bank issued new login data (`3072`); `loginName` tells it. A `FinTsLoginRefusedError` for the old data. |
179
+ | `FinTsDialogAbortedError` | The bank ended the dialog; the next call logs in anew. |
180
+ | `FinTsBankError` | Any other rejection; `feedback` holds the return codes. |
181
+ | `FinTsUnsupportedError` | The parameter data do not offer the transaction; the client did not send it, though it may have logged in. |
182
+ | `FinTsTanSetupError` | No TAN method or medium to log in with; pass a medium to `selectTanMethod`, or choose another method. |
183
+ | `FinTsProtocolError` | The bank's answer could not be read. The message leaves out the data, which may be account data. |
184
+ | `FinTsTransportError` | No connection, an HTTP error (`statusCode`), a timeout or an abort by `close`. The message may have reached the bank. |
185
+ | `FinTsStateError` | The client was closed, or called from inside one of its callbacks. |
186
+ | `TypeError` | An invalid argument, such as an empty PIN or a TAN that the TAN method rules out; the message never shows PIN or TAN. |
187
+
188
+ The client never repeats a call by itself: the bank's answer cannot tell whether an order went through.
189
+
190
+ It reads only complete answers to the message it sent: one cut short, broken, with more than 200,000 data elements, or with the number or dialog ID of another message fails the call with `FinTsProtocolError`. The first message of every dialog goes out with the same number and dialog ID, though, so the answer to another user's first message passes: a relay must never mix up answers. A query whose pages hold more than 128 MiB, or more than 200,000 statements and transactions, fails with `FinTsProtocolError` as well: ask for a shorter period then.
191
+
192
+ MT940 statements must balance. Where a bank wraps a purpose at a fixed width, a payer could start a line of it like a field, such as `:61:`, which would add a transaction; `getTransactions` fails with `FinTsProtocolError` instead, and `Mt940.parse` with `FormatError`. The check misses such lines that add up to nothing, and statements without balances, such as pending transactions (MT942).
193
+
194
+ ## Refused logins
195
+
196
+ Banks lock the online access after a few failed logins. So once the bank has refused a login, the client does not send the PIN again: every call that would fails with the refusal, a `FinTsLoginRefusedError`, without contacting the bank. Any error in the answer to a login refuses it, since a return code does not always tell whether the PIN was the reason, and so does a wrong PIN the bank reports later in the dialog.
197
+
198
+ Only where the bank names a reason the client can take care of does the same call log in once more, once per reason: with another TAN method where the bank lists those the user may use without the one the login was signed with (`3920`, after the one-step method only next to no error but `9050` or `9800`) or demands strong authentication of a one-step login (`9075`), and with a new customer system ID where the bank rejects the old one (`9391`). None of them follows a wrong PIN, and a call sends at most three logins the bank refuses.
199
+
200
+ A login whose answer is lost, times out, is aborted by `close` or cannot be read may have counted as a failed attempt all the same. After two such logins in a row the client refuses the login itself, with a `FinTsLoginRefusedError` whose `feedback` is empty; it cannot tell a login that never reached the bank, so a connection that fails twice in a row stops it as well. A login the bank takes clears the count.
201
+
202
+ The refusal and that count are part of `client.state`, so a client restored from it does not send the PIN either, as long as the clients of the user take turns with the state (see [Usage](#usage)); `client.loginRefusal` tells it before any call. Lift it with a PIN the user has entered anew, unless the bank has changed the login data:
203
+
204
+ ```ts
205
+ const refusal = client.loginRefusal;
206
+ if (refusal instanceof FinTsLoginDataChangedError) {
207
+ // The old login data are retired: build a new client with refusal.loginName as userId, or its userId and customerId.
208
+ } else if (refusal !== undefined) {
209
+ showNotice(refusal.message); // the bank's errors, or why the client stopped by itself
210
+ await client.clearLoginRefusal(await askForPin());
211
+ }
212
+ ```
213
+
214
+ Never pass `clearLoginRefusal` a stored PIN without asking the user: that is how a retrying app locks the access. It also forgets the TAN medium, which the bank may have refused instead, such as that of a replaced phone, so that the next call looks the media up anew.
138
215
 
139
216
  ## Differences from the Dart package
140
217
 
141
218
  - Errors are named `…Error` instead of `…Exception`; invalid arguments are `TypeError`s, malformed data `FormatError`s.
142
219
  - `getTransactions(account, { from, to, format })` takes an options object; enums are string unions such as `'mt940'`.
143
220
  - The polling parameters of `TanMethod` are in seconds: `decoupledFirstPollDelaySeconds`, `decoupledPollIntervalSeconds`.
144
- - `HttpTransport` takes a `fetch` and `timeoutMs`; `TanImage.toDataUrl()` is new.
145
- - The saved state has the same layout, so a state saved by one package can be restored by the other.
221
+ - New: the `fetch` and `timeoutMs` of `HttpTransport`, `TanImage.toDataUrl()`, `Camt.parse`, `loginRefusal`, `clearLoginRefusal`.
222
+ - `close` aborts a request on its way instead of waiting for the answer.
223
+ - The saved state has the same layout, so either package restores the other's; only this one keeps a refused login in it.
224
+ - `FinTsConfig` rejects login data the bank would refuse, before the PIN goes out.
225
+ - Answers, flicker codes and TAN images are read strictly; SVG images are refused.
226
+ - Values the bank issued, such as a TAN medium name in UTF-8, go back byte for byte.
227
+ - New instructions of the bank replace the open app approval prompt while the client polls.
228
+ - MT940 statements must balance.
229
+ - camt batch bookings are split only where their details add up, the creditor ID is the creditor's, and `DtTm` counts on its German day.
230
+ - A SEPA keyword in the text the payer or payee writes (`SVWZ+`, `ABWA+`, `ABWE+`) never replaces a value the bank stated.
146
231
 
147
232
  ## Development
148
233
 
149
234
  ```sh
150
235
  npm install
151
- npm run check # type check, lint, format check and tests
236
+ npm run check # type check, lint, unused code, format check and tests with coverage
152
237
  npm run build
153
238
  ```
154
239
 
240
+ The check fails for Node.js APIs in `src/` that browsers lack, for files, exports and dependencies nothing uses, or only
241
+ the tests ([knip](https://knip.dev)), and for coverage below the thresholds in `vitest.config.ts`, which each module
242
+ that guards what goes to and comes from the bank has to reach on its own. The coverage report is in
243
+ `coverage/index.html`.
244
+
155
245
  ## License
156
246
 
157
247
  BSD 3-Clause, see [LICENSE](LICENSE). Commercial use is allowed; a product that ships the library must reproduce its copyright notice and license in its documentation or notices.
@@ -0,0 +1,48 @@
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, type FinTsConfigInit } from './core/config.js';
6
+ import { type FinTsLoginRefusedError } from './core/errors.js';
7
+ import type { Transaction } from './core/transaction.js';
8
+ import { TanMedium, type TanMethod } from './tan/tan-models.js';
9
+ /** What `FinTs3Client` takes. */
10
+ export interface FinTs3ClientInit {
11
+ /** Where and as whom to log in: a `FinTsConfig`, or what its constructor takes. */
12
+ config: FinTsConfig | FinTsConfigInit;
13
+ /**
14
+ * 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
15
+ * refused the login. It is out of reach of `JSON.stringify` and Node.js's `util.inspect` of the client, but not of a
16
+ * debugger or the browser's DevTools: never log the client. A client whose `options.state` holds a refused login
17
+ * does not send it (`FinTsClient.loginRefusal`).
18
+ */
19
+ pin: string;
20
+ /** How the client reaches the user and the bank. */
21
+ options: FinTsClientOptions;
22
+ }
23
+ /** FinTS 3.0 PIN/TAN client, the version German banks use. */
24
+ export declare class FinTs3Client implements FinTsClient {
25
+ #private;
26
+ /**
27
+ * A client with the session restored from `FinTsClientOptions.state` where it was saved for this user and bank.
28
+ *
29
+ * Throws `TypeError` if the configuration is invalid, as the constructor of `FinTsConfig` tells, or the PIN is empty,
30
+ * begins or ends with a blank, or has characters FinTS 3.0 cannot send: it uses ISO 8859-1, so `€` and most
31
+ * non-Latin letters are not possible.
32
+ */
33
+ constructor(init: FinTs3ClientInit);
34
+ get state(): string;
35
+ get loginRefusal(): FinTsLoginRefusedError | undefined;
36
+ get selectedTanMethod(): string | undefined;
37
+ get selectedTanMedium(): string | undefined;
38
+ selectTanMethod(securityFunction: string, tanMedium?: string): Promise<void>;
39
+ clearLoginRefusal(pin: string): Promise<void>;
40
+ getBankInfo(): Promise<BankInfo>;
41
+ getTanMethods(): Promise<TanMethod[]>;
42
+ getTanMedia(): Promise<TanMedium[]>;
43
+ getAccounts(): Promise<Account[]>;
44
+ getBalance(account: Account): Promise<Balance>;
45
+ getTransactions(account: Account, query?: TransactionQuery): Promise<Transaction[]>;
46
+ close(): Promise<void>;
47
+ }
48
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAc,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EAA0C,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACvG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAQzD,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAShE,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,MAAM,EAAE,WAAW,GAAG,eAAe,CAAC;IACtC;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AAeD,8DAA8D;AAC9D,qBAAa,YAAa,YAAW,WAAW;;IAkC9C;;;;;;OAMG;gBACS,IAAI,EAAE,gBAAgB;IAmBlC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,YAAY,IAAI,sBAAsB,GAAG,SAAS,CAErD;IAED,IAAI,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAE1C;IAED,IAAI,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAE1C;IAED,eAAe,CAAC,gBAAgB,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5E,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU7C,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;IAIhC,aAAa,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAIrC,WAAW,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAenC,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAIjC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAaxC,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,GAAE,gBAAqB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAyB7F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAyCvB"}