backend-manager 2.5.124 → 3.0.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.
@@ -0,0 +1,733 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const _ = require('lodash');
4
+ const uuid = require('uuid');
5
+ let JSON5;
6
+
7
+ function BackendAssistant() {
8
+ this.meta = {};
9
+ this.initialized = false;
10
+ }
11
+
12
+ function tryParse(input) {
13
+ var ret;
14
+
15
+ JSON5 = JSON5 || require('json5');
16
+
17
+ try {
18
+ ret = JSON5.parse(input);
19
+ } catch (e) {
20
+ ret = input
21
+ }
22
+ return ret;
23
+ }
24
+
25
+ BackendAssistant.prototype.init = function (ref, options) {
26
+ const self = this;
27
+
28
+ options = options || {};
29
+ options.accept = options.accept || 'json';
30
+ options.showOptionsLog = typeof options.showOptionsLog !== 'undefined' ? options.showOptionsLog : false;
31
+ options.optionsLogString = typeof options.optionsLogString !== 'undefined' ? options.optionsLogString : '\n\n\n\n\n';
32
+ options.fileSavePath = options.fileSavePath || process.env.npm_package_name || '';
33
+
34
+ const now = new Date();
35
+
36
+ self.meta = {};
37
+
38
+ self.meta.startTime = {};
39
+ self.meta.startTime.timestamp = now.toISOString();
40
+ self.meta.startTime.timestampUNIX = Math.round((now.getTime()) / 1000);
41
+
42
+ self.meta.name = options.functionName || process.env.FUNCTION_TARGET || 'unnamed';
43
+ self.meta.environment = options.environment || self.getEnvironment();
44
+ self.meta.type = options.functionType || process.env.FUNCTION_SIGNATURE_TYPE || 'unknown';
45
+
46
+ self.ref = {};
47
+ ref = ref || {};
48
+ self.ref.res = ref.res || {};
49
+ self.ref.req = ref.req || {};
50
+ self.ref.admin = ref.admin || {};
51
+ self.ref.functions = ref.functions || {};
52
+ self.ref.Manager = ref.Manager || {};
53
+
54
+ // Set ID
55
+ try {
56
+ self.id = self.ref.Manager.Utilities().randomId();
57
+ } catch {
58
+ self.id = now.getTime();
59
+ }
60
+
61
+ // Set stuff about request
62
+ self.request = {};
63
+ self.request.referrer = (self.ref.req.headers || {}).referrer || (self.ref.req.headers || {}).referer || '';
64
+ self.request.method = (self.ref.req.method || undefined);
65
+
66
+ // Set geolocation data
67
+ self.request.geolocation = {
68
+ ip: self.getHeaderIp(self.ref.req.headers),
69
+ continent: self.getHeaderContinent(self.ref.req.headers),
70
+ country: self.getHeaderCountry(self.ref.req.headers),
71
+ region: self.getHeaderRegion(self.ref.req.headers),
72
+ city: self.getHeaderCity(self.ref.req.headers),
73
+ latitude: self.getHeaderLatitude(self.ref.req.headers),
74
+ longitude: self.getHeaderLongitude(self.ref.req.headers),
75
+ };
76
+
77
+ // Set client data
78
+ self.request.client = {
79
+ userAgent: self.getHeaderUserAgent(self.ref.req.headers),
80
+ language: self.getHeaderLanguage(self.ref.req.headers),
81
+ platform: self.getHeaderPlatform(self.ref.req.headers),
82
+ mobile: self.getHeaderMobile(self.ref.req.headers),
83
+ };
84
+
85
+ // Deprecated notice for old properties
86
+ Object.defineProperty(self.request, 'ip', {
87
+ get: function() {
88
+ console.error('⛔️ [Deprecation]: request.ip is deprecated, use request.geolocation.ip instead');
89
+ return self.request.geolocation.ip;
90
+ }
91
+ });
92
+ Object.defineProperty(self.request, 'country', {
93
+ get: function() {
94
+ console.error('⛔️ [Deprecation]: request.country is deprecated, use request.geolocation.country instead');
95
+ return self.request.geolocation.country;
96
+ }
97
+ });
98
+ Object.defineProperty(self.request, 'userAgent', {
99
+ get: function() {
100
+ console.error('⛔️ [Deprecation]: request.userAgent is deprecated, use request.client.userAgent instead');
101
+ return self.request.client.userAgent;
102
+ }
103
+ });
104
+
105
+ /*
106
+ MORE HEADERS TO GET
107
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Platform-Version
108
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Model
109
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Mobile
110
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version-List
111
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version
112
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Arch
113
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA
114
+ */
115
+
116
+ self.request.type = (self.ref.req.xhr || _.get(self.ref.req, 'headers.accept', '').indexOf('json') > -1) || (_.get(self.ref.req, 'headers.content-type', '').indexOf('json') > -1) ? 'ajax' : 'form';
117
+ self.request.path = (self.ref.req.path || '');
118
+ self.request.user = self.resolveAccount({authenticated: false});
119
+ if (options.accept === 'json') {
120
+ self.request.body = tryParse(self.ref.req.body || '{}');
121
+ self.request.query = tryParse(self.ref.req.query || '{}');
122
+ }
123
+
124
+ self.request.headers = (self.ref.req.headers || {});
125
+ self.request.data = Object.assign(
126
+ {},
127
+ _.cloneDeep(self.request.body || {}),
128
+ _.cloneDeep(self.request.query || {})
129
+ );
130
+ self.request.multipartData = {
131
+ fields: {},
132
+ files: {},
133
+ };
134
+
135
+ // Log the request
136
+ // if (Object.keys(self.request.data).length > 0) {
137
+ // self.log('Request:', self.request.data, {
138
+ // ip: self.request.ip,
139
+
140
+ // }, {environment: 'production'});
141
+ // }
142
+
143
+ // Constants
144
+ self.constant = {};
145
+ self.constant.pastTime = {};
146
+ self.constant.pastTime.timestamp = '1999-01-01T00:00:00Z';
147
+ self.constant.pastTime.timestampUNIX = 915148800;
148
+
149
+ if (
150
+ (self.meta.environment === 'development')
151
+ && ((self.request.method !== 'OPTIONS') || (self.request.method === 'OPTIONS' && options.showOptionsLog))
152
+ && (self.request.method !== 'undefined')
153
+ // && (self.request.method !== 'undefined' && typeof self.request.method !== 'undefined')
154
+ ) {
155
+ console.log(options.optionsLogString);
156
+ }
157
+
158
+ self.tmpdir = path.resolve(os.tmpdir(), options.fileSavePath, uuid.v4());
159
+
160
+ self.initialized = true;
161
+
162
+ return self;
163
+ };
164
+
165
+ BackendAssistant.prototype.getEnvironment = function () {
166
+ // return (process.env.FUNCTIONS_EMULATOR === true || process.env.FUNCTIONS_EMULATOR === 'true' || process.env.ENVIRONMENT !== 'production' ? 'development' : 'production')
167
+ if (process.env.ENVIRONMENT === 'production') {
168
+ return 'production';
169
+ } else if (
170
+ process.env.ENVIRONMENT === 'development'
171
+ || process.env.FUNCTIONS_EMULATOR === true
172
+ || process.env.FUNCTIONS_EMULATOR === 'true'
173
+ || process.env.TERM_PROGRAM === 'Apple_Terminal'
174
+ || process.env.TERM_PROGRAM === 'vscode'
175
+ ) {
176
+ return 'development';
177
+ } else {
178
+ return 'production'
179
+ }
180
+ };
181
+
182
+ BackendAssistant.prototype.logProd = function () {
183
+ const self = this;
184
+
185
+ self._log.apply(self, args);
186
+ };
187
+
188
+ BackendAssistant.prototype.log = function () {
189
+ const self = this;
190
+
191
+ let args = Array.prototype.slice.call(arguments);
192
+ let last = args[args.length - 1];
193
+ let override = last && typeof last === 'object' && last.environment === 'production';
194
+
195
+ if (self.meta.environment === 'development' || override) {
196
+ if (override) {
197
+ args.pop();
198
+ }
199
+ self._log.apply(self, args);
200
+ }
201
+ };
202
+
203
+ BackendAssistant.prototype.error = function () {
204
+ const self = this;
205
+
206
+ let args = Array.prototype.slice.call(arguments);
207
+
208
+ args.unshift('error');
209
+ self.log.apply(self, args);
210
+ };
211
+
212
+ BackendAssistant.prototype.warn = function () {
213
+ const self = this;
214
+
215
+ let args = Array.prototype.slice.call(arguments);
216
+
217
+ args.unshift('warn');
218
+ self.log.apply(self, args);
219
+ };
220
+
221
+ BackendAssistant.prototype._log = function() {
222
+ const self = this;
223
+
224
+ // 1. Convert args to a normal array
225
+ let logs = [...Array.prototype.slice.call(arguments)];
226
+
227
+ // 2. Prepend log prefix log string
228
+ logs.unshift(`[${self.meta.name}/${self.id} @ ${new Date().toISOString()}]:`);
229
+
230
+ // 3. Pass along arguments to console.log
231
+ if (logs[1] === 'error') {
232
+ logs.splice(1,1)
233
+ console.error.apply(console, logs);
234
+ } else if (logs[1] === 'warn') {
235
+ logs.splice(1,1)
236
+ console.warn.apply(console, logs);
237
+ } else if (logs[1] === 'log') {
238
+ logs.splice(1,1)
239
+ console.log.apply(console, logs);
240
+ } else {
241
+ console.log.apply(console, logs);
242
+ }
243
+ }
244
+
245
+ BackendAssistant.prototype.errorManager = function(e, options) {
246
+ const self = this;
247
+
248
+ options = options || {};
249
+ options.code = typeof options.code === 'undefined' ? 500 : options.code;
250
+ options.log = typeof options.log === 'undefined' ? true : options.log;
251
+ options.sentry = typeof options.sentry === 'undefined' ? true : options.sentry;
252
+ options.send = typeof options.send === 'undefined' ? true : options.send;
253
+
254
+ const newError = e instanceof Error ? e : new Error(e);
255
+
256
+ // Attach properties
257
+ Object.keys(options)
258
+ .forEach((item, i) => {
259
+ Object.assign(newError , { [item]: options[item] })
260
+ });
261
+
262
+
263
+ // Log the error
264
+ if (options.log) {
265
+ self.error(newError);
266
+ }
267
+
268
+ // Send error to Sentry
269
+ if (options.sentry) {
270
+ self.ref.Manager.libraries.sentry.captureException(newError);
271
+ }
272
+
273
+ // Quit and respond to the request
274
+ if (options.send && self.ref.res && self.ref.res.status) {
275
+ self.ref.res.status(options.code).send(newError ? newError.message || newError : 'Unknown error');
276
+ }
277
+
278
+ return {
279
+ error: newError,
280
+ }
281
+ }
282
+
283
+
284
+ BackendAssistant.prototype.authenticate = async function (options) {
285
+ const self = this;
286
+
287
+ let admin = self.ref.admin;
288
+ let functions = self.ref.functions;
289
+ let req = self.ref.req;
290
+ let res = self.ref.res;
291
+ let data = self.request.data;
292
+ let idToken;
293
+
294
+ options = options || {};
295
+ options.resolve = typeof options.resolve === 'undefined' ? true : options.resolve;
296
+
297
+ const logOptions = {environment: options.log ? 'production' : 'development'}
298
+
299
+ function _resolve(user) {
300
+ user = user || {};
301
+ user.authenticated = typeof user.authenticated === 'undefined'
302
+ ? false
303
+ : user.authenticated;
304
+
305
+ if (options.resolve) {
306
+ self.request.user = self.resolveAccount(user);
307
+ return self.request.user;
308
+ } else {
309
+ return user;
310
+ }
311
+ }
312
+
313
+ if (req.headers && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
314
+ // Read the ID Token from the Authorization header.
315
+ idToken = req.headers.authorization.split('Bearer ')[1];
316
+ self.log('Found "Authorization" header', idToken, logOptions);
317
+ } else if (req.cookies && req.cookies.__session) {
318
+ // Read the ID Token from cookie.
319
+ idToken = req.cookies.__session;
320
+ self.log('Found "__session" cookie', idToken, logOptions);
321
+ } else if (data.backendManagerKey || data.authenticationToken) {
322
+ // Check with custom BEM Token
323
+ let storedApiKey;
324
+ try {
325
+ const workingConfig = _.get(self.ref.Manager, 'config') || functions.config();
326
+ storedApiKey = _.get(workingConfig, 'backend_manager.key', '')
327
+ } catch (e) {
328
+
329
+ }
330
+
331
+ idToken = data.backendManagerKey || data.authenticationToken;
332
+
333
+ self.log('Found "backendManagerKey" or "authenticationToken" parameter', {storedApiKey: storedApiKey, idToken: idToken}, logOptions);
334
+
335
+ if (storedApiKey && (storedApiKey === data.backendManagerKey || storedApiKey === data.authenticationToken)) {
336
+ self.request.user.authenticated = true;
337
+ self.request.user.roles.admin = true;
338
+ return _resolve(self.request.user);
339
+ }
340
+ } else if (options.apiKey) {
341
+ self.log('Found "options.apiKey"', options.apiKey, logOptions);
342
+ if (options.apiKey.includes('test')) {
343
+ return _resolve(self.request.user);
344
+ }
345
+ await admin.firestore().collection(`users`)
346
+ .where('api.privateKey', '==', options.apiKey)
347
+ .get()
348
+ .then(function(querySnapshot) {
349
+ querySnapshot.forEach(function(doc) {
350
+ self.request.user = doc.data();
351
+ self.request.user.authenticated = true;
352
+ });
353
+ })
354
+ .catch(function(error) {
355
+ console.error('Error getting documents: ', error);
356
+ });
357
+
358
+ return _resolve(self.request.user);
359
+ } else {
360
+ self.log('No Firebase ID token was able to be extracted.',
361
+ 'Make sure you authenticate your request by providing either the following HTTP header:',
362
+ 'Authorization: Bearer <Firebase ID Token>',
363
+ 'or by passing a "__session" cookie',
364
+ 'or by passing backendManagerKey or authenticationToken in the body or query', logOptions);
365
+
366
+ return _resolve(self.request.user);
367
+ }
368
+
369
+ // Check with firebase
370
+ try {
371
+ const decodedIdToken = await admin.auth().verifyIdToken(idToken);
372
+ if (options.debug) {
373
+ self.log('Token correctly decoded', decodedIdToken.email, decodedIdToken.user_id, logOptions);
374
+ }
375
+ await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
376
+ .get()
377
+ .then(async function (doc) {
378
+ if (doc.exists) {
379
+ self.request.user = Object.assign({}, self.request.user, doc.data());
380
+ }
381
+ self.request.user.authenticated = true;
382
+ self.request.user.auth.uid = decodedIdToken.user_id;
383
+ self.request.user.auth.email = decodedIdToken.email;
384
+ if (options.debug) {
385
+ self.log('Found user doc', self.request.user, logOptions)
386
+ }
387
+ })
388
+ return _resolve(self.request.user);
389
+ } catch (error) {
390
+ self.error('Error while verifying Firebase ID token:', error, logOptions);
391
+ return _resolve(self.request.user);
392
+ }
393
+ };
394
+
395
+ BackendAssistant.prototype.resolveAccount = function (user) {
396
+ const ResolveAccount = new (require('resolve-account'))();
397
+
398
+ return ResolveAccount.resolve(undefined, user)
399
+ }
400
+
401
+ BackendAssistant.prototype.parseRepo = function (repo) {
402
+ let repoSplit = repo.split('/');
403
+ for (var i = 0; i < repoSplit.length; i++) {
404
+ repoSplit[i] = repoSplit[i].replace('.git', '');
405
+ }
406
+ repoSplit = repoSplit.filter(function(value, index, arr){
407
+ return value !== 'http:' &&
408
+ value !== 'https:' &&
409
+ value !== '' &&
410
+ value !== 'github.com';
411
+ });
412
+ return {
413
+ user: repoSplit[0],
414
+ name: repoSplit[1],
415
+ }
416
+ };
417
+
418
+ BackendAssistant.prototype.getHeaderIp = function (headers) {
419
+ headers = headers || {};
420
+
421
+ return (
422
+ // these are present for cloudflare requests (11/21/2020)
423
+ headers['cf-connecting-ip']
424
+ || headers['fastly-temp-xff']
425
+
426
+ // these are present for non-cloudflare requests (11/21/2020)
427
+ || headers['x-appengine-user-ip']
428
+ || headers['x-forwarded-for']
429
+
430
+ // Not sure about these
431
+ // || headers['fastly-client-ip']
432
+
433
+ // If unsure, return local IP
434
+ || '127.0.0.1'
435
+ )
436
+ .split(',')[0]
437
+ .trim();
438
+ }
439
+
440
+ BackendAssistant.prototype.getHeaderContinent = function (headers) {
441
+ headers = headers || {};
442
+
443
+ return (
444
+ // these are present for cloudflare requests (11/21/2020)
445
+ headers['cf-ipcontinent']
446
+
447
+ // If unsure, return ZZ
448
+ || 'ZZ'
449
+ )
450
+ .split(',')[0]
451
+ .trim();
452
+ }
453
+
454
+ BackendAssistant.prototype.getHeaderCountry = function (headers) {
455
+ headers = headers || {};
456
+
457
+ return (
458
+ // these are present for cloudflare requests (11/21/2020)
459
+ headers['cf-ipcountry']
460
+
461
+ //
462
+ || headers['x-country-code']
463
+
464
+ // these are present for non-cloudflare requests (11/21/2020)
465
+ || headers['x-appengine-country']
466
+
467
+ // If unsure, return ZZ
468
+ || 'ZZ'
469
+ )
470
+ .split(',')[0]
471
+ .trim();
472
+ }
473
+
474
+ BackendAssistant.prototype.getHeaderRegion = function (headers) {
475
+ headers = headers || {};
476
+
477
+ return (
478
+ // these are present for cloudflare requests (11/21/2020)
479
+ headers['cf-region']
480
+
481
+ // these are present for non-cloudflare requests (11/21/2020)
482
+ || headers['x-appengine-region']
483
+
484
+ // If unsure, return unknown
485
+ || 'Unknown'
486
+ )
487
+ .split(',')[0]
488
+ .trim();
489
+ }
490
+
491
+ BackendAssistant.prototype.getHeaderCity = function (headers) {
492
+ headers = headers || {};
493
+
494
+ return (
495
+ // these are present for cloudflare requests (11/21/2020)
496
+ headers['cf-ipcity']
497
+
498
+ || headers['x-appengine-city']
499
+
500
+ // If unsure, return unknown
501
+ || 'Unknown'
502
+ )
503
+ .split(',')[0]
504
+ .trim();
505
+ }
506
+
507
+ BackendAssistant.prototype.getHeaderLatitude = function (headers) {
508
+ headers = headers || {};
509
+
510
+ return parseFloat((
511
+ // these are present for cloudflare requests (11/21/2020)
512
+ headers['cf-iplatitude']
513
+
514
+ || (headers['x-appengine-citylatlong'] || '').split(',')[0]
515
+
516
+ // If unsure, return unknown
517
+ || '0'
518
+ )
519
+ .split(',')[0]
520
+ .trim());
521
+ }
522
+
523
+ BackendAssistant.prototype.getHeaderLongitude = function (headers) {
524
+ headers = headers || {};
525
+
526
+ return parseFloat((
527
+ // Cloudflare requests
528
+ headers['cf-iplongitude']
529
+
530
+ || (headers['x-appengine-citylatlong'] || '').split(',')[1]
531
+
532
+ // If unsure, return unknown
533
+ || '0'
534
+ )
535
+ .split(',')[0]
536
+ .trim());
537
+ }
538
+
539
+
540
+ BackendAssistant.prototype.getHeaderUserAgent = function (headers) {
541
+ headers = headers || {};
542
+
543
+ return (
544
+ headers['user-agent']
545
+ || ''
546
+ )
547
+ .trim();
548
+ }
549
+
550
+ BackendAssistant.prototype.getHeaderLanguage = function (headers) {
551
+ headers = headers || {};
552
+
553
+ return (
554
+ headers['accept-language']
555
+ || headers['x-orig-accept-language']
556
+ || ''
557
+ )
558
+ .trim();
559
+ }
560
+
561
+ BackendAssistant.prototype.getHeaderPlatform = function (headers) {
562
+ headers = headers || {};
563
+
564
+ return (
565
+ headers['sec-ch-ua-platform']
566
+ || ''
567
+ )
568
+ .replace(/"/ig, '')
569
+ .trim();
570
+ }
571
+
572
+ BackendAssistant.prototype.getHeaderMobile = function (headers) {
573
+ headers = headers || {};
574
+
575
+ // Will be ?0 if fale or ?1 if true
576
+ const mobile = (headers['sec-ch-ua-mobile'] || '').replace(/\?/ig, '');
577
+
578
+ return mobile === '1' || mobile === true || mobile === 'true';
579
+ }
580
+
581
+ /**
582
+ * Parses a 'multipart/form-data' upload request
583
+ *
584
+ * @param {Object} req Cloud Function request context.
585
+ * @param {Object} res Cloud Function response context.
586
+ */
587
+ // https://cloud.google.com/functions/docs/writing/http#multipart_data
588
+ BackendAssistant.prototype.parseMultipartFormData = function (options) {
589
+ const self = this;
590
+ return new Promise(function(resolve, reject) {
591
+ if (!self.initialized) {
592
+ return reject(new Error('Cannot run .parseMultipartForm() until .init() has been called'));
593
+ }
594
+ const existingData = self.request.multipartData;
595
+ // console.log('-----existingData', existingData, Object.keys(_.get(existingData, 'fields', {})).length, Object.keys(_.get(existingData, 'files', {})).length);
596
+ if (Object.keys(_.get(existingData, 'fields', {})).length + Object.keys(_.get(existingData, 'files', {})).length > 0) {
597
+ return resolve(existingData);
598
+ }
599
+
600
+ options = options || {};
601
+
602
+ const fs = require('fs');
603
+ const req = self.ref.req;
604
+ const res = self.ref.res;
605
+
606
+ // Node.js doesn't have a built-in multipart/form-data parsing library.
607
+ // Instead, we can use the 'busboy' library from NPM to parse these requests.
608
+ const busboy = require('busboy');
609
+ const jetpack = require('fs-jetpack');
610
+
611
+ // if (req.method !== 'POST') {
612
+ // // Return a "method not allowed" error
613
+ // return res.status(405).end();
614
+ // }
615
+ options.headers = options.headers || req.headers;
616
+ options.limits = options.limits || {};
617
+
618
+ // console.log('++++++++options.headers', options.headers);
619
+ // console.log('++++++++req.rawBody', req.rawBody);
620
+ // console.log('++++++++options.limits', options.limits);
621
+ // console.log('----req.rawBody', req.rawBody);
622
+
623
+ // https://github.com/mscdex/busboy
624
+ // https://github.com/mscdex/busboy/issues/266
625
+ const bb = busboy({
626
+ headers: options.headers,
627
+ limits: options.limits,
628
+ });
629
+
630
+ // This object will accumulate all the fields, keyed by their name
631
+ const fields = {};
632
+
633
+ // This object will accumulate all the uploaded files, keyed by their name.
634
+ const uploads = {};
635
+
636
+ // This code will process each non-file field in the form.
637
+ bb.on('field', (fieldname, val, info) => {
638
+ // console.log(`Processed field ${fieldname}: ${val}.`);
639
+ fields[fieldname] = val;
640
+ });
641
+
642
+ const fileWrites = [];
643
+
644
+ // This code will process each file uploaded.
645
+ bb.on('file', (fieldname, file, info) => {
646
+ // file.on('error', (e) => {
647
+ // console.error('File error', e);
648
+ // });
649
+ // Note: os.tmpdir() points to an in-memory file system on GCF
650
+ // Thus, any files in it must fit in the instance's memory.
651
+ jetpack.dir(self.tmpdir)
652
+
653
+ const filename = info.filename;
654
+ const filepath = path.join(self.tmpdir, filename);
655
+ uploads[fieldname] = filepath;
656
+ const writeStream = fs.createWriteStream(filepath);
657
+ file.pipe(writeStream);
658
+
659
+
660
+ // File was processed by Busboy; wait for it to be written.
661
+ // Note: GCF may not persist saved files across invocations.
662
+ // Persistent files must be kept in other locations
663
+ // (such as Cloud Storage buckets).
664
+ const promise = new Promise((resolve, reject) => {
665
+ file.on('end', () => {
666
+ writeStream.end();
667
+ });
668
+ writeStream.on('finish', resolve);
669
+ writeStream.on('error', reject);
670
+ });
671
+ fileWrites.push(promise);
672
+ });
673
+
674
+ // bb.on('error', async (e) => {
675
+ // console.error('Busboy error', e);
676
+ // })
677
+
678
+ // Triggered once all uploaded files are processed by Busboy.
679
+ // We still need to wait for the disk writes (saves) to complete.
680
+ bb.on('finish', async () => {
681
+ await Promise.all(fileWrites);
682
+
683
+ /**
684
+ * TODO(developer): Process saved files here
685
+ */
686
+ // for (const file in uploads) {
687
+ // fs.unlinkSync(uploads[file]);
688
+ // }
689
+ // res.send();
690
+ self.request.multipartData = {
691
+ fields: fields,
692
+ files: uploads,
693
+ }
694
+
695
+ return resolve(self.request.multipartData);
696
+ });
697
+
698
+ // Because of an error when using in both Optiic glitch server and ITWCW firebase functions
699
+ if (req.rawBody) {
700
+ return bb.end(req.rawBody);
701
+ } else {
702
+ return req.pipe(bb);
703
+ }
704
+ });
705
+ }
706
+
707
+ function stringify(obj, replacer, spaces, cycleReplacer) {
708
+ return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
709
+ }
710
+
711
+ // https://github.com/moll/json-stringify-safe/blob/master/stringify.js
712
+ function serializer(replacer, cycleReplacer) {
713
+ var stack = [], keys = []
714
+
715
+ if (cycleReplacer == null) cycleReplacer = function(key, value) {
716
+ if (stack[0] === value) return '[Circular ~]'
717
+ return `[Circular ~.${keys.slice(0, stack.indexOf(value)).join('.')}]`;
718
+ }
719
+
720
+ return function(key, value) {
721
+ if (stack.length > 0) {
722
+ var thisPos = stack.indexOf(this)
723
+ ~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
724
+ ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
725
+ if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value)
726
+ }
727
+ else stack.push(value)
728
+
729
+ return replacer == null ? value : replacer.call(this, key, value)
730
+ }
731
+ }
732
+
733
+ module.exports = BackendAssistant;