backend-manager 3.0.64 → 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.64",
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
+ }
@@ -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
  };
@@ -392,10 +392,12 @@ BackendAssistant.prototype.errorManager = function(e, options) {
392
392
  if (options.send && self.ref.res && self.ref.res.status) {
393
393
  self.ref.res
394
394
  .status(options.code)
395
- .send(newError
396
- ? newError.message || newError
397
- : 'Unknown error'
398
- );
395
+ .send((
396
+ newError?.message
397
+ // ? newError.stack
398
+ ? newError.message
399
+ : newError
400
+ ) || 'Unknown error');
399
401
  }
400
402
 
401
403
  return {
@@ -488,11 +490,11 @@ BackendAssistant.prototype.authenticate = async function (options) {
488
490
  }
489
491
  }
490
492
 
491
- if (req.headers && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
493
+ if (req?.headers?.authorization?.startsWith('Bearer ')) {
492
494
  // Read the ID Token from the Authorization header.
493
495
  idToken = req.headers.authorization.split('Bearer ')[1];
494
496
  self.log('Found "Authorization" header', idToken, logOptions);
495
- } else if (req.cookies && req.cookies.__session) {
497
+ } else if (req?.cookies?.__session) {
496
498
  // Read the ID Token from cookie.
497
499
  idToken = req.cookies.__session;
498
500
  self.log('Found "__session" cookie', idToken, logOptions);
@@ -517,29 +519,31 @@ BackendAssistant.prototype.authenticate = async function (options) {
517
519
  }
518
520
  } else if (options.apiKey) {
519
521
  self.log('Found "options.apiKey"', options.apiKey, logOptions);
522
+
520
523
  if (options.apiKey.includes('test')) {
521
524
  return _resolve(self.request.user);
522
525
  }
526
+
523
527
  await admin.firestore().collection(`users`)
524
- .where('api.privateKey', '==', options.apiKey)
525
- .get()
526
- .then(function(querySnapshot) {
527
- querySnapshot.forEach(function(doc) {
528
- self.request.user = doc.data();
529
- 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);
530
538
  });
531
- })
532
- .catch(function(error) {
533
- console.error('Error getting documents: ', error);
534
- });
535
539
 
536
540
  return _resolve(self.request.user);
537
541
  } else {
538
- self.log('No Firebase ID token was able to be extracted.',
539
- 'Make sure you authenticate your request by providing either the following HTTP header:',
540
- 'Authorization: Bearer <Firebase ID Token>',
541
- 'or by passing a "__session" cookie',
542
- '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);
543
547
 
544
548
  return _resolve(self.request.user);
545
549
  }