@coderoftime/fints 0.0.0-stage → 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 (249) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/LICENSE +28 -0
  3. package/README.md +246 -2
  4. package/dist/client.d.ts +48 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +199 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/core/account.d.ts +168 -0
  9. package/dist/core/account.d.ts.map +1 -0
  10. package/dist/core/account.js +245 -0
  11. package/dist/core/account.js.map +1 -0
  12. package/dist/core/amount.d.ts +63 -0
  13. package/dist/core/amount.d.ts.map +1 -0
  14. package/dist/core/amount.js +192 -0
  15. package/dist/core/amount.js.map +1 -0
  16. package/dist/core/balance.d.ts +55 -0
  17. package/dist/core/balance.d.ts.map +1 -0
  18. package/dist/core/balance.js +54 -0
  19. package/dist/core/balance.js.map +1 -0
  20. package/dist/core/bank-info.d.ts +49 -0
  21. package/dist/core/bank-info.d.ts.map +1 -0
  22. package/dist/core/bank-info.js +52 -0
  23. package/dist/core/bank-info.js.map +1 -0
  24. package/dist/core/client.d.ts +161 -0
  25. package/dist/core/client.d.ts.map +1 -0
  26. package/dist/core/client.js +2 -0
  27. package/dist/core/client.js.map +1 -0
  28. package/dist/core/config.d.ts +80 -0
  29. package/dist/core/config.d.ts.map +1 -0
  30. package/dist/core/config.js +153 -0
  31. package/dist/core/config.js.map +1 -0
  32. package/dist/core/errors.d.ts +153 -0
  33. package/dist/core/errors.d.ts.map +1 -0
  34. package/dist/core/errors.js +247 -0
  35. package/dist/core/errors.js.map +1 -0
  36. package/dist/core/feedback.d.ts +72 -0
  37. package/dist/core/feedback.d.ts.map +1 -0
  38. package/dist/core/feedback.js +127 -0
  39. package/dist/core/feedback.js.map +1 -0
  40. package/dist/core/transaction.d.ts +103 -0
  41. package/dist/core/transaction.d.ts.map +1 -0
  42. package/dist/core/transaction.js +96 -0
  43. package/dist/core/transaction.js.map +1 -0
  44. package/dist/core/transport.d.ts +64 -0
  45. package/dist/core/transport.d.ts.map +1 -0
  46. package/dist/core/transport.js +149 -0
  47. package/dist/core/transport.js.map +1 -0
  48. package/dist/formats/camt.d.ts +32 -0
  49. package/dist/formats/camt.d.ts.map +1 -0
  50. package/dist/formats/camt.js +242 -0
  51. package/dist/formats/camt.js.map +1 -0
  52. package/dist/formats/mt940.d.ts +54 -0
  53. package/dist/formats/mt940.d.ts.map +1 -0
  54. package/dist/formats/mt940.js +376 -0
  55. package/dist/formats/mt940.js.map +1 -0
  56. package/dist/formats/readers.d.ts +15 -0
  57. package/dist/formats/readers.d.ts.map +1 -0
  58. package/dist/formats/readers.js +42 -0
  59. package/dist/formats/readers.js.map +1 -0
  60. package/dist/formats/record-budget.d.ts +7 -0
  61. package/dist/formats/record-budget.d.ts.map +1 -0
  62. package/dist/formats/record-budget.js +17 -0
  63. package/dist/formats/record-budget.js.map +1 -0
  64. package/dist/formats/sepa-purpose.d.ts +28 -0
  65. package/dist/formats/sepa-purpose.d.ts.map +1 -0
  66. package/dist/formats/sepa-purpose.js +89 -0
  67. package/dist/formats/sepa-purpose.js.map +1 -0
  68. package/dist/index.d.ts +28 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +27 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/session/app-approval.d.ts +66 -0
  73. package/dist/session/app-approval.d.ts.map +1 -0
  74. package/dist/session/app-approval.js +226 -0
  75. package/dist/session/app-approval.js.map +1 -0
  76. package/dist/session/dialog-runner.d.ts +31 -0
  77. package/dist/session/dialog-runner.d.ts.map +1 -0
  78. package/dist/session/dialog-runner.js +111 -0
  79. package/dist/session/dialog-runner.js.map +1 -0
  80. package/dist/session/login-guard.d.ts +78 -0
  81. package/dist/session/login-guard.d.ts.map +1 -0
  82. package/dist/session/login-guard.js +181 -0
  83. package/dist/session/login-guard.js.map +1 -0
  84. package/dist/session/sepa-accounts.d.ts +40 -0
  85. package/dist/session/sepa-accounts.d.ts.map +1 -0
  86. package/dist/session/sepa-accounts.js +112 -0
  87. package/dist/session/sepa-accounts.js.map +1 -0
  88. package/dist/session/session-setup.d.ts +80 -0
  89. package/dist/session/session-setup.d.ts.map +1 -0
  90. package/dist/session/session-setup.js +182 -0
  91. package/dist/session/session-setup.js.map +1 -0
  92. package/dist/session/session-state.d.ts +50 -0
  93. package/dist/session/session-state.d.ts.map +1 -0
  94. package/dist/session/session-state.js +182 -0
  95. package/dist/session/session-state.js.map +1 -0
  96. package/dist/session/signed-dialog.d.ts +77 -0
  97. package/dist/session/signed-dialog.d.ts.map +1 -0
  98. package/dist/session/signed-dialog.js +170 -0
  99. package/dist/session/signed-dialog.js.map +1 -0
  100. package/dist/session/tan-selection.d.ts +96 -0
  101. package/dist/session/tan-selection.d.ts.map +1 -0
  102. package/dist/session/tan-selection.js +189 -0
  103. package/dist/session/tan-selection.js.map +1 -0
  104. package/dist/session/user-prompts.d.ts +65 -0
  105. package/dist/session/user-prompts.d.ts.map +1 -0
  106. package/dist/session/user-prompts.js +184 -0
  107. package/dist/session/user-prompts.js.map +1 -0
  108. package/dist/tan/challenge.d.ts +135 -0
  109. package/dist/tan/challenge.d.ts.map +1 -0
  110. package/dist/tan/challenge.js +184 -0
  111. package/dist/tan/challenge.js.map +1 -0
  112. package/dist/tan/flicker.d.ts +42 -0
  113. package/dist/tan/flicker.d.ts.map +1 -0
  114. package/dist/tan/flicker.js +296 -0
  115. package/dist/tan/flicker.js.map +1 -0
  116. package/dist/tan/tan-image.d.ts +22 -0
  117. package/dist/tan/tan-image.d.ts.map +1 -0
  118. package/dist/tan/tan-image.js +43 -0
  119. package/dist/tan/tan-image.js.map +1 -0
  120. package/dist/tan/tan-models.d.ts +149 -0
  121. package/dist/tan/tan-models.d.ts.map +1 -0
  122. package/dist/tan/tan-models.js +153 -0
  123. package/dist/tan/tan-models.js.map +1 -0
  124. package/dist/util/async.d.ts +22 -0
  125. package/dist/util/async.d.ts.map +1 -0
  126. package/dist/util/async.js +45 -0
  127. package/dist/util/async.js.map +1 -0
  128. package/dist/util/bytes.d.ts +22 -0
  129. package/dist/util/bytes.d.ts.map +1 -0
  130. package/dist/util/bytes.js +128 -0
  131. package/dist/util/bytes.js.map +1 -0
  132. package/dist/util/call-context.d.ts +12 -0
  133. package/dist/util/call-context.d.ts.map +1 -0
  134. package/dist/util/call-context.js +30 -0
  135. package/dist/util/call-context.js.map +1 -0
  136. package/dist/util/dates.d.ts +55 -0
  137. package/dist/util/dates.d.ts.map +1 -0
  138. package/dist/util/dates.js +189 -0
  139. package/dist/util/dates.js.map +1 -0
  140. package/dist/util/errors.d.ts +17 -0
  141. package/dist/util/errors.d.ts.map +1 -0
  142. package/dist/util/errors.js +24 -0
  143. package/dist/util/errors.js.map +1 -0
  144. package/dist/util/immutable-map.d.ts +16 -0
  145. package/dist/util/immutable-map.d.ts.map +1 -0
  146. package/dist/util/immutable-map.js +38 -0
  147. package/dist/util/immutable-map.js.map +1 -0
  148. package/dist/util/json.d.ts +17 -0
  149. package/dist/util/json.d.ts.map +1 -0
  150. package/dist/util/json.js +62 -0
  151. package/dist/util/json.js.map +1 -0
  152. package/dist/util/text.d.ts +37 -0
  153. package/dist/util/text.d.ts.map +1 -0
  154. package/dist/util/text.js +107 -0
  155. package/dist/util/text.js.map +1 -0
  156. package/dist/util/xml.d.ts +36 -0
  157. package/dist/util/xml.d.ts.map +1 -0
  158. package/dist/util/xml.js +194 -0
  159. package/dist/util/xml.js.map +1 -0
  160. package/dist/v3/account-extension.d.ts +25 -0
  161. package/dist/v3/account-extension.d.ts.map +1 -0
  162. package/dist/v3/account-extension.js +173 -0
  163. package/dist/v3/account-extension.js.map +1 -0
  164. package/dist/v3/bank-connection.d.ts +34 -0
  165. package/dist/v3/bank-connection.d.ts.map +1 -0
  166. package/dist/v3/bank-connection.js +58 -0
  167. package/dist/v3/bank-connection.js.map +1 -0
  168. package/dist/v3/dialog.d.ts +112 -0
  169. package/dist/v3/dialog.d.ts.map +1 -0
  170. package/dist/v3/dialog.js +241 -0
  171. package/dist/v3/dialog.js.map +1 -0
  172. package/dist/v3/message.d.ts +85 -0
  173. package/dist/v3/message.d.ts.map +1 -0
  174. package/dist/v3/message.js +303 -0
  175. package/dist/v3/message.js.map +1 -0
  176. package/dist/v3/parameter-store.d.ts +75 -0
  177. package/dist/v3/parameter-store.d.ts.map +1 -0
  178. package/dist/v3/parameter-store.js +143 -0
  179. package/dist/v3/parameter-store.js.map +1 -0
  180. package/dist/v3/parameters.d.ts +105 -0
  181. package/dist/v3/parameters.d.ts.map +1 -0
  182. package/dist/v3/parameters.js +450 -0
  183. package/dist/v3/parameters.js.map +1 -0
  184. package/dist/v3/protocol.d.ts +61 -0
  185. package/dist/v3/protocol.d.ts.map +1 -0
  186. package/dist/v3/protocol.js +113 -0
  187. package/dist/v3/protocol.js.map +1 -0
  188. package/dist/v3/queries.d.ts +51 -0
  189. package/dist/v3/queries.d.ts.map +1 -0
  190. package/dist/v3/queries.js +120 -0
  191. package/dist/v3/queries.js.map +1 -0
  192. package/dist/v3/segments.d.ts +106 -0
  193. package/dist/v3/segments.d.ts.map +1 -0
  194. package/dist/v3/segments.js +325 -0
  195. package/dist/v3/segments.js.map +1 -0
  196. package/dist/v3/syntax.d.ts +127 -0
  197. package/dist/v3/syntax.d.ts.map +1 -0
  198. package/dist/v3/syntax.js +367 -0
  199. package/dist/v3/syntax.js.map +1 -0
  200. package/package.json +67 -4
  201. package/src/client.ts +252 -0
  202. package/src/core/account.ts +323 -0
  203. package/src/core/amount.ts +211 -0
  204. package/src/core/balance.ts +85 -0
  205. package/src/core/bank-info.ts +66 -0
  206. package/src/core/client.ts +186 -0
  207. package/src/core/config.ts +182 -0
  208. package/src/core/errors.ts +277 -0
  209. package/src/core/feedback.ts +140 -0
  210. package/src/core/transaction.ts +161 -0
  211. package/src/core/transport.ts +189 -0
  212. package/src/formats/camt.ts +266 -0
  213. package/src/formats/mt940.ts +419 -0
  214. package/src/formats/readers.ts +49 -0
  215. package/src/formats/record-budget.ts +18 -0
  216. package/src/formats/sepa-purpose.ts +94 -0
  217. package/src/index.ts +70 -0
  218. package/src/session/app-approval.ts +278 -0
  219. package/src/session/dialog-runner.ts +122 -0
  220. package/src/session/login-guard.ts +221 -0
  221. package/src/session/sepa-accounts.ts +115 -0
  222. package/src/session/session-setup.ts +184 -0
  223. package/src/session/session-state.ts +204 -0
  224. package/src/session/signed-dialog.ts +192 -0
  225. package/src/session/tan-selection.ts +203 -0
  226. package/src/session/user-prompts.ts +193 -0
  227. package/src/tan/challenge.ts +261 -0
  228. package/src/tan/flicker.ts +308 -0
  229. package/src/tan/tan-image.ts +38 -0
  230. package/src/tan/tan-models.ts +252 -0
  231. package/src/util/async.ts +48 -0
  232. package/src/util/bytes.ts +129 -0
  233. package/src/util/call-context.ts +41 -0
  234. package/src/util/dates.ts +210 -0
  235. package/src/util/errors.ts +27 -0
  236. package/src/util/immutable-map.ts +47 -0
  237. package/src/util/json.ts +67 -0
  238. package/src/util/text.ts +112 -0
  239. package/src/util/xml.ts +183 -0
  240. package/src/v3/account-extension.ts +185 -0
  241. package/src/v3/bank-connection.ts +67 -0
  242. package/src/v3/dialog.ts +283 -0
  243. package/src/v3/message.ts +373 -0
  244. package/src/v3/parameter-store.ts +160 -0
  245. package/src/v3/parameters.ts +524 -0
  246. package/src/v3/protocol.ts +148 -0
  247. package/src/v3/queries.ts +190 -0
  248. package/src/v3/segments.ts +374 -0
  249. package/src/v3/syntax.ts +369 -0
@@ -0,0 +1,89 @@
1
+ /** SEPA keywords German banks put into the purpose text, each followed by `+`. */
2
+ const keywords = [
3
+ 'EREF',
4
+ 'KREF',
5
+ 'MREF',
6
+ 'CRED',
7
+ 'DEBT',
8
+ 'COAM',
9
+ 'OAMT',
10
+ 'SVWZ',
11
+ 'ABWA',
12
+ 'ABWE',
13
+ 'IBAN',
14
+ 'BIC',
15
+ 'PURP',
16
+ 'SQTP',
17
+ ];
18
+ /** The filler SEPA prescribes where the payer gave no end-to-end reference. */
19
+ const notProvided = 'NOTPROVIDED';
20
+ /** `reference`, or `undefined` if it is missing or the filler for a reference the payer did not give. */
21
+ export function providedReference(reference) {
22
+ return reference === notProvided ? undefined : reference;
23
+ }
24
+ const keywordPattern = new RegExp(`(${keywords.join('|')})\\+`, 'g');
25
+ /** The keywords German banks write after the remittance information (`SVWZ`). */
26
+ const afterRemittance = new Set(['ABWA', 'ABWE']);
27
+ /** Parser for the SEPA keywords in a purpose text. */
28
+ export const SepaPurpose = Object.freeze({
29
+ /**
30
+ * Splits `purpose` into its SEPA keywords (EREF, KREF, MREF, CRED, SVWZ, …) and their values.
31
+ *
32
+ * A keyword counts unless a capital letter comes right before it, so `XEREF+` does not open an EREF value. After a
33
+ * digit or a small letter it does: a purpose line cut at 35 characters is joined to the next one without a space, so
34
+ * `…4711EREF+` may be where one line ends and the next begins.
35
+ *
36
+ * The remittance information (`SVWZ+`) and the ultimate debtor and creditor (`ABWA+`, `ABWE+`) are text the payer or
37
+ * the payee writes, which banks put after every other keyword. So from the first of them on only `ABWA+` and `ABWE+`
38
+ * start a value: a keyword in that text, such as `IBAN+`, is part of it. A keyword that occurs twice has no value:
39
+ * which of the two the bank wrote cannot be told, since a reference the payee chose, such as `EREF+`, comes before
40
+ * the bank's `CRED+` and may hold `CRED+…` and `SVWZ+`, which turns the bank's value into remittance information.
41
+ */
42
+ parse(purpose) {
43
+ return split(purpose, new Set());
44
+ },
45
+ /**
46
+ * Like `parse` for a purpose the bank sends in `parts`, such as the MT940 subfields `?20` to `?29`, which continue
47
+ * each other without a separator.
48
+ *
49
+ * A keyword also counts at the start of a part, where the previous value may end in a capital letter, as in
50
+ * `EREF+NOTPROVIDED` followed by a part `MREF+…`.
51
+ */
52
+ parseParts(parts) {
53
+ let text = '';
54
+ const partStarts = new Set();
55
+ for (const part of parts) {
56
+ partStarts.add(text.length);
57
+ text += part;
58
+ }
59
+ return split(text, partStarts);
60
+ },
61
+ });
62
+ function split(purpose, partStarts) {
63
+ const atWordStart = (index) => index === 0 || partStarts.has(index) || !isCapitalLetter(purpose, index - 1);
64
+ const occurrences = new Map();
65
+ const matches = [];
66
+ let inPayerText = false;
67
+ for (const m of purpose.matchAll(keywordPattern)) {
68
+ if (!atWordStart(m.index))
69
+ continue;
70
+ const keyword = m[1];
71
+ occurrences.set(keyword, (occurrences.get(keyword) ?? 0) + 1);
72
+ if (inPayerText && !afterRemittance.has(keyword))
73
+ continue;
74
+ inPayerText ||= keyword === 'SVWZ' || afterRemittance.has(keyword);
75
+ matches.push(m);
76
+ }
77
+ const result = new Map();
78
+ matches.forEach((m, i) => {
79
+ const end = i + 1 < matches.length ? matches[i + 1].index : purpose.length;
80
+ if (occurrences.get(m[1]) === 1)
81
+ result.set(m[1], purpose.substring(m.index + m[0].length, end).trim());
82
+ });
83
+ return result;
84
+ }
85
+ function isCapitalLetter(text, index) {
86
+ const c = text.charCodeAt(index);
87
+ return c >= 0x41 && c <= 0x5a;
88
+ }
89
+ //# sourceMappingURL=sepa-purpose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sepa-purpose.js","sourceRoot":"","sources":["../../src/formats/sepa-purpose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * FinTS 3.0 client for German banks: accounts, balances and transactions with PIN/TAN, PSD2 strong customer
3
+ * authentication and decoupled app approval.
4
+ *
5
+ * Start with `FinTs3Client`. The public API is what this file names; everything else is internal.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export { FinTs3Client, type FinTs3ClientInit } from './client.js';
10
+ export { Account, AccountDataStatus, type AccountCategory, type AccountInit } from './core/account.js';
11
+ export { Amount } from './core/amount.js';
12
+ export { Balance, type BalanceInit } from './core/balance.js';
13
+ export { BankInfo, BankMessage } from './core/bank-info.js';
14
+ export type { BankMessageHandler, FinTsClient, FinTsClientOptions, MessageDirection, MessageLogger, TransactionQuery, WarningHandler, } from './core/client.js';
15
+ export { FinTsConfig, type FinTsConfigInit } from './core/config.js';
16
+ export { FinTsBankError, FinTsDecoupledTimeoutError, FinTsDialogAbortedError, FinTsError, FinTsLoginDataChangedError, FinTsLoginRefusedError, FinTsPinError, FinTsProtocolError, FinTsStateError, FinTsTanCancelledError, FinTsTanError, FinTsTanSetupError, FinTsTransportError, FinTsUnsupportedError, } from './core/errors.js';
17
+ export { FeedbackCodes, FinTsFeedback } from './core/feedback.js';
18
+ export { Transaction, type TransactionFormat, type TransactionInit, type TransactionStatus, } from './core/transaction.js';
19
+ export { HttpTransport, type FinTsTransport, type HttpTransportOptions } from './core/transport.js';
20
+ export { Camt, type CamtStatement } from './formats/camt.js';
21
+ export { Mt940, type Mt940Options, type Mt940Statement } from './formats/mt940.js';
22
+ export { SepaPurpose } from './formats/sepa-purpose.js';
23
+ export { TanAnswer, TanChallenge, type TanCallback, type TanChallengeInit, type TanMediumSelector, type TanMethodSelector, } from './tan/challenge.js';
24
+ export { FlickerCode, type HhdVersion } from './tan/flicker.js';
25
+ export { TanImage } from './tan/tan-image.js';
26
+ export { TanMedium, TanMethod, type TanMediumInit, type TanMediumRequirement, type TanMediumStatus, type TanMethodInit, } from './tan/tan-models.js';
27
+ export { FormatError } from './util/errors.js';
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACvG,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,uBAAuB,EACvB,UAAU,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EACL,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EACL,SAAS,EACT,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EACL,SAAS,EACT,SAAS,EACT,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * FinTS 3.0 client for German banks: accounts, balances and transactions with PIN/TAN, PSD2 strong customer
3
+ * authentication and decoupled app approval.
4
+ *
5
+ * Start with `FinTs3Client`. The public API is what this file names; everything else is internal.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export { FinTs3Client } from './client.js';
10
+ export { Account, AccountDataStatus } from './core/account.js';
11
+ export { Amount } from './core/amount.js';
12
+ export { Balance } from './core/balance.js';
13
+ export { BankInfo, BankMessage } from './core/bank-info.js';
14
+ export { FinTsConfig } from './core/config.js';
15
+ export { FinTsBankError, FinTsDecoupledTimeoutError, FinTsDialogAbortedError, FinTsError, FinTsLoginDataChangedError, FinTsLoginRefusedError, FinTsPinError, FinTsProtocolError, FinTsStateError, FinTsTanCancelledError, FinTsTanError, FinTsTanSetupError, FinTsTransportError, FinTsUnsupportedError, } from './core/errors.js';
16
+ export { FeedbackCodes, FinTsFeedback } from './core/feedback.js';
17
+ export { Transaction, } from './core/transaction.js';
18
+ export { HttpTransport } from './core/transport.js';
19
+ export { Camt } from './formats/camt.js';
20
+ export { Mt940 } from './formats/mt940.js';
21
+ export { SepaPurpose } from './formats/sepa-purpose.js';
22
+ export { TanAnswer, TanChallenge, } from './tan/challenge.js';
23
+ export { FlickerCode } from './tan/flicker.js';
24
+ export { TanImage } from './tan/tan-image.js';
25
+ export { TanMedium, TanMethod, } from './tan/tan-models.js';
26
+ export { FormatError } from './util/errors.js';
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAyB,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAA0C,MAAM,mBAAmB,CAAC;AACvG,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAoB,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAU5D,OAAO,EAAE,WAAW,EAAwB,MAAM,kBAAkB,CAAC;AACrE,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,uBAAuB,EACvB,UAAU,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EACL,WAAW,GAIZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAkD,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,IAAI,EAAsB,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAA0C,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EACL,SAAS,EACT,YAAY,GAKb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAmB,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EACL,SAAS,EACT,SAAS,GAKV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { type FinTsFeedback } from '../core/feedback.js';
2
+ import { type TanAnswer, type TanChallenge } from '../tan/challenge.js';
3
+ import type { TanMethod } from '../tan/tan-models.js';
4
+ import type { Approval } from '../v3/dialog.js';
5
+ /** What a wait reads of the bank's answer to a status request: its return codes. */
6
+ interface StatusAnswer {
7
+ readonly feedback: readonly FinTsFeedback[];
8
+ }
9
+ /** Shows a challenge and returns the user's answer. */
10
+ type AskUser = (challenge: TanChallenge) => Promise<TanAnswer>;
11
+ /** Waits for `delayMs`, or until `interrupt` settles. */
12
+ type Pause = (delayMs: number, interrupt?: Promise<unknown>) => Promise<void>;
13
+ /** What an `AppApprovalWait` needs: `ask` shows a challenge, `pause` waits, `receive` takes in each status response. */
14
+ export interface AppApprovalHooks<R> {
15
+ readonly ask: AskUser;
16
+ readonly pause: Pause;
17
+ /** Takes in each status response; throws if it reports errors. */
18
+ readonly receive: (response: R) => void;
19
+ }
20
+ /**
21
+ * Waits for the user to approve a message in the banking app, and for the bank to confirm it (PIN/TAN B.4.2.2).
22
+ *
23
+ * The bank's parameters decide who triggers the status requests:
24
+ * - The client polls by itself while the callback shows the instructions (`PollingWhileShown`) where the bank allows
25
+ * automatic status requests and announces no push message (3957), and also where the user cannot report the
26
+ * approval: then polling is the one way left to complete it, whatever the other parameters say (step 2a).
27
+ * - Otherwise the client asks after each report of the user (`PollingAfterReports`), which stands in for the push
28
+ * message the client cannot receive.
29
+ *
30
+ * The bank's limit on status requests holds either way, and two requests are at least a second apart. One instance
31
+ * handles one `Approval`; `run` may be called once. A failure of `ask` or `pause`, such as a closed client, ends the
32
+ * wait.
33
+ */
34
+ export declare function appApprovalWait<R extends StatusAnswer>(approval: Approval<R>, hooks: AppApprovalHooks<R>): AppApprovalWait<R>;
35
+ export declare abstract class AppApprovalWait<R extends StatusAnswer> {
36
+ protected readonly approval: Approval<R>;
37
+ protected readonly hooks: AppApprovalHooks<R>;
38
+ private requests;
39
+ /** The time the client waited by itself, which counts towards the longest wait. */
40
+ protected waitedMs: number;
41
+ constructor(approval: Approval<R>, hooks: AppApprovalHooks<R>);
42
+ /**
43
+ * Returns the bank's response once the approval is through. Throws `FinTsTanCancelledError` if the user cancels and
44
+ * `FinTsDecoupledTimeoutError` if the approval takes too long.
45
+ */
46
+ abstract run(): Promise<R>;
47
+ protected get method(): TanMethod;
48
+ /** Whether the user may report the approval (PIN/TAN, "Manuelle Bestätigung möglich"). */
49
+ protected get userConfirms(): boolean;
50
+ protected get pollIntervalMs(): number;
51
+ protected get firstPollDelayMs(): number;
52
+ /**
53
+ * Sends the next status request and returns the answer, once `receive` has taken it in.
54
+ *
55
+ * Throws if the answer reports errors, and `FinTsDecoupledTimeoutError` if the approval is still pending after as
56
+ * many requests as the bank allows, or after the longest wait.
57
+ */
58
+ protected requestStatus(): Promise<R>;
59
+ /**
60
+ * `challenge` as the next prompt, with the new instructions of `pending`, the last status answer, where it gives any
61
+ * (PIN/TAN B.4.2.2.1, step 2b).
62
+ */
63
+ protected nextPrompt(challenge: TanChallenge, pending: R): TanChallenge;
64
+ }
65
+ export {};
66
+ //# sourceMappingURL=app-approval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-approval.d.ts","sourceRoot":"","sources":["../../src/session/app-approval.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAA6B,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,oFAAoF;AACpF,UAAU,YAAY;IACpB,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;CAC7C;AAYD,uDAAuD;AACvD,KAAK,OAAO,GAAG,CAAC,SAAS,EAAE,YAAY,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;AAE/D,yDAAyD;AACzD,KAAK,KAAK,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9E,wHAAwH;AACxH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;CACzC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,EACpD,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EACrB,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GACzB,eAAe,CAAC,CAAC,CAAC,CAIpB;AAED,8BAAsB,eAAe,CAAC,CAAC,SAAS,YAAY;IAOxD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAP/C,OAAO,CAAC,QAAQ,CAAK;IAErB,mFAAmF;IACnF,SAAS,CAAC,QAAQ,SAAK;gBAGF,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EACrB,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAG/C;;;OAGG;IACH,QAAQ,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;IAE1B,SAAS,KAAK,MAAM,IAAI,SAAS,CAEhC;IAED,0FAA0F;IAC1F,SAAS,KAAK,YAAY,IAAI,OAAO,CAEpC;IAED,SAAS,KAAK,cAAc,IAAI,MAAM,CAMrC;IAED,SAAS,KAAK,gBAAgB,IAAI,MAAM,CAEvC;IAED;;;;;OAKG;cACa,aAAa,IAAI,OAAO,CAAC,CAAC,CAAC;IAe3C;;;OAGG;IACH,SAAS,CAAC,UAAU,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,GAAG,YAAY;CAGxE"}
@@ -0,0 +1,226 @@
1
+ import { FinTsDecoupledTimeoutError, FinTsTanCancelledError } from '../core/errors.js';
2
+ import { FeedbackCodes, hasCode } from '../core/feedback.js';
3
+ import { nextAttempt, withResolved } from '../tan/challenge.js';
4
+ import { Deferred } from '../util/async.js';
5
+ import { invalidArgument } from '../util/errors.js';
6
+ /** How long to wait for an app approval when the bank does not say. */
7
+ const defaultMaxStatusPolls = 60;
8
+ const defaultFirstPollDelayMs = 2_000;
9
+ const defaultPollIntervalMs = 2_000;
10
+ /** Bounds for the bank's polling parameters, against faulty parameter data. */
11
+ const minPollIntervalMs = 1_000;
12
+ const maxPollDelayMs = 60_000;
13
+ const maxApprovalWaitMs = 10 * 60_000;
14
+ /**
15
+ * Waits for the user to approve a message in the banking app, and for the bank to confirm it (PIN/TAN B.4.2.2).
16
+ *
17
+ * The bank's parameters decide who triggers the status requests:
18
+ * - The client polls by itself while the callback shows the instructions (`PollingWhileShown`) where the bank allows
19
+ * automatic status requests and announces no push message (3957), and also where the user cannot report the
20
+ * approval: then polling is the one way left to complete it, whatever the other parameters say (step 2a).
21
+ * - Otherwise the client asks after each report of the user (`PollingAfterReports`), which stands in for the push
22
+ * message the client cannot receive.
23
+ *
24
+ * The bank's limit on status requests holds either way, and two requests are at least a second apart. One instance
25
+ * handles one `Approval`; `run` may be called once. A failure of `ask` or `pause`, such as a closed client, ends the
26
+ * wait.
27
+ */
28
+ export function appApprovalWait(approval, hooks) {
29
+ const method = approval.challenge.method;
30
+ const automatic = !method.decoupledManualConfirmation || (method.decoupledAutomaticPolling && !approval.waitForPush);
31
+ return automatic ? new PollingWhileShown(approval, hooks) : new PollingAfterReports(approval, hooks);
32
+ }
33
+ export class AppApprovalWait {
34
+ approval;
35
+ hooks;
36
+ requests = 0;
37
+ /** The time the client waited by itself, which counts towards the longest wait. */
38
+ waitedMs = 0;
39
+ constructor(approval, hooks) {
40
+ this.approval = approval;
41
+ this.hooks = hooks;
42
+ }
43
+ get method() {
44
+ return this.approval.challenge.method;
45
+ }
46
+ /** Whether the user may report the approval (PIN/TAN, "Manuelle Bestätigung möglich"). */
47
+ get userConfirms() {
48
+ return this.method.decoupledManualConfirmation;
49
+ }
50
+ get pollIntervalMs() {
51
+ return clamp(seconds(this.method.decoupledPollIntervalSeconds) ?? defaultPollIntervalMs, minPollIntervalMs, maxPollDelayMs);
52
+ }
53
+ get firstPollDelayMs() {
54
+ return clamp(seconds(this.method.decoupledFirstPollDelaySeconds) ?? defaultFirstPollDelayMs, 0, maxPollDelayMs);
55
+ }
56
+ /**
57
+ * Sends the next status request and returns the answer, once `receive` has taken it in.
58
+ *
59
+ * Throws if the answer reports errors, and `FinTsDecoupledTimeoutError` if the approval is still pending after as
60
+ * many requests as the bank allows, or after the longest wait.
61
+ */
62
+ async requestStatus() {
63
+ const response = await this.approval.checkStatus();
64
+ this.requests++;
65
+ this.hooks.receive(response);
66
+ // A maximum of 0 allows any number of requests (PIN/TAN, "Maximale Anzahl Statusabfragen Decoupled").
67
+ const maxRequests = this.method.decoupledMaxPolls ?? defaultMaxStatusPolls;
68
+ if (isPending(response) &&
69
+ ((maxRequests > 0 && this.requests >= maxRequests) || this.waitedMs >= maxApprovalWaitMs)) {
70
+ throw new FinTsDecoupledTimeoutError();
71
+ }
72
+ return response;
73
+ }
74
+ /**
75
+ * `challenge` as the next prompt, with the new instructions of `pending`, the last status answer, where it gives any
76
+ * (PIN/TAN B.4.2.2.1, step 2b).
77
+ */
78
+ nextPrompt(challenge, pending) {
79
+ return nextAttempt(challenge, this.approval.instructionsIn(pending));
80
+ }
81
+ }
82
+ /**
83
+ * The client polls by itself while the callback shows the instructions, and settles the `TanChallenge.resolved` of a
84
+ * prompt when it needs no more answer to it.
85
+ *
86
+ * `run` shows the prompt, waits the bank's first delay, then asks the bank until it confirms, waiting the interval in
87
+ * between. A status answer with new instructions replaces the prompt with one that shows them. The callback's answer
88
+ * only records what the user did, and counts while its prompt is shown:
89
+ * - `stopped` settles on a cancel, a TAN or a failing callback; the loop stops before its next request.
90
+ * - `report` settles on a report of the approval, which cuts the current wait short once, though not below a second.
91
+ * The report closed the prompt, so it is shown anew while the bank has not confirmed yet.
92
+ * - A user who cannot report the approval has every answer but `TanAnswer.cancelled` ignored.
93
+ */
94
+ class PollingWhileShown extends AppApprovalWait {
95
+ /** Settles when the prompt shown now needs no more answer, which its `TanChallenge.resolved` tells the callback. */
96
+ shown;
97
+ /** Settles when the user cancels or the callback fails; `stopReason` holds the error that ends the wait. */
98
+ stopped = new Deferred();
99
+ stopReason;
100
+ /** Settles when the user reports the approval in the prompt shown now; `undefined` once the report was used. */
101
+ report;
102
+ async run() {
103
+ let challenge = this.approval.challenge;
104
+ this.show(challenge);
105
+ try {
106
+ await this.sleep(this.firstPollDelayMs);
107
+ for (;;) {
108
+ this.throwIfStopped();
109
+ // A request under way is awaited even when the user cancels meanwhile; its answer counts.
110
+ const response = await this.requestStatus();
111
+ if (!isPending(response))
112
+ return response;
113
+ this.throwIfStopped();
114
+ // A report closed the prompt, but the bank has not confirmed the approval yet; or the bank's instructions changed.
115
+ const reported = this.userConfirms && this.report === undefined;
116
+ const next = this.nextPrompt(challenge, response);
117
+ if (reported || next.message !== challenge.message)
118
+ this.show((challenge = next));
119
+ await this.sleep(this.pollIntervalMs, minPollIntervalMs);
120
+ }
121
+ }
122
+ finally {
123
+ this.shown?.resolve();
124
+ }
125
+ }
126
+ /** Shows `challenge` in place of the prompt before, without waiting for the answer. */
127
+ show(challenge) {
128
+ this.shown?.resolve();
129
+ const shown = (this.shown = new Deferred());
130
+ const report = (this.report = this.userConfirms ? new Deferred() : undefined);
131
+ // Asked a microtask later: the app closes the prompt before on its `resolved` first, and then opens this one.
132
+ void Promise.resolve()
133
+ .then(() => this.hooks.ask(withResolved(challenge, shown.promise)))
134
+ .then((answer) => {
135
+ if (shown.isCompleted)
136
+ return;
137
+ switch (answer.kind) {
138
+ case 'cancelled':
139
+ this.stop(new FinTsTanCancelledError());
140
+ break;
141
+ case 'approved':
142
+ report?.resolve();
143
+ break;
144
+ case 'tan':
145
+ if (report !== undefined)
146
+ this.stop(tanForApproval());
147
+ break;
148
+ // The user cannot report the approval: the client waits for the bank.
149
+ }
150
+ }, (error) => {
151
+ if (!shown.isCompleted)
152
+ this.stop(error);
153
+ });
154
+ }
155
+ stop(error) {
156
+ if (this.stopped.isCompleted)
157
+ return;
158
+ this.stopReason = { error };
159
+ this.stopped.resolve();
160
+ }
161
+ throwIfStopped() {
162
+ if (this.stopReason !== undefined)
163
+ throw this.stopReason.error;
164
+ }
165
+ /**
166
+ * Waits for `delayMs`, or until the user cancels or reports the approval; a report does not cut the wait below
167
+ * `floorMs`. A wait cut short counts towards the longest wait with its floor only.
168
+ */
169
+ async sleep(delayMs, floorMs = 0) {
170
+ const report = this.report;
171
+ if (floorMs > 0)
172
+ await this.hooks.pause(floorMs, this.stopped.promise);
173
+ const interrupt = report === undefined ? this.stopped.promise : Promise.race([this.stopped.promise, report.promise]);
174
+ await this.hooks.pause(delayMs - floorMs, interrupt);
175
+ const cutShort = report?.isCompleted ?? false;
176
+ this.waitedMs += cutShort ? floorMs : delayMs;
177
+ // One report, one early request.
178
+ if (cutShort)
179
+ this.report = undefined;
180
+ }
181
+ }
182
+ /**
183
+ * The client asks the bank after each report of the user; while the approval is pending, the user is asked again with
184
+ * the bank's latest instructions.
185
+ */
186
+ class PollingAfterReports extends AppApprovalWait {
187
+ async run() {
188
+ let challenge = this.approval.challenge;
189
+ await this.confirm(challenge);
190
+ for (;;) {
191
+ const response = await this.requestStatus();
192
+ if (!isPending(response))
193
+ return response;
194
+ challenge = this.nextPrompt(challenge, response);
195
+ // The prompt comes back at once; however fast the user reports again, the next request waits a second.
196
+ await Promise.all([this.confirm(challenge), this.hooks.pause(minPollIntervalMs)]);
197
+ this.waitedMs += minPollIntervalMs;
198
+ }
199
+ }
200
+ /** Shows `challenge` and waits until the user reports the approval or cancels. */
201
+ async confirm(challenge) {
202
+ const answer = await this.hooks.ask(challenge);
203
+ switch (answer.kind) {
204
+ case 'approved':
205
+ return;
206
+ case 'cancelled':
207
+ throw new FinTsTanCancelledError();
208
+ case 'tan':
209
+ throw tanForApproval();
210
+ }
211
+ }
212
+ }
213
+ /** Whether `response` tells that the approval is still pending (return code 3956). */
214
+ function isPending(response) {
215
+ return hasCode(response.feedback, FeedbackCodes.decoupledStillPending);
216
+ }
217
+ function tanForApproval() {
218
+ return invalidArgument('Answered an approval in the banking app with a TAN', 'tanCallback');
219
+ }
220
+ function seconds(value) {
221
+ return value === undefined ? undefined : value * 1000;
222
+ }
223
+ function clamp(value, min, max) {
224
+ return value < min ? min : value > max ? max : value;
225
+ }
226
+ //# sourceMappingURL=app-approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-approval.js","sourceRoot":"","sources":["../../src/session/app-approval.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { Query } from '../v3/queries.js';
2
+ import type { SessionSetup } from './session-setup.js';
3
+ import type { SignedDialog } from './signed-dialog.js';
4
+ /**
5
+ * Keeps one dialog open for all calls and runs the queries in it.
6
+ *
7
+ * A dialog the bank has ended is not used again: the call that learns of it fails, and the next call logs in anew. The
8
+ * client does not repeat the call by itself, since the bank's answer cannot tell a dialog that had ended before from an
9
+ * order it refuses along with ending the dialog, and a new login may cost the user another approval.
10
+ */
11
+ export declare class DialogRunner {
12
+ private readonly setup;
13
+ private dialog;
14
+ constructor(setup: SessionSetup);
15
+ /** The open dialog, or a new one once the setup is complete and the user has passed strong authentication. */
16
+ open(): Promise<SignedDialog>;
17
+ private login;
18
+ /** Ends the open dialog, if there is one. */
19
+ end(): Promise<void>;
20
+ /**
21
+ * Runs the query `build` returns in the session's dialog and reads all its pages.
22
+ *
23
+ * The query is built once the dialog is open, since building it takes the parameter data.
24
+ */
25
+ query<T>(build: () => Query<T>): Promise<T>;
26
+ /** Sends `order` in `dialog` and completes its approval, which only the `firstPage` of a query may ask for. */
27
+ private run;
28
+ /** Sends the order built by `order` until the bank has delivered all pages (Formals B.6.3, "Aufsetzpunkt"). */
29
+ private fetchPages;
30
+ }
31
+ //# sourceMappingURL=dialog-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-runner.d.ts","sourceRoot":"","sources":["../../src/session/dialog-runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAYvD;;;;;;GAMG;AACH,qBAAa,YAAY;IAGX,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,OAAO,CAAC,MAAM,CAA2B;gBAEZ,KAAK,EAAE,YAAY;IAEhD,8GAA8G;IACxG,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC;YASrB,KAAK;IAanB,6CAA6C;IACvC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAM1B;;;;OAIG;IACG,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAOjD,+GAA+G;YACjG,GAAG;IAYjB,+GAA+G;YACjG,UAAU;CAmCzB"}
@@ -0,0 +1,111 @@
1
+ import { FinTsBankError, FinTsProtocolError, parseBankData } from '../core/errors.js';
2
+ import { continuations, FeedbackCodes, hasCode } from '../core/feedback.js';
3
+ /** Most pages the client fetches for one query, far more than banks send. */
4
+ const maxPages = 1000;
5
+ /**
6
+ * Most data one query may hold, summed over its pages as `IncomingMessage3.size` measures it: the transport and the
7
+ * parser bound a single response only. The statements and transactions read from them have a budget of their own
8
+ * (`RecordBudget`).
9
+ */
10
+ const maxQueryMebibytes = 128;
11
+ /**
12
+ * Keeps one dialog open for all calls and runs the queries in it.
13
+ *
14
+ * A dialog the bank has ended is not used again: the call that learns of it fails, and the next call logs in anew. The
15
+ * client does not repeat the call by itself, since the bank's answer cannot tell a dialog that had ended before from an
16
+ * order it refuses along with ending the dialog, and a new login may cost the user another approval.
17
+ */
18
+ export class DialogRunner {
19
+ setup;
20
+ dialog;
21
+ constructor(setup) {
22
+ this.setup = setup;
23
+ }
24
+ /** The open dialog, or a new one once the setup is complete and the user has passed strong authentication. */
25
+ async open() {
26
+ const current = this.dialog;
27
+ if (current?.isActive === true)
28
+ return current;
29
+ this.dialog = undefined;
30
+ const dialog = await this.setup.remedied(() => this.login());
31
+ this.dialog = dialog;
32
+ return dialog;
33
+ }
34
+ async login() {
35
+ const dialog = await this.setup.newDialog();
36
+ try {
37
+ await dialog.login();
38
+ return dialog;
39
+ }
40
+ catch (error) {
41
+ // After a refused login the dialog is discarded already, and ending it sends nothing. After any other failure,
42
+ // such as a cancelled TAN, the bank still knows the dialog.
43
+ await dialog.end();
44
+ throw error;
45
+ }
46
+ }
47
+ /** Ends the open dialog, if there is one. */
48
+ async end() {
49
+ const dialog = this.dialog;
50
+ this.dialog = undefined;
51
+ await dialog?.end();
52
+ }
53
+ /**
54
+ * Runs the query `build` returns in the session's dialog and reads all its pages.
55
+ *
56
+ * The query is built once the dialog is open, since building it takes the parameter data.
57
+ */
58
+ async query(build) {
59
+ const dialog = await this.open();
60
+ const query = build();
61
+ const pages = await this.fetchPages(dialog, query.order);
62
+ return parseBankData(() => query.read(pages));
63
+ }
64
+ /** Sends `order` in `dialog` and completes its approval, which only the `firstPage` of a query may ask for. */
65
+ async run(dialog, order, firstPage) {
66
+ try {
67
+ return firstPage ? await dialog.send(order) : await dialog.sendFurtherPage(order);
68
+ }
69
+ catch (error) {
70
+ // The bank rejected the order and the dialog stays usable, or the bank ended the dialog or rejected the PIN and
71
+ // the dialog is discarded already. A cancelled or failed approval, or a broken connection, leaves the dialog in an
72
+ // unknown state.
73
+ if (!(error instanceof FinTsBankError))
74
+ await this.end();
75
+ throw error;
76
+ }
77
+ }
78
+ /** Sends the order built by `order` until the bank has delivered all pages (Formals B.6.3, "Aufsetzpunkt"). */
79
+ async fetchPages(dialog, order) {
80
+ const pages = [];
81
+ const seen = new Set();
82
+ let size = 0;
83
+ let continuation;
84
+ for (;;) {
85
+ const page = await this.run(dialog, order(continuation), continuation === undefined);
86
+ pages.push(page);
87
+ size += page.size;
88
+ if (size > maxQueryMebibytes * 1024 * 1024) {
89
+ throw new FinTsProtocolError(`The bank sends more than ${maxQueryMebibytes} MiB for one query; ask for a shorter period`);
90
+ }
91
+ const positions = [...continuations(page.feedback)];
92
+ // A message carries one order, so the bank names one position at most.
93
+ if (positions.length > 1)
94
+ throw new FinTsProtocolError('The bank names several positions to continue at');
95
+ continuation = positions[0];
96
+ if (continuation === undefined) {
97
+ // Read as the last page, more data without a position would look like all there is.
98
+ if (hasCode(page.feedback, FeedbackCodes.moreData)) {
99
+ throw new FinTsProtocolError('The bank has more data, but names no position to continue at');
100
+ }
101
+ return pages;
102
+ }
103
+ // A repeated position or endless pages would keep the client busy forever.
104
+ if (seen.has(continuation) || pages.length === maxPages) {
105
+ throw new FinTsProtocolError('The bank does not stop sending pages');
106
+ }
107
+ seen.add(continuation);
108
+ }
109
+ }
110
+ }
111
+ //# sourceMappingURL=dialog-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-runner.js","sourceRoot":"","sources":["../../src/session/dialog-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAO5E,6EAA6E;AAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC;AAEtB;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B;;;;;;GAMG;AACH,MAAM,OAAO,YAAY;IAGM;IAFrB,MAAM,CAA2B;IAEzC,YAA6B,KAAmB;QAAnB,UAAK,GAAL,KAAK,CAAc;IAAG,CAAC;IAEpD,8GAA8G;IAC9G,KAAK,CAAC,IAAI;QACR,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,IAAI,OAAO,EAAE,QAAQ,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC;QAC/C,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,+GAA+G;YAC/G,4DAA4D;YAC5D,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;YACnB,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,KAAK,CAAC,GAAG;QACP,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,MAAM,MAAM,EAAE,GAAG,EAAE,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAI,KAAqB;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACzD,OAAO,aAAa,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,+GAA+G;IACvG,KAAK,CAAC,GAAG,CAAC,MAAoB,EAAE,KAAkB,EAAE,SAAkB;QAC5E,IAAI,CAAC;YACH,OAAO,SAAS,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACpF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gHAAgH;YAChH,mHAAmH;YACnH,iBAAiB;YACjB,IAAI,CAAC,CAAC,KAAK,YAAY,cAAc,CAAC;gBAAE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;YACzD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,+GAA+G;IACvG,KAAK,CAAC,UAAU,CACtB,MAAoB,EACpB,KAAwD;QAExD,MAAM,KAAK,GAAuB,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,YAAgC,CAAC;QACrC,SAAS,CAAC;YACR,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,YAAY,CAAC,EAAE,YAAY,KAAK,SAAS,CAAC,CAAC;YACrF,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;YAClB,IAAI,IAAI,GAAG,iBAAiB,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;gBAC3C,MAAM,IAAI,kBAAkB,CAC1B,4BAA4B,iBAAiB,8CAA8C,CAC5F,CAAC;YACJ,CAAC;YACD,MAAM,SAAS,GAAG,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YACpD,uEAAuE;YACvE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM,IAAI,kBAAkB,CAAC,iDAAiD,CAAC,CAAC;YAC1G,YAAY,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,oFAAoF;gBACpF,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACnD,MAAM,IAAI,kBAAkB,CAAC,8DAA8D,CAAC,CAAC;gBAC/F,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC;YACD,2EAA2E;YAC3E,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACxD,MAAM,IAAI,kBAAkB,CAAC,sCAAsC,CAAC,CAAC;YACvE,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;CACF"}