backend-manager 3.0.63 → 3.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "3.0.63",
3
+ "version": "3.1.0",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -64,10 +64,9 @@
64
64
  "sizeitup": "^1.0.7",
65
65
  "uid-generator": "^2.0.0",
66
66
  "ultimate-jekyll-poster": "^0.0.15",
67
- "universal-analytics": "^0.5.3",
68
67
  "uuid": "^9.0.1",
69
68
  "wonderful-fetch": "^1.0.1",
70
69
  "wonderful-log": "^1.0.5",
71
70
  "yargs": "^17.7.2"
72
71
  }
73
- }
72
+ }
@@ -163,6 +163,15 @@ Module.prototype.clearFirestore = function() {
163
163
  })
164
164
  }
165
165
 
166
+ // Clear temporary/usage in firestore by deleting the doc
167
+ await libraries.admin.firestore().doc(`temporary/usage`).delete()
168
+ .then(r => {
169
+ assistant.log(`cron/daily/reset-usage() [firestore]: Deleted temporary/usage`);
170
+ })
171
+ .catch(e => {
172
+ assistant.errorManager(`Error deleting temporary/usage: ${e}`, {sentry: false, send: false, log: true})
173
+ })
174
+
166
175
  return resolve();
167
176
  });
168
177
  }
@@ -0,0 +1,217 @@
1
+ // const ua = require('universal-analytics');
2
+ const get = require('lodash/get');
3
+ const fetch = require('wonderful-fetch');
4
+
5
+ const uuidRegex = /[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}/;
6
+ let uuidv5;
7
+
8
+ const BLACKLISTED_USER_AGENTS = new RegExp([
9
+ /(node-fetch)/,
10
+ ].map(r => r.source).join(''));
11
+
12
+ function Analytics(Manager, options) {
13
+ let self = this;
14
+ self.Manager = Manager;
15
+ // self._request = self.Manager._inner || {};
16
+
17
+ // Set id and secret
18
+ // const analyticsId = get(self.Manager, 'config.google_analytics.id', undefined);
19
+ self.analyticsId = self?.Manager?.config?.google_analytics?.id;
20
+ self.analyticsSecret = self?.Manager?.config?.google_analytics?.secret;
21
+
22
+ // Fix options
23
+ options = options || {};
24
+
25
+ // Set properties
26
+ self._assistant = options.assistant || Manager.Assistant();
27
+ self._request = {
28
+ ip: get(self._assistant, 'request.geolocation.ip', '127.0.0.1'),
29
+ country: get(self._assistant, 'request.geolocation.country', ''),
30
+ referrer: get(self._assistant, 'request.referrer', ''),
31
+ userAgent: get(self._assistant, 'request.client.userAgent', ''),
32
+ name: get(self._assistant, 'meta.name', ''),
33
+ }
34
+
35
+ self._request.userAgent = self._request.userAgent.match(BLACKLISTED_USER_AGENTS) ? '' : self._request.userAgent;
36
+
37
+ self._ds = 'app';
38
+ self._uuid = options.uuid || self._request.ip || self.Manager.SERVER_UUID;
39
+ self._uuid = self._uuid.match(uuidRegex) ? self._uuid : self.generateId(self._uuid);
40
+ self._debug = typeof options.debug === 'undefined' ? (self.Manager.assistant.meta.environment === 'development') : options.debug;
41
+ self._pageview = typeof options.pageview === 'undefined' ? true : options.pageview;
42
+ self._version = self.Manager.package.version;
43
+ self._initialized = false;
44
+
45
+ if (!self.analyticsId) {
46
+ console.log('Not initializing because missing analyticsId', self.analyticsId);
47
+ return self;
48
+ } else if (!self.analyticsSecret) {
49
+ console.log('Not initializing because missing analyticsSecret', self.analyticsSecret);
50
+ return self;
51
+ }
52
+
53
+ // self.user = ua(self.analyticsId, self._uuid, {
54
+ // strictCidFormat: false, // https://analytics.google.com/analytics/web/#/report-home/a104885300w228822596p215709578
55
+ // });
56
+ // self.user.set('ds', 'app');
57
+
58
+ // // https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#sc
59
+ // if (self._uuid) {
60
+ // self.user.set('uid', self._uuid);
61
+ // }
62
+ // if (self._request.ip) {
63
+ // self.user.set('uip', encodeURIComponent(self._request.ip));
64
+ // }
65
+ // if (self._request.userAgent) {
66
+ // self.user.set('ua', encodeURIComponent(self._request.userAgent));
67
+ // }
68
+ // if (self._request.referrer) {
69
+ // self.user.set('dr', encodeURIComponent(self._request.referrer));
70
+ // }
71
+
72
+ self._initialized = true;
73
+
74
+ if (self._pageview) {
75
+ self.pageview({
76
+ path: self._request.name,
77
+ location: self._request.name,
78
+ host: self._request.name,
79
+ title: self._request.name,
80
+ })
81
+ }
82
+
83
+ return self;
84
+ }
85
+
86
+ Analytics.prototype.generateId = function (id) {
87
+ let self = this;
88
+ uuidv5 = uuidv5 || require('uuid').v5;
89
+ let namespace = get(self.Manager, 'config.backend_manager.namespace', undefined);
90
+
91
+ return id && namespace ? uuidv5(id, namespace) : undefined;
92
+ };
93
+
94
+ Analytics.prototype.pageview = function (options) {
95
+ let self = this;
96
+ options = options || {};
97
+ options.path = options.path || self._request.name;
98
+ options.location = options.location || self._request.name;
99
+ options.host = options.host || self._request.name;
100
+ options.title = options.title || self._request.name;
101
+
102
+ if (!self._initialized) {
103
+ return self;
104
+ } else if (self._debug) {
105
+ console.log('Skipping Analytics.pageview() because in development', self._uuid, options);
106
+ return self;
107
+ }
108
+
109
+ // self.user.pageview({
110
+ // dp: options.path,
111
+ // dl: options.location,
112
+ // dh: options.host,
113
+ // dt: options.title,
114
+ // }).send();
115
+
116
+ self.send();
117
+
118
+ return self;
119
+ };
120
+
121
+ Analytics.prototype.event = function (options) {
122
+ let self = this;
123
+ options = options || {};
124
+ options.category = options.category;
125
+ options.action = options.action;
126
+ options.label = options.label;
127
+ options.value = options.value;
128
+ options.path = options.path || self._request.name;
129
+
130
+ if (!self._initialized) {
131
+ return self;
132
+ } else if (self._debug) {
133
+ console.log('Skipping Analytics.event() because in development', self._uuid, options);
134
+ return self;
135
+ }
136
+
137
+ // self.user.event({
138
+ // ec: options.category,
139
+ // ea: options.action,
140
+ // el: options.label,
141
+ // ev: options.value,
142
+ // // dp: options.path || window.location.href,
143
+ // }).send();
144
+
145
+ self.send({
146
+ name: 'tutorial_begin',
147
+ params: {
148
+ campaign_id: 'google_1234',
149
+ campaign: 'Summer_fun',
150
+ source: 'google',
151
+ medium: 'cpc',
152
+ term: 'summer+travel',
153
+ content: 'logolink',
154
+ session_id: '123',
155
+ engagement_time_msec: '100',
156
+ }
157
+ });
158
+
159
+ return self;
160
+ };
161
+
162
+ Analytics.prototype.send = function (event) {
163
+ let self = this;
164
+
165
+ if (!self._initialized) {
166
+ return self;
167
+ } else if (self._debug) {
168
+ console.log('Skipping Analytics.event() because in development', self._uuid, options);
169
+ return self;
170
+ }
171
+
172
+ // self.user = ua(self.analyticsId, self._uuid, {
173
+ // strictCidFormat: false, // https://analytics.google.com/analytics/web/#/report-home/a104885300w228822596p215709578
174
+ // });
175
+ // self.user.set('ds', 'app');
176
+
177
+ // // https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#sc
178
+ // if (self._uuid) {
179
+ // self.user.set('uid', self._uuid);
180
+ // }
181
+ // if (self._request.ip) {
182
+ // self.user.set('uip', encodeURIComponent(self._request.ip));
183
+ // }
184
+ // if (self._request.userAgent) {
185
+ // self.user.set('ua', encodeURIComponent(self._request.userAgent));
186
+ // }
187
+ // if (self._request.referrer) {
188
+ // self.user.set('dr', encodeURIComponent(self._request.referrer));
189
+ // }
190
+
191
+ /*
192
+ https://stackoverflow.com/questions/68773179/what-should-the-client-id-be-when-sending-events-to-google-analytics-4-using-the
193
+ https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag
194
+
195
+
196
+ */
197
+
198
+ // Send event
199
+ fetch(`https://www.google-analytics.com/mp/collect?measurement_id=${self.analyticsId}&api_secret=${self.analyticsSecret}`, {
200
+ method: 'post',
201
+ body: {
202
+ client_id: self._uuid,
203
+ user_id: self._uuid,
204
+ events: [event],
205
+ },
206
+ })
207
+ .then((r) => {
208
+ console.log('Analytics.send(): Sent', r);
209
+ })
210
+ .catch((e) => {
211
+ console.error('Analytics.send(): Error sending', e);
212
+ });
213
+
214
+ return self;
215
+ };
216
+
217
+ module.exports = Analytics;
@@ -1,5 +1,7 @@
1
- const ua = require('universal-analytics');
1
+ // const ua = require('universal-analytics');
2
2
  const get = require('lodash/get');
3
+ const fetch = require('wonderful-fetch');
4
+
3
5
  const uuidRegex = /[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}/;
4
6
  let uuidv5;
5
7
 
@@ -8,13 +10,16 @@ const BLACKLISTED_USER_AGENTS = new RegExp([
8
10
  ].map(r => r.source).join(''));
9
11
 
10
12
  function Analytics(Manager, options) {
11
- let self = this;
13
+ const self = this;
12
14
  self.Manager = Manager;
13
15
  // self._request = self.Manager._inner || {};
14
16
 
15
- const analyticsId = get(self.Manager, 'config.google_analytics.id', undefined);
17
+ // Set id and secret
18
+ // const analyticsId = get(self.Manager, 'config.google_analytics.id', undefined);
19
+ self.analyticsId = self?.Manager?.config?.google_analytics?.id;
20
+ self.analyticsSecret = self?.Manager?.config?.google_analytics?.secret;
16
21
 
17
- // Fix optios
22
+ // Fix options
18
23
  options = options || {};
19
24
 
20
25
  // Set properties
@@ -29,36 +34,40 @@ function Analytics(Manager, options) {
29
34
 
30
35
  self._request.userAgent = self._request.userAgent.match(BLACKLISTED_USER_AGENTS) ? '' : self._request.userAgent;
31
36
 
37
+ self._data_soruce = 'server';
32
38
  self._uuid = options.uuid || self._request.ip || self.Manager.SERVER_UUID;
33
39
  self._uuid = self._uuid.match(uuidRegex) ? self._uuid : self.generateId(self._uuid);
34
- self._debug = typeof options.debug === 'undefined' ? (self.Manager.assistant.meta.environment === 'development') : options.debug;
40
+ self._debug = typeof options.debug === 'undefined' ? self._assistant.meta.environment === 'development' : options.debug;
35
41
  self._pageview = typeof options.pageview === 'undefined' ? true : options.pageview;
36
42
  self._version = self.Manager.package.version;
37
43
  self._initialized = false;
38
44
 
39
- if (!analyticsId) {
40
- console.log('Not initializing because missing analyticsId', analyticsId);
45
+ if (!self.analyticsId) {
46
+ self._assistant.log('analytics(): Not initializing because missing analyticsId', self.analyticsId);
47
+ return self;
48
+ } else if (!self.analyticsSecret) {
49
+ self._assistant.log('analytics(): Not initializing because missing analyticsSecret', self.analyticsSecret);
41
50
  return self;
42
51
  }
43
52
 
44
- self.user = ua(analyticsId, self._uuid, {
45
- strictCidFormat: false, // https://analytics.google.com/analytics/web/#/report-home/a104885300w228822596p215709578
46
- });
47
- self.user.set('ds', 'app');
48
-
49
- // https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#sc
50
- if (self._uuid) {
51
- self.user.set('uid', self._uuid);
52
- }
53
- if (self._request.ip) {
54
- self.user.set('uip', encodeURIComponent(self._request.ip));
55
- }
56
- if (self._request.userAgent) {
57
- self.user.set('ua', encodeURIComponent(self._request.userAgent));
58
- }
59
- if (self._request.referrer) {
60
- self.user.set('dr', encodeURIComponent(self._request.referrer));
61
- }
53
+ // self.user = ua(self.analyticsId, self._uuid, {
54
+ // strictCidFormat: false, // https://analytics.google.com/analytics/web/#/report-home/a104885300w228822596p215709578
55
+ // });
56
+ // self.user.set('ds', 'app');
57
+
58
+ // // https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#sc
59
+ // if (self._uuid) {
60
+ // self.user.set('uid', self._uuid);
61
+ // }
62
+ // if (self._request.ip) {
63
+ // self.user.set('uip', encodeURIComponent(self._request.ip));
64
+ // }
65
+ // if (self._request.userAgent) {
66
+ // self.user.set('ua', encodeURIComponent(self._request.userAgent));
67
+ // }
68
+ // if (self._request.referrer) {
69
+ // self.user.set('dr', encodeURIComponent(self._request.referrer));
70
+ // }
62
71
 
63
72
  self._initialized = true;
64
73
 
@@ -75,61 +84,150 @@ function Analytics(Manager, options) {
75
84
  }
76
85
 
77
86
  Analytics.prototype.generateId = function (id) {
78
- let self = this;
87
+ const self = this;
79
88
  uuidv5 = uuidv5 || require('uuid').v5;
80
89
  let namespace = get(self.Manager, 'config.backend_manager.namespace', undefined);
81
90
 
82
91
  return id && namespace ? uuidv5(id, namespace) : undefined;
83
92
  };
84
93
 
94
+ // Disabled because we are using the Measurement Protocol (12/19/2023)
85
95
  Analytics.prototype.pageview = function (options) {
86
- let self = this;
96
+ // const self = this;
97
+ // options = options || {};
98
+ // options.path = options.path || self._request.name;
99
+ // options.location = options.location || self._request.name;
100
+ // options.host = options.host || self._request.name;
101
+ // options.title = options.title || self._request.name;
102
+
103
+ // if (!self._initialized) {
104
+ // return self;
105
+ // } else if (self._debug) {
106
+ // self._assistant.log('analytics(): Skipping Analytics.pageview() because in development', self._uuid, options);
107
+ // return self;
108
+ // }
109
+
110
+ // // self.user.pageview({
111
+ // // dp: options.path,
112
+ // // dl: options.location,
113
+ // // dh: options.host,
114
+ // // dt: options.title,
115
+ // // }).send();
116
+
117
+ // // self.send();
118
+
119
+ // return self;
120
+ };
121
+
122
+ Analytics.prototype.event = function (options) {
123
+ const self = this;
124
+
125
+ // Fix options
87
126
  options = options || {};
88
- options.path = options.path || self._request.name;
89
- options.location = options.location || self._request.name;
90
- options.host = options.host || self._request.name;
91
- options.title = options.title || self._request.name;
92
127
 
93
128
  if (!self._initialized) {
94
129
  return self;
95
130
  } else if (self._debug) {
96
- console.log('Skipping Analytics.pageview() because in development', self._uuid, options);
131
+ self._assistant.log('analytics(): Skipping Analytics.event() because in development', self._uuid, options);
97
132
  return self;
98
133
  }
99
134
 
100
- self.user.pageview({
101
- dp: options.path,
102
- dl: options.location,
103
- dh: options.host,
104
- dt: options.title,
105
- }).send();
135
+ // self.user.event({
136
+ // ec: options.category,
137
+ // ea: options.action,
138
+ // el: options.label,
139
+ // ev: options.value,
140
+ // // dp: options.path || window.location.href,
141
+ // }).send();
142
+
143
+ self.send({
144
+ name: options.name,
145
+ params: options.params
146
+ });
106
147
 
107
148
  return self;
108
149
  };
109
150
 
110
- Analytics.prototype.event = function (options) {
111
- let self = this;
112
- options = options || {};
113
- options.category = options.category;
114
- options.action = options.action;
115
- options.label = options.label;
116
- options.value = options.value;
117
- options.path = options.path || self._request.name;
151
+ Analytics.prototype.send = function (event) {
152
+ const self = this;
118
153
 
119
154
  if (!self._initialized) {
120
155
  return self;
121
156
  } else if (self._debug) {
122
- console.log('Skipping Analytics.event() because in development', self._uuid, options);
157
+ self._assistant.log('analytics(): Skipping Analytics.event() because in development', self._uuid, event);
123
158
  return self;
124
159
  }
125
160
 
126
- self.user.event({
127
- ec: options.category,
128
- ea: options.action,
129
- el: options.label,
130
- ev: options.value,
131
- // dp: options.path || window.location.href,
132
- }).send();
161
+ /*
162
+ https://stackoverflow.com/questions/68773179/what-should-the-client-id-be-when-sending-events-to-google-analytics-4-using-the
163
+ https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag
164
+
165
+
166
+ https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#sc
167
+ https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports
168
+ https://stackoverflow.com/questions/43049662/how-to-send-measurement-protocol-if-there-is-no-clientid
169
+
170
+ https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=gtag
171
+ https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag
172
+ https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events
173
+ https://developers.google.com/analytics/devguides/collection/protocol/ga4/ua-feature-matrix
174
+ */
175
+
176
+ // Format event
177
+ event = event || {};
178
+ event.name = event.name || '';
179
+ event.timestamp_micros = new Date().getTime() * 1000,
180
+ event.params = event.params || {};
181
+ event.params.session_id = self._assistant.id;
182
+ event.params.engagement_time_msec = `${new Date().getTime() - new Date(self._assistant.meta.startTime.timestamp).getTime()}`;
183
+ event.params.event_source = self._data_soruce;
184
+ event.params.ip_override = self._request.ip;
185
+ event.params.user_agent = self._request.userAgent;
186
+ event.params.page_location = self._request.name;
187
+ event.params.page_referrer = self._request.referrer;
188
+ event.params.page_title = self._request.name;
189
+
190
+ // "event_source": "server",
191
+ // "page_location": "https:\/\/www.yourdomain.com\/page2",
192
+ // "page_referrer": "\/page1",
193
+ // "page_title": "Page 2",
194
+ // "ip_override": "xxx.xxx.xxx.0",
195
+ // "campaign": "your_campaign",
196
+ // "source": "your_source",
197
+ // "medium": "your_medium",
198
+ // "term": "your_term",
199
+ // "content": "your_content"
200
+
201
+ const body = {
202
+ client_id: self._uuid,
203
+ user_id: self._uuid,
204
+ // ip_override: self._request.ip,
205
+ // user_agent: self._request.userAgent,
206
+ events: [event],
207
+ }
208
+
209
+ // Log
210
+ if (self._assistant.meta.environment === 'development') {
211
+ self._assistant.log('analytics().send(): Sending...', JSON.stringify(body));
212
+ }
213
+
214
+ // Send event
215
+ fetch(`https://www.google-analytics.com/mp/collect?measurement_id=${self.analyticsId}&api_secret=${self.analyticsSecret}`, {
216
+ // fetch(`https://www.google-analytics.com/debug/mp/collect?measurement_id=${self.analyticsId}&api_secret=${self.analyticsSecret}`, {
217
+ method: 'post',
218
+ response: 'text',
219
+ tries: 2,
220
+ timeout: 30000,
221
+ body: body,
222
+ })
223
+ .then((r) => {
224
+ if (self._assistant.meta.environment === 'development') {
225
+ self._assistant.log('analytics().send(): Success', r);
226
+ }
227
+ })
228
+ .catch((e) => {
229
+ self._assistant.error('analytics().send(): Failed', e);
230
+ });
133
231
 
134
232
  return self;
135
233
  };
@@ -33,6 +33,11 @@ BackendAssistant.prototype.init = function (ref, options) {
33
33
 
34
34
  const now = new Date();
35
35
 
36
+ // Attached libraries - used in .errorify()
37
+ self.analytics = null;
38
+ self.usage = null;
39
+
40
+ // Set meta
36
41
  self.meta = {};
37
42
 
38
43
  self.meta.startTime = {};
@@ -43,6 +48,7 @@ BackendAssistant.prototype.init = function (ref, options) {
43
48
  self.meta.environment = options.environment || self.getEnvironment();
44
49
  self.meta.type = options.functionType || process.env.FUNCTION_SIGNATURE_TYPE || 'unknown';
45
50
 
51
+ // Set ref
46
52
  self.ref = {};
47
53
  ref = ref || {};
48
54
  self.ref.res = ref.res || {};
@@ -50,14 +56,17 @@ BackendAssistant.prototype.init = function (ref, options) {
50
56
  self.ref.admin = ref.admin || {};
51
57
  self.ref.functions = ref.functions || {};
52
58
  self.ref.Manager = ref.Manager || {};
59
+ self.Manager = self.ref.Manager;
53
60
 
54
61
  // Set ID
55
62
  try {
56
- self.id = self.ref.Manager.Utilities().randomId();
63
+ self.id = self.Manager.Utilities().randomId();
57
64
  } catch {
58
65
  self.id = now.getTime();
59
66
  }
60
67
 
68
+ self.tag = `${self.meta.name}/${self.id}`;
69
+
61
70
  // Set stuff about request
62
71
  self.request = {};
63
72
  self.request.referrer = (self.ref.req.headers || {}).referrer || (self.ref.req.headers || {}).referer || '';
@@ -146,6 +155,7 @@ BackendAssistant.prototype.init = function (ref, options) {
146
155
  self.constant.pastTime.timestamp = '1999-01-01T00:00:00Z';
147
156
  self.constant.pastTime.timestampUNIX = 915148800;
148
157
 
158
+ // Log options
149
159
  if (
150
160
  (self.meta.environment === 'development')
151
161
  && ((self.request.method !== 'OPTIONS') || (self.request.method === 'OPTIONS' && options.showOptionsLog))
@@ -155,8 +165,10 @@ BackendAssistant.prototype.init = function (ref, options) {
155
165
  console.log(options.optionsLogString);
156
166
  }
157
167
 
168
+ // Set tmpdir
158
169
  self.tmpdir = path.resolve(os.tmpdir(), options.fileSavePath, uuid.v4());
159
170
 
171
+ // Set initialized
160
172
  self.initialized = true;
161
173
 
162
174
  return self;
@@ -279,7 +291,7 @@ BackendAssistant.prototype._log = function() {
279
291
  const logs = [...Array.prototype.slice.call(arguments)];
280
292
 
281
293
  // 2. Prepend log prefix log string
282
- logs.unshift(`[${self.meta.name}/${self.id} @ ${new Date().toISOString()}]:`);
294
+ logs.unshift(`[${self.tag} @ ${new Date().toISOString()}]:`);
283
295
 
284
296
  // 3. Pass along arguments to console.log
285
297
  if (logs[1] === 'error') {
@@ -335,22 +347,36 @@ BackendAssistant.prototype._log = function() {
335
347
  BackendAssistant.prototype.errorManager = function(e, options) {
336
348
  const self = this;
337
349
 
350
+ // Set options
338
351
  options = options || {};
339
- options.code = typeof options.code === 'undefined' ? 500 : options.code;
340
- options.log = typeof options.log === 'undefined' ? true : options.log;
341
- options.sentry = typeof options.sentry === 'undefined' ? true : options.sentry;
342
- options.send = typeof options.send === 'undefined' ? true : options.send;
343
-
352
+ options.code = typeof options.code === 'undefined'
353
+ ? 500
354
+ : options.code;
355
+ options.log = typeof options.log === 'undefined'
356
+ ? true
357
+ : options.log;
358
+ options.sentry = typeof options.sentry === 'undefined'
359
+ ? true
360
+ : options.sentry;
361
+ options.send = typeof options.send === 'undefined'
362
+ ? true
363
+ : options.send;
364
+
365
+ // Construct error
344
366
  const newError = e instanceof Error
345
367
  ? e
346
368
  : new Error(stringify(e));
347
369
 
370
+ options.code = newError.code || options.code;
371
+
348
372
  // Attach properties
349
373
  Object.keys(options)
350
374
  .forEach((item, i) => {
351
- Object.assign(newError , { [item]: options[item] })
375
+ Object.assign(newError, { [item]: options[item] });
352
376
  });
353
377
 
378
+ // Attach properties
379
+ _attachHeaderProperties(self, options);
354
380
 
355
381
  // Log the error
356
382
  if (options.log) {
@@ -359,12 +385,19 @@ BackendAssistant.prototype.errorManager = function(e, options) {
359
385
 
360
386
  // Send error to Sentry
361
387
  if (options.sentry) {
362
- self.ref.Manager.libraries.sentry.captureException(newError);
388
+ self.Manager.libraries.sentry.captureException(newError);
363
389
  }
364
390
 
365
391
  // Quit and respond to the request
366
392
  if (options.send && self.ref.res && self.ref.res.status) {
367
- self.ref.res.status(options.code).send(newError ? newError.message || newError : 'Unknown error');
393
+ self.ref.res
394
+ .status(options.code)
395
+ .send((
396
+ newError?.message
397
+ // ? newError.stack
398
+ ? newError.message
399
+ : newError
400
+ ) || 'Unknown error');
368
401
  }
369
402
 
370
403
  return {
@@ -372,6 +405,38 @@ BackendAssistant.prototype.errorManager = function(e, options) {
372
405
  }
373
406
  }
374
407
 
408
+ BackendAssistant.prototype.errorify = BackendAssistant.prototype.errorManager;
409
+
410
+ BackendAssistant.prototype.respond = function(response, options) {
411
+ const self = this;
412
+
413
+ // Set options
414
+ options = options || {};
415
+ options.code = typeof options.code === 'undefined'
416
+ ? 200
417
+ : options.code;
418
+ options.log = typeof options.log === 'undefined'
419
+ ? true
420
+ : options.log;
421
+
422
+ // Attach properties
423
+ _attachHeaderProperties(self, options);
424
+
425
+ // Log the error
426
+ if (options.log) {
427
+ self.log(`Responding with ${options.code} code:`, JSON.stringify(response));
428
+ }
429
+
430
+ // Send response
431
+ self.ref.res.status(options.code);
432
+
433
+ if (typeof response === 'string') {
434
+ self.ref.res.send(response);
435
+ } else {
436
+ self.ref.res.json(response);
437
+ }
438
+ }
439
+
375
440
  function stringify(e) {
376
441
  if (typeof e === 'string') {
377
442
  return e;
@@ -380,6 +445,21 @@ function stringify(e) {
380
445
  }
381
446
  }
382
447
 
448
+ function _attachHeaderProperties(self, options) {
449
+ // Create headers
450
+ const headers = {
451
+ code: options.code,
452
+ tag: self.tag,
453
+ usage: {
454
+ current: self?.usage?.getUsage() || {},
455
+ limits: self?.usage?.getLimit() || {},
456
+ },
457
+ additional: options.additional || {},
458
+ }
459
+
460
+ // Attach properties
461
+ self.ref.res.header('bm-properties', JSON.stringify(headers));
462
+ }
383
463
 
384
464
  BackendAssistant.prototype.authenticate = async function (options) {
385
465
  const self = this;
@@ -410,11 +490,11 @@ BackendAssistant.prototype.authenticate = async function (options) {
410
490
  }
411
491
  }
412
492
 
413
- if (req.headers && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
493
+ if (req?.headers?.authorization?.startsWith('Bearer ')) {
414
494
  // Read the ID Token from the Authorization header.
415
495
  idToken = req.headers.authorization.split('Bearer ')[1];
416
496
  self.log('Found "Authorization" header', idToken, logOptions);
417
- } else if (req.cookies && req.cookies.__session) {
497
+ } else if (req?.cookies?.__session) {
418
498
  // Read the ID Token from cookie.
419
499
  idToken = req.cookies.__session;
420
500
  self.log('Found "__session" cookie', idToken, logOptions);
@@ -422,7 +502,7 @@ BackendAssistant.prototype.authenticate = async function (options) {
422
502
  // Check with custom BEM Token
423
503
  let storedApiKey;
424
504
  try {
425
- const workingConfig = _.get(self.ref.Manager, 'config') || functions.config();
505
+ const workingConfig = _.get(self.Manager, 'config') || functions.config();
426
506
  storedApiKey = _.get(workingConfig, 'backend_manager.key', '')
427
507
  } catch (e) {
428
508
 
@@ -439,29 +519,31 @@ BackendAssistant.prototype.authenticate = async function (options) {
439
519
  }
440
520
  } else if (options.apiKey) {
441
521
  self.log('Found "options.apiKey"', options.apiKey, logOptions);
522
+
442
523
  if (options.apiKey.includes('test')) {
443
524
  return _resolve(self.request.user);
444
525
  }
526
+
445
527
  await admin.firestore().collection(`users`)
446
- .where('api.privateKey', '==', options.apiKey)
447
- .get()
448
- .then(function(querySnapshot) {
449
- querySnapshot.forEach(function(doc) {
450
- self.request.user = doc.data();
451
- self.request.user.authenticated = true;
528
+ .where('api.privateKey', '==', options.apiKey)
529
+ .get()
530
+ .then(function(querySnapshot) {
531
+ querySnapshot.forEach(function(doc) {
532
+ self.request.user = doc.data();
533
+ self.request.user.authenticated = true;
534
+ });
535
+ })
536
+ .catch(function(error) {
537
+ console.error('Error getting documents: ', error);
452
538
  });
453
- })
454
- .catch(function(error) {
455
- console.error('Error getting documents: ', error);
456
- });
457
539
 
458
540
  return _resolve(self.request.user);
459
541
  } else {
460
- self.log('No Firebase ID token was able to be extracted.',
461
- 'Make sure you authenticate your request by providing either the following HTTP header:',
462
- 'Authorization: Bearer <Firebase ID Token>',
463
- 'or by passing a "__session" cookie',
464
- 'or by passing backendManagerKey or authenticationToken in the body or query', logOptions);
542
+ // self.log('No Firebase ID token was able to be extracted.',
543
+ // 'Make sure you authenticate your request by providing either the following HTTP header:',
544
+ // 'Authorization: Bearer <Firebase ID Token>',
545
+ // 'or by passing a "__session" cookie',
546
+ // 'or by passing backendManagerKey or authenticationToken in the body or query', logOptions);
465
547
 
466
548
  return _resolve(self.request.user);
467
549
  }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Middleware
3
+ * Used to handle middleware for the assistant
4
+ */
5
+
6
+ const path = require('path');
7
+
8
+ function Middleware(m) {
9
+ const self = this;
10
+
11
+ self.Manager = m;
12
+ }
13
+
14
+ Middleware.prototype.run = function (library, req, res, options) {
15
+ const self = this;
16
+ const Manager = self.Manager;
17
+ const { cors } = Manager.libraries;
18
+
19
+ return cors(req, res, async () => {
20
+ const assistant = Manager.Assistant({req: req, res: res});
21
+
22
+ const data = assistant.request.data;
23
+ const geolocation = assistant.request.geolocation;
24
+ const client = assistant.request.client;
25
+
26
+ // Set options
27
+ options = options || {};
28
+ options.setupAnalytics = typeof options.setupAnalytics === 'boolean' ? options.setupAnalytics : true;
29
+ options.setupUsage = typeof options.setupUsage === 'boolean' ? options.setupUsage : true;
30
+
31
+ // Log
32
+ assistant.log(`Middleware.process(): Request (${geolocation.ip} @ ${geolocation.country}, ${geolocation.region}, ${geolocation.city})`, JSON.stringify(data));
33
+
34
+ // Load library
35
+ try {
36
+ library = path.resolve(process.cwd(), `${library}.js`);
37
+ library = new (require(library))();
38
+ } catch (e) {
39
+ assistant.errorManager(`Unable to load library @ (${library}): ${e.message}`, {sentry: true, send: true, log: true});
40
+ }
41
+
42
+ // Setup analytics
43
+ if (options.setupAnalytics) {
44
+ assistant.analytics = Manager.Analytics({
45
+ assistant: assistant,
46
+ uuid: assistant.request.geolocation.ip,
47
+ })
48
+ }
49
+
50
+ // Setup usage
51
+ if (options.setupUsage) {
52
+ assistant.usage = await Manager.Usage().init(assistant);
53
+ }
54
+
55
+ // Process
56
+ try {
57
+ library.main(assistant, req, res)
58
+ // .then(result => {
59
+ // return res.status(200).json(result);
60
+ // })
61
+ .catch(e => {
62
+ assistant.errorManager(e, {sentry: true, send: true, log: true});
63
+ });
64
+ } catch (e) {
65
+ assistant.errorManager(e, {sentry: true, send: true, log: true});
66
+ }
67
+ });
68
+ };
69
+
70
+ module.exports = Middleware;
@@ -244,7 +244,11 @@ Usage.prototype.getUsage = function (path) {
244
244
  const Manager = self.Manager;
245
245
  const assistant = self.assistant;
246
246
 
247
- return _.get(self.user, `usage.${path}.period`, 0);
247
+ if (path) {
248
+ return _.get(self.user, `usage.${path}.period`, 0);
249
+ } else {
250
+ return self.user.usage;
251
+ }
248
252
  };
249
253
 
250
254
  Usage.prototype.getLimit = function (path) {
@@ -252,7 +256,13 @@ Usage.prototype.getLimit = function (path) {
252
256
  const Manager = self.Manager;
253
257
  const assistant = self.assistant;
254
258
 
255
- return _.get(self.app, `products.${self.options.app}-${self.user.plan.id}.limits.${path}`, 0);
259
+ const key = `products.${self.options.app}-${self.user.plan.id}.limits`;
260
+
261
+ if (path) {
262
+ return _.get(self.app, `${key}.${path}`, 0);
263
+ } else {
264
+ return _.get(self.app, key, {});
265
+ }
256
266
  };
257
267
 
258
268
  Usage.prototype.update = function () {
@@ -606,6 +606,12 @@ Manager.prototype.Usage = function () {
606
606
  return new self.libraries.Usage(self, ...arguments);
607
607
  };
608
608
 
609
+ Manager.prototype.Middleware = function () {
610
+ const self = this;
611
+ self.libraries.Middleware = self.libraries.Middleware || require('./helpers/middleware.js');
612
+ return new self.libraries.Middleware(self, ...arguments);
613
+ };
614
+
609
615
  Manager.prototype.Settings = function () {
610
616
  const self = this;
611
617
  self.libraries.Settings = self.libraries.Settings || require('./helpers/settings.js');