backend-manager 3.2.99 → 3.2.101

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.99",
3
+ "version": "3.2.101",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -0,0 +1,215 @@
1
+ const fetch = require('node-fetch');
2
+ const wonderfulFetch = require('wonderful-fetch');
3
+ const Poster = require('ultimate-jekyll-poster');
4
+ const pathApi = require('path');
5
+ const { get } = require('lodash');
6
+
7
+ function Module() {
8
+
9
+ }
10
+
11
+ Module.prototype.main = function () {
12
+ const self = this;
13
+ const Manager = self.Manager;
14
+ const Api = self.Api;
15
+ const assistant = self.assistant;
16
+ const payload = self.payload;
17
+
18
+ return new Promise(async function(resolve, reject) {
19
+ // Perform checks
20
+ if (!payload.user.roles.admin && !payload.user.roles.blogger) {
21
+ return reject(assistant.errorify(`Admin required.`, {code: 401}));
22
+ }
23
+
24
+ // Get repo info
25
+ const repoInfo = assistant.parseRepo(get(self.Manager.config, 'github.repo_website'));
26
+ const poster = new Poster();
27
+
28
+ assistant.log(`main(): Creating post...`, repoInfo);
29
+
30
+ // Set defaults
31
+ const githubUser = get(self.Manager.config, 'github.user');
32
+ const githubKey = get(self.Manager.config, 'github.key');
33
+
34
+ // Save to disk OR commit
35
+ poster.onDownload = function (meta) {
36
+ return new Promise(async function(resolve, reject) {
37
+ const tempPath = meta.tempPath;
38
+ const finalPath = poster.removeDirDot(meta.finalPath);
39
+
40
+ // Log
41
+ assistant.log(`onDownload(): tempPath`, tempPath);
42
+ assistant.log(`onDownload(): finalPath`, finalPath);
43
+
44
+ // Check for missing paths (need to check for meta.finalPath as well because it's not always set)
45
+ if (!tempPath || !finalPath || !meta.finalPath) {
46
+ return reject(new Error(`onDownload(): tempPath or finalPath is missing`));
47
+ }
48
+
49
+ // Save to disk
50
+ poster.readImage(tempPath)
51
+ .then(image => {
52
+ self.createFile(githubUser, repoInfo.user, repoInfo.name, githubKey, finalPath, image)
53
+ .then(() => {resolve()})
54
+ .catch((e) => {reject(e)})
55
+ })
56
+ .catch((e) => {reject(e)})
57
+
58
+ });
59
+ }
60
+
61
+ // Create post
62
+ const finalPost = await poster.create(payload.data).catch(e => e);
63
+ if (finalPost instanceof Error) {
64
+ return reject(assistant.errorify(`Failed to post: ${finalPost}`, {code: 500}));
65
+ }
66
+
67
+ // Request indexing
68
+ // DEPRECATED
69
+ // try {
70
+ // const url = get(self.Manager.config, 'brand.url');
71
+ // const encoded = encodeURIComponent(`${url}/sitemap.xml`);
72
+
73
+ // wonderfulFetch(`https://www.google.com/ping?sitemap=${encoded}`)
74
+
75
+ // // TODO
76
+ // // https://developers.google.com/search/apis/indexing-api/v3/prereqs
77
+ // // https://developers.google.com/search/apis/indexing-api/v3/using-api#url
78
+ // } catch (e) {
79
+ // assistant.error(`Failed to ping google: ${e}`);
80
+ // }
81
+
82
+ // Set file path and content
83
+ const filePath = poster.removeDirDot(finalPost.path);
84
+ const fileContent = finalPost.content;
85
+
86
+ // Save post OR commit
87
+ await self.createFile(githubUser, repoInfo.user, repoInfo.name, githubKey, filePath, fileContent)
88
+ .then(() => {
89
+ return resolve({data: finalPost});
90
+ })
91
+ .catch((e) => {
92
+ return reject(assistant.errorify(`Failed to post: ${e}`, {code: 500}));
93
+ })
94
+
95
+ });
96
+
97
+ };
98
+
99
+ // HELPERS //
100
+ Module.prototype.createFile = function (user, repoUser, repoName, key, path, contents) {
101
+ const self = this;
102
+ const Manager = self.Manager;
103
+ const Api = self.Api;
104
+ const assistant = self.assistant;
105
+ const payload = self.payload;
106
+
107
+ return new Promise(async (resolve, reject) => {
108
+ let fileParsed = pathApi.parse(path);
109
+ let base64Data = Buffer.from(contents).toString('base64');
110
+ let sha;
111
+
112
+ // Log
113
+ assistant.log(`createFile(): Writing file to ${repoUser}/${repoName}/${path}`);
114
+
115
+ // Try to get sha
116
+ try {
117
+ let branch = repoName === 'ultimate-jekyll' ? 'template' : 'master';
118
+ let pathGet = `https://api.github.com/repos/${repoUser}/${repoName}/git/trees/${branch}:${encodeURIComponent(pathApi.dirname(path))}`;
119
+
120
+ // Log
121
+ assistant.log(`createFile(): pathGet`, pathGet);
122
+
123
+ // Make request
124
+ await makeRequest({
125
+ method: 'GET',
126
+ url: pathGet,
127
+ body: {
128
+ },
129
+ timeout: 30000,
130
+ json: true,
131
+ headers: {
132
+ 'User-Agent': user,
133
+ // 'Authorization': `Basic ${user}:${key}`,
134
+ 'Authorization': `Basic ${Buffer.from(user + ':' + key).toString('base64')}`,
135
+ }
136
+ })
137
+ .then(function (resp) {
138
+ // sha = resp.sha;
139
+ sha = resp.tree.find(function (element) {
140
+ // console.log('checiing', element.path, fileParsed.base);
141
+ return element.path === fileParsed.base;
142
+ });
143
+ sha = sha.sha;
144
+ });
145
+ } catch (e) {
146
+ sha = null;
147
+ }
148
+
149
+ let pathPut = `https://api.github.com/repos/${repoUser}/${repoName}/contents/${path}`;
150
+ let writeRequest = {
151
+ // url: `https://api.github.com/repos/:owner/:repo/contents/:path`,
152
+ method: 'PUT',
153
+ url: pathPut,
154
+ body: {
155
+ message: `BackendManager Post: ${new Date().toISOString()}`,
156
+ content: base64Data,
157
+ },
158
+ timeout: 30000,
159
+ json: true,
160
+ headers: {
161
+ 'User-Agent': user,
162
+ // 'Authorization': `Basic ${user}:${key}`,
163
+ 'Authorization': `Basic ${Buffer.from(user + ':' + key).toString('base64')}`,
164
+ }
165
+ }
166
+
167
+ // Log
168
+ assistant.log(`createFile(): pathPut`, pathPut);
169
+
170
+ // Add sha if it exists
171
+ if (sha) {
172
+ writeRequest.body.sha = sha;
173
+ }
174
+
175
+ // Make request
176
+ await makeRequest(writeRequest)
177
+ .then((json) => {
178
+ if (!json || (json.message && (json.message === 'Not Found' || json.message.includes('Invalid request'))) ) {
179
+ return reject(new Error(json.message));
180
+ }
181
+ })
182
+ .catch((e) => {
183
+ return reject(e);
184
+ })
185
+ return resolve(true)
186
+ });
187
+ }
188
+
189
+ function makeRequest(options) {
190
+ return new Promise(function(resolve, reject) {
191
+ options.headers = options.headers || {};
192
+ options.headers['Content-Type'] = 'application/json';
193
+
194
+ let hasBody = Object.keys(options.body || {}).length > 0
195
+
196
+ fetch(options.url, {
197
+ method: options.method,
198
+ body: hasBody ? JSON.stringify(options.body) : undefined,
199
+ timeout: 30000,
200
+ headers: options.headers,
201
+ auth: options.auth,
202
+ })
203
+ .then(res => res.json())
204
+ .then(json => {
205
+ return resolve(json);
206
+ })
207
+ .catch(e => {
208
+ // console.error('e', e);
209
+ return reject(e);
210
+ })
211
+
212
+ });
213
+ }
214
+
215
+ module.exports = Module;
@@ -1,8 +1,17 @@
1
- const fetch = require('node-fetch');
2
- const wonderfulFetch = require('wonderful-fetch');
3
- const Poster = require('ultimate-jekyll-poster');
4
- const pathApi = require('path');
5
- const { get } = require('lodash');
1
+ const fetch = require('wonderful-fetch');
2
+ const moment = require('moment');
3
+ const jetpack = require('fs-jetpack');
4
+ const powertools = require('node-powertools');
5
+ const uuidv4 = require('uuid').v4;
6
+
7
+ const POST_TEMPLATE = jetpack.read(`${__dirname}/templates/post.html`);
8
+ const IMAGE_TAG = `{%- include /master/helpers/blog-image.html name="{name}" alt="{alt}" -%}`;
9
+ const IMAGE_PATH_SRC = `./assets/_src/images/blog/posts/post-{id}/`;
10
+ const IMAGE_PATH_REG = `/assets/images/blog/posts/post-{id}/`;
11
+ const AD_TAG = `{% include /master/modules/adunits/adsense-in-article.html index="{index}" %}`;
12
+
13
+ const IMAGE_REGEX = /(?:!\[(.*?)\]\((.*?)\))/img;
14
+ const LINK_REGEX = /(?:\[(.*?)\]\((.*?)\))/img;
6
15
 
7
16
  function Module() {
8
17
 
@@ -21,195 +30,161 @@ Module.prototype.main = function () {
21
30
  return reject(assistant.errorify(`Admin required.`, {code: 401}));
22
31
  }
23
32
 
24
- // Get repo info
25
- const repoInfo = assistant.parseRepo(get(self.Manager.config, 'github.repo_website'));
26
- const poster = new Poster();
33
+ // Log payload
34
+ assistant.log(`main(): payload.data`, payload.data);
35
+
36
+ // Set now
37
+ const now = assistant.meta.startTime.timestamp;
38
+ const bemRepo = assistant.parseRepo(Manager?.config?.github?.repo_website);
39
+
40
+ // Check for required values
41
+ if (!payload.data.payload.title) {
42
+ return reject(assistant.errorify(`Missing required parameter: title`, {code: 400}));
43
+ } else if (!payload.data.payload.url) {
44
+ return reject(assistant.errorify(`Missing required parameter: url`, {code: 400}));
45
+ } else if (!payload.data.payload.excerpt) {
46
+ return reject(assistant.errorify(`Missing required parameter: excerpt`, {code: 400}));
47
+ } else if (!payload.data.payload.headerImageURL) {
48
+ return reject(assistant.errorify(`Missing required parameter: headerImageURL`, {code: 400}));
49
+ } else if (!payload.data.payload.body) {
50
+ return reject(assistant.errorify(`Missing required parameter: body`, {code: 400}));
51
+ }
27
52
 
28
- assistant.log(`main(): Creating post...`, repoInfo);
53
+ // Fix other values
54
+ payload.data.payload.author = payload.data.payload.author || 'alex';
55
+ payload.data.payload.affiliate = payload.data.payload.affiliate || '';
56
+ payload.data.payload.tags = payload.data.payload.tags || '';
57
+ payload.data.payload.categories = payload.data.payload.categories || '';
29
58
 
30
- // Set defaults
31
- const githubUser = get(self.Manager.config, 'github.user');
32
- const githubKey = get(self.Manager.config, 'github.key');
33
-
34
- // Save to disk OR commit
35
- poster.onDownload = function (meta) {
36
- return new Promise(async function(resolve, reject) {
37
- const tempPath = meta.tempPath;
38
- const finalPath = poster.removeDirDot(meta.finalPath);
39
-
40
- // Log
41
- assistant.log(`onDownload(): tempPath`, tempPath);
42
- assistant.log(`onDownload(): finalPath`, finalPath);
43
-
44
- // Check for missing paths (need to check for meta.finalPath as well because it's not always set)
45
- if (!tempPath || !finalPath || !meta.finalPath) {
46
- return reject(new Error(`onDownload(): tempPath or finalPath is missing`));
47
- }
48
-
49
- // Save to disk
50
- poster.readImage(tempPath)
51
- .then(image => {
52
- self.createFile(githubUser, repoInfo.user, repoInfo.name, githubKey, finalPath, image)
53
- .then(() => {resolve()})
54
- .catch((e) => {reject(e)})
55
- })
56
- .catch((e) => {reject(e)})
57
-
58
- });
59
- }
59
+ // Fix even more values
60
+ payload.data.payload.layout = payload.data.payload.layout || 'app/blog/post';
61
+ payload.data.payload.date = payload.data.payload.date || moment(now).format('YYYY-MM-DD');
62
+ payload.data.payload.id = payload.data.payload.id || Math.round(new Date(now).getTime() / 1000);
63
+ payload.data.payload.path = payload.data.payload.path || `./_posts/${moment(now).format('YYYY')}/guest`;
64
+ payload.data.payload.githubUser = payload.data.payload.githubUser || bemRepo.user;
65
+ payload.data.payload.githubRepo = payload.data.payload.githubRepo || bemRepo.name;
60
66
 
61
- // Create post
62
- const finalPost = await poster.create(payload.data).catch(e => e);
63
- if (finalPost instanceof Error) {
64
- return reject(assistant.errorify(`Failed to post: ${finalPost}`, {code: 500}));
65
- }
67
+ // Log
68
+ assistant.log(`main(): Creating post...`, payload.data.payload);
66
69
 
67
- // Request indexing
68
- // DEPRECATED
69
- // try {
70
- // const url = get(self.Manager.config, 'brand.url');
71
- // const encoded = encodeURIComponent(`${url}/sitemap.xml`);
72
-
73
- // wonderfulFetch(`https://www.google.com/ping?sitemap=${encoded}`)
74
-
75
- // // TODO
76
- // // https://developers.google.com/search/apis/indexing-api/v3/prereqs
77
- // // https://developers.google.com/search/apis/indexing-api/v3/using-api#url
78
- // } catch (e) {
79
- // assistant.error(`Failed to ping google: ${e}`);
80
- // }
81
-
82
- // Set file path and content
83
- const filePath = poster.removeDirDot(finalPost.path);
84
- const fileContent = finalPost.content;
85
-
86
- // Save post OR commit
87
- await self.createFile(githubUser, repoInfo.user, repoInfo.name, githubKey, filePath, fileContent)
88
- .then(() => {
89
- return resolve({data: finalPost});
90
- })
91
- .catch((e) => {
92
- return reject(assistant.errorify(`Failed to post: ${e}`, {code: 500}));
93
- })
70
+ // Extract all images
71
+ await self.extractImages();
72
+
73
+ // Set defaults
74
+ const finalContent = powertools.template(POST_TEMPLATE, payload.data.payload);
75
+
76
+ // Log
77
+ assistant.log(`main(): finalContent`, finalContent);
94
78
 
95
79
  });
96
80
 
97
81
  };
98
82
 
99
- // HELPERS //
100
- Module.prototype.createFile = function (user, repoUser, repoName, key, path, contents) {
83
+ // Extract images
84
+ Module.prototype.extractImages = function () {
101
85
  const self = this;
102
86
  const Manager = self.Manager;
103
87
  const Api = self.Api;
104
88
  const assistant = self.assistant;
105
89
  const payload = self.payload;
106
90
 
107
- return new Promise(async (resolve, reject) => {
108
- let fileParsed = pathApi.parse(path);
109
- let base64Data = Buffer.from(contents).toString('base64');
110
- let sha;
91
+ return new Promise(async function(resolve, reject) {
92
+ // Extract images
93
+ const matches = payload.data.payload.body.matchAll(IMAGE_REGEX);
94
+ const images = Array.from(matches).map(match => ({
95
+ src: match[2] || '',
96
+ alt: match[1] || uuidv4(),
97
+ }));
111
98
 
112
99
  // Log
113
- assistant.log(`createFile(): Writing file to ${repoUser}/${repoName}/${path}`);
100
+ assistant.log(`extractImages(): images`, images);
114
101
 
115
- // Try to get sha
116
- try {
117
- let branch = repoName === 'ultimate-jekyll' ? 'template' : 'master';
118
- let pathGet = `https://api.github.com/repos/${repoUser}/${repoName}/git/trees/${branch}:${encodeURIComponent(pathApi.dirname(path))}`;
119
-
120
- // Log
121
- assistant.log(`createFile(): pathGet`, pathGet);
122
-
123
- // Make request
124
- await makeRequest({
125
- method: 'GET',
126
- url: pathGet,
127
- body: {
128
- },
129
- timeout: 30000,
130
- json: true,
131
- headers: {
132
- 'User-Agent': user,
133
- // 'Authorization': `Basic ${user}:${key}`,
134
- 'Authorization': `Basic ${Buffer.from(user + ':' + key).toString('base64')}`,
135
- }
136
- })
137
- .then(function (resp) {
138
- // sha = resp.sha;
139
- sha = resp.tree.find(function (element) {
140
- // console.log('checiing', element.path, fileParsed.base);
141
- return element.path === fileParsed.base;
142
- });
143
- sha = sha.sha;
144
- });
145
- } catch (e) {
146
- sha = null;
147
- }
148
-
149
- let pathPut = `https://api.github.com/repos/${repoUser}/${repoName}/contents/${path}`;
150
- let writeRequest = {
151
- // url: `https://api.github.com/repos/:owner/:repo/contents/:path`,
152
- method: 'PUT',
153
- url: pathPut,
154
- body: {
155
- message: `BackendManager Post: ${new Date().toISOString()}`,
156
- content: base64Data,
157
- },
158
- timeout: 30000,
159
- json: true,
160
- headers: {
161
- 'User-Agent': user,
162
- // 'Authorization': `Basic ${user}:${key}`,
163
- 'Authorization': `Basic ${Buffer.from(user + ':' + key).toString('base64')}`,
102
+ if (!images) {
103
+ return resolve();
164
104
  }
165
- }
166
105
 
167
- // Log
168
- assistant.log(`createFile(): pathPut`, pathPut);
106
+ // Loop through images
107
+ for (let index = 0; index < images.length; index++) {
108
+ const image = images[index];
109
+
110
+ // download image
111
+ const download = await self.downloadImage(image.src, image.alt);
112
+
113
+ // Log
114
+ assistant.log(`extractImages(): download`, download);
169
115
 
170
- // Add sha if it exists
171
- if (sha) {
172
- writeRequest.body.sha = sha;
173
- }
116
+ // const src = image.src;
117
+ // const alt = image.alt;
118
+ // const hyphenated = hyphenate(alt);
119
+ // const tag = powertools.template(IMAGE_TAG, {name: hyphenate(alt), alt: alt});
174
120
 
175
- // Make request
176
- await makeRequest(writeRequest)
177
- .then((json) => {
178
- if (!json || (json.message && (json.message === 'Not Found' || json.message.includes('Invalid request'))) ) {
179
- return reject(new Error(json.message));
121
+ // console.log(`Image ${index + 1}: src=${image.src}, alt='${image.alt}'`);
180
122
  }
181
- })
182
- .catch((e) => {
183
- return reject(e);
184
- })
185
- return resolve(true)
123
+
124
+ // const image = images[i];
125
+ // const src = image.match(LINK_REGEX)[2];
126
+ // const alt = image.match(LINK_REGEX)[1] || uuidv4();
127
+ // const name = hyphenate(alt);
128
+ // const tag = IMAGE_TAG.replace('{name}', name).replace('{alt}', alt);
129
+
130
+ // // Log
131
+ // assistant.log(`extractImages(): original`, image.match(LINK_REGEX));
132
+ // assistant.log(`extractImages(): image`, image);
133
+ // assistant.log(`extractImages(): src`, src);
134
+ // assistant.log(`extractImages(): alt`, alt);
135
+ // assistant.log(`extractImages(): name`, name);
136
+ // assistant.log(`extractImages(): tag`, tag);
137
+
138
+ // // Replace image
139
+ // payload.data.payload.body = payload.data.payload.body
140
+ // .replace(image, tag);
141
+
142
+ // // Save image
143
+ // await self.saveImage(src, id, name);
144
+
145
+ // Resolve
146
+ return resolve();
186
147
  });
187
- }
148
+ };
149
+
150
+ // Downlaod image
151
+ Module.prototype.downloadImage = function (src, alt) {
152
+ const self = this;
153
+ const Manager = self.Manager;
154
+ const Api = self.Api;
155
+ const assistant = self.assistant;
156
+ const payload = self.payload;
188
157
 
189
- function makeRequest(options) {
190
- return new Promise(function(resolve, reject) {
191
- options.headers = options.headers || {};
192
- options.headers['Content-Type'] = 'application/json';
193
-
194
- let hasBody = Object.keys(options.body || {}).length > 0
195
-
196
- fetch(options.url, {
197
- method: options.method,
198
- body: hasBody ? JSON.stringify(options.body) : undefined,
199
- timeout: 30000,
200
- headers: options.headers,
201
- auth: options.auth,
202
- })
203
- .then(res => res.json())
204
- .then(json => {
205
- return resolve(json);
206
- })
207
- .catch(e => {
208
- // console.error('e', e);
209
- return reject(e);
210
- })
158
+ return new Promise(async function(resolve, reject) {
159
+ // Log
160
+ assistant.log(`downloadImage(): src=${src}, alt=${alt}`);
211
161
 
162
+ // Get image
163
+ const image = await fetch(src, {
164
+ method: 'GET',
165
+ download: true,
166
+ })
167
+ .then((r) => {
168
+ // Log
169
+ assistant.log(`downloadImage(): Result`, r);
170
+
171
+ // Save image
172
+ return resolve(r);
173
+ })
174
+ .catch((e) => reject(e));
212
175
  });
176
+ };
177
+
178
+ function hyphenate(s) {
179
+ return s
180
+ // Remove everything that is not a letter or a number
181
+ .replace(/[^a-zA-Z0-9]/g, '-')
182
+ // Replace multiple hyphens with a single hyphen
183
+ .replace(/-+/g, '-')
184
+ // Remove leading and trailing hyphens
185
+ .replace(/^-|-$/g, '')
186
+ // Lowercase
187
+ .toLowerCase();
213
188
  }
214
189
 
215
190
  module.exports = Module;
@@ -0,0 +1,15 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: {layout}
4
+
5
+ ### POST ONLY ###
6
+ post:
7
+ title: "{title}"
8
+ excerpt: "{excerpt}"
9
+ author: {author}
10
+ id: {id}
11
+ tags: {tags}
12
+ categories: {categories}
13
+ affiliate-search-term: {affiliate}
14
+ ---
15
+ {body}
@@ -11,7 +11,7 @@ Module.prototype.main = function () {
11
11
 
12
12
  return new Promise(async function(resolve, reject) {
13
13
  // Log
14
- assistant.log('assistant.cwd', assistant.cwd)
14
+ assistant.log('Manager.cwd', Manager.cwd)
15
15
 
16
16
  // If not dev, quit
17
17
  if (!assistant.isDevelopment()) {
@@ -26,16 +26,18 @@ Module.prototype.main = function () {
26
26
  // Load the hook
27
27
  let hook;
28
28
  try {
29
- hook = (new (require(pathify(`${assistant.cwd}/${payload.data.payload.path}.js`)))()).main(Manager);
29
+ hook = (new (require(pathify(`${Manager.cwd}/${payload.data.payload.path}.js`)))());
30
30
  } catch (e) {
31
- hook = (new (require(pathify(`${assistant.cwd}/methods/hooks/${payload.data.payload.path}.js`)))()).main(Manager);
31
+ hook = (new (require(pathify(`${Manager.cwd}/methods/hooks/${payload.data.payload.path}.js`)))());
32
32
  }
33
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}));
34
+ // Run the hook
35
+ try {
36
+ const result = await hook.main(Manager);
37
+
38
+ return resolve({data: result, status: 200});
39
+ } catch (e) {
40
+ return reject(assistant.errorify(e.message, {code: 500}));
39
41
  }
40
42
  });
41
43
  };
@@ -118,10 +118,12 @@ Module.prototype.main = function () {
118
118
  if (user.providerData.filter(function (item) {
119
119
  if (item.providerId !== 'anonymous') {
120
120
  analytics.event({
121
- category: 'engagement',
122
- action: 'signup',
123
- label: item.providerId,
121
+ name: 'sign_up',
122
+ params: {
123
+ method: item.providerId,
124
+ },
124
125
  });
126
+
125
127
  return true
126
128
  }
127
129
  }).length < 1) {
@@ -33,9 +33,8 @@ Module.prototype.main = function () {
33
33
  uuid: user.uid,
34
34
  })
35
35
  .event({
36
- category: 'engagement',
37
- action: 'user-delete',
38
- // label: 'regular',
36
+ name: 'user-delete',
37
+ params: {},
39
38
  });
40
39
 
41
40
  // Delete user record
@@ -57,9 +57,8 @@ Module.prototype.main = function () {
57
57
  uuid: _.get(dataBefore, 'link.user.data.uid', undefined),
58
58
  })
59
59
  .event({
60
- category: 'engagement',
61
- action: 'notification-unsubscribe',
62
- // label: 'regular',
60
+ name: 'notification-unsubscribe',
61
+ params: {},
63
62
  });
64
63
 
65
64
  assistant.log('Notification subscription deleted:', dataBefore);
@@ -87,9 +86,8 @@ Module.prototype.main = function () {
87
86
  uuid: _.get(dataAfter, 'link.user.data.uid', undefined),
88
87
  })
89
88
  .event({
90
- category: 'engagement',
91
- action: 'notification-subscribe',
92
- // label: 'regular',
89
+ name: 'notification-subscribe',
90
+ params: {},
93
91
  });
94
92
 
95
93
  assistant.log('Notification subscription created:', dataAfter);
@@ -24,11 +24,10 @@ let Module = {
24
24
  uuid: user.auth.uid,
25
25
  })
26
26
  .event({
27
- category: 'admin',
28
- action: 'authenticate-test',
29
- // label: '',
27
+ name: 'authenticate-test',
28
+ params: {},
30
29
  });
31
-
30
+
32
31
  assistant.log('Request:', assistant.request.data);
33
32
  assistant.log('Result user:', user);
34
33
  return res.status(200).json({status: 200, user: user });
@@ -25,9 +25,8 @@ let Module = {
25
25
  uuid: user.auth.uid,
26
26
  })
27
27
  .event({
28
- category: 'admin',
29
- action: 'create-test-accounts',
30
- // label: '',
28
+ name: 'create-test-accounts',
29
+ params: {},
31
30
  });
32
31
 
33
32
  let assistant = self.assistant;
@@ -24,9 +24,8 @@ let Module = {
24
24
  uuid: user.auth.uid,
25
25
  })
26
26
  .event({
27
- category: 'admin',
28
- action: 'webhook-test',
29
- // label: '',
27
+ name: 'webhook-test',
28
+ params: {},
30
29
  });
31
30
 
32
31
  assistant.log(assistant.request);
@@ -1,5 +1,6 @@
1
1
  const fetch = require('wonderful-fetch');
2
2
  const moment = require('moment');
3
+ const crypto = require('crypto');
3
4
  let uuidv5;
4
5
 
5
6
  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}/;
@@ -25,6 +26,7 @@ function Analytics(Manager, options) {
25
26
  ip: self.assistant?.request?.geolocation?.ip || '127.0.0.1',
26
27
  country: self.assistant?.request?.geolocation?.country || '',
27
28
  city: self.assistant?.request?.geolocation?.city || '',
29
+ region: self.assistant?.request?.geolocation?.region || '',
28
30
  referrer: self.assistant?.request?.referrer || '',
29
31
  userAgent: self.assistant?.request?.client?.userAgent || '',
30
32
  language: (self.assistant?.request?.client?.language || '').split(',')[0],
@@ -45,13 +47,14 @@ function Analytics(Manager, options) {
45
47
  options.pageview = typeof options.pageview === 'undefined' ? true : options.pageview;
46
48
  options.version = options.version || Manager.package.version;
47
49
  options.userProperties = options.userProperties || {};
50
+ options.userData = options.userData || {};
48
51
 
49
52
  // Set user
50
53
  // https://www.optimizesmart.com/how-to-create-and-use-user-properties-in-ga4/
51
54
  // https://developers.google.com/analytics/devguides/collection/protocol/ga4/user-properties?client_type=gtag
52
55
  // https://support.google.com/analytics/answer/12980150?hl=en&co=GENIE.Platform%3DAndroid
53
56
  const authUser = self.assistant?.usage?.user;
54
- self.user = {
57
+ self.userProperties = {
55
58
  app_version: {
56
59
  value: options.version,
57
60
  },
@@ -126,9 +129,33 @@ function Analytics(Manager, options) {
126
129
  // dr? (referrer)
127
130
  };
128
131
 
132
+ // Fix user data
133
+ // https://developers.google.com/analytics/devguides/collection/ga4/uid-data
134
+ self.userData = {
135
+ sha256_email_address: authUser?.auth?.email
136
+ ? toSHA256(authUser?.auth?.email)
137
+ : undefined,
138
+ sha256_phone_number: authUser?.personal?.telephone?.number
139
+ ? toSHA256(authUser?.personal?.telephone?.countryCode + authUser?.personal?.telephone?.number)
140
+ : undefined,
141
+ address: {
142
+ sha256_first_name: authUser?.personal?.name?.first
143
+ ? toSHA256(authUser?.personal?.name?.first)
144
+ : undefined,
145
+ sha256_last_name: authUser?.personal?.name?.last
146
+ ? toSHA256(authUser?.personal?.name?.last)
147
+ : undefined,
148
+ // sha256_street: TODO,
149
+ city: self.request.city || undefined,
150
+ region: self.request.region || undefined,
151
+ // postal_code: TODO,
152
+ country: self.request.country || undefined,
153
+ }
154
+ }
155
+
129
156
  // Merge user properties
130
- self.user = {
131
- ...self.user,
157
+ self.userProperties = {
158
+ ...self.userProperties,
132
159
  ...options.userProperties,
133
160
  };
134
161
 
@@ -169,7 +196,8 @@ Analytics.prototype.generateId = function (id) {
169
196
  const assistant = self.assistant;
170
197
  const options = self.options;
171
198
  const request = self.request;
172
- const user = self.user;
199
+ const userProperties = self.userProperties;
200
+ const userData = self.userData;
173
201
 
174
202
  // Load uuidv5
175
203
  uuidv5 = uuidv5 || require('uuid').v5;
@@ -189,14 +217,16 @@ Analytics.prototype.event = function (payload) {
189
217
  const assistant = self.assistant;
190
218
  const options = self.options;
191
219
  const request = self.request;
192
- const user = self.user;
220
+ const userProperties = self.userProperties;
221
+ const userData = self.userData;
193
222
 
194
223
  // Fix payload
195
224
  // https://support.google.com/analytics/answer/13316687?hl=en#zippy=%2Cweb
196
225
  // https://support.google.com/analytics/answer/9268042?sjid=4476481583372132143-NC
197
226
  // https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events#screen_view
198
-
199
227
  payload = payload || {};
228
+
229
+ // Fix event name
200
230
  payload.name = (payload.name || '')
201
231
  // Replace anything not a letter, number, or underscore with an underscore
202
232
  .replace(/[^a-zA-Z0-9_]/g, '_')
@@ -204,6 +234,8 @@ Analytics.prototype.event = function (payload) {
204
234
  .replace(/^_+|_+$/g, '')
205
235
  // Remove multiple underscores
206
236
  .replace(/_+/g, '_');
237
+
238
+ // Fix event params
207
239
  payload.params = payload.params || {};
208
240
 
209
241
  // Check if initialized
@@ -268,14 +300,14 @@ Analytics.prototype.event = function (payload) {
268
300
  // ]
269
301
  // }
270
302
 
271
-
272
303
  // Build url and body
273
304
  const url = `https://www.google-analytics.com/mp/collect?measurement_id=${self.analyticsId}&api_secret=${self.analyticsSecret}`;
274
305
  const body = {
275
306
  client_id: self.options.uuid,
276
307
  user_id: self.options.uuid,
277
308
  // timestamp_micros: new Date().getTime() * 1000,
278
- user_properties: user,
309
+ user_properties: userProperties,
310
+ user_data: userData,
279
311
  // consent: {},
280
312
  // non_personalized_ads: false,
281
313
  events: [payload],
@@ -419,4 +451,16 @@ Analytics.prototype.event = function (payload) {
419
451
  // return self;
420
452
  // };
421
453
 
454
+ /*
455
+ Unlike gtag, which automatically hashes sensitive user-provided data, the Measurement Protocol requires a developer to hash sensitive user-provided data using a secure one-way hashing algorithm called SHA256 and encode it using hex string format prior to calling the API.
456
+
457
+ All user data fields starting with the sha256 prefix in their name should be only populated with hashed and hex-encoded values.
458
+
459
+ The following example code performs the necessary encryption and encoding steps:
460
+ */
461
+
462
+ function toSHA256(value) {
463
+ return crypto.createHash('sha256').update(value).digest('hex');
464
+ }
465
+
422
466
  module.exports = Analytics;
@@ -450,46 +450,16 @@ Manager.prototype.init = function (exporter, options) {
450
450
  }
451
451
 
452
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
- // });
453
+ self.Analytics({
454
+ assistant: self.assistant,
455
+ uuid: self.SERVER_UUID,
456
+ })
457
+ .event({
458
+ name: 'admin/initialized',
459
+ params: {
460
+ // screen_class: 'MainActivity',
461
+ },
462
+ });
493
463
 
494
464
  // Return
495
465
  return self;