backend-manager 3.2.96 → 3.2.97

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.2.96",
3
+ "version": "3.2.97",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -36,6 +36,7 @@
36
36
  "dependencies": {
37
37
  "@firebase/rules-unit-testing": "^2.0.7",
38
38
  "@google-cloud/storage": "^7.9.0",
39
+ "@octokit/rest": "^19.0.13",
39
40
  "@sendgrid/mail": "^7.7.0",
40
41
  "@sentry/node": "^6.19.7",
41
42
  "busboy": "^1.6.0",
@@ -71,4 +72,4 @@
71
72
  "wonderful-log": "^1.0.5",
72
73
  "yargs": "^17.7.2"
73
74
  }
74
- }
75
+ }
@@ -0,0 +1,95 @@
1
+ const { Octokit } = require('@octokit/rest');
2
+
3
+ function Module() {
4
+
5
+ }
6
+
7
+ Module.prototype.main = function () {
8
+ const self = this;
9
+ const Manager = self.Manager;
10
+ const Api = self.Api;
11
+ const assistant = self.assistant;
12
+ const payload = self.payload;
13
+
14
+ return new Promise(async function(resolve, reject) {
15
+ // Setup Octokit
16
+ const octokit = new Octokit({
17
+ auth: Manager?.config?.github?.key,
18
+ });
19
+
20
+ // Setup options
21
+ payload.data.payload.url = payload.data.payload.url || '';
22
+
23
+ // Check for required parameters
24
+ if (!payload.data.payload.url) {
25
+ return reject(assistant.errorify(`Missing required parameter: url`, {code: 400}));
26
+ }
27
+
28
+ let url;
29
+ try {
30
+ url = new URL(payload.data.payload.url);
31
+ } catch (e) {
32
+ return reject(assistant.errorify(`Invalid URL`, {code: 400}));
33
+ }
34
+
35
+ // Get the post
36
+ const filename = url.pathname.replace(/blog|\//ig, '')
37
+ const repoInfo = assistant.parseRepo(self?.Manager?.config?.github?.repo_website);
38
+ const query = `title+repo:${repoInfo.user}/${repoInfo.name}+filename:${filename}`;
39
+
40
+ assistant.log('Running search', query, repoInfo);
41
+
42
+ // Using octokit, search the repo for the file matching the url
43
+ // https://stackoverflow.com/questions/25564760/how-can-i-search-file-name-in-specific-github-repository
44
+ // https://docs.github.com/en/search-github/searching-on-github/searching-code#search-by-filename
45
+ const results = await octokit.rest.search.code({
46
+ q: query,
47
+ }).catch(e => e);
48
+
49
+ // Log
50
+ assistant.log('Results', results);
51
+
52
+ // Check for errors
53
+ if (results instanceof Error) {
54
+ return reject(assistant.errorify(`Error searching for post: ${results}`, {code: 500}));
55
+ } else if (results?.data?.total_count === 0) {
56
+ return reject(assistant.errorify(`Post not found`, {code: 404}));
57
+ }
58
+
59
+ // Get the first results
60
+ const firstResult = results.data.items[0];
61
+
62
+ // Fetch the content of the post
63
+ const post = await octokit.rest.repos.getContent({
64
+ owner: repoInfo.user,
65
+ repo: repoInfo.name,
66
+ path: firstResult.path,
67
+ }).catch(e => e);
68
+
69
+
70
+ // Log
71
+ assistant.log('Post', post);
72
+
73
+ // Check for errors
74
+ if (post instanceof Error) {
75
+ return reject(assistant.errorify(`Error fetching post: ${post}`, {code: 500}));
76
+ }
77
+
78
+ // Decode the content
79
+ const fullContent = Buffer.from(post.data.content, 'base64').toString();
80
+ const splitContent = fullContent.split('---');
81
+ const frontmatter = splitContent[1].trim();
82
+ const body = splitContent.slice(2).join('---').trim();
83
+
84
+ // Return
85
+ return resolve({
86
+ data: {
87
+ frontmatter: frontmatter,
88
+ body: body,
89
+ }
90
+ });
91
+ });
92
+
93
+ };
94
+
95
+ module.exports = Module;
@@ -0,0 +1,52 @@
1
+ function Module() {
2
+
3
+ }
4
+
5
+ Module.prototype.main = function () {
6
+ const self = this;
7
+ const Manager = self.Manager;
8
+ const Api = self.Api;
9
+ const assistant = self.assistant;
10
+ const payload = self.payload;
11
+
12
+ return new Promise(async function(resolve, reject) {
13
+ // Log
14
+ assistant.log('assistant.cwd', assistant.cwd)
15
+
16
+ // If not dev, quit
17
+ if (!assistant.isDevelopment()) {
18
+ return reject(assistant.errorify(`This command is only available in development mode`, {code: 401}));
19
+ }
20
+
21
+ // Check for required options
22
+ if (!payload.data.payload.path) {
23
+ return reject(assistant.errorify(`Missing required parameter: path`, {code: 400}));
24
+ }
25
+
26
+ // Load the hook
27
+ let hook;
28
+ try {
29
+ hook = (new (require(pathify(`${assistant.cwd}/${payload.data.payload.path}.js`)))()).main(Manager);
30
+ } catch (e) {
31
+ hook = (new (require(pathify(`${assistant.cwd}/methods/hooks/${payload.data.payload.path}.js`)))()).main(Manager);
32
+ }
33
+
34
+ //
35
+ if (payload.data.payload.status >= 200 && payload.data.payload.status <= 299) {
36
+ return resolve({data: payload.data.payload.response, status: payload.data.payload.status});
37
+ } else if (payload.data.payload.status >= 400 && payload.data.payload.status <= 599) {
38
+ return reject(assistant.errorify(payload.data.payload.response || 'Unknown error message provided', {code: payload.data.payload.status}));
39
+ }
40
+ });
41
+ };
42
+
43
+ function pathify(path) {
44
+ const fixed = path
45
+ // Replace .js
46
+ .replace('.js', '')
47
+
48
+ // Return
49
+ return `${fixed}.js`;
50
+ }
51
+
52
+ module.exports = Module;
@@ -1,235 +1,421 @@
1
- // const ua = require('universal-analytics');
2
- const get = require('lodash/get');
3
1
  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}/;
2
+ const moment = require('moment');
6
3
  let uuidv5;
7
4
 
8
- const BLACKLISTED_USER_AGENTS = new RegExp([
9
- /(node-fetch)/,
10
- ].map(r => r.source).join(''));
5
+ const UUID_REGEX = /[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
+ const BLOCKED_USER_AGENTS = [
7
+ /(node-fetch)/ig,
8
+ ];
11
9
 
12
10
  function Analytics(Manager, options) {
13
11
  const self = this;
14
- self.Manager = Manager;
15
- // self._request = self.Manager._inner || {};
16
12
 
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;
13
+ // Set initialized
14
+ self.initialized = false;
21
15
 
22
16
  // Fix options
23
17
  options = options || {};
24
18
 
25
19
  // 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', ''),
20
+ self.Manager = Manager;
21
+ self.assistant = options?.assistant || Manager.Assistant();
22
+
23
+ // Set request properties
24
+ self.request = {
25
+ ip: self.assistant?.request?.geolocation?.ip || '127.0.0.1',
26
+ country: self.assistant?.request?.geolocation?.country || '',
27
+ city: self.assistant?.request?.geolocation?.city || '',
28
+ referrer: self.assistant?.request?.referrer || '',
29
+ userAgent: self.assistant?.request?.client?.userAgent || '',
30
+ language: (self.assistant?.request?.client?.language || '').split(',')[0],
31
+ mobile: self.assistant?.request?.client?.mobile || false,
32
+ platform: self.assistant?.request?.client?.platform || '',
33
+ name: self.assistant?.meta?.name || '',
33
34
  }
34
35
 
35
- self._request.userAgent = self._request.userAgent.match(BLACKLISTED_USER_AGENTS) ? '' : self._request.userAgent;
36
+ // Remove blacklisted user agents
37
+ self.request.userAgent = BLOCKED_USER_AGENTS.some((regex) => self.request.userAgent.match(regex))
38
+ ? ''
39
+ : self.request.userAgent;
36
40
 
37
- self._data_soruce = 'server';
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._assistant.isDevelopment() : options.debug;
41
- self._pageview = typeof options.pageview === 'undefined' ? true : options.pageview;
42
- self._version = self.Manager.package.version;
43
- self._initialized = false;
41
+ // Fix options
42
+ options.dataSource = options.dataSource || 'server';
43
+ options.uuid = options.uuid || self.request.ip || Manager.SERVER_UUID;
44
+ options.isDevelopment = typeof options.isDevelopment === 'undefined' ? self.assistant.isDevelopment() : options.isDevelopment;
45
+ options.pageview = typeof options.pageview === 'undefined' ? true : options.pageview;
46
+ options.version = options.version || Manager.package.version;
47
+ options.userProperties = options.userProperties || {};
48
+
49
+ // Set user
50
+ // https://www.optimizesmart.com/how-to-create-and-use-user-properties-in-ga4/
51
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/user-properties?client_type=gtag
52
+ // https://support.google.com/analytics/answer/12980150?hl=en&co=GENIE.Platform%3DAndroid
53
+ const authUser = self.assistant?.usage?.user;
54
+ self.user = {
55
+ app_version: {
56
+ value: options.version,
57
+ },
58
+ // browser: {
59
+ // value: self.request.userAgent,
60
+ // },
61
+ device_category: {
62
+ value: self.request.mobile ? 'mobile' : 'desktop',
63
+ },
64
+ // device_model: {
65
+ // value: 'None',
66
+ // },
67
+ operating_system: {
68
+ value: self.request.platform,
69
+ },
70
+ // os_version: {
71
+ // value: 'None',
72
+ // },
73
+ // os_with_version: {
74
+ // value: 'None',
75
+ // },
76
+ platform: {
77
+ value: 'web',
78
+ },
79
+ // screen_resolution: {
80
+ // value: 'None',
81
+ // },
82
+ age: {
83
+ value: authUser?.personal?.birthday?.timestampUNIX
84
+ ? new Date().getFullYear() - new Date(authUser?.personal?.birthday?.timestampUNIX).getFullYear()
85
+ : 'None',
86
+ },
87
+ country: {
88
+ value: self.request.country
89
+ },
90
+ city: {
91
+ value: self.request.city
92
+ },
93
+ gender: {
94
+ value: authUser?.personal?.gender
95
+ ? authUser?.personal?.gender
96
+ : 'None',
97
+ },
98
+ // interests: {
99
+ // value: 'None',
100
+ // },
101
+ language: {
102
+ value: self.request.language,
103
+ },
104
+
105
+ // TODO
106
+ // Add custom events for user properties, like plan ID, etc, draw from self.assistant.usage, etc
107
+ authenticated: {
108
+ value: authUser?.auth?.uid ? true : false,
109
+ },
110
+ plan_id: {
111
+ value: authUser?.plan?.id || 'basic',
112
+ },
113
+ plan_trial_activated: {
114
+ value: authUser?.plan?.trial?.activated || false,
115
+ },
116
+ activity_created: {
117
+ value: moment(authUser?.activity?.created?.timestampUNIX
118
+ ? authUser?.activity?.created?.timestamp
119
+ : self.assistant.meta.startTime.timestamp).format('YYYY-MM-DD'),
120
+ },
121
+
122
+ // ds? 'app
123
+ // uid?
124
+ // uip?
125
+ // ua?
126
+ // dr? (referrer)
127
+ };
128
+
129
+ // Merge user properties
130
+ self.user = {
131
+ ...self.user,
132
+ ...options.userProperties,
133
+ };
134
+
135
+ // Set id and secret
136
+ self.analyticsId = self?.Manager?.config?.google_analytics?.id;
137
+ self.analyticsSecret = self?.Manager?.config?.google_analytics?.secret;
44
138
 
139
+ // Check if we have the required properties
45
140
  if (!self.analyticsId) {
46
- self._assistant.log('analytics(): Not initializing because missing analyticsId', self.analyticsId);
141
+ self.assistant.log('analytics(): Not initializing because missing analyticsId', self.analyticsId);
47
142
  return self;
48
143
  } else if (!self.analyticsSecret) {
49
- self._assistant.log('analytics(): Not initializing because missing analyticsSecret', self.analyticsSecret);
144
+ self.assistant.log('analytics(): Not initializing because missing analyticsSecret', self.analyticsSecret);
50
145
  return self;
51
146
  }
52
147
 
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');
148
+ // Automatically convert the supplied uuid to a valid uuid (in case the user supplies something else like an IP or email)
149
+ options.uuid = options.uuid.match(UUID_REGEX)
150
+ ? options.uuid
151
+ : self.generateId(options.uuid);
57
152
 
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
- // }
153
+ // Attach options
154
+ self.options = options;
71
155
 
72
- self._initialized = true;
156
+ // Set initialized
157
+ self.initialized = true;
73
158
 
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
- }
159
+ // Send pageview if enabled
160
+ // .. Removed
82
161
 
162
+ // Return
83
163
  return self;
84
164
  }
85
165
 
86
166
  Analytics.prototype.generateId = function (id) {
87
167
  const 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
- // Disabled because we are using the Measurement Protocol (12/19/2023)
95
- Analytics.prototype.pageview = function (options) {
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
- // }
168
+ const Manager = self.Manager;
169
+ const assistant = self.assistant;
170
+ const options = self.options;
171
+ const request = self.request;
172
+ const user = self.user;
109
173
 
110
- // // self.user.pageview({
111
- // // dp: options.path,
112
- // // dl: options.location,
113
- // // dh: options.host,
114
- // // dt: options.title,
115
- // // }).send();
174
+ // Load uuidv5
175
+ uuidv5 = uuidv5 || require('uuid').v5;
116
176
 
117
- // // self.send();
177
+ // Get namespace
178
+ const namespace = Manager?.config?.backend_manager?.namespace || undefined;
118
179
 
119
- // return self;
180
+ // Generate id
181
+ return id && namespace
182
+ ? uuidv5(id, namespace)
183
+ : undefined;
120
184
  };
121
185
 
122
- Analytics.prototype.event = function (options) {
186
+ Analytics.prototype.event = function (payload) {
123
187
  const self = this;
124
-
125
- // Fix options
126
- options = options || {};
127
-
128
- if (!self._initialized) {
188
+ const Manager = self.Manager;
189
+ const assistant = self.assistant;
190
+ const options = self.options;
191
+ const request = self.request;
192
+ const user = self.user;
193
+
194
+ // Fix payload
195
+ // https://support.google.com/analytics/answer/13316687?hl=en#zippy=%2Cweb
196
+ // https://support.google.com/analytics/answer/9268042?sjid=4476481583372132143-NC
197
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events#screen_view
198
+ payload = payload || {};
199
+ payload.name = (payload.name || '')
200
+ // Replace slashes and spaces with underscores
201
+ .replace(/\/| /g, '_')
202
+ // Remove leading and trailing underscores
203
+ .replace(/^_+|_+$/g, '')
204
+ // Remove multiple underscores
205
+ .replace(/_+/g, '_');
206
+ payload.params = payload.params || {};
207
+
208
+ // Check if initialized
209
+ if (!self.initialized) {
129
210
  return self;
130
- } else if (self._debug) {
131
- self._assistant.log('analytics(): Skipping Analytics.event() because in development', self._uuid, options);
211
+ } else if (options.isDevelopment) {
212
+ assistant.log('analytics().event(): Skipping because in development', options.uuid, JSON.stringify(payload));
132
213
  return self;
133
214
  }
134
215
 
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
- });
147
-
148
- return self;
149
- };
150
-
151
- Analytics.prototype.send = function (event) {
152
- const self = this;
153
-
154
- if (!self._initialized) {
155
- return self;
156
- } else if (self._debug) {
157
- self._assistant.log('analytics(): Skipping Analytics.event() because in development', self._uuid, event);
158
- return self;
159
- }
216
+ // https://stackoverflow.com/questions/71871458/how-to-send-user-properties-to-measurement-protocol-google-analytics-4
217
+
218
+ // USer properties
219
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/user-properties?client_type=gtag
220
+
221
+ // raw
222
+ // https://stackoverflow.com/questions/69105735/google-analytics-4-measurement-protocol-api-used-without-gtag-js-or-firebase
223
+
224
+ // Fix payload
225
+ payload.params.event_source = options.dataSource;
226
+ payload.params.page_location = request.name; // Supposed to be domain
227
+ // payload.params.page_location = `${INSERT DOMAIN HERE}${request.name}`; // Supposed to be domain
228
+ payload.params.page_title = request.name; // Supposed to be title
229
+ payload.params.ip_override = request.ip;
230
+ payload.params.user_agent = request.userAgent;
231
+ payload.params.page_referrer = request.referrer;
232
+ // https://stackoverflow.com/questions/70708893/google-analytics-4-measurement-protocol-shows-events-but-no-users/71811327#71811327
233
+ payload.params.engagement_time_msec = new Date().getTime() - new Date(assistant.meta.startTime.timestamp).getTime();
234
+ // payload.params.engagement_time_msec = 1;
235
+ payload.params.debug_mode = false;
236
+ payload.params.session_id = assistant.id;
237
+ // payload.params.campaign = 'your_campaign';
238
+ // payload.params.source = 'your_source';
239
+ // payload.params.medium = 'your_medium';
240
+ // payload.params.term = 'your_term';
241
+ // payload.params.content = 'your_content';
242
+
243
+
244
+ // https://stackoverflow.com/questions/75998626/city-is-not-populating-in-ga4-measurement-protocol-api
245
+ // {
246
+ // "client_id": "6909975079.1681323722",
247
+ // "events": [
248
+ // {
249
+ // "name": "page_view",
250
+ // "params": {
251
+ // "page_title": "Wedding: QR Code",
252
+ // "hostname": "scan.example.com",
253
+ // "landing_page": "/eRCU",
254
+ // "page_location": "https://scan.example.com/eRCU",
255
+ // "page_referrer": "https://scan.example.com/eRCU",
256
+ // "city": "Mumbai",
257
+ // "continent_code": "AS",
258
+ // "country_code": "IN",
259
+ // "country": "India",
260
+ // "latitude": "19.0748",
261
+ // "longitude": "72.8856",
262
+ // "session_id": 1681313623,
263
+ // "engagement_time_msec": 264,
264
+ // "ip_address": "XX.36.XXX.14"
265
+ // }
266
+ // }
267
+ // ]
268
+ // }
160
269
 
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
270
 
271
+ // Build url and body
272
+ const url = `https://www.google-analytics.com/mp/collect?measurement_id=${self.analyticsId}&api_secret=${self.analyticsSecret}`;
201
273
  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],
274
+ client_id: self.options.uuid,
275
+ user_id: self.options.uuid,
276
+ // timestamp_micros: new Date().getTime() * 1000,
277
+ user_properties: user,
278
+ // consent: {},
279
+ // non_personalized_ads: false,
280
+ events: [payload],
207
281
  }
208
282
 
209
- // Log
210
- if (self._assistant.isDevelopment()) {
211
- self._assistant.log('analytics().send(): Sending...', JSON.stringify(body));
283
+ // Log full payload
284
+ if (assistant.isDevelopment()) {
285
+ assistant.log('analytics().event(): Sending...', url, JSON.stringify(body));
212
286
  }
213
287
 
214
288
  // 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}`, {
289
+ fetch(url, {
217
290
  method: 'post',
218
291
  response: 'text',
219
292
  tries: 2,
220
293
  timeout: 30000,
294
+ // headers: {
295
+ // "Content-Type": "application/json"
296
+ // },
221
297
  body: body,
222
298
  })
223
299
  .then((r) => {
224
- if (self._assistant.isDevelopment()) {
225
- self._assistant.log('analytics().send(): Success', r);
300
+ if (assistant.isDevelopment()) {
301
+ assistant.log('analytics().event(): Success', r);
226
302
  }
227
303
  })
228
304
  .catch((e) => {
229
- self._assistant.error('analytics().send(): Failed', e);
305
+ assistant.error('analytics().event(): Failed', e);
230
306
  });
231
307
 
308
+ // Return
232
309
  return self;
233
310
  };
234
311
 
312
+
313
+
314
+
315
+ // Analytics.prototype.send = function (event) {
316
+ // const self = this;
317
+
318
+ // // Check if initialized
319
+ // if (!self.initialized) {
320
+ // return self;
321
+ // } else if (self.options.isDevelopment) {
322
+ // assistant.log('analytics(): Skipping Analytics.event() because in development', self.options.uuid, event);
323
+ // return self;
324
+ // }
325
+
326
+ // /*
327
+ // https://stackoverflow.com/questions/68773179/what-should-the-client-id-be-when-sending-events-to-google-analytics-4-using-the
328
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag
329
+
330
+
331
+ // https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#sc
332
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports
333
+ // https://stackoverflow.com/questions/43049662/how-to-send-measurement-protocol-if-there-is-no-clientid
334
+
335
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=gtag
336
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag
337
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events
338
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/ua-feature-matrix
339
+ // */
340
+
341
+
342
+ // /*
343
+
344
+ // */
345
+
346
+ // // Format event
347
+ // // event = event || {};
348
+ // // event.name = event.name || '';
349
+ // // event.params = event.params || {};
350
+ // // event.params.engagement_time_msec = `${new Date().getTime() - new Date(self.assistant.meta.startTime.timestamp).getTime()}`;
351
+ // // event.params.event_source = self.options.dataSource;
352
+ // // event.params.ip_override = self.request.ip;
353
+ // // event.params.user_agent = self.request.userAgent;
354
+ // // event.params.page_location = self.request.name;
355
+ // // event.params.page_referrer = self.request.referrer;
356
+ // // event.params.page_title = self.request.name;
357
+
358
+ // event = event || {};
359
+ // event.name = event.name || '';
360
+ // event.params = event.params || {};
361
+ // // event.params.session_id = self.assistant.id;
362
+ // // event.params.engagement_time_msec = `${new Date().getTime() - new Date(self.assistant.meta.startTime.timestamp).getTime()}`;
363
+ // // event.params.event_source = self.options.dataSource;
364
+ // // event.params.ip_override = self.request.ip;
365
+ // // event.params.user_agent = self.request.userAgent;
366
+ // // event.params.page_location = self.request.name;
367
+ // // event.params.page_referrer = self.request.referrer;
368
+ // // event.params.page_title = self.request.name;
369
+
370
+ // // "event_source": "server",
371
+ // // "page_location": "https:\/\/www.yourdomain.com\/page2",
372
+ // // "page_referrer": "\/page1",
373
+ // // "page_title": "Page 2",
374
+ // // "ip_override": "xxx.xxx.xxx.0",
375
+ // // "campaign": "your_campaign",
376
+ // // "source": "your_source",
377
+ // // "medium": "your_medium",
378
+ // // "term": "your_term",
379
+ // // "content": "your_content"
380
+
381
+ // const url = `https://www.google-analytics.com/mp/collect?measurement_id=${self.analyticsId}&api_secret=${self.analyticsSecret}`;
382
+ // const body = {
383
+ // client_id: self.options.uuid,
384
+ // user_id: self.options.uuid,
385
+ // // timestamp_micros: new Date().getTime() * 1000,
386
+ // user_properties: {},
387
+ // // consent: {},
388
+ // // non_personalized_ads: false,
389
+ // events: [event],
390
+ // }
391
+
392
+ // // Log
393
+ // if (self.assistant.isDevelopment()) {
394
+ // assistant.log('analytics().send(): Sending...', url, JSON.stringify(body));
395
+ // }
396
+
397
+ // // Send event
398
+ // fetch(url, {
399
+ // // fetch(`https://www.google-analytics.com/debug/mp/collect?measurement_id=${self.analyticsId}&api_secret=${self.analyticsSecret}`, {
400
+ // method: 'post',
401
+ // response: 'text',
402
+ // tries: 2,
403
+ // timeout: 30000,
404
+ // // headers: {
405
+ // // "Content-Type": "application/json"
406
+ // // },
407
+ // body: body,
408
+ // })
409
+ // .then((r) => {
410
+ // if (self.assistant.isDevelopment()) {
411
+ // assistant.log('analytics().send(): Success', r);
412
+ // }
413
+ // })
414
+ // .catch((e) => {
415
+ // self.assistant.error('analytics().send(): Failed', e);
416
+ // });
417
+
418
+ // return self;
419
+ // };
420
+
235
421
  module.exports = Analytics;
@@ -53,7 +53,17 @@ Settings.prototype.resolve = function (assistant, schema, settings) {
53
53
  // assistant.log('resolvedValue:', resolvedValue);
54
54
 
55
55
  // Check if this node is marked as required
56
- if (schemaNode.required && typeof originalValue === 'undefined') {
56
+ let isRequired = false;
57
+ if (typeof schemaNode.required === 'function') {
58
+ isRequired = schemaNode.required(assistant);
59
+ } else if (typeof schemaNode.required === 'boolean') {
60
+ isRequired = schemaNode.required;
61
+ }
62
+
63
+ // assistant.log('isRequired:', isRequired);
64
+
65
+ // If the key is required and the original value is undefined, throw an error
66
+ if (isRequired && typeof originalValue === 'undefined') {
57
67
  throw assistant.errorify(`Required key {${path}} is missing in settings`, {code: 400});
58
68
  }
59
69
 
@@ -436,6 +436,7 @@ Manager.prototype.init = function (exporter, options) {
436
436
  self.storage();
437
437
  }
438
438
 
439
+ // Fetch stats
439
440
  if (self.assistant.isDevelopment() && options.fetchStats) {
440
441
  setTimeout(function () {
441
442
  self.assistant.log('Fetching meta/stats...');
@@ -445,9 +446,52 @@ Manager.prototype.init = function (exporter, options) {
445
446
  .then(doc => {
446
447
  self.assistant.log('meta/stats', doc.data());
447
448
  })
448
- }, 3000);
449
+ }, 100);
449
450
  }
450
451
 
452
+ // Send analytics
453
+ // self.Analytics({
454
+ // assistant: self.assistant,
455
+ // uuid: self.SERVER_UUID,
456
+ // isDevelopment: false,
457
+ // })
458
+ // .event({
459
+ // // category: 'admin',
460
+ // // action: 'initialized',
461
+ // // label: '',
462
+ // // name: 'admin/initialized',
463
+ // // name: 'screen_view',
464
+ // // params: {
465
+ // // screen_class: 'MainActivity',
466
+ // // },
467
+ // });
468
+
469
+ // self.Analytics({
470
+ // assistant: self.assistant,
471
+ // // uuid: '256675177.1692831448',
472
+ // // uuid: self.SERVER_UUID,
473
+ // uuid: 'a5b43c29-7bb3-514b-b7bb-33782cd8d802',
474
+ // isDevelopment: false,
475
+ // })
476
+ // .event({
477
+ // "name": "purchase",
478
+ // "params": {
479
+ // "transaction_id": "T12345",
480
+ // "value": 123.45,
481
+ // "currency": "USD",
482
+ // "items": [
483
+ // {
484
+ // "item_id": "sku1234",
485
+ // "item_name": "widget",
486
+ // "quantity": 1,
487
+ // "item_brand": "Google",
488
+ // "price": 123.45
489
+ // }
490
+ // ]
491
+ // }
492
+ // });
493
+
494
+ // Return
451
495
  return self;
452
496
  };
453
497