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