backend-manager 4.1.3 → 4.2.1

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.
@@ -1,1051 +0,0 @@
1
- const os = require('os');
2
- const path = require('path');
3
- const _ = require('lodash');
4
- const uuid = require('uuid');
5
- let JSON5;
6
-
7
- const LOG_LEVELS = {
8
- error: 'error',
9
- warn: 'warn',
10
- info: 'info',
11
- debug: 'debug',
12
- log: 'log',
13
- notice: 'NOTICE',
14
- critical: 'CRITICAL',
15
- emergency: 'EMERGENCY'
16
- };
17
-
18
- function BackendAssistant() {
19
- const self = this;
20
-
21
- // Set ref
22
- self.meta = {};
23
- self.initialized = false;
24
-
25
- // Add log methods
26
- Object.keys(LOG_LEVELS)
27
- .forEach((level) => {
28
- // Skip log because it is already a method
29
- // if (level === 'log') {
30
- // return;
31
- // }
32
-
33
- // Add log method
34
- BackendAssistant.prototype[level] = function() {
35
- const self = this;
36
- const args = Array.prototype.slice.call(arguments);
37
-
38
- // Prepend level to args
39
- args.unshift(level);
40
- // self.log.apply(this, args);
41
- self._log.apply(this, args);
42
- };
43
- });
44
-
45
- return self;
46
- }
47
-
48
- function tryParse(input) {
49
- var ret;
50
-
51
- JSON5 = JSON5 || require('json5');
52
-
53
- try {
54
- ret = JSON5.parse(input);
55
- } catch (e) {
56
- ret = input
57
- }
58
- return ret;
59
- }
60
-
61
- BackendAssistant.prototype.init = function (ref, options) {
62
- const self = this;
63
-
64
- // Set options
65
- options = options || {};
66
- options.accept = options.accept || 'json';
67
- options.showOptionsLog = typeof options.showOptionsLog !== 'undefined' ? options.showOptionsLog : false;
68
- options.optionsLogString = typeof options.optionsLogString !== 'undefined' ? options.optionsLogString : '\n\n\n\n\n';
69
- options.fileSavePath = options.fileSavePath || process.env.npm_package_name || '';
70
-
71
- // Set now
72
- const now = new Date();
73
-
74
- // Attached libraries - used in .errorify()
75
- self.analytics = null;
76
- self.usage = null;
77
- self.settings = null;
78
-
79
- // Set meta
80
- self.meta = {};
81
-
82
- self.meta.startTime = {};
83
- self.meta.startTime.timestamp = now.toISOString();
84
- self.meta.startTime.timestampUNIX = Math.round((now.getTime()) / 1000);
85
-
86
- self.meta.name = options.functionName || process.env.FUNCTION_TARGET || 'unnamed';
87
- self.meta.environment = options.environment || self.getEnvironment();
88
- self.meta.type = options.functionType || process.env.FUNCTION_SIGNATURE_TYPE || 'unknown';
89
-
90
- // Set ref
91
- self.ref = {};
92
- ref = ref || {};
93
- self.ref.res = ref.res || {};
94
- self.ref.req = ref.req || {};
95
- self.ref.admin = ref.admin || {};
96
- self.ref.functions = ref.functions || {};
97
- self.ref.Manager = ref.Manager || {};
98
- self.Manager = self.ref.Manager;
99
-
100
- // Set ID
101
- try {
102
- const headers = self?.ref?.req.headers || {};
103
-
104
- self.id = headers['function-execution-id']
105
- || headers['X-Cloud-Trace-Context']
106
- || self.Manager.Utilities().randomId();
107
- } catch {
108
- self.id = now.getTime();
109
- }
110
-
111
- // Set tag
112
- self.tag = `${self.meta.name}/${self.id}`;
113
-
114
- // Set logger prefix
115
- self.logPrefix = '';
116
-
117
- // Set stuff about request
118
- self.request = {};
119
- self.request.referrer = (self.ref.req.headers || {}).referrer || (self.ref.req.headers || {}).referer || '';
120
- self.request.method = (self.ref.req.method || undefined);
121
-
122
- // Set geolocation data
123
- self.request.geolocation = {
124
- ip: self.getHeaderIp(self.ref.req.headers),
125
- continent: self.getHeaderContinent(self.ref.req.headers),
126
- country: self.getHeaderCountry(self.ref.req.headers),
127
- region: self.getHeaderRegion(self.ref.req.headers),
128
- city: self.getHeaderCity(self.ref.req.headers),
129
- latitude: self.getHeaderLatitude(self.ref.req.headers),
130
- longitude: self.getHeaderLongitude(self.ref.req.headers),
131
- };
132
-
133
- // Set client data
134
- self.request.client = {
135
- userAgent: self.getHeaderUserAgent(self.ref.req.headers),
136
- language: self.getHeaderLanguage(self.ref.req.headers),
137
- platform: self.getHeaderPlatform(self.ref.req.headers),
138
- mobile: self.getHeaderMobile(self.ref.req.headers),
139
- };
140
-
141
- // Deprecated notice for old properties
142
- Object.defineProperty(self.request, 'ip', {
143
- get: function() {
144
- console.error('⛔️ [Deprecation]: request.ip is deprecated, use request.geolocation.ip instead');
145
- return self.request.geolocation.ip;
146
- }
147
- });
148
- Object.defineProperty(self.request, 'country', {
149
- get: function() {
150
- console.error('⛔️ [Deprecation]: request.country is deprecated, use request.geolocation.country instead');
151
- return self.request.geolocation.country;
152
- }
153
- });
154
- Object.defineProperty(self.request, 'userAgent', {
155
- get: function() {
156
- console.error('⛔️ [Deprecation]: request.userAgent is deprecated, use request.client.userAgent instead');
157
- return self.request.client.userAgent;
158
- }
159
- });
160
-
161
- /*
162
- MORE HEADERS TO GET
163
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Platform-Version
164
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Model
165
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Mobile
166
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version-List
167
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version
168
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Arch
169
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA
170
- */
171
-
172
- // Set request type
173
- if (
174
- self.ref.req.xhr || (self.ref.req?.headers?.accept || '').includes('json')
175
- || (self.ref.req?.headers?.['content-type'] || '').includes('json')
176
- ) {
177
- self.request.type = 'ajax';
178
- } else {
179
- self.request.type = 'form';
180
- }
181
- self.request.path = self.ref.req.path || '';
182
- self.request.user = self.resolveAccount({authenticated: false});
183
-
184
- // Set body and query
185
- if (options.accept === 'json') {
186
- self.request.body = tryParse(self.ref.req.body || '{}');
187
- self.request.query = tryParse(self.ref.req.query || '{}');
188
- }
189
-
190
- // Set headers
191
- self.request.headers = self.ref.req.headers || {};
192
-
193
- // Merge data
194
- self.request.data = _.merge({}, self.request.body, self.request.query);
195
-
196
- // Set multipart data
197
- self.request.multipartData = {
198
- fields: {},
199
- files: {},
200
- };
201
-
202
- // Log the request
203
- // if (Object.keys(self.request.data).length > 0) {
204
- // self.log('Request:', self.request.data, {
205
- // ip: self.request.ip,
206
- // });
207
- // }
208
-
209
- // Constants
210
- self.constant = {};
211
- self.constant.pastTime = {};
212
- self.constant.pastTime.timestamp = '1999-01-01T00:00:00Z';
213
- self.constant.pastTime.timestampUNIX = 915148800;
214
-
215
- // Schema
216
- // self.schema = {
217
- // dir: '',
218
- // name: '',
219
- // }
220
-
221
- // Log options
222
- if (
223
- (self.isDevelopment())
224
- && ((self.request.method !== 'OPTIONS') || (self.request.method === 'OPTIONS' && options.showOptionsLog))
225
- && (self.request.method !== 'undefined')
226
- // && (self.request.method !== 'undefined' && typeof self.request.method !== 'undefined')
227
- ) {
228
- console.log(options.optionsLogString);
229
- }
230
-
231
- // Set tmpdir
232
- self.tmpdir = path.resolve(os.tmpdir(), options.fileSavePath, uuid.v4());
233
-
234
- // Set initialized
235
- self.initialized = true;
236
-
237
- return self;
238
- };
239
-
240
- BackendAssistant.prototype.getEnvironment = function () {
241
- // return (process.env.FUNCTIONS_EMULATOR === true || process.env.FUNCTIONS_EMULATOR === 'true' || process.env.ENVIRONMENT !== 'production' ? 'development' : 'production')
242
- if (process.env.ENVIRONMENT === 'production') {
243
- return 'production';
244
- } else if (
245
- process.env.ENVIRONMENT === 'development'
246
- || process.env.FUNCTIONS_EMULATOR === true
247
- || process.env.FUNCTIONS_EMULATOR === 'true'
248
- || process.env.TERM_PROGRAM === 'Apple_Terminal'
249
- || process.env.TERM_PROGRAM === 'vscode'
250
- ) {
251
- return 'development';
252
- } else {
253
- return 'production'
254
- }
255
- };
256
-
257
- BackendAssistant.prototype.isDevelopment = function () {
258
- const self = this;
259
-
260
- return self.meta.environment === 'development';
261
- }
262
-
263
- BackendAssistant.prototype.isProduction = function () {
264
- const self = this;
265
-
266
- return self.meta.environment === 'production';
267
- }
268
-
269
- BackendAssistant.prototype.logProd = function () {
270
- const self = this;
271
-
272
- self._log.apply(self, args);
273
- };
274
-
275
- BackendAssistant.prototype._log = function () {
276
- const self = this;
277
- const logs = [...arguments];
278
- const prefix = self.logPrefix ? ` ${self.logPrefix}:` : ':';
279
-
280
- // Prepend log prefix log string
281
- logs.unshift(`[${self.tag} @ ${new Date().toISOString()}]${prefix}`);
282
-
283
- // Get the log level
284
- const level = logs[1];
285
-
286
- // Pass along arguments to console.log
287
- if (LOG_LEVELS[level]) {
288
- logs.splice(1, 1);
289
-
290
- // Determine how to log
291
- if (level in console) {
292
- console[level].apply(console, logs);
293
- } else if (self.isDevelopment()) {
294
- console.log.apply(console, logs);
295
- } else {
296
- self.ref.functions.logger.write({
297
- severity: LOG_LEVELS[level].toUpperCase(),
298
- message: logs,
299
- });
300
- }
301
-
302
- // Write with wonderful-log
303
- if (self.Manager?.libraries?.logger?.[level]) {
304
- self.Manager?.libraries?.logger?.[level](...logs)
305
- }
306
- } else {
307
- console.log.apply(console, logs);
308
- }
309
- };
310
-
311
- BackendAssistant.prototype.setLogPrefix = function (s) {
312
- const self = this;
313
-
314
- // Set logger prefix
315
- self.logPrefix = s
316
-
317
- return self;
318
- };
319
-
320
- BackendAssistant.prototype.clearLogPrefix = function () {
321
- const self = this;
322
-
323
- // Set logger prefix
324
- self.logPrefix = '';
325
-
326
- return self;
327
- };
328
-
329
- BackendAssistant.prototype.getLogPrefix = function () {
330
- const self = this;
331
-
332
- return self.logPrefix;
333
- };
334
-
335
- BackendAssistant.prototype.getUser = function () {
336
- const self = this;
337
-
338
- return self?.usage?.user || self.request.user;
339
- }
340
-
341
- BackendAssistant.prototype.errorify = function (e, options) {
342
- const self = this;
343
- const res = self.ref.res;
344
-
345
- // Set options
346
- options = options || {};
347
-
348
- // Code: default to 500, or else use the user's option
349
- const isCodeSet = typeof options.code !== 'undefined';
350
- options.code = !isCodeSet
351
- ? 500
352
- : options.code;
353
-
354
- // Sentry: default to false, or else use the user's option
355
- options.sentry = typeof options.sentry === 'undefined'
356
- ? false
357
- : options.sentry;
358
-
359
- // Log: default to sentry, or else use the user's option
360
- options.log = typeof options.log === 'undefined'
361
- ? options.sentry
362
- : options.log;
363
-
364
- // Send: default to false, or else use the user's option
365
- options.send = typeof options.send === 'undefined'
366
- ? false
367
- : options.send;
368
-
369
- // Stack: default to false, or else use the user's option
370
- options.stack = typeof options.stack === 'undefined'
371
- ? false
372
- : options.stack;
373
-
374
- // Construct error
375
- const newError = e instanceof Error
376
- ? e
377
- : new Error(stringifyNonStrings(e));
378
-
379
- // Fix code
380
- // options.code = newError.code || options.code;
381
- options.code = isCodeSet ? options.code : newError.code || options.code;
382
- options.code = parseInt(options.code);
383
- options.code = isBetween(options.code, 400, 599) ? options.code : 500;
384
-
385
- // Attach properties
386
- _attachHeaderProperties(self, options, newError);
387
-
388
- // Log the error
389
- if (options.log) {
390
- self.error(newError);
391
- }
392
-
393
- // Send error to Sentry
394
- if (options.sentry) {
395
- self.Manager.libraries.sentry.captureException(newError);
396
- }
397
-
398
- // Quit and respond to the request only if the assistant has a res (it sometimes does not, like in auth().onCreate() triggers)
399
- if (options.send && res?.status) {
400
- let sendable = newError?.stack && options.stack
401
- ? newError?.stack
402
- : newError?.message;
403
-
404
- // Set error
405
- sendable = `${sendable || newError || 'Unknown error'}`;
406
-
407
- // Attach tag
408
- if (newError.tag) {
409
- // sendable = `(${newError.tag}) ${sendable}`;
410
- sendable = `${sendable} (${newError.tag})`;
411
- }
412
-
413
- // Clear log prefix before sending
414
- self.clearLogPrefix();
415
-
416
- // Log
417
- if (options.log) {
418
- self.log(`Sending response (${options.code}):`, JSON.stringify(sendable));
419
- }
420
-
421
- // Send response
422
- res
423
- .status(options.code)
424
- .send(sendable);
425
- }
426
-
427
- return newError;
428
- }
429
-
430
- BackendAssistant.prototype.errorManager = BackendAssistant.prototype.errorify;
431
-
432
- BackendAssistant.prototype.redirect = function(response, options) {
433
- const self = this;
434
- const res = self.ref.res;
435
-
436
- // Set options
437
- options = options || {};
438
- options.code = typeof options.code === 'undefined'
439
- ? 302
440
- : options.code;
441
-
442
- return self.respond(response, options);
443
- }
444
-
445
- BackendAssistant.prototype.respond = function(response, options) {
446
- const self = this;
447
- const res = self.ref.res;
448
-
449
- // Set options
450
- options = options || {};
451
- options.code = typeof options.code === 'undefined'
452
- ? 200
453
- : options.code;
454
- options.log = typeof options.log === 'undefined'
455
- ? true
456
- : options.log;
457
-
458
- // Fix code
459
- options.code = parseInt(options.code);
460
-
461
- // Handle error
462
- const isErrorCode = isBetween(options.code, 400, 599);
463
- if (
464
- response instanceof Error
465
- || isErrorCode
466
- ) {
467
- options.code = isErrorCode ? options.code : undefined;
468
- options.send = true;
469
-
470
- return self.errorify(response, options);
471
- }
472
-
473
- // Attach properties
474
- _attachHeaderProperties(self, options);
475
-
476
- // Send response
477
- res.status(options.code);
478
-
479
- // Log function
480
- function _log(text) {
481
- if (options.log) {
482
- self.log(`${text} (${options.code}):`, JSON.stringify(response));
483
- }
484
- }
485
-
486
- // Clear log prefix before sending
487
- self.clearLogPrefix();
488
-
489
- // Redirect
490
- const isRedirect = isBetween(options.code, 300, 399);
491
- if (isRedirect) {
492
- // Log
493
- _log(`Redirecting`);
494
-
495
- // Send
496
- return res.redirect(response);
497
- }
498
-
499
- // Log
500
- _log(`Sending response`);
501
-
502
- // If it is an object, send as json
503
- if (
504
- response
505
- && typeof response === 'object'
506
- && typeof res.json === 'function'
507
- ) {
508
- return res.json(response);
509
- } else {
510
- return res.send(response);
511
- }
512
- }
513
-
514
- function isBetween(value, min, max) {
515
- return value >= min && value <= max;
516
- }
517
-
518
- function stringifyNonStrings(e) {
519
- if (typeof e === 'string') {
520
- return e;
521
- } else {
522
- return JSON.stringify(e);
523
- }
524
- }
525
-
526
- function _attachHeaderProperties(self, options, error) {
527
- // Create headers
528
- const headers = {
529
- code: options.code,
530
- tag: self.tag,
531
- usage: {
532
- current: self?.usage?.getUsage() || {},
533
- limits: self?.usage?.getLimit() || {},
534
- },
535
- additional: options.additional || {},
536
- }
537
- const req = self.ref.req;
538
- const res = self.ref.res;
539
-
540
- // Attach properties if this assistant has a res (it sometimes does not, like in auth().onCreate() triggers)
541
- if (res?.header && res?.get) {
542
- res.header('bm-properties', JSON.stringify(headers));
543
-
544
- // Add bm-properties to Access-Control-Expose-Headers
545
- const existingExposed = res.get('Access-Control-Expose-Headers') || '';
546
- const newExposed = `${existingExposed}, bm-properties`.replace(/^, /, '');
547
-
548
- if (!existingExposed.match(/bm-properties/i)) {
549
- res.header('Access-Control-Expose-Headers', newExposed);
550
- }
551
- }
552
-
553
- // Attach properties
554
- if (error) {
555
- Object.keys(headers)
556
- .forEach((item, i) => {
557
- error[item] = headers[item];
558
- });
559
- }
560
- }
561
-
562
- BackendAssistant.prototype.authenticate = async function (options) {
563
- const self = this;
564
-
565
- // Shortcuts
566
- let admin = self.ref.admin;
567
- let functions = self.ref.functions;
568
- let req = self.ref.req;
569
- let res = self.ref.res;
570
- let data = self.request.data;
571
-
572
- // Build the ID token from the request
573
- let idToken;
574
- let backendManagerKey;
575
- // let user;
576
-
577
- // Set options
578
- options = options || {};
579
- options.resolve = typeof options.resolve === 'undefined' ? true : options.resolve;
580
- options.debug = typeof options.debug === 'undefined' ? false : options.debug;
581
-
582
- function _resolve(user) {
583
- user = user || {};
584
- user.authenticated = typeof user.authenticated === 'undefined'
585
- ? false
586
- : user.authenticated;
587
-
588
- if (options.resolve) {
589
- self.request.user = self.resolveAccount(user);
590
- return self.request.user;
591
- } else {
592
- return user;
593
- }
594
- }
595
-
596
- // Extract the BEM token
597
- // Having this is separate from the ID token allows for the user to be authenticated as an ADMIN
598
- if (options.backendManagerKey || data.backendManagerKey) {
599
- // Read token from backendManagerKey or authenticationToken or apiKey
600
- backendManagerKey = options.backendManagerKey || data.backendManagerKey;
601
-
602
- // Log the token
603
- self.log('Found "backendManagerKey" parameter', backendManagerKey);
604
- }
605
-
606
- // Extract the token / API key
607
- // This is the main token that will be used to authenticate the user (it can be a JWT or a user's API key)
608
- if (req?.headers?.authorization && req?.headers?.authorization?.startsWith('Bearer ')) {
609
- // Read the ID Token from the Authorization header.
610
- idToken = req.headers.authorization.split('Bearer ')[1];
611
-
612
- // Log the token
613
- self.log('Found "Authorization" header', idToken);
614
- } else if (req?.cookies?.__session) {
615
- // Read the ID Token from cookie.
616
- idToken = req.cookies.__session;
617
-
618
- // Log the token
619
- self.log('Found "__session" cookie', idToken);
620
- } else if (
621
- options.authenticationToken || data.authenticationToken
622
- || options.apiKey || data.apiKey
623
- ) {
624
- // Read token OR API Key from options or data
625
- idToken = options.authenticationToken || data.authenticationToken
626
- || options.apiKey || data.apiKey;
627
-
628
- // Log the token
629
- self.log('Found "authenticationToken" parameter', idToken);
630
- } else {
631
- // No token found
632
- return _resolve(self.request.user);
633
- }
634
-
635
- // Check if the token is a JWT
636
- if (isJWT(idToken)) {
637
- // Check with firebase
638
- try {
639
- const decodedIdToken = await admin.auth().verifyIdToken(idToken);
640
-
641
- // Log the token
642
- if (options.debug) {
643
- self.log('JWT token decoded', decodedIdToken.email, decodedIdToken.user_id);
644
- }
645
-
646
- // Get the user
647
- await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
648
- .get()
649
- .then((doc) => {
650
- // Set the user
651
- if (doc.exists) {
652
- self.request.user = Object.assign({}, self.request.user, doc.data());
653
- self.request.user.authenticated = true;
654
- self.request.user.auth.uid = decodedIdToken.user_id;
655
- self.request.user.auth.email = decodedIdToken.email;
656
- }
657
-
658
- // Log the user
659
- if (options.debug) {
660
- self.log('Found user doc', self.request.user)
661
- }
662
- })
663
-
664
- // Return the user
665
- return _resolve(self.request.user);
666
- } catch (error) {
667
- self.error('Error while verifying JWT:', error);
668
- return _resolve(self.request.user);
669
- }
670
- } else {
671
- // Query by API key
672
- await admin.firestore().collection(`users`)
673
- .where('api.privateKey', '==', apiKey)
674
- .get()
675
- .then(function(querySnapshot) {
676
- querySnapshot.forEach(function(doc) {
677
- self.request.user = doc.data();
678
- self.request.user = Object.assign({}, self.request.user, doc.data());
679
- self.request.user.authenticated = true;
680
- });
681
- })
682
- .catch(function(error) {
683
- console.error('Error getting documents: ', error);
684
- });
685
-
686
- return _resolve(self.request.user);
687
- }
688
- };
689
-
690
- BackendAssistant.prototype.resolveAccount = function (user) {
691
- const ResolveAccount = new (require('resolve-account'))();
692
-
693
- return ResolveAccount.resolve(undefined, user)
694
- }
695
-
696
- BackendAssistant.prototype.parseRepo = function (repo) {
697
- let repoSplit = repo.split('/');
698
- for (var i = 0; i < repoSplit.length; i++) {
699
- repoSplit[i] = repoSplit[i].replace('.git', '');
700
- }
701
- repoSplit = repoSplit.filter(function(value, index, arr){
702
- return value !== 'http:' &&
703
- value !== 'https:' &&
704
- value !== '' &&
705
- value !== 'github.com';
706
- });
707
- return {
708
- user: repoSplit[0],
709
- name: repoSplit[1],
710
- }
711
- };
712
-
713
- BackendAssistant.prototype.getHeaderIp = function (headers) {
714
- headers = headers || {};
715
-
716
- return (
717
- // these are present for cloudflare requests (11/21/2020)
718
- headers['cf-connecting-ip']
719
- || headers['fastly-temp-xff']
720
-
721
- // these are present for non-cloudflare requests (11/21/2020)
722
- || headers['x-appengine-user-ip']
723
- || headers['x-forwarded-for']
724
-
725
- // Not sure about these
726
- // || headers['fastly-client-ip']
727
-
728
- // If unsure, return local IP
729
- || '127.0.0.1'
730
- )
731
- .split(',')[0]
732
- .trim();
733
- }
734
-
735
- BackendAssistant.prototype.getHeaderContinent = function (headers) {
736
- headers = headers || {};
737
-
738
- return (
739
- // these are present for cloudflare requests (11/21/2020)
740
- headers['cf-ipcontinent']
741
-
742
- // If unsure, return ZZ
743
- || 'ZZ'
744
- )
745
- .split(',')[0]
746
- .trim();
747
- }
748
-
749
- BackendAssistant.prototype.getHeaderCountry = function (headers) {
750
- headers = headers || {};
751
-
752
- return (
753
- // these are present for cloudflare requests (11/21/2020)
754
- headers['cf-ipcountry']
755
-
756
- //
757
- || headers['x-country-code']
758
-
759
- // these are present for non-cloudflare requests (11/21/2020)
760
- || headers['x-appengine-country']
761
-
762
- // If unsure, return ZZ
763
- || 'ZZ'
764
- )
765
- .split(',')[0]
766
- .trim();
767
- }
768
-
769
- BackendAssistant.prototype.getHeaderRegion = function (headers) {
770
- headers = headers || {};
771
-
772
- return (
773
- // these are present for cloudflare requests (11/21/2020)
774
- headers['cf-region']
775
-
776
- // these are present for non-cloudflare requests (11/21/2020)
777
- || headers['x-appengine-region']
778
-
779
- // If unsure, return unknown
780
- || 'Unknown'
781
- )
782
- .split(',')[0]
783
- .trim();
784
- }
785
-
786
- BackendAssistant.prototype.getHeaderCity = function (headers) {
787
- headers = headers || {};
788
-
789
- return (
790
- // these are present for cloudflare requests (11/21/2020)
791
- headers['cf-ipcity']
792
-
793
- || headers['x-appengine-city']
794
-
795
- // If unsure, return unknown
796
- || 'Unknown'
797
- )
798
- .split(',')[0]
799
- .trim();
800
- }
801
-
802
- BackendAssistant.prototype.getHeaderLatitude = function (headers) {
803
- headers = headers || {};
804
-
805
- return parseFloat((
806
- // these are present for cloudflare requests (11/21/2020)
807
- headers['cf-iplatitude']
808
-
809
- || (headers['x-appengine-citylatlong'] || '').split(',')[0]
810
-
811
- // If unsure, return unknown
812
- || '0'
813
- )
814
- .split(',')[0]
815
- .trim());
816
- }
817
-
818
- BackendAssistant.prototype.getHeaderLongitude = function (headers) {
819
- headers = headers || {};
820
-
821
- return parseFloat((
822
- // Cloudflare requests
823
- headers['cf-iplongitude']
824
-
825
- || (headers['x-appengine-citylatlong'] || '').split(',')[1]
826
-
827
- // If unsure, return unknown
828
- || '0'
829
- )
830
- .split(',')[0]
831
- .trim());
832
- }
833
-
834
-
835
- BackendAssistant.prototype.getHeaderUserAgent = function (headers) {
836
- headers = headers || {};
837
-
838
- return (
839
- headers['user-agent']
840
- || ''
841
- )
842
- .trim();
843
- }
844
-
845
- BackendAssistant.prototype.getHeaderLanguage = function (headers) {
846
- headers = headers || {};
847
-
848
- return (
849
- headers['accept-language']
850
- || headers['x-orig-accept-language']
851
- || ''
852
- )
853
- .trim();
854
- }
855
-
856
- BackendAssistant.prototype.getHeaderPlatform = function (headers) {
857
- headers = headers || {};
858
-
859
- return (
860
- headers['sec-ch-ua-platform']
861
- || ''
862
- )
863
- .replace(/"/ig, '')
864
- .trim();
865
- }
866
-
867
- BackendAssistant.prototype.getHeaderMobile = function (headers) {
868
- headers = headers || {};
869
-
870
- // Will be ?0 if fale or ?1 if true
871
- const mobile = (headers['sec-ch-ua-mobile'] || '').replace(/\?/ig, '');
872
-
873
- return mobile === '1' || mobile === true || mobile === 'true';
874
- }
875
-
876
- /**
877
- * Parses a 'multipart/form-data' upload request
878
- *
879
- * @param {Object} req Cloud Function request context.
880
- * @param {Object} res Cloud Function response context.
881
- */
882
- // https://cloud.google.com/functions/docs/writing/http#multipart_data
883
- BackendAssistant.prototype.parseMultipartFormData = function (options) {
884
- const self = this;
885
- return new Promise(function(resolve, reject) {
886
- if (!self.initialized) {
887
- return reject(new Error('Cannot run .parseMultipartForm() until .init() has been called'));
888
- }
889
- const existingData = self.request.multipartData;
890
- const getFields = existingData?.fields || {};
891
- const getFiles = existingData?.files || {};
892
-
893
- // If there are already fields or files, return them
894
- if (Object.keys(getFields).length + Object.keys(getFiles).length > 0) {
895
- return resolve(existingData);
896
- }
897
-
898
- // Set options
899
- options = options || {};
900
-
901
- // Set headers
902
- const fs = require('fs');
903
- const req = self.ref.req;
904
- const res = self.ref.res;
905
-
906
- // Node.js doesn't have a built-in multipart/form-data parsing library.
907
- // Instead, we can use the 'busboy' library from NPM to parse these requests.
908
- const busboy = require('busboy');
909
- const jetpack = require('fs-jetpack');
910
-
911
- // if (req.method !== 'POST') {
912
- // // Return a "method not allowed" error
913
- // return res.status(405).end();
914
- // }
915
- options.headers = options.headers || req.headers;
916
- options.limits = options.limits || {};
917
-
918
- // console.log('++++++++options.headers', options.headers);
919
- // console.log('++++++++req.rawBody', req.rawBody);
920
- // console.log('++++++++options.limits', options.limits);
921
- // console.log('----req.rawBody', req.rawBody);
922
-
923
- // https://github.com/mscdex/busboy
924
- // https://github.com/mscdex/busboy/issues/266
925
- const bb = busboy({
926
- headers: options.headers,
927
- limits: options.limits,
928
- });
929
-
930
- // This object will accumulate all the fields, keyed by their name
931
- const fields = {};
932
-
933
- // This object will accumulate all the uploaded files, keyed by their name.
934
- const uploads = {};
935
-
936
- // This code will process each non-file field in the form.
937
- bb.on('field', (fieldname, val, info) => {
938
- // console.log(`Processed field ${fieldname}: ${val}.`);
939
- fields[fieldname] = val;
940
- });
941
-
942
- const fileWrites = [];
943
-
944
- // This code will process each file uploaded.
945
- bb.on('file', (fieldname, file, info) => {
946
- // file.on('error', (e) => {
947
- // console.error('File error', e);
948
- // });
949
- // Note: os.tmpdir() points to an in-memory file system on GCF
950
- // Thus, any files in it must fit in the instance's memory.
951
- jetpack.dir(self.tmpdir)
952
-
953
- const filename = info.filename;
954
- const filepath = path.join(self.tmpdir, filename);
955
- uploads[fieldname] = filepath;
956
- const writeStream = fs.createWriteStream(filepath);
957
- file.pipe(writeStream);
958
-
959
-
960
- // File was processed by Busboy; wait for it to be written.
961
- // Note: GCF may not persist saved files across invocations.
962
- // Persistent files must be kept in other locations
963
- // (such as Cloud Storage buckets).
964
- const promise = new Promise((resolve, reject) => {
965
- file.on('end', () => {
966
- writeStream.end();
967
- });
968
- writeStream.on('finish', resolve);
969
- writeStream.on('error', reject);
970
- });
971
- fileWrites.push(promise);
972
- });
973
-
974
- // bb.on('error', async (e) => {
975
- // console.error('Busboy error', e);
976
- // })
977
-
978
- // Triggered once all uploaded files are processed by Busboy.
979
- // We still need to wait for the disk writes (saves) to complete.
980
- bb.on('finish', async () => {
981
- await Promise.all(fileWrites);
982
-
983
- /**
984
- * TODO(developer): Process saved files here
985
- */
986
- // for (const file in uploads) {
987
- // fs.unlinkSync(uploads[file]);
988
- // }
989
- // res.send();
990
- self.request.multipartData = {
991
- fields: fields,
992
- files: uploads,
993
- }
994
-
995
- return resolve(self.request.multipartData);
996
- });
997
-
998
- // Because of an error when using in both Optiic glitch server and ITWCW firebase functions
999
- if (req.rawBody) {
1000
- return bb.end(req.rawBody);
1001
- } else {
1002
- return req.pipe(bb);
1003
- }
1004
- });
1005
- }
1006
-
1007
- const isJWT = (token) => {
1008
- // Ensure the token has three parts separated by dots
1009
- const parts = token.split('.');
1010
-
1011
- try {
1012
- // Decode the header (first part) to verify it is JSON
1013
- const header = JSON.parse(Buffer.from(parts[0], 'base64').toString('utf8'));
1014
- // Check for expected JWT keys in the header
1015
- return header.alg && header.typ === 'JWT';
1016
- } catch (err) {
1017
- // If parsing fails, it's not a valid JWT
1018
- return false;
1019
- }
1020
- };
1021
-
1022
- // Not sure what this is for? But it has a good serializer code
1023
- // Disabled 2024-03-21 because there was another stringify() function that i was intending to use but it was actually using this
1024
- // It was adding escaped quotes to strings
1025
- // function stringify(obj, replacer, spaces, cycleReplacer) {
1026
- // return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
1027
- // }
1028
-
1029
- // // https://github.com/moll/json-stringify-safe/blob/master/stringify.js
1030
- // function serializer(replacer, cycleReplacer) {
1031
- // var stack = [], keys = []
1032
-
1033
- // if (cycleReplacer == null) cycleReplacer = function(key, value) {
1034
- // if (stack[0] === value) return '[Circular ~]'
1035
- // return `[Circular ~.${keys.slice(0, stack.indexOf(value)).join('.')}]`;
1036
- // }
1037
-
1038
- // return function(key, value) {
1039
- // if (stack.length > 0) {
1040
- // var thisPos = stack.indexOf(this)
1041
- // ~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
1042
- // ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
1043
- // if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value)
1044
- // }
1045
- // else stack.push(value)
1046
-
1047
- // return replacer == null ? value : replacer.call(this, key, value)
1048
- // }
1049
- // }
1050
-
1051
- module.exports = BackendAssistant;