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 +1 -1
- package/src/manager/functions/core/actions/api/admin/create-post copy.js +215 -0
- package/src/manager/functions/core/actions/api/admin/create-post.js +142 -167
- package/src/manager/functions/core/actions/api/admin/templates/post.html +15 -0
- package/src/manager/functions/core/actions/api/test/run-hook.js +10 -8
- package/src/manager/functions/core/events/auth/on-create.js +5 -3
- package/src/manager/functions/core/events/auth/on-delete.js +2 -3
- package/src/manager/functions/core/events/firestore/on-subscription.js +4 -6
- package/src/manager/functions/test/authenticate.js +3 -4
- package/src/manager/functions/test/create-test-accounts.js +2 -3
- package/src/manager/functions/test/webhook.js +2 -3
- package/src/manager/helpers/analytics.js +52 -8
- package/src/manager/index.js +10 -40
package/package.json
CHANGED
|
@@ -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('
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
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
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
//
|
|
62
|
-
|
|
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
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
//
|
|
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
|
-
//
|
|
100
|
-
Module.prototype.
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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(`
|
|
100
|
+
assistant.log(`extractImages(): images`, images);
|
|
114
101
|
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
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;
|
|
@@ -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('
|
|
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(`${
|
|
29
|
+
hook = (new (require(pathify(`${Manager.cwd}/${payload.data.payload.path}.js`)))());
|
|
30
30
|
} catch (e) {
|
|
31
|
-
hook = (new (require(pathify(`${
|
|
31
|
+
hook = (new (require(pathify(`${Manager.cwd}/methods/hooks/${payload.data.payload.path}.js`)))());
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
name: 'sign_up',
|
|
122
|
+
params: {
|
|
123
|
+
method: item.providerId,
|
|
124
|
+
},
|
|
124
125
|
});
|
|
126
|
+
|
|
125
127
|
return true
|
|
126
128
|
}
|
|
127
129
|
}).length < 1) {
|
|
@@ -57,9 +57,8 @@ Module.prototype.main = function () {
|
|
|
57
57
|
uuid: _.get(dataBefore, 'link.user.data.uid', undefined),
|
|
58
58
|
})
|
|
59
59
|
.event({
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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 });
|
|
@@ -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.
|
|
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.
|
|
131
|
-
...self.
|
|
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
|
|
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
|
|
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:
|
|
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;
|
package/src/manager/index.js
CHANGED
|
@@ -450,46 +450,16 @@ Manager.prototype.init = function (exporter, options) {
|
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
// Send analytics
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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;
|