@coderoftime/fints 0.2.0 → 0.3.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 (215) hide show
  1. package/CHANGELOG.md +377 -0
  2. package/README.md +31 -23
  3. package/dist/client.d.ts +3 -2
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +52 -9
  6. package/dist/client.js.map +1 -1
  7. package/dist/core/account.d.ts +15 -4
  8. package/dist/core/account.d.ts.map +1 -1
  9. package/dist/core/account.js +84 -36
  10. package/dist/core/account.js.map +1 -1
  11. package/dist/core/amount.d.ts +13 -3
  12. package/dist/core/amount.d.ts.map +1 -1
  13. package/dist/core/amount.js +42 -8
  14. package/dist/core/amount.js.map +1 -1
  15. package/dist/core/balance.d.ts +3 -0
  16. package/dist/core/balance.d.ts.map +1 -1
  17. package/dist/core/balance.js +5 -0
  18. package/dist/core/balance.js.map +1 -1
  19. package/dist/core/bank-info.d.ts +20 -3
  20. package/dist/core/bank-info.d.ts.map +1 -1
  21. package/dist/core/bank-info.js +25 -4
  22. package/dist/core/bank-info.js.map +1 -1
  23. package/dist/core/client.d.ts +42 -15
  24. package/dist/core/client.d.ts.map +1 -1
  25. package/dist/core/config.d.ts +15 -7
  26. package/dist/core/config.d.ts.map +1 -1
  27. package/dist/core/config.js +29 -14
  28. package/dist/core/config.js.map +1 -1
  29. package/dist/core/errors.d.ts +17 -5
  30. package/dist/core/errors.d.ts.map +1 -1
  31. package/dist/core/errors.js +25 -7
  32. package/dist/core/errors.js.map +1 -1
  33. package/dist/core/feedback.d.ts +7 -0
  34. package/dist/core/feedback.d.ts.map +1 -1
  35. package/dist/core/feedback.js +10 -1
  36. package/dist/core/feedback.js.map +1 -1
  37. package/dist/core/transaction.d.ts +5 -0
  38. package/dist/core/transaction.d.ts.map +1 -1
  39. package/dist/core/transaction.js +12 -0
  40. package/dist/core/transaction.js.map +1 -1
  41. package/dist/core/transport.d.ts +22 -7
  42. package/dist/core/transport.d.ts.map +1 -1
  43. package/dist/core/transport.js +51 -11
  44. package/dist/core/transport.js.map +1 -1
  45. package/dist/formats/camt.d.ts +7 -4
  46. package/dist/formats/camt.d.ts.map +1 -1
  47. package/dist/formats/camt.js +53 -54
  48. package/dist/formats/camt.js.map +1 -1
  49. package/dist/formats/mt940.d.ts +18 -6
  50. package/dist/formats/mt940.d.ts.map +1 -1
  51. package/dist/formats/mt940.js +83 -66
  52. package/dist/formats/mt940.js.map +1 -1
  53. package/dist/formats/readers.d.ts.map +1 -1
  54. package/dist/formats/readers.js +6 -2
  55. package/dist/formats/readers.js.map +1 -1
  56. package/dist/formats/sepa-purpose.d.ts +7 -4
  57. package/dist/formats/sepa-purpose.d.ts.map +1 -1
  58. package/dist/formats/sepa-purpose.js +18 -9
  59. package/dist/formats/sepa-purpose.js.map +1 -1
  60. package/dist/session/dialog-runner.d.ts +5 -2
  61. package/dist/session/dialog-runner.d.ts.map +1 -1
  62. package/dist/session/dialog-runner.js +23 -7
  63. package/dist/session/dialog-runner.js.map +1 -1
  64. package/dist/session/login-guard.d.ts +4 -4
  65. package/dist/session/login-guard.d.ts.map +1 -1
  66. package/dist/session/login-guard.js +29 -14
  67. package/dist/session/login-guard.js.map +1 -1
  68. package/dist/session/session-setup.d.ts +14 -5
  69. package/dist/session/session-setup.d.ts.map +1 -1
  70. package/dist/session/session-setup.js +30 -9
  71. package/dist/session/session-setup.js.map +1 -1
  72. package/dist/session/session-state.d.ts +4 -3
  73. package/dist/session/session-state.d.ts.map +1 -1
  74. package/dist/session/session-state.js +28 -9
  75. package/dist/session/session-state.js.map +1 -1
  76. package/dist/session/signed-dialog.d.ts +19 -13
  77. package/dist/session/signed-dialog.d.ts.map +1 -1
  78. package/dist/session/signed-dialog.js +45 -30
  79. package/dist/session/signed-dialog.js.map +1 -1
  80. package/dist/session/user-prompts.d.ts +6 -1
  81. package/dist/session/user-prompts.d.ts.map +1 -1
  82. package/dist/session/user-prompts.js +19 -4
  83. package/dist/session/user-prompts.js.map +1 -1
  84. package/dist/tan/challenge.d.ts +15 -13
  85. package/dist/tan/challenge.d.ts.map +1 -1
  86. package/dist/tan/challenge.js +32 -26
  87. package/dist/tan/challenge.js.map +1 -1
  88. package/dist/tan/flicker.d.ts +5 -1
  89. package/dist/tan/flicker.d.ts.map +1 -1
  90. package/dist/tan/flicker.js +42 -30
  91. package/dist/tan/flicker.js.map +1 -1
  92. package/dist/tan/tan-image.d.ts +4 -1
  93. package/dist/tan/tan-image.d.ts.map +1 -1
  94. package/dist/tan/tan-image.js +8 -2
  95. package/dist/tan/tan-image.js.map +1 -1
  96. package/dist/tan/tan-models.d.ts +9 -1
  97. package/dist/tan/tan-models.d.ts.map +1 -1
  98. package/dist/tan/tan-models.js +14 -2
  99. package/dist/tan/tan-models.js.map +1 -1
  100. package/dist/util/arguments.d.ts +13 -0
  101. package/dist/util/arguments.d.ts.map +1 -0
  102. package/dist/util/arguments.js +40 -0
  103. package/dist/util/arguments.js.map +1 -0
  104. package/dist/util/async.d.ts +4 -1
  105. package/dist/util/async.d.ts.map +1 -1
  106. package/dist/util/async.js +6 -3
  107. package/dist/util/async.js.map +1 -1
  108. package/dist/util/bytes.d.ts +1 -1
  109. package/dist/util/bytes.d.ts.map +1 -1
  110. package/dist/util/bytes.js +3 -3
  111. package/dist/util/bytes.js.map +1 -1
  112. package/dist/util/dates.d.ts +3 -10
  113. package/dist/util/dates.d.ts.map +1 -1
  114. package/dist/util/dates.js +49 -39
  115. package/dist/util/dates.js.map +1 -1
  116. package/dist/util/errors.d.ts +5 -3
  117. package/dist/util/errors.d.ts.map +1 -1
  118. package/dist/util/errors.js +8 -4
  119. package/dist/util/errors.js.map +1 -1
  120. package/dist/util/immutable-map.d.ts +5 -2
  121. package/dist/util/immutable-map.d.ts.map +1 -1
  122. package/dist/util/immutable-map.js +15 -12
  123. package/dist/util/immutable-map.js.map +1 -1
  124. package/dist/util/inspect.d.ts +11 -0
  125. package/dist/util/inspect.d.ts.map +1 -0
  126. package/dist/util/inspect.js +14 -0
  127. package/dist/util/inspect.js.map +1 -0
  128. package/dist/util/text.d.ts +18 -2
  129. package/dist/util/text.d.ts.map +1 -1
  130. package/dist/util/text.js +50 -5
  131. package/dist/util/text.js.map +1 -1
  132. package/dist/util/xml.d.ts +6 -4
  133. package/dist/util/xml.d.ts.map +1 -1
  134. package/dist/util/xml.js +106 -44
  135. package/dist/util/xml.js.map +1 -1
  136. package/dist/v3/bank-connection.d.ts +23 -3
  137. package/dist/v3/bank-connection.d.ts.map +1 -1
  138. package/dist/v3/bank-connection.js +66 -6
  139. package/dist/v3/bank-connection.js.map +1 -1
  140. package/dist/v3/dialog.d.ts +6 -2
  141. package/dist/v3/dialog.d.ts.map +1 -1
  142. package/dist/v3/dialog.js +20 -9
  143. package/dist/v3/dialog.js.map +1 -1
  144. package/dist/v3/message.d.ts +18 -10
  145. package/dist/v3/message.d.ts.map +1 -1
  146. package/dist/v3/message.js +92 -75
  147. package/dist/v3/message.js.map +1 -1
  148. package/dist/v3/parameter-store.d.ts +43 -7
  149. package/dist/v3/parameter-store.d.ts.map +1 -1
  150. package/dist/v3/parameter-store.js +69 -15
  151. package/dist/v3/parameter-store.js.map +1 -1
  152. package/dist/v3/parameters.d.ts +12 -11
  153. package/dist/v3/parameters.d.ts.map +1 -1
  154. package/dist/v3/parameters.js +57 -35
  155. package/dist/v3/parameters.js.map +1 -1
  156. package/dist/v3/protocol.d.ts +16 -9
  157. package/dist/v3/protocol.d.ts.map +1 -1
  158. package/dist/v3/protocol.js +57 -26
  159. package/dist/v3/protocol.js.map +1 -1
  160. package/dist/v3/queries.d.ts +2 -2
  161. package/dist/v3/queries.d.ts.map +1 -1
  162. package/dist/v3/queries.js +24 -16
  163. package/dist/v3/queries.js.map +1 -1
  164. package/dist/v3/segments.d.ts +18 -11
  165. package/dist/v3/segments.d.ts.map +1 -1
  166. package/dist/v3/segments.js +30 -21
  167. package/dist/v3/segments.js.map +1 -1
  168. package/dist/v3/syntax.d.ts +15 -11
  169. package/dist/v3/syntax.d.ts.map +1 -1
  170. package/dist/v3/syntax.js +30 -15
  171. package/dist/v3/syntax.js.map +1 -1
  172. package/package.json +1 -1
  173. package/src/client.ts +58 -17
  174. package/src/core/account.ts +91 -35
  175. package/src/core/amount.ts +42 -9
  176. package/src/core/balance.ts +6 -0
  177. package/src/core/bank-info.ts +28 -4
  178. package/src/core/client.ts +42 -15
  179. package/src/core/config.ts +37 -18
  180. package/src/core/errors.ts +27 -7
  181. package/src/core/feedback.ts +12 -1
  182. package/src/core/transaction.ts +13 -0
  183. package/src/core/transport.ts +62 -16
  184. package/src/formats/camt.ts +62 -51
  185. package/src/formats/mt940.ts +114 -78
  186. package/src/formats/readers.ts +6 -2
  187. package/src/formats/sepa-purpose.ts +17 -9
  188. package/src/session/dialog-runner.ts +34 -7
  189. package/src/session/login-guard.ts +29 -13
  190. package/src/session/session-setup.ts +27 -9
  191. package/src/session/session-state.ts +25 -8
  192. package/src/session/signed-dialog.ts +60 -30
  193. package/src/session/user-prompts.ts +28 -3
  194. package/src/tan/challenge.ts +50 -36
  195. package/src/tan/flicker.ts +40 -29
  196. package/src/tan/tan-image.ts +9 -2
  197. package/src/tan/tan-models.ts +16 -2
  198. package/src/util/arguments.ts +40 -0
  199. package/src/util/async.ts +10 -4
  200. package/src/util/bytes.ts +3 -3
  201. package/src/util/dates.ts +52 -38
  202. package/src/util/errors.ts +9 -4
  203. package/src/util/immutable-map.ts +15 -12
  204. package/src/util/inspect.ts +14 -0
  205. package/src/util/text.ts +47 -5
  206. package/src/util/xml.ts +105 -38
  207. package/src/v3/bank-connection.ts +65 -6
  208. package/src/v3/dialog.ts +18 -8
  209. package/src/v3/message.ts +81 -72
  210. package/src/v3/parameter-store.ts +82 -16
  211. package/src/v3/parameters.ts +68 -41
  212. package/src/v3/protocol.ts +61 -28
  213. package/src/v3/queries.ts +26 -18
  214. package/src/v3/segments.ts +32 -20
  215. package/src/v3/syntax.ts +31 -14
package/CHANGELOG.md CHANGED
@@ -1,3 +1,380 @@
1
+ ## 0.3.0
2
+
3
+ An app may notice these changes: arguments and options of another type than declared, such as a number as `bankCode`
4
+ or a `state` that is no string, and a user ID or customer ID of more than 30 characters fail with a `TypeError` before
5
+ the bank is contacted; the client reads its options once; `close` waits up to five seconds for the request on its way,
6
+ so the call it ends fails with `FinTsStateError` where the answer comes in time; a TAN goes out as the user typed it;
7
+ `console.log` shows the models of the package only by what identifies no person, account or TAN, and `toString` of
8
+ `FinTsFeedback` and `BankMessage` leaves out the bank's texts; `feedback` and `source` of an error are getters; and more
9
+ malformed bank data fail with `FinTsProtocolError`. Where these notes compare with the Dart package, they mean its
10
+ source after its release 0.2.0, unless they name 0.2.0, which differs in more ways: it keeps no refused login in the
11
+ saved state.
12
+
13
+ ### Breaking
14
+
15
+ - `getTransactions` rejects a `format` other than `'auto'`, `'mt940'` and `'camt'` with a `TypeError`, before it waits
16
+ for other calls or contacts the bank: one such as `'CAMT'` asked for MT940, and `null` meant `'auto'`, as `undefined`
17
+ still does.
18
+ - `getBalance` and `getTransactions` reject anything but an `Account`, such as one written to JSON and read back, with a
19
+ `TypeError` before they wait for other calls or contact the bank. They logged in, fetched the SEPA account list, and
20
+ then failed with an unrelated `TypeError`.
21
+ - `selectTanMethod` rejects a TAN method or medium that is no string, and a missing method, with a `TypeError` before it
22
+ ends the dialog. A number as the method was saved and then dropped at the next login, which asked `tanMethodSelector`
23
+ instead; a medium of another type went out as its text or as a data element group, which the bank refused as a login;
24
+ and a state saved in between could not be restored.
25
+ - `FinTs3Client` throws a `TypeError` for options of another type, before contacting the bank: a missing `tanCallback`
26
+ or one that is no function, another callback that is neither a function nor `undefined` or `null`, a `state` that is
27
+ neither a string nor `undefined` or `null`, such as the parsed JSON a store hands back, and a `transport` without the
28
+ functions `post` and `close`. A callback that was no function failed the call after its messages had gone out,
29
+ `tanCallback` at the first TAN challenge after the PIN-signed login; a `state` that was no string was ignored; and a
30
+ transport without `close` made `close` reject.
31
+ - The client reads its options once, when it is built, as the Dart package does: assigning another `tanCallback`,
32
+ selector, `onBankMessage` or `onWarning` to the options object afterwards no longer changes what the client calls.
33
+ - `Account`, `Amount` and `FinTsConfig` throw a `TypeError` ('Invalid argument (<name>): …', without the value) for an
34
+ argument of another type than declared, as the Dart package's types refuse it: such as a number as `bankCode`,
35
+ `accountNumber`, `iban`, `currency` or `customerId`, a missing `bankCode`, a string as `minorUnits`, `scale` or
36
+ `typeCode`, anything but an array of strings as `allowedTransactions`, anything but a boolean as `allowInsecureUrl`,
37
+ and anything but an `AccountDataStatus` as `dataStatus`. `Amount.parseDecimal` does for a `value` or `currency` that
38
+ is no string. Such an account passed the constructor, threw in `toString`, and failed `getBalance` and
39
+ `getTransactions` only after the login, or matched none of the bank's accounts, so that the query failed with
40
+ `FinTsUnsupportedError` or went out without the bank's IBAN and BIC. A `dataStatus` written to JSON and read back
41
+ failed with an unrelated `TypeError` ('object is not iterable'), and a string counted as an empty status. An amount
42
+ kept a number as its currency, and a string as `minorUnits` threw a `RangeError` that showed it as a valid value.
43
+ - `FinTsConfig` throws a `TypeError` for a `userId` or `customerId` longer than 30 characters, the length FinTS 3.0
44
+ gives them (Formals, data type `id`). The bank refused such a login with a syntax error, which the client counted as
45
+ a refused login, so it sent the PIN no more.
46
+ - A `FinTsConfig` is frozen once its constructor has checked it: assigning to a field throws a `TypeError` in
47
+ strict-mode code and does nothing otherwise, and a subclass cannot add fields.
48
+ - As in the Dart package, `Mt940.parse` and `Mt940.parseTransactions` throw a `TypeError` for a `status` other than
49
+ `'booked'` and `'pending'`, `null` included, which meant `'booked'`, and for a `currency` that is no string;
50
+ `Camt.parse` and `Camt.parseTransactions` for a status other than these, `undefined` and `null`. Such a status became
51
+ the transactions' `status`, and such a currency that of their amounts. `SepaPurpose.parseParts` throws a `TypeError`
52
+ for a string, whose characters it took for parts, and for parts that are no strings.
53
+ - camt documents are read as XML 1.0 has them, failing `getTransactions` with `FinTsProtocolError` and `Camt.parse` with
54
+ `FormatError` for a name that is no XML name, such as `<1x>`, an end tag with another namespace prefix than its start
55
+ tag, a second XML declaration or one inside or after the root element, a processing instruction without a target, and
56
+ an attribute stated twice.
57
+ - camt: a `Dt` that holds a date and time is read like a `DtTm`. One whose time the client cannot read, such as
58
+ `2024-03-15T25:00:00` or `2024-03-15T10:00`, fails `getTransactions` with `FinTsProtocolError` and `Camt.parse` with
59
+ `FormatError` instead of counting on the date written. So do a UTC offset beyond ±14:00, such as `+15:00`, which XML
60
+ Schema does not allow and which the client used to read as an offset, and `24:00:00` with a fraction of a second other
61
+ than zero.
62
+ - `BankInfo.maxMessageSize`, `minTimeout` and `maxTimeout` are `undefined` for a value above 9999, the four digits HIBPA
63
+ allows, as in the Dart package. The client then sends messages of any size and starts a new dialog after 5 idle
64
+ minutes. `TanChallenge.plainMessage` leaves a structured challenge of more than 65,536 characters as the bank sent it,
65
+ as the Dart package does.
66
+ - `close` no longer aborts a request on its way at once: it waits up to five seconds for the answer and only then aborts
67
+ it, and the dialog ends then have five seconds once that request is over, so `close` may take about ten seconds where
68
+ the bank has stopped answering. Where the answer comes in time, the running call fails with `FinTsStateError` instead
69
+ of `FinTsTransportError`, also where that answer completes it, unless the answer brings an error of its own, such as a
70
+ rejected PIN, which it fails with instead; a request aborted after the five seconds still fails it with
71
+ `FinTsTransportError`. A call whose last answer arrives after `close`, within those five seconds, fails with
72
+ `FinTsStateError` instead of returning its result.
73
+ - A TAN goes out as the user typed it, except for full-width characters, which count as their ASCII counterparts, and
74
+ the ideographic space U+3000, which counts as a blank, as in the Dart package. Unicode NFKC changed it before:
75
+ superscript, circled and mathematical digits counted as digits, and characters that ISO 8859-1 holds went out altered,
76
+ such as `²` as `2` or a no-break space inside the TAN as a space, which the bank counts as a failed attempt; `µ`, `½`
77
+ and the like failed with a `TypeError` as not sendable. Other characters outside ISO 8859-1, and digits other than 0
78
+ to 9 for a `numericTan` method, fail the call with a `TypeError` before it is sent.
79
+ - `FinTsFeedback.toString` gives the code only, such as `FinTsFeedback(3072)`, and `BankMessage.toString` gives
80
+ `BankMessage(…)`, as `console.log` showed them already. So `String(f)`, template strings and loggers that call
81
+ `toString` no longer collect the bank's texts and parameters, which with return code 3072 hold the new login data, or
82
+ a message that may address the user by name. To show them to the user, use `code`, `text` and `parameters`, or
83
+ `subject` and `text`. The message of a `FinTsBankError` stays as it was, with the code, text and parameters of each
84
+ error, such as `9010: Konto gesperrt [DE89370400440532013000]`.
85
+ - `FinTsBankError.feedback` and `FormatError.source` are getters instead of own properties of the error, so
86
+ `Object.keys`, object spread and `JSON.stringify` of the error leave them out.
87
+
88
+ ### Security
89
+
90
+ - Plain JavaScript can no longer change a `FinTsConfig` once checked, which could send the PIN to an http URL, or login
91
+ data the constructor had not checked, to the bank.
92
+ - `FinTsConfig` refuses an `allowInsecureUrl` that is no boolean, such as the string `'false'` read from an environment
93
+ variable, with a `TypeError`: it allowed plain http to a loopback host, where the PIN passes any proxy the runtime's
94
+ `fetch` takes from the environment.
95
+ - `console.log` and `util.inspect` on Node.js, Deno and Bun show the objects of this package only by what identifies no
96
+ person, account or TAN. An `Account`, `Balance`, `Transaction`, `TanMethod` or `TanMedium` shows as its `toString`, a
97
+ `TanChallenge` by its method, kind and attempt, a `FinTsFeedback` by its code and a `TanAnswer` by its kind.
98
+ `AccountDataStatus`, `BankInfo`, `FinTsConfig`, `FlickerCode` and `TanImage` show codes, versions and sizes only, the
99
+ statements of `Mt940.parse` and `Camt.parse` the last digits of the account and the number of transactions, and a
100
+ `BankMessage` shows as `BankMessage(…)`. Until now they showed every field: the bank's texts, references and
101
+ parameters (with return code 3072 the new login data), TAN media names, the TAN, the challenge data and image, the
102
+ login name and product ID, and a statement's full account. An error shows its message and stack, and a
103
+ `FinTsTransportError` its `statusCode`; the message of a `FinTsBankError` holds the texts and parameters of its
104
+ errors, as before. `JSON.stringify` and the browser console still show every field.
105
+ - Node.js's report of an uncaught error ignores what `console.log` shows of an object. It printed the whole `feedback`
106
+ of a `FinTsBankError`, including the texts, references and parameters of its successes and warnings and, with return
107
+ code 3072, the new login data, as well as the `source` of a `FormatError`. `JSON.stringify` of such an error, and
108
+ loggers that copy an error's fields, got them too. Both are getters now, so an uncaught `FinTsBankError` or
109
+ `FormatError` prints its message and stack only, as in the Dart package, and `JSON.stringify` leaves them out.
110
+ - `TanMedium.toString` leaves out the medium's name, which the user may have chosen after themselves, such as
111
+ `Max’s iPhone`: it gives `TanMedium(M, active)`, or, for a card, the last digits of its number,
112
+ `TanMedium(…2014, active)`, as before for a card without a name.
113
+ - A PIN that begins or ends with U+0085 (NEL) fails with a `TypeError` before it is sent, like one with other blanks at
114
+ either end. It went out with the byte 0x85, which the bank could count as a failed attempt.
115
+ - New login data (3072) next to an error in the answer to the login's TAN or app approval, such as 9941 for a wrong TAN,
116
+ an aborted dialog (9800) or any other error but a rejected PIN, end the old ones as well, as in the Dart package: the
117
+ call fails with `FinTsLoginDataChangedError`, the client ends the dialog where the bank keeps it open, and no later
118
+ call logs in with the old data. Until now the call failed with `FinTsTanError`, `FinTsDialogAbortedError` or
119
+ `FinTsBankError`, and the next call logged in again with the old login data and the PIN, which the bank may count as a
120
+ failed attempt, and asked for another TAN.
121
+ - A refused login, and the count of logins whose answer was lost, are restored from a saved state whose owner names the
122
+ server by something other than a string, such as a number or `null`, as in the Dart package; the rest of such a state
123
+ is dropped, as for another server. Until now the whole state was dropped, and the next call sent the PIN the bank had
124
+ refused.
125
+ - A `state` that is no string, such as the parsed JSON a json column returns, fails the `FinTs3Client` constructor with
126
+ a `TypeError`: the client ignored it, and with it a refused login it held, and sent the PIN again.
127
+ - A refused login stays refused where the `loginGuard` section of a saved state for the same user is damaged, such as a
128
+ return code without its code or `unreadLogins` that is no integer: the client restores a `FinTsLoginRefusedError`
129
+ without return codes and sends the PIN no more until `clearLoginRefusal`. Such damage dropped the refusal, and the
130
+ next call sent the refused PIN again. A state without the section, as fints-ts 0.1 and the Dart package 0.2.0 save
131
+ it, restores as before.
132
+ - `HttpTransport` copies each part of an answer as it arrives, as the Dart package does. An answer within
133
+ `maxResponseBytes` that a server sent in one-byte parts took gigabytes of memory and could end a Node.js process with
134
+ an out-of-memory error, which the app cannot catch. A `fetch` that refills the part it handed out corrupted the
135
+ answer.
136
+ - The documentation of `HttpTransport` and `allowInsecureUrl` warns that plain http to a local server goes wherever the
137
+ global `fetch` sends it, as https does. Whatever replaces `globalThis.fetch`, such as a test's stub, and on Node.js a
138
+ global dispatcher get every message with the PIN. A proxy that the runtime's `fetch` takes from the environment, as
139
+ Node.js does with `NODE_USE_ENV_PROXY=1`, and Deno and Bun do from `HTTP_PROXY`, sees the PIN unencrypted: exempt the
140
+ host with `NO_PROXY` or pass a `fetch` that connects directly. The Dart package connects to the loopback host
141
+ directly, also bypassing a client set with `runWithClient`.
142
+ - The limits on bank data are those of the Dart package. The following fail the call with `FinTsProtocolError`, and
143
+ `Mt940.parse`, `Camt.parse` and `Amount.parseDecimal` with `FormatError`: an answer with more than 1,000,000 data
144
+ elements and segments in its plain or in its encrypted part, or with more than one encrypted part (HNVSD); XML nested
145
+ more than 100 levels deep, an empty element such as `<a/>` included, or with more than 500,000 nodes besides text or
146
+ one per 16 characters, whichever is more; an MT940 field of more than 65,536 characters; and an amount written with
147
+ more than 64 characters. The limits were 200,000 data elements per answer, the encrypted part included, and XML of 256
148
+ levels or 1,000,000 elements and attributes; fields and amounts had none. A hostile answer can now take up to about
149
+ 210 MB of memory while it is read, instead of about 25 MB. The 128 MiB a query may hold count each data element its
150
+ pages keep as 512 bytes instead of 256, and no longer count the elements of the encrypted part's envelope, so a query
151
+ of many short elements stops after half as many. The 200,000 statements and transactions per query stay.
152
+ - XML text or an attribute value of megabytes of `&`, or of unknown entities such as `&a;`, fails at its first bad
153
+ reference. The parser first found every reference, which took more than a second and about a gigabyte of memory for
154
+ 12 MB, beyond the 210 MB the limits allow.
155
+ - camt: remittance information (`Ustrd`) of more than 65,536 characters, as for an MT940 field, fails
156
+ `getTransactions` with `FinTsProtocolError` and `Camt.parse` with `FormatError`. Megabytes of SEPA keywords took
157
+ about 40 times their size in memory.
158
+ - camt: blanks alone between elements, such as the line breaks of an indented document, are no longer kept as text,
159
+ which halves the memory such a document takes while it is read.
160
+ - MT940: the counterparty's IBAN and BIC come from the subfields `?31` and `?30` only, where the bank states them. A
161
+ payer could write `IBAN+…` and `BIC+…` into the end-to-end reference, and these filled `counterpartyIban` and
162
+ `counterpartyBic` where the subfields stated none. Both values stay readable in `purposeCodes`.
163
+ - In a browser, `HttpTransport` sends no cookies and no referrer with a message, and the browser's cache keeps no copy
164
+ of the exchange (`credentials: 'omit'`, `referrerPolicy: 'no-referrer'`, `cache: 'no-store'`). With the defaults,
165
+ cookies of the page and its URL as referrer went along to a server on the page's own origin.
166
+ - The relay server of the README refuses a body without `Content-Length` or of more than 1 MiB before reading it:
167
+ update relays built from it. The documentation of `FinTsTransport.post` says that the client bounds neither the time
168
+ of the exchange nor the size of the answer, so a transport of the app's own has to, as `HttpTransport` does.
169
+
170
+ ### Fixed
171
+
172
+ - `close` ends the dialog of a request it aborts after five seconds, unless that request opened the dialog, whose ID
173
+ only the answer tells: the end goes out after the abort, under the next message number, since the bank may have
174
+ received the aborted message. Until now the abort dropped the dialog without an end, and the bank kept it open until
175
+ its timeout. The answer to that end refuses the login only for a rejected PIN, as at any dialog end, not for an error
176
+ such as a message number the bank did not expect because the aborted message never reached it.
177
+ - `close` gives the dialog ends five seconds once the request on its way, if any, is over: then the client stops
178
+ waiting for them, a transport that takes the abort, as `HttpTransport` does, stops them, and none goes out later. A
179
+ bank that has stopped answering held up `close` for the end of an open dialog until the transport's timeout, 90
180
+ seconds by default.
181
+ - The answer to a request on its way when `close` is called counts as any other, as in the Dart package: the logger gets
182
+ it, and the saved state keeps the bank and user parameter data, the customer system ID, the TAN medium and the SEPA
183
+ account list it brings. `close` aborted it at once, so a client restored from the state repeated the anonymous dialog,
184
+ the synchronization, which has the bank register another customer system ID, the lookup of the TAN media or the SEPA
185
+ account list, and its next login reported user parameter data version 0. The dialog of that request is ended as well,
186
+ also where the request opened it, whose ID only the answer tells, and a login answered so no longer counts as one
187
+ whose answer was lost.
188
+ - A dialog end that `close` keeps from going out, once the time after the request on its way is up, no longer reaches
189
+ the logger as an outgoing message, as in the Dart package: the logger showed a dialog end the bank never received.
190
+ - A TAN medium name the app passes to `selectTanMethod`, also from the saved state, goes out without blanks at either
191
+ end (Formals B.4.1), and one of blanks only as no name, as in the Dart package: a bank that knows the medium without
192
+ them refused the login, and the state kept the refusal. `selectedTanMedium` and the state keep the name as the app
193
+ passed it. A name the bank issued still goes back as the bank sent it, blanks at either end included, and the medium
194
+ in use of a state saved by 0.2.0, which does not tell the two apart, as 0.2.0 sent it.
195
+ - `selectTanMethod(method, null)` leaves the TAN medium to the next login, which looks the media up where the method
196
+ needs one, as without a medium and as in the Dart package. The login went out without a medium, which the bank
197
+ refused, and the client kept the refusal until `clearLoginRefusal`.
198
+ - A `tanMethodSelector` or `tanMediumSelector` that returns `null` picks the first method or medium, as for `undefined`,
199
+ instead of failing the call with a `TypeError`; the selector types allow `null`.
200
+ - `null` for `logger`, `onBankMessage`, `onWarning`, `tanMethodSelector`, `tanMediumSelector` or `state` means none, as
201
+ in the Dart package. `logger: null` failed every call that exchanged messages, and `close`, after their traffic;
202
+ `onBankMessage: null` and `onWarning: null` failed a call whose answer carried a bank message or a warning; and a
203
+ `null` selector failed the call after the PIN-signed synchronization or the lookup of the TAN media.
204
+ - `null` counts as not given for the details of an `Account`, whose `toString` threw for an `accountNumber` of `null`,
205
+ for the `customerId` of `FinTsConfig`, which is then the user ID, where a `TypeError` without the package's message
206
+ was thrown, and for the `currency` of `Mt940.parse`, which gave amounts in the currency `null` instead of the
207
+ `FormatError` 'MT940 statement without currency', as in the Dart package.
208
+ - Where the bank parameter data rule out a login, by the PIN's length or the message's size, the client fetches them
209
+ anew in an anonymous dialog, which carries no PIN, and tries the login once more, which goes out only if the new
210
+ limits allow it; where they rule it out as well, the call fails with the error that names the new limits, as in the
211
+ Dart package. Until now outdated parameter data, as saved ones may be, failed every call that logs in with a
212
+ `TypeError` (`FinTsProtocolError` for the size) until the app dropped `client.state`. It takes only the limits from
213
+ the anonymous answer, and only from parameter data of another version than those kept, the PIN's length only where
214
+ they state it. Those limits serve until the login brings the user's parameter data, which the login asks for by
215
+ reporting version 0, so the login the new limits judge may be up to two bytes shorter than the one the old ones
216
+ stopped. The anonymous parameter data never replace the user's. A bank that does not answer anonymously cannot refresh
217
+ them; the call then fails as before, without sending the PIN.
218
+ - A SEPA account list saved empty, as fints-ts 0.1 and the Dart package 0.2.0 saved it, is ignored on restore, as the
219
+ Dart package does after 0.2.0, so the client asks the bank for it again. Until the bank changed its user parameter
220
+ data, such a state kept the client from asking: `getAccounts` returned the accounts without their BIC, and an account
221
+ the app built from its IBAN went out without one, which a bank may refuse, or failed with `FinTsUnsupportedError`
222
+ where the user parameter data name the account by its number only.
223
+ - `TanChallenge.tanMediumName` is `undefined` for a challenge in the dialog that looks up the TAN media, or where the
224
+ bank echoes it, instead of the placeholder `noref`.
225
+ - The client no longer keeps the result of its last call, such as the transactions, until its next call or `close`.
226
+ - `console.log` and `util.inspect` show an `Amount` as its `toString`, such as `-123.45 EUR`, as the Dart package prints
227
+ it, instead of its fields `minorUnits`, `scale` and `currency`.
228
+ - An answer that is no FinTS message at all, such as the web page of a proxy or a maintenance notice, fails with the
229
+ `FinTsProtocolError` 'Malformed bank data: Not a FinTS message: no message header' instead of 'Malformed bank data:
230
+ Segment without terminator'. A message that starts with its header and is cut short still reports what is wrong with
231
+ it.
232
+ - `HttpTransport` refuses an answer whose `Content-Length` announces more than `maxResponseBytes` at once, without
233
+ reading it, with the same `FinTsTransportError` as for a body over the limit, as the Dart package does. A server that
234
+ announced more and then stalled held the call until the timeout, 90 seconds by default. One that sent the whole answer
235
+ had it read up to the limit before the refusal.
236
+ - `getTransactions` reads statement data that a bank sends as text instead of binary data, as it did for booked camt
237
+ documents: booked and pending MT940 (HIKAZ) and the pending camt document (HICAZ) were dropped without an error. A
238
+ malformed or overlong binary length still fails the call with `FinTsProtocolError`.
239
+ - `TransactionFormat` `auto` takes camt.052 where the bank offers it and MT940 cannot be requested for the account (the
240
+ bank offers no `HKKAZ` version the client implements, or PIN/TAN does not permit it), instead of failing with
241
+ `FinTsUnsupportedError`.
242
+ - A FinTS 3.0 time `240000` is midnight of the next day. So `Balance.bookedAsOfTime`, `Balance.bookingTime` and
243
+ `TanChallenge.validUntil` are set for it instead of `undefined`, and the account extension of the user parameter data
244
+ is read with it instead of being dropped as malformed, BIC, balance and data status included. `bookedAsOf` and
245
+ `bookingDate` stay the stated day.
246
+ - camt: a booking or value date given as `DtTm` without a `T` after the date, such as `2023-12-31`,
247
+ `2024-01-02 10:00:00` or `2024-01-02t10:00:00`, is the date written, as in the Dart package, instead of failing
248
+ `getTransactions` with `FinTsProtocolError`. Whatever follows the date is not read then. A `DtTm` or `Dt` with the
249
+ time `24:00:00` is midnight of the next day, as a FinTS 3.0 time `240000` is. A `Dt` with date and time counts on its
250
+ German day, as a `DtTm` does: `2024-03-15T23:30:00Z` is 16 March.
251
+ - MT940 and camt: `SVWZ+` after `ABWA+` or `ABWE+`, the order some banks write them in, starts the remittance
252
+ information, which went into the ultimate debtor or creditor, leaving the whole text, keywords and all, as the
253
+ purpose. Any other keyword in that text still starts no value, and one that occurs twice still has none.
254
+ - MT940: `getTransactions` joins pages with `@@` where none of them holds a line break. After a first page without `@@`,
255
+ such as `:20:X`, the `@@` of the other pages were read as text, and the query returned no transactions without an
256
+ error.
257
+ - camt: a comment ends only after its `<!--`, so that `<!-->` starts one instead of closing at once, which turned the
258
+ comment into text; an attribute counts by its name, namespace prefix and all, so that `q:Ccy` is not the currency of
259
+ an amount; an element named with a leading colon, such as `<:Ntry>`, is not the unprefixed one.
260
+ - The `EREF`, `MREF` and `CRED` of camt `purposeCodes` keep the place the remittance text gives them, before `SVWZ` as
261
+ banks write them, instead of following all other keywords. Their values still come from their elements only.
262
+ - MT940: the booking date of an MT942 lies between its value date and the creation date `:13D:`, which the statement's
263
+ balances otherwise bound, instead of in the year closest to the value date: a booking `1230` with value date
264
+ 2023-06-15 in an MT942 created on 2024-01-02 is dated 2023-12-30, not 2022-12-30.
265
+ - MT940: a statement whose closing balance is dated before its opening balance, or whose creation date `:13D:` cannot be
266
+ read or is stated twice, is a `FormatError` (`FinTsProtocolError` in `getTransactions`) instead of being read.
267
+ - MT940: a statement that balances is read also where a sum on the way lies beyond what an `Amount` holds (about 90
268
+ trillion at two decimal places, less with more decimals), as camt batch bookings are. It failed `getTransactions` with
269
+ `FinTsProtocolError` 'Malformed bank data: MT940 amounts too large to add up', and `Mt940.parse` with `FormatError`. A
270
+ statement that does not balance fails with 'MT940 transactions do not add up to the closing balance' however large its
271
+ amounts, as in the Dart package.
272
+ - MT940: the subfield that ends a `:86:` field keeps the blanks at its end, as in the Dart package and before 0.2.0.
273
+ Where the bank writes it before subfields it continues, such as `?33Mustermann?32Max `, the purpose, the
274
+ counterparty's name and the SEPA keywords are joined with that blank (`Max Mustermann`, not `MaxMustermann`). Only the
275
+ statement end and line feeds are dropped from the end of a field.
276
+ - camt: a batch booking whose details add up to it is split also where their sum on the way lies beyond what an `Amount`
277
+ holds (about 90 trillion at two decimal places), whatever the order of the details; such a batch stayed one booking.
278
+ - U+0085 (NEL), the byte 0x85 in ISO 8859-1 text, counts as a blank wherever the client drops the blanks around a value,
279
+ as Unicode and the Dart package have it. That covers what it reads: MT940, SEPA purpose and camt values (a camt `Sts`
280
+ of `PDNG` followed by it reads as pending, not booked), the parameter data, such as the account owner, a segment's
281
+ type and numbers, amounts and challenge texts; and what it sends: segment values, the login details of `FinTsConfig`
282
+ and the TAN the user enters. The IBAN and BIC of an `Account`, which go without any blanks, drop it as well.
283
+ - camt: U+0085 (NEL), the byte 0x85 in ISO 8859-1, counts as white space between the parts of a start or end tag and
284
+ between the nodes outside the root element, as other Unicode white space did and as in the Dart package. Such a
285
+ document failed `getTransactions` with `FinTsProtocolError` and `Camt.parse` with `FormatError`.
286
+ - Bank text read as UTF-8 that starts with two byte order marks loses both, as in the Dart package; the second stayed as
287
+ U+FEFF in data element texts and the message log, and on an MT940 page after the first it failed `getTransactions`
288
+ with `FinTsProtocolError`.
289
+ - The security control reference that ties the signature head (HNSHK) to the signature end (HNSHA) is drawn uniformly:
290
+ its first nine digits up to 794967295 came up a quarter more often than the rest.
291
+ - A number in the parameter data beyond 9007199254740991 (2^53 - 1), which a JavaScript number holds only approximately,
292
+ counts as not stated, as a malformed one does. Such a version of the bank or user parameter data went back to the bank
293
+ as `100000000000000000000` or `1e+22` instead of `0`. A minimum PIN length of that size failed every login with a
294
+ `TypeError`. Polling parameters of that size let a decoupled TAN method poll every 60 seconds with no limit on the
295
+ number of requests other than the 10 minutes, instead of the defaults. An account type of that size made `category`
296
+ `'other'` instead of `'unknown'`.
297
+ - A number in the parameter data written `-0`, such as a version or an account type, reads as 0, as in the Dart package,
298
+ not as JavaScript's negative zero, which `util.inspect` shows as `-0` and `Object.is` tells apart from 0.
299
+ - An account in the user parameter data whose national account has the older layout without sub-account (number, country
300
+ and bank code, such as `1234567890:280:12345678`) keeps its country and bank code, as in the Dart package. It got the
301
+ sub-account `280`, the country code `12345678` and the configured bank code, so queries for it named an account the
302
+ bank does not know. Three elements whose last is a country code, such as `1234567890:01:280` or `987654321::040`,
303
+ still read as number, sub-account and country code: a country code has three digits, a German bank code eight.
304
+ - `getAccounts` completes an account of the user parameter data with the IBAN and BIC of the SEPA account list also
305
+ where one of the two states no bank or country code, which matches any, as in `Account.matches` and the Dart package.
306
+ This happens with a saved state edited so, or where a code the bank sent was only a byte order mark. The list entry
307
+ came out as an account of its own, and an account the app built from its IBAN went out as that entry, without bank
308
+ code, permitted transactions and currency.
309
+ - Up to three further pages of a query may ask for an approval, as its first page may: the client asks `tanCallback`, or
310
+ waits for the app approval, as for the first page. Until now only the first page could, and a bank that asked again
311
+ for a later page failed every such query with `FinTsProtocolError`, without asking the user. Where a fourth further
312
+ page of the same query asks for an approval, the call still fails with `FinTsProtocolError`, before the user is asked,
313
+ and the dialog ends. A query thus asks the user four times at most, and an approval the user cancels still fails the
314
+ call with `FinTsTanCancelledError` and loses the pages fetched so far. The Dart package 0.2.0 asks for every page, up
315
+ to 1000.
316
+ - `close` no longer waits for a transport that ignores the abort: five seconds after `close`, the request on its way
317
+ fails with a `FinTsTransportError` whatever the transport, and the dialog ends then have five seconds of their own.
318
+ A custom transport that ignored the `AbortSignal` and had no timeout of its own held up the call, and with it
319
+ `close`, forever.
320
+ - A network error whose cause has no message of its own, as Node.js reports a refused or failed connection to a host
321
+ with both IPv6 and IPv4 addresses, gives the reasons of the attempts or the error code, such as
322
+ `fetch failed: connect ECONNREFUSED ::1:3000; connect ECONNREFUSED 127.0.0.1:3000`, instead of just `fetch failed: `.
323
+ - The bank's dialog abort message (Formals B.7.6) fails the call with a `FinTsBankError` that carries the bank's
324
+ return codes and text, and ends the dialog. The bank sends it unencrypted, with the dialog ID `unbekannt` and message
325
+ number 9999, where it cannot tell them, such as for a broken message header, an unknown bank code or a bank locked
326
+ for a release. Such an answer to any message but the first of a dialog failed with the `FinTsProtocolError` 'The
327
+ answer belongs to another message than the one sent', which lost the bank's text, and the client then sent a dialog
328
+ end to the dialog the bank had aborted.
329
+ - Balance and transaction queries that name the account by its international account (`HKSAL` 7 and 8, `HKKAZ` 7,
330
+ `HKCAZ`) name an account with BIC by IBAN and BIC only, without account number, sub-account and bank code, where the
331
+ bank parameter data rule out the national account (`HISPAS` 'Nationale Kontoverbindung erlaubt' `N`). They no longer
332
+ send an IBAN without its BIC, which the specification rules out (Messages B.3.2): such an account goes out with its
333
+ account number, sub-account and bank code instead, where it has an account number. Until now these queries always
334
+ sent the national account, and an IBAN without BIC with an empty BIC.
335
+ - The account extension of the user parameter data (BIC, data status and complete balance) is read where the bank
336
+ pads it with U+0085 (NEL) or U+00A0, as the client allows for the other fields. Such an extension was ignored, so the
337
+ client fetched the BIC with the SEPA account list and the balance with a balance query.
338
+ - A synchronization with the one-step method that the bank answers with `9955` (one-step TAN method not allowed) and
339
+ `3920`, as PIN/TAN B.8.2 prescribes for a user bound to two-step methods, is repeated with a method the bank lists,
340
+ with or without `9800`. It refused the login for good and forgot the PIN.
341
+ - A call whose TAN method prompt after a refused login ends with an error, such as `close` during
342
+ `tanMethodSelector`, fails with that error and leaves no refusal behind. The refusal, which named a TAN method the
343
+ user may not use and not the PIN, outlasted the call: the client forgot the PIN, saved the refusal in `client.state`,
344
+ and refused every login until `clearLoginRefusal`.
345
+ - Plain JavaScript can no longer change `Transaction.purposeCodes` or `AccountDataStatus.inventory` through the map
346
+ inside them: `ImmutableMap` keeps its entries in an ECMAScript-private field.
347
+ - A flicker code whose start code states a length of 0, or sets bit 6 of its length, is refused, as a data element of
348
+ such a length already was. `FlickerCode.parse` read an empty start code, which rendered as ASCII data of length 0,
349
+ and dropped bit 6.
350
+ - A flicker code whose tenth control byte announces another one is refused. It was read as if the list had ended
351
+ there, and the rest of the code was read from the wrong position.
352
+ - A blank in the data of a flicker code, such as `1,00 EUR`, stays part of the data. The client removed every white
353
+ space before reading the code, so such a code in HHD_UC was refused and the challenge had no `flickerCode`.
354
+ - A challenge whose HHD_UC is neither a photoTAN image nor a flicker code takes the flicker code of its challenge text
355
+ (`CHLGUC`). `TanChallenge.flickerCode` was `undefined` then, although `plainMessage` left out the code.
356
+ - Challenge text counts as a combined challenge only where it starts with `CHLGUC`, after blanks, and the four digits
357
+ after it state the length of the code. Elsewhere it is shown as the bank sent it: `plainMessage` dropped everything
358
+ up to a `CHLGTEXT` anywhere in the text, such as in a payment purpose the bank copied in, and a `CHLGUC` in the
359
+ middle of the text gave a flicker code.
360
+ - `TanImage.tryParse` refuses a container that declares an image of 0 bytes. It gave an image whose `toDataUrl()` was
361
+ an empty `data:` URL.
362
+ - MT940: the balance date `0` or `000000`, which the specification allows for the first statement of an account,
363
+ reads as no date (`openingDate` or `closingDate` `undefined`). It failed `getTransactions` with
364
+ `FinTsProtocolError` and `Mt940.parse` with `FormatError`.
365
+ - MT940: the `:61:` customer reference `KREF+` reads as the `KREF+` value of `:86:`, as the specification has it,
366
+ instead of `'KREF+'` for every such transaction.
367
+ - MT940: a bank code in `?30` next to an IBAN in `?31` is `counterpartyBankCode`, not `counterpartyBic`.
368
+ - `Mt940.parse` reads text that starts with a byte order mark or blanks, as `fs.readFile(…, 'utf8')` returns it. It
369
+ dropped the first `:20:` and returned its statement without `reference`.
370
+ - camt: a transaction whose remittance information is structured only (`Strd/CdtrRefInf/Ref`, such as an RF creditor
371
+ reference) takes that reference as its `purpose`, which was `undefined` or the `AddtlTxInf`.
372
+ - The README example saves `client.state` after `close`, not before, also where `close` fails: the answers `close`
373
+ waits for may bring a PIN the bank rejects at the dialog end, which a client restored from a state saved before would
374
+ send again, as well as the parameter data and the customer system ID of a request on its way.
375
+ - The README says what the type declarations need: the global `URL`, `AbortSignal` and `fetch` types, from the
376
+ TypeScript lib `DOM` or `@types/node`, instead of a TypeScript version.
377
+
1
378
  ## 0.2.0
2
379
 
3
380
  ### Breaking
package/README.md CHANGED
@@ -2,7 +2,7 @@
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), 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.
5
+ It is a port of the Dart package [`fints`](https://pub.dev/packages/fints) (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
 
@@ -20,7 +20,7 @@ It is a port of the Dart package [`fints`](https://pub.dev/packages/fints), with
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 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.
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 use the global `URL`, `AbortSignal` and `fetch` types: include the TypeScript lib `DOM` or `@types/node`.
24
24
 
25
25
  ## Usage
26
26
 
@@ -62,12 +62,15 @@ try {
62
62
  const balance = await client.getBalance(accounts[0]!);
63
63
  const transactions = await client.getTransactions(accounts[0]!, { from: new Date(2024, 0, 1) });
64
64
  } finally {
65
- saveEncrypted(client.state); // no PIN or TAN, but account data
66
- await client.close();
65
+ try {
66
+ await client.close();
67
+ } finally {
68
+ saveEncrypted(client.state); // after close, whose answers may change it; no PIN or TAN, but login and account data
69
+ }
67
70
  }
68
71
  ```
69
72
 
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`.
73
+ 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, and so may up to three of its further pages; where a fourth asks for one, the call fails with `FinTsProtocolError` before the user is asked.
71
74
 
72
75
  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
76
 
@@ -75,6 +78,8 @@ A saved state belongs to one client at a time. Run the clients of one bank user
75
78
 
76
79
  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
80
 
81
+ `console.log` and `util.inspect` on Node.js, Deno and Bun show the objects of this package only by what identifies no person, account or TAN: codes, kinds, counts, dates and the last digits of an IBAN or card number, such as `Account(…3000, Girokonto)`, `TanChallenge(TanMethod(942, pushTAN), decoupled, attempt 1)` or `FinTsFeedback(9010)`. They leave out the bank's texts, references and parameters, TAN media names, TANs, challenge data and images, login data, and the amounts of balances and transactions; an `Amount` on its own shows as its `toString`, such as `-123.45 EUR`. `toString` of a `FinTsFeedback` and of a `BankMessage` leaves out the bank's texts as well: show `code` and `text`, or `subject` and `text`, to the user. An error shows its message and stack, and a `FinTsTransportError` its `statusCode`, also where Node.js reports it uncaught; the message of a `FinTsBankError`, and so its stack, holds the bank's texts and parameters of its errors, such as an IBAN, so log its `codes` instead. `JSON.stringify` and the browser console show every field, IBANs, names, amounts, purposes and the TAN of a `TanAnswer` included, so that a server can pass the data to its own front end: neither belongs in logs. Only `JSON.stringify` of an error leaves out the `feedback` of a `FinTsBankError` and the `source` of a `FormatError`.
82
+
78
83
  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.
79
84
 
80
85
  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.
@@ -97,7 +102,7 @@ There are three ways to set it up:
97
102
  method: 'POST',
98
103
  headers: { 'Content-Type': contentType, 'X-FinTS-URL': url.href },
99
104
  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
105
+ signal, // aborted five seconds after the client is closed
101
106
  redirect: 'error', // never send the PIN on to another address
102
107
  cache: 'no-store',
103
108
  });
@@ -111,16 +116,20 @@ There are three ways to set it up:
111
116
  };
112
117
  ```
113
118
 
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`:
119
+ Until it is closed, the client limits neither the time of an exchange nor the size of an answer, so a transport of your own has to, unless it goes through a relay that does, as this one does. 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
120
 
116
121
  ```ts
117
122
  import { FinTsTransportError, HttpTransport } from '@coderoftime/fints';
118
123
 
119
124
  const bank = new HttpTransport();
125
+ const maxBodyBytes = 1024 * 1024; // far more than a message of the client takes
120
126
 
121
127
  // Behind the app's login and a rate limit per session; bankUrl is the bank the user chose, as URL.href writes it.
122
128
  export async function relay(request: Request, session: { bankUrl: string }): Promise<Response> {
123
129
  if (request.headers.get('X-FinTS-URL') !== session.bankUrl) return new Response(null, { status: 403 });
130
+ // Checked before the body is read, of which the server reads no more than the length states.
131
+ const length = Number(request.headers.get('Content-Length') ?? NaN);
132
+ if (!(length <= maxBodyBytes)) return new Response(null, { status: 413 });
124
133
  try {
125
134
  const body = new Uint8Array(await request.arrayBuffer());
126
135
  const answer = await bank.post(new URL(session.bankUrl), body, 'text/plain', request.signal);
@@ -135,7 +144,7 @@ There are three ways to set it up:
135
144
 
136
145
  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
146
 
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.
147
+ 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, with a timeout and a limit on the size of the answer, or `HttpTransport` where `fetch` is not bound by CORS.
139
148
 
140
149
  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.
141
150
 
@@ -157,7 +166,7 @@ export function tanCallback(challenge: TanChallenge): Promise<TanAnswer> {
157
166
  // TanAnswer.approved or TanAnswer.cancelled, then clears pending.value.
158
167
  ```
159
168
 
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`:
169
+ 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. The same holds for the bank messages and warnings of `onBankMessage` and `onWarning`, whose number and length the bank chooses without a limit, so bound what the app shows of them. `white-space: pre-line` keeps the line breaks of `plainMessage`:
161
170
 
162
171
  ```vue
163
172
  <div v-if="pending">
@@ -187,7 +196,7 @@ The dialog shows the challenge's text with `{{ }}`, which Vue escapes, never wit
187
196
 
188
197
  The client never repeats a call by itself: the bank's answer cannot tell whether an order went through.
189
198
 
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.
199
+ It reads only complete answers to the message it sent: one cut short, broken, or with the number or dialog ID of another message fails the call with `FinTsProtocolError`. Only the bank's dialog abort message, which names neither (dialog ID `unbekannt`, message number 9999), answers any message: it fails the call with a `FinTsBankError` that holds the bank's text, and ends the dialog. 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, each data element counted as 512 bytes, or more than 200,000 statements and transactions, fails with `FinTsProtocolError` as well: ask for a shorter period then. So that a broken or hostile server cannot block the app or exhaust its memory, the client also refuses an answer with more than 1,000,000 data elements and segments in its plain or in its encrypted part, or with more than one encrypted part; XML nested more than 100 levels deep, or with more than 500,000 nodes besides text or one per 16 characters, whichever is more; an MT940 field or camt remittance information (`Ustrd`) of more than 65,536 characters; and an amount written with more than 64 characters. `Mt940.parse`, `Camt.parse` and `Amount.parseDecimal` throw `FormatError` for these.
191
200
 
192
201
  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
202
 
@@ -195,11 +204,11 @@ MT940 statements must balance. Where a bank wraps a purpose at a fixed width, a
195
204
 
196
205
  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
206
 
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.
207
+ 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`, `9800` or `9955`) 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
208
 
200
209
  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
210
 
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:
211
+ 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)); where their part of the state is damaged, the client restores a refusal whose `feedback` is empty. `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
212
 
204
213
  ```ts
205
214
  const refusal = client.loginRefusal;
@@ -215,19 +224,18 @@ Never pass `clearLoginRefusal` a stored PIN without asking the user: that is how
215
224
 
216
225
  ## Differences from the Dart package
217
226
 
218
- - Errors are named `…Error` instead of `…Exception`; invalid arguments are `TypeError`s, malformed data `FormatError`s.
227
+ This section compares with the Dart package as its source stands after its release 0.2.0, which differs in more ways. Both keep the saved state in the same layout, a refused login included, so each restores the other's. The Dart package 0.2.0 keeps no refused login, though: restored from a state of this package, it sends the PIN the bank refused. They differ in:
228
+
229
+ - Errors are named `…Error` instead of `…Exception`; invalid arguments are `TypeError`s, malformed data `FormatError`s, and a closed client or a call from inside a callback a `FinTsStateError`. Some messages are worded differently.
219
230
  - `getTransactions(account, { from, to, format })` takes an options object; enums are string unions such as `'mt940'`.
220
231
  - The polling parameters of `TanMethod` are in seconds: `decoupledFirstPollDelaySeconds`, `decoupledPollIntervalSeconds`.
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.
232
+ - No FinTS 4.1, so no `FinTs4Client`, `FinTsClient.version` or `BankInfo.bic`. `FinTsConfig` checks the login data against what FinTS 3.0 can send, which the Dart package does in `FinTs3Client`, and also refuses a user ID or customer ID of more than 30 characters, which the Dart package sends.
233
+ - `HttpTransport` takes a `fetch` and `timeoutMs` instead of an HTTP client and a `Duration`. `FinTsTransport.post` takes an `AbortSignal`, with which `close` aborts a request on its way after five seconds, also through a custom transport, and then stops waiting for it whatever the transport does; the Dart package aborts only its own `HttpTransport` (`abortRequests`) and waits for any other transport up to that transport's timeout.
234
+ - HTTP goes through the runtime's `fetch`, with its proxies, timeouts and trusted certificates. Plain http to a local server, which `allowInsecureUrl` allows, goes wherever the global `fetch` sends it, as https does: to whatever replaces `globalThis.fetch`, such as a test's stub, to a global dispatcher on Node.js, and through any proxy that `fetch` takes from the environment (Node.js with `NODE_USE_ENV_PROXY=1`, Deno and Bun with `HTTP_PROXY`), which sees the PIN. Exempt the host with `NO_PROXY=localhost,127.0.0.1,::1` or pass a `fetch` that connects directly. The Dart package connects to the loopback host directly, also bypassing a client set with `runWithClient`, and on native platforms follows `HTTPS_PROXY` and `NO_PROXY` for https, which Node.js does only with `NODE_USE_ENV_PROXY=1`.
235
+ - A call from inside a callback fails on Node.js, Deno and Bun only; in browsers it waits forever, where the Dart package fails on every platform.
236
+ - `console.log` and `util.inspect` show the objects of this package as [Usage](#usage) describes, where the Dart package prints most of them as `Instance of '…'`, and `toString` of a `FinTsFeedback` or a `BankMessage` leaves out the bank's texts, which the Dart package prints. The browser console shows every field, where the Dart package on the web prints `toString`.
237
+ - `JSON.stringify` gives every field of a model, the TAN of a `TanAnswer` included, and of an error its `name`, and the `statusCode` of a `FinTsTransportError`; the Dart package's models and errors have no JSON form.
238
+ - Some bank data are read and sent otherwise. In MT940, the counterparty's IBAN and BIC come from the subfields `?31` and `?30` only, not from `IBAN+` and `BIC+` in the payer's reference, a bank code in `?30` next to an IBAN is the bank code, the customer reference `KREF+` stands for the one of `:86:`, and the balance date `0` or `000000` for none. A camt purpose falls back to the structured creditor reference, and remittance information of more than 65,536 characters is refused. Challenge text counts as a combined chipTAN challenge only where it starts with `CHLGUC` and states the length of its code. The bank's dialog abort message fails the call with a `FinTsBankError`. A balance or transaction query that names the account by its international account (KTI) leaves out the national account where the bank parameter data rule it out, unless the account has no BIC, and sends no IBAN without its BIC, unless the account has no account number. A one-step login that the bank answers with `9955` and `3920` is repeated with a two-step method.
231
239
 
232
240
  ## Development
233
241
 
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Account } from './core/account.js';
1
+ import { Account } from './core/account.js';
2
2
  import type { Balance } from './core/balance.js';
3
3
  import type { BankInfo } from './core/bank-info.js';
4
4
  import type { FinTsClient, FinTsClientOptions, TransactionQuery } from './core/client.js';
@@ -28,7 +28,8 @@ export declare class FinTs3Client implements FinTsClient {
28
28
  *
29
29
  * Throws `TypeError` if the configuration is invalid, as the constructor of `FinTsConfig` tells, or the PIN is empty,
30
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.
31
+ * non-Latin letters are not possible. Throws `TypeError` as well for an option of another type than
32
+ * `FinTsClientOptions` names, as `FinTsClientOptions` tells.
32
33
  */
33
34
  constructor(init: FinTs3ClientInit);
34
35
  get state(): string;
@@ -1 +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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,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,EAAqB,MAAM,uBAAuB,CAAC;AAS5E,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAUhE,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;AA2CD,8DAA8D;AAC9D,qBAAa,YAAa,YAAW,WAAW;;IAkC9C;;;;;;;OAOG;gBACS,IAAI,EAAE,gBAAgB;IAyBlC,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;IAEK,eAAe,CAAC,gBAAgB,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlF,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;IAI3B,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAe9C,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,GAAE,gBAAqB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAqB7F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA4CvB"}