@actual-app/sync-server 25.4.0-alpha.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 (137) hide show
  1. package/.dockerignore +12 -0
  2. package/README.md +19 -0
  3. package/app.js +11 -0
  4. package/babel.config.json +3 -0
  5. package/bin/@actual-app/sync-server +55 -0
  6. package/docker/alpine.Dockerfile +62 -0
  7. package/docker/ubuntu.Dockerfile +63 -0
  8. package/docker-compose.yml +29 -0
  9. package/jest.config.json +19 -0
  10. package/jest.global-setup.js +101 -0
  11. package/jest.global-teardown.js +6 -0
  12. package/migrations/1694360000000-create-folders.js +25 -0
  13. package/migrations/1694360479680-create-account-db.js +30 -0
  14. package/migrations/1694362247011-create-secret-table.js +16 -0
  15. package/migrations/1702667624000-rename-nordigen-secrets.js +19 -0
  16. package/migrations/1718889148000-openid.js +41 -0
  17. package/migrations/1719409568000-multiuser.js +116 -0
  18. package/package.json +64 -0
  19. package/src/account-db.js +239 -0
  20. package/src/accounts/openid.js +361 -0
  21. package/src/accounts/password.js +149 -0
  22. package/src/app-account.js +155 -0
  23. package/src/app-admin.js +410 -0
  24. package/src/app-admin.test.js +381 -0
  25. package/src/app-gocardless/README.md +198 -0
  26. package/src/app-gocardless/app-gocardless.js +274 -0
  27. package/src/app-gocardless/bank-factory.js +91 -0
  28. package/src/app-gocardless/banks/abanca_caglesmm.js +22 -0
  29. package/src/app-gocardless/banks/abnamro_abnanl2a.js +57 -0
  30. package/src/app-gocardless/banks/american_express_aesudef1.js +40 -0
  31. package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +31 -0
  32. package/src/app-gocardless/banks/bank.interface.ts +51 -0
  33. package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +39 -0
  34. package/src/app-gocardless/banks/bankinter_bkbkesmm.js +24 -0
  35. package/src/app-gocardless/banks/belfius_gkccbebb.js +17 -0
  36. package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +61 -0
  37. package/src/app-gocardless/banks/bnp_be_gebabebb.js +73 -0
  38. package/src/app-gocardless/banks/cbc_cregbebb.js +34 -0
  39. package/src/app-gocardless/banks/commerzbank_cobadeff.js +51 -0
  40. package/src/app-gocardless/banks/danskebank_dabno22.js +39 -0
  41. package/src/app-gocardless/banks/direkt_heladef1822.js +18 -0
  42. package/src/app-gocardless/banks/easybank_bawaatww.js +50 -0
  43. package/src/app-gocardless/banks/entercard_swednokk.js +40 -0
  44. package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +46 -0
  45. package/src/app-gocardless/banks/hype_hyeeit22.js +74 -0
  46. package/src/app-gocardless/banks/ing_ingbrobu.js +70 -0
  47. package/src/app-gocardless/banks/ing_ingddeff.js +47 -0
  48. package/src/app-gocardless/banks/ing_pl_ingbplpw.js +46 -0
  49. package/src/app-gocardless/banks/integration-bank.js +115 -0
  50. package/src/app-gocardless/banks/isybank_itbbitmm.js +18 -0
  51. package/src/app-gocardless/banks/kbc_kredbebb.js +33 -0
  52. package/src/app-gocardless/banks/lhv-lhvbee22.js +36 -0
  53. package/src/app-gocardless/banks/mbank_retail_brexplpw.js +56 -0
  54. package/src/app-gocardless/banks/nationwide_naiagb21.js +46 -0
  55. package/src/app-gocardless/banks/nbg_ethngraaxxx.js +51 -0
  56. package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +74 -0
  57. package/src/app-gocardless/banks/revolut_revolt21.js +37 -0
  58. package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +28 -0
  59. package/src/app-gocardless/banks/seb_kort_bank_ab.js +58 -0
  60. package/src/app-gocardless/banks/seb_privat.js +29 -0
  61. package/src/app-gocardless/banks/sparnord_spnodk22.js +24 -0
  62. package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +61 -0
  63. package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +30 -0
  64. package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +19 -0
  65. package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +50 -0
  66. package/src/app-gocardless/banks/swedbank_habalv22.js +47 -0
  67. package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +21 -0
  68. package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +61 -0
  69. package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +53 -0
  70. package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +22 -0
  71. package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +34 -0
  72. package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +110 -0
  73. package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +54 -0
  74. package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +206 -0
  75. package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +302 -0
  76. package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +202 -0
  77. package/src/app-gocardless/banks/tests/integration_bank.spec.js +158 -0
  78. package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +38 -0
  79. package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +68 -0
  80. package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +171 -0
  81. package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +105 -0
  82. package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +48 -0
  83. package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +42 -0
  84. package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +133 -0
  85. package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +256 -0
  86. package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +102 -0
  87. package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +57 -0
  88. package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +54 -0
  89. package/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +36 -0
  90. package/src/app-gocardless/banks/virgin_nrnbgb22.js +39 -0
  91. package/src/app-gocardless/errors.js +84 -0
  92. package/src/app-gocardless/gocardless-node.types.ts +497 -0
  93. package/src/app-gocardless/gocardless.types.ts +93 -0
  94. package/src/app-gocardless/link.html +18 -0
  95. package/src/app-gocardless/services/gocardless-service.js +620 -0
  96. package/src/app-gocardless/services/tests/fixtures.js +181 -0
  97. package/src/app-gocardless/services/tests/gocardless-service.spec.js +537 -0
  98. package/src/app-gocardless/tests/bank-factory.spec.js +20 -0
  99. package/src/app-gocardless/tests/utils.spec.js +162 -0
  100. package/src/app-gocardless/util/handle-error.js +16 -0
  101. package/src/app-gocardless/utils.js +45 -0
  102. package/src/app-openid.js +108 -0
  103. package/src/app-pluggyai/app-pluggyai.js +215 -0
  104. package/src/app-pluggyai/pluggyai-service.js +120 -0
  105. package/src/app-secrets.js +61 -0
  106. package/src/app-simplefin/app-simplefin.js +418 -0
  107. package/src/app-sync/errors.js +13 -0
  108. package/src/app-sync/services/files-service.js +243 -0
  109. package/src/app-sync/tests/services/files-service.test.js +250 -0
  110. package/src/app-sync/validation.js +77 -0
  111. package/src/app-sync.js +391 -0
  112. package/src/app-sync.test.js +877 -0
  113. package/src/app.js +145 -0
  114. package/src/config-types.ts +44 -0
  115. package/src/db.js +58 -0
  116. package/src/load-config.js +307 -0
  117. package/src/migrations.js +36 -0
  118. package/src/run-migrations.js +8 -0
  119. package/src/scripts/disable-openid.js +44 -0
  120. package/src/scripts/enable-openid.js +53 -0
  121. package/src/scripts/health-check.js +23 -0
  122. package/src/scripts/reset-password.js +51 -0
  123. package/src/secrets.test.js +83 -0
  124. package/src/services/secrets-service.js +94 -0
  125. package/src/services/user-service.js +272 -0
  126. package/src/sql/messages.sql +9 -0
  127. package/src/sync-simple.js +95 -0
  128. package/src/util/hash.js +5 -0
  129. package/src/util/middlewares.js +62 -0
  130. package/src/util/paths.js +13 -0
  131. package/src/util/payee-name.js +45 -0
  132. package/src/util/prompt.js +88 -0
  133. package/src/util/title/index.js +59 -0
  134. package/src/util/title/lower-case.js +93 -0
  135. package/src/util/title/specials.js +21 -0
  136. package/src/util/validate-user.js +68 -0
  137. package/tsconfig.json +21 -0
@@ -0,0 +1,61 @@
1
+ import express from 'express';
2
+
3
+ import { getAccountDb, isAdmin } from './account-db.js';
4
+ import { secretsService } from './services/secrets-service.js';
5
+ import {
6
+ requestLoggerMiddleware,
7
+ validateSessionMiddleware,
8
+ } from './util/middlewares.js';
9
+
10
+ const app = express();
11
+
12
+ export { app as handlers };
13
+ app.use(express.json());
14
+ app.use(requestLoggerMiddleware);
15
+ app.use(validateSessionMiddleware);
16
+
17
+ app.post('/', async (req, res) => {
18
+ let method;
19
+ try {
20
+ const result = getAccountDb().first(
21
+ 'SELECT method FROM auth WHERE active = 1',
22
+ );
23
+ method = result?.method;
24
+ } catch (error) {
25
+ console.error('Failed to fetch auth method:', error);
26
+ return res.status(500).send({
27
+ status: 'error',
28
+ reason: 'database-error',
29
+ details: 'Failed to validate authentication method',
30
+ });
31
+ }
32
+ const { name, value } = req.body;
33
+
34
+ if (method === 'openid') {
35
+ const canSaveSecrets = isAdmin(res.locals.user_id);
36
+
37
+ if (!canSaveSecrets) {
38
+ res.status(403).send({
39
+ status: 'error',
40
+ reason: 'not-admin',
41
+ details: 'You have to be admin to set secrets',
42
+ });
43
+
44
+ return;
45
+ }
46
+ }
47
+
48
+ secretsService.set(name, value);
49
+
50
+ res.status(200).send({ status: 'ok' });
51
+ });
52
+
53
+ app.get('/:name', async (req, res) => {
54
+ const name = req.params.name;
55
+ const keyExists = secretsService.exists(name);
56
+ if (keyExists) {
57
+ res.sendStatus(204);
58
+ } else {
59
+ res.status(404).send('key not found');
60
+ }
61
+ });
@@ -0,0 +1,418 @@
1
+ import https from 'https';
2
+
3
+ import express from 'express';
4
+
5
+ import { handleError } from '../app-gocardless/util/handle-error.js';
6
+ import { SecretName, secretsService } from '../services/secrets-service.js';
7
+ import { requestLoggerMiddleware } from '../util/middlewares.js';
8
+
9
+ const app = express();
10
+ export { app as handlers };
11
+ app.use(express.json());
12
+ app.use(requestLoggerMiddleware);
13
+
14
+ app.post(
15
+ '/status',
16
+ handleError(async (req, res) => {
17
+ const token = secretsService.get(SecretName.simplefin_token);
18
+ const configured = token != null && token !== 'Forbidden';
19
+
20
+ res.send({
21
+ status: 'ok',
22
+ data: {
23
+ configured,
24
+ },
25
+ });
26
+ }),
27
+ );
28
+
29
+ app.post(
30
+ '/accounts',
31
+ handleError(async (req, res) => {
32
+ let accessKey = secretsService.get(SecretName.simplefin_accessKey);
33
+
34
+ try {
35
+ if (accessKey == null || accessKey === 'Forbidden') {
36
+ const token = secretsService.get(SecretName.simplefin_token);
37
+ if (token == null || token === 'Forbidden') {
38
+ throw new Error('No token');
39
+ } else {
40
+ accessKey = await getAccessKey(token);
41
+ secretsService.set(SecretName.simplefin_accessKey, accessKey);
42
+ if (accessKey == null || accessKey === 'Forbidden') {
43
+ throw new Error('No access key');
44
+ }
45
+ }
46
+ }
47
+ } catch {
48
+ invalidToken(res);
49
+ return;
50
+ }
51
+
52
+ try {
53
+ const accounts = await getAccounts(accessKey, null, null, null, true);
54
+
55
+ res.send({
56
+ status: 'ok',
57
+ data: {
58
+ accounts: accounts.accounts,
59
+ },
60
+ });
61
+ } catch (e) {
62
+ serverDown(e, res);
63
+ return;
64
+ }
65
+ }),
66
+ );
67
+
68
+ app.post(
69
+ '/transactions',
70
+ handleError(async (req, res) => {
71
+ const { accountId, startDate } = req.body;
72
+
73
+ const accessKey = secretsService.get(SecretName.simplefin_accessKey);
74
+
75
+ if (accessKey == null || accessKey === 'Forbidden') {
76
+ invalidToken(res);
77
+ return;
78
+ }
79
+
80
+ if (Array.isArray(accountId) !== Array.isArray(startDate)) {
81
+ console.log({ accountId, startDate });
82
+ throw new Error(
83
+ 'accountId and startDate must either both be arrays or both be strings',
84
+ );
85
+ }
86
+ if (Array.isArray(accountId) && accountId.length !== startDate.length) {
87
+ console.log({ accountId, startDate });
88
+ throw new Error('accountId and startDate arrays must be the same length');
89
+ }
90
+
91
+ const earliestStartDate = Array.isArray(startDate)
92
+ ? startDate.reduce((a, b) => (a < b ? a : b))
93
+ : startDate;
94
+ let results;
95
+ try {
96
+ results = await getTransactions(
97
+ accessKey,
98
+ Array.isArray(accountId) ? accountId : [accountId],
99
+ new Date(earliestStartDate),
100
+ );
101
+ } catch (e) {
102
+ if (e.message === 'Forbidden') {
103
+ invalidToken(res);
104
+ } else {
105
+ serverDown(e, res);
106
+ }
107
+ return;
108
+ }
109
+
110
+ let response = {};
111
+ if (Array.isArray(accountId)) {
112
+ for (let i = 0; i < accountId.length; i++) {
113
+ const id = accountId[i];
114
+ response[id] = getAccountResponse(results, id, new Date(startDate[i]));
115
+ }
116
+ } else {
117
+ response = getAccountResponse(results, accountId, new Date(startDate));
118
+ }
119
+
120
+ if (results.hasError) {
121
+ res.send({
122
+ status: 'ok',
123
+ data: !Array.isArray(accountId)
124
+ ? results.errors[accountId][0]
125
+ : {
126
+ ...response,
127
+ errors: results.errors,
128
+ },
129
+ });
130
+ return;
131
+ }
132
+
133
+ res.send({
134
+ status: 'ok',
135
+ data: response,
136
+ });
137
+ }),
138
+ );
139
+
140
+ function logAccountError(results, accountId, data) {
141
+ const errors = results.errors[accountId] || [];
142
+ errors.push(data);
143
+ results.errors[accountId] = errors;
144
+ results.hasError = true;
145
+ }
146
+
147
+ function getAccountResponse(results, accountId, startDate) {
148
+ const account =
149
+ !results?.accounts || results.accounts.find(a => a.id === accountId);
150
+ if (!account) {
151
+ console.log(
152
+ `The account "${accountId}" was not found. Here were the accounts returned:`,
153
+ );
154
+ if (results?.accounts) {
155
+ results.accounts.forEach(a => console.log(`${a.id} - ${a.org.name}`));
156
+ }
157
+ logAccountError(results, accountId, {
158
+ error_type: 'ACCOUNT_MISSING',
159
+ error_code: 'ACCOUNT_MISSING',
160
+ reason: `The account "${accountId}" was not found. Try unlinking and relinking the account.`,
161
+ });
162
+ return;
163
+ }
164
+
165
+ const needsAttention = results.sferrors.find(
166
+ e => e === `Connection to ${account.org.name} may need attention`,
167
+ );
168
+ if (needsAttention) {
169
+ logAccountError(results, accountId, {
170
+ error_type: 'ACCOUNT_NEEDS_ATTENTION',
171
+ error_code: 'ACCOUNT_NEEDS_ATTENTION',
172
+ reason:
173
+ 'The account needs your attention at <a href="https://bridge.simplefin.org/auth/login">SimpleFIN</a>.',
174
+ });
175
+ }
176
+
177
+ const startingBalance = parseInt(account.balance.replace('.', ''));
178
+ const date = getDate(new Date(account['balance-date'] * 1000));
179
+
180
+ const balances = [
181
+ {
182
+ balanceAmount: {
183
+ amount: account.balance,
184
+ currency: account.currency,
185
+ },
186
+ balanceType: 'expected',
187
+ referenceDate: date,
188
+ },
189
+ {
190
+ balanceAmount: {
191
+ amount: account.balance,
192
+ currency: account.currency,
193
+ },
194
+ balanceType: 'interimAvailable',
195
+ referenceDate: date,
196
+ },
197
+ ];
198
+
199
+ const all = [];
200
+ const booked = [];
201
+ const pending = [];
202
+
203
+ for (const trans of account.transactions) {
204
+ const newTrans = {};
205
+
206
+ let dateToUse = 0;
207
+
208
+ if (trans.pending ?? trans.posted === 0) {
209
+ newTrans.booked = false;
210
+ dateToUse = trans.transacted_at;
211
+ } else {
212
+ newTrans.booked = true;
213
+ dateToUse = trans.posted;
214
+ }
215
+
216
+ const transactionDate = new Date(dateToUse * 1000);
217
+
218
+ if (transactionDate < startDate) {
219
+ continue;
220
+ }
221
+
222
+ newTrans.sortOrder = dateToUse;
223
+ newTrans.date = getDate(transactionDate);
224
+ newTrans.payeeName = trans.payee;
225
+ newTrans.notes = trans.description;
226
+ newTrans.transactionAmount = { amount: trans.amount, currency: 'USD' };
227
+ newTrans.transactionId = trans.id;
228
+ newTrans.valueDate = newTrans.bookingDate;
229
+
230
+ if (trans.transacted_at) {
231
+ newTrans.transactedDate = getDate(new Date(trans.transacted_at * 1000));
232
+ }
233
+
234
+ if (trans.posted) {
235
+ newTrans.postedDate = getDate(new Date(trans.posted * 1000));
236
+ }
237
+
238
+ if (newTrans.booked) {
239
+ booked.push(newTrans);
240
+ } else {
241
+ pending.push(newTrans);
242
+ }
243
+ all.push(newTrans);
244
+ }
245
+
246
+ const sortFunction = (a, b) => b.sortOrder - a.sortOrder;
247
+
248
+ const bookedSorted = booked.sort(sortFunction);
249
+ const pendingSorted = pending.sort(sortFunction);
250
+ const allSorted = all.sort(sortFunction);
251
+
252
+ return {
253
+ balances,
254
+ startingBalance,
255
+ transactions: {
256
+ all: allSorted,
257
+ booked: bookedSorted,
258
+ pending: pendingSorted,
259
+ },
260
+ };
261
+ }
262
+
263
+ function invalidToken(res) {
264
+ res.send({
265
+ status: 'ok',
266
+ data: {
267
+ error_type: 'INVALID_ACCESS_TOKEN',
268
+ error_code: 'INVALID_ACCESS_TOKEN',
269
+ status: 'rejected',
270
+ reason:
271
+ 'Invalid SimpleFIN access token. Reset the token and re-link any broken accounts.',
272
+ },
273
+ });
274
+ }
275
+
276
+ function serverDown(e, res) {
277
+ console.log(e);
278
+ res.send({
279
+ status: 'ok',
280
+ data: {
281
+ error_type: 'SERVER_DOWN',
282
+ error_code: 'SERVER_DOWN',
283
+ status: 'rejected',
284
+ reason: 'There was an error communicating with SimpleFIN.',
285
+ },
286
+ });
287
+ }
288
+
289
+ function parseAccessKey(accessKey) {
290
+ let scheme = null;
291
+ let rest = null;
292
+ let auth = null;
293
+ let username = null;
294
+ let password = null;
295
+ let baseUrl = null;
296
+ if (!accessKey || !accessKey.match(/^.*\/\/.*:.*@.*$/)) {
297
+ console.log(`Invalid SimpleFIN access key: ${accessKey}`);
298
+ throw new Error(`Invalid access key`);
299
+ }
300
+ [scheme, rest] = accessKey.split('//');
301
+ [auth, rest] = rest.split('@');
302
+ [username, password] = auth.split(':');
303
+ baseUrl = `${scheme}//${rest}`;
304
+ return {
305
+ baseUrl,
306
+ username,
307
+ password,
308
+ };
309
+ }
310
+
311
+ async function getAccessKey(base64Token) {
312
+ const token = Buffer.from(base64Token, 'base64').toString();
313
+ const options = {
314
+ method: 'POST',
315
+ port: 443,
316
+ headers: { 'Content-Length': 0 },
317
+ };
318
+ return new Promise((resolve, reject) => {
319
+ const req = https.request(new URL(token), options, res => {
320
+ res.on('data', d => {
321
+ resolve(d.toString());
322
+ });
323
+ });
324
+ req.on('error', e => {
325
+ reject(e);
326
+ });
327
+ req.end();
328
+ });
329
+ }
330
+
331
+ async function getTransactions(accessKey, accounts, startDate, endDate) {
332
+ const now = new Date();
333
+ startDate = startDate || new Date(now.getFullYear(), now.getMonth(), 1);
334
+ endDate = endDate || new Date(now.getFullYear(), now.getMonth() + 1, 1);
335
+ console.log(`${getDate(startDate)} - ${getDate(endDate)}`);
336
+ return await getAccounts(accessKey, accounts, startDate, endDate);
337
+ }
338
+
339
+ function getDate(date) {
340
+ return date.toISOString().split('T')[0];
341
+ }
342
+
343
+ function normalizeDate(date) {
344
+ return (date.valueOf() - date.getTimezoneOffset() * 60 * 1000) / 1000;
345
+ }
346
+
347
+ async function getAccounts(
348
+ accessKey,
349
+ accounts,
350
+ startDate,
351
+ endDate,
352
+ noTransactions = false,
353
+ ) {
354
+ const sfin = parseAccessKey(accessKey);
355
+ const options = {
356
+ headers: {
357
+ Authorization: `Basic ${Buffer.from(
358
+ `${sfin.username}:${sfin.password}`,
359
+ ).toString('base64')}`,
360
+ },
361
+ };
362
+ const params = [];
363
+ if (!noTransactions) {
364
+ if (startDate) {
365
+ params.push(`start-date=${normalizeDate(startDate)}`);
366
+ }
367
+ if (endDate) {
368
+ params.push(`end-date=${normalizeDate(endDate)}`);
369
+ }
370
+
371
+ params.push(`pending=1`);
372
+ } else {
373
+ params.push(`balances-only=1`);
374
+ }
375
+
376
+ if (accounts) {
377
+ accounts.forEach(id => {
378
+ params.push(`account=${encodeURIComponent(id)}`);
379
+ });
380
+ }
381
+
382
+ let queryString = '';
383
+ if (params.length > 0) {
384
+ queryString += '?' + params.join('&');
385
+ }
386
+ return new Promise((resolve, reject) => {
387
+ const req = https.request(
388
+ new URL(`${sfin.baseUrl}/accounts${queryString}`),
389
+ options,
390
+ res => {
391
+ let data = '';
392
+ res.on('data', d => {
393
+ data += d;
394
+ });
395
+ res.on('end', () => {
396
+ if (res.statusCode === 403) {
397
+ reject(new Error('Forbidden'));
398
+ } else {
399
+ try {
400
+ const results = JSON.parse(data);
401
+ results.sferrors = results.errors;
402
+ results.hasError = false;
403
+ results.errors = {};
404
+ resolve(results);
405
+ } catch (e) {
406
+ console.log(`Error parsing JSON response: ${data}`);
407
+ reject(e);
408
+ }
409
+ }
410
+ });
411
+ },
412
+ );
413
+ req.on('error', e => {
414
+ reject(e);
415
+ });
416
+ req.end();
417
+ });
418
+ }
@@ -0,0 +1,13 @@
1
+ export class FileNotFound extends Error {
2
+ constructor(params = {}) {
3
+ super("File does not exist or you don't have access to it");
4
+ this.details = params;
5
+ }
6
+ }
7
+
8
+ export class GenericFileError extends Error {
9
+ constructor(message, params = {}) {
10
+ super(message);
11
+ this.details = params;
12
+ }
13
+ }