gologin 1.0.40 → 1.0.43

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.
@@ -113,6 +113,93 @@ class BrowserUserDataManager {
113
113
  const result = fileContent.replace(/\$\$GOLOGIN_FONTS\$\$/g, path.join(profilePath, FONTS_DIR_NAME));
114
114
  await writeFile(path.join(profilePath, 'Default', 'fonts_config'), result);
115
115
  }
116
+
117
+ static setExtPaths(settings = {}, profileExtensionsCheckRes = []) {
118
+ const formattedLocalExtArray = profileExtensionsCheckRes.map((el) => {
119
+ const [extFolderName = ''] = el.split(path.sep).reverse();
120
+ const [originalId] = extFolderName.split('@');
121
+ if (!originalId) {
122
+ return null;
123
+ }
124
+
125
+ return {
126
+ path: el,
127
+ originalId,
128
+ }
129
+ }).filter(Boolean);
130
+
131
+ if (!formattedLocalExtArray.length) {
132
+ return;
133
+ }
134
+
135
+ const extensionsSettings = settings.extensions?.settings || {};
136
+ const extensionsEntries = Object.entries(extensionsSettings);
137
+
138
+ extensionsEntries.forEach((extensionObj) => {
139
+ const [extensionsId] = extensionObj;
140
+ const localExtObj = formattedLocalExtArray.find(el => el.originalId === extensionsId);
141
+ if (!localExtObj) {
142
+ return;
143
+ }
144
+
145
+ extensionsSettings[extensionsId].path = localExtObj?.path || '';
146
+ });
147
+
148
+ return extensionsSettings;
149
+ }
150
+
151
+ static async setOriginalExtPaths(settings = {}, originalExtensionsFolder = '') {
152
+ if (!originalExtensionsFolder) {
153
+ return null;
154
+ }
155
+
156
+ const extensionsSettings = settings.extensions?.settings || {};
157
+ const extensionsEntries = Object.entries(extensionsSettings);
158
+
159
+ const originalExtensionsList = await readdir(originalExtensionsFolder).catch(() => []);
160
+ if (!originalExtensionsList.length) {
161
+ return null;
162
+ }
163
+
164
+ const promises = originalExtensionsList.map(async (originalId) => {
165
+ const extFolderPath = path.join(originalExtensionsFolder, originalId);
166
+ const extFolderContent = await readdir(extFolderPath);
167
+ if (!extFolderPath.length) {
168
+ return {};
169
+ }
170
+
171
+ if (extFolderContent.includes('manifest.json')) {
172
+ return {
173
+ originalId,
174
+ path: path.join(originalExtensionsFolder, originalId),
175
+ };
176
+ }
177
+
178
+ const [version] = extFolderContent;
179
+ return {
180
+ originalId,
181
+ path: path.join(originalExtensionsFolder, originalId, version),
182
+ };
183
+ });
184
+ const originalExtPaths = await Promise.all(promises);
185
+
186
+ extensionsEntries.forEach((extensionObj) => {
187
+ const [extensionsId] = extensionObj;
188
+ const extPath = extensionsSettings[extensionsId].path;
189
+ if (!/chrome-extensions/.test(extPath)) {
190
+ return;
191
+ }
192
+
193
+ const originalExtPath = originalExtPaths.find(el => el.originalId === extensionsId);
194
+ if (!originalExtPath) {
195
+ return;
196
+ }
197
+
198
+ extensionsSettings[extensionsId].path = originalExtPath.path || '';
199
+ });
200
+
201
+ return extensionsSettings;
202
+ }
116
203
  }
117
204
 
118
205
  module.exports = {
@@ -0,0 +1,55 @@
1
+ const { access, unlink } = require('fs').promises;
2
+ const decompress = require('decompress');
3
+ const decompressUnzip = require('decompress-unzip');
4
+
5
+ class ExtensionsExtractor {
6
+ static extractExtension(source, dest) {
7
+ if (!(source && dest)) {
8
+ throw new Error('Missing parameter');
9
+ }
10
+
11
+ return access(source)
12
+ .then(() =>
13
+ withRetry({
14
+ fn() {
15
+ return decompress(source, dest, {
16
+ plugins: [decompressUnzip()],
17
+ filter: file => !file.path.endsWith('/')
18
+ })
19
+ }
20
+ })
21
+ );
22
+ }
23
+
24
+ static deleteExtensionArchive(dest) {
25
+ if (!dest) {
26
+ throw new Error('Missing parameter');
27
+ }
28
+
29
+ return access(dest)
30
+ .then(
31
+ () => unlink(dest),
32
+ () => Promise.resolve()
33
+ )
34
+ }
35
+ }
36
+
37
+ const withRetry = optionsOrUndefined => {
38
+ const opts = optionsOrUndefined || {};
39
+ const callCounter = opts.callCounter || 1;
40
+ const fnToProducePromise = opts.fn;
41
+ const callLimit = opts.limit || 5;
42
+ delete opts.callCounter;
43
+ return fnToProducePromise(opts).catch(err => {
44
+ console.error(err);
45
+ if (callCounter >= callLimit) {
46
+ return Promise.reject(err);
47
+ }
48
+ opts.callCounter = callCounter + 1;
49
+ return new Promise(resolve => process.nextTick(resolve)).then(() =>
50
+ withRetry(opts)
51
+ );
52
+ });
53
+ };
54
+
55
+ module.exports = ExtensionsExtractor;
@@ -0,0 +1,322 @@
1
+ const path = require('path');
2
+ const request = require('requestretry').defaults({ timeout: 60000 });
3
+ const fs = require('fs');
4
+ const { mkdir, readdir, rmdir } = require('fs').promises;
5
+ const os = require('os');
6
+
7
+ const ExtensionsExtractor = require('./extensions-extractor');
8
+
9
+ const HOMEDIR = os.homedir();
10
+ const CHROME_EXT_DIR_NAME = 'chrome-extensions';
11
+ const EXTENSIONS_PATH = path.join(HOMEDIR, '.gologin', 'extensions');
12
+ const CHROME_EXTENSIONS_PATH = path.join(EXTENSIONS_PATH, CHROME_EXT_DIR_NAME);
13
+ const EXTENSION_URL = 'https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D{ext_id}%26uc&prodversion=97.0.4692.71';
14
+
15
+ class ExtensionsManager {
16
+ #USER_AGENT = '';
17
+ #API_BASE_URL = '';
18
+ #ACCESS_TOKEN = '';
19
+ #existedChromeExtensions = [];
20
+ #inited = false;
21
+ #useLocalExtStorage = false;
22
+ #deleteProfileExtFolders = false;
23
+ #deleteWidevineCdmFolder = false;
24
+
25
+ constructor() {
26
+ if (!ExtensionsManager.instance) {
27
+ ExtensionsManager.instance = this;
28
+ }
29
+
30
+ return ExtensionsManager.instance;
31
+ }
32
+
33
+ get isInited() { return this.#inited }
34
+ get useLocalExtStorage() { return this.#useLocalExtStorage }
35
+ get deleteProfileExtFolders() { return this.#deleteProfileExtFolders }
36
+ get deleteWidevineCdmFolder() { return this.#deleteWidevineCdmFolder }
37
+
38
+ set userAgent(userAgent) {
39
+ if (!userAgent) {
40
+ return;
41
+ }
42
+
43
+ this.#USER_AGENT = userAgent;
44
+ }
45
+
46
+ set accessToken(accessToken) {
47
+ if (!accessToken) {
48
+ return;
49
+ }
50
+
51
+ this.#ACCESS_TOKEN = accessToken;
52
+ }
53
+
54
+ set apiUrl(apiUrl) {
55
+ if (!apiUrl) {
56
+ return;
57
+ }
58
+
59
+ this.#API_BASE_URL = apiUrl;
60
+ }
61
+
62
+ init() {
63
+ if (this.#inited) {
64
+ return Promise.resolve();
65
+ }
66
+
67
+ return mkdir(CHROME_EXTENSIONS_PATH, { recursive: true })
68
+ .then(() => readdir(CHROME_EXTENSIONS_PATH))
69
+ .then(filesList => {
70
+ this.#existedChromeExtensions = filesList;
71
+ this.#inited = true;
72
+ })
73
+ .catch((e) => console.log('ExtensionsManager init error:', e));
74
+ }
75
+
76
+ get existedChromeExtensionsList() {
77
+ return this.#existedChromeExtensions;
78
+ }
79
+
80
+ async checkChromeExtensions(profileExtensions = []) {
81
+ if (!(Array.isArray(profileExtensions) && profileExtensions.length)) {
82
+ return [];
83
+ }
84
+
85
+ const extensionsToDownload = this.#getExtensionsToDownload(profileExtensions);
86
+ if (!extensionsToDownload) {
87
+ return [];
88
+ }
89
+
90
+ const downloadedArchives = await this.downloadChromeExtensions(extensionsToDownload);
91
+ const filteredArchives = downloadedArchives.filter(Boolean);
92
+ const promises = composeExtractionPromises(filteredArchives);
93
+
94
+ await Promise.all(promises);
95
+ return this.getExtensionsStrToIncludeAsOrbitaParam(profileExtensions);
96
+ }
97
+
98
+ #getExtensionsToDownload(profileExtensions) {
99
+ const existedOriginalIds = this.#existedChromeExtensions.map((val) => {
100
+ const [originalId] = val.split('@');
101
+ return originalId;
102
+ });
103
+
104
+ return profileExtensions.reduce((res, val) => {
105
+ const [originalId] = val.split('@');
106
+ const extensionExists = existedOriginalIds.includes(originalId);
107
+ if (!extensionExists) {
108
+ res.push(val);
109
+ }
110
+
111
+ return res;
112
+ }, []);
113
+ }
114
+
115
+ async downloadChromeExtensions(idsToDownload = []) {
116
+ if (!(Array.isArray(idsToDownload) && idsToDownload.length)) {
117
+ return [];
118
+ }
119
+
120
+ const promises = idsToDownload.map(async (id) => {
121
+ const [originalId] = id.split('@');
122
+ const extUrl = EXTENSION_URL.replace('{ext_id}', originalId);
123
+
124
+ const uploadedProfileMetadata = await getExtMetadata(extUrl);
125
+
126
+ const reqPath = uploadedProfileMetadata.req.path;
127
+ const extVer = getExtVersion(reqPath);
128
+
129
+ const buffer = await new Promise((res) => {
130
+ const chunks = []
131
+ request.get(extUrl, {
132
+ maxAttempts: 3,
133
+ retryDelay: 1000,
134
+ timeout: 8 * 1000,
135
+ fullResponse: false,
136
+ })
137
+ .on('data', (data) => chunks.push(data))
138
+ .on('end', () => res(Buffer.concat(chunks)));
139
+ });
140
+
141
+ let zipExt;
142
+ try {
143
+ zipExt = crxToZip(buffer);
144
+ } catch (e) {
145
+ console.log(e);
146
+ return '';
147
+ }
148
+
149
+ const archiveZipPath = path.join(CHROME_EXTENSIONS_PATH, originalId + '@' + extVer + '.zip');
150
+
151
+ const archiveZip = fs.createWriteStream(archiveZipPath);
152
+ archiveZip.write(zipExt);
153
+ archiveZip.close();
154
+
155
+ return new Promise(r => archiveZip.on('close', () => r(archiveZipPath)));
156
+ });
157
+
158
+ return Promise.all(promises);
159
+ }
160
+
161
+ async getExtensionsPolicies() {
162
+ const globalExtConfig = await request.get(`${this.#API_BASE_URL}/gologin-settings/chrome_ext_policies`, {
163
+ headers: {
164
+ Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
165
+ 'user-agent': this.#USER_AGENT,
166
+ },
167
+ json: true,
168
+ maxAttempts: 2,
169
+ retryDelay: 1000,
170
+ timeout: 10 * 1000,
171
+ fullResponse: false,
172
+ });
173
+
174
+ const chromeExtPolicies = globalExtConfig?.chromeExtPolicies || {};
175
+ const {
176
+ useLocalExtStorage = false,
177
+ deleteProfileExtFolders = false,
178
+ deleteWidevineCdmFolder = false,
179
+ } = chromeExtPolicies;
180
+
181
+ this.#useLocalExtStorage = useLocalExtStorage;
182
+ this.#deleteProfileExtFolders = deleteProfileExtFolders;
183
+ this.#deleteWidevineCdmFolder = deleteWidevineCdmFolder;
184
+ }
185
+
186
+ async getExtensionsStrToIncludeAsOrbitaParam(profileExtensions = []) {
187
+ if (!(Array.isArray(profileExtensions) && profileExtensions.length)) {
188
+ return [];
189
+ }
190
+
191
+ const chromeExtList = await readdir(CHROME_EXTENSIONS_PATH);
192
+ if (!chromeExtList) {
193
+ return [];
194
+ }
195
+
196
+ const formattedIdsList = chromeExtList.map((el) => {
197
+ const [originalId] = el.split('@');
198
+ return {
199
+ originalId,
200
+ folderName: el,
201
+ };
202
+ });
203
+
204
+ return profileExtensions.map((el) => {
205
+ const extExisted = formattedIdsList.find(chromeExtPathElem => chromeExtPathElem.originalId === el);
206
+ if (!extExisted) {
207
+ return '';
208
+ }
209
+
210
+ return path.join(CHROME_EXTENSIONS_PATH, extExisted.folderName);
211
+ }).filter(Boolean);
212
+ }
213
+
214
+ async updateExtensions() {
215
+ const fileList = await readdir(CHROME_EXTENSIONS_PATH).catch(() => []);
216
+ if (!fileList.length) {
217
+ return;
218
+ }
219
+
220
+ const oldFolders = [];
221
+
222
+ const versionCheckPromises = fileList.map(async (extension) => {
223
+ if (!extension.includes('@')) {
224
+ return '';
225
+ }
226
+
227
+ const [originalId, currentVersion] = extension.split('@');
228
+ const extUrl = EXTENSION_URL.replace('{ext_id}', originalId);
229
+ const uploadedProfileMetadata = await getExtMetadata(extUrl);
230
+ const reqPath = uploadedProfileMetadata.req.path;
231
+ const availableVersion = getExtVersion(reqPath);
232
+
233
+ if (currentVersion === availableVersion) {
234
+ return '';
235
+ }
236
+
237
+ oldFolders.push(path.join(CHROME_EXTENSIONS_PATH, extension));
238
+ return originalId;
239
+ });
240
+
241
+ const extensionsNames = (await Promise.all(versionCheckPromises)).filter(Boolean);
242
+ const archivesPaths = (await this.downloadChromeExtensions(extensionsNames)).filter(Boolean);
243
+ const extractionPromises = composeExtractionPromises(archivesPaths);
244
+ await Promise.all(extractionPromises);
245
+
246
+ const removeFoldersPromises = oldFolders.map(folder => (
247
+ rmdir(folder, { recursive: true, maxRetries: 3 }).catch(() => {})
248
+ ));
249
+
250
+ return Promise.all(removeFoldersPromises);
251
+ }
252
+ }
253
+
254
+ const crxToZip = (buf) => {
255
+ if (buf[0] === 80 && buf[1] === 75 && buf[2] === 3 && buf[3] === 4) {
256
+ return buf;
257
+ }
258
+
259
+ if (!(buf[0] === 67 || buf[1] === 114 || buf[2] === 50 || buf[3] === 52)) {
260
+ throw new Error('Invalid header: Does not start with Cr24');
261
+ }
262
+
263
+ const isV3 = buf[4] === 3;
264
+ const isV2 = buf[4] === 2;
265
+
266
+ if (!(isV2 || isV3) || buf[5] || buf[6] || buf[7]) {
267
+ throw new Error('Unexpected crx format version number.');
268
+ }
269
+
270
+ if (isV2) {
271
+ const publicKeyLength = calcLength(buf[8], buf[9], buf[10], buf[11]);
272
+ const signatureLength = calcLength(buf[12], buf[13], buf[14], buf[15]);
273
+
274
+ const zipStartOffset = 16 + publicKeyLength + signatureLength;
275
+ return buf.slice(zipStartOffset, buf.length);
276
+ }
277
+
278
+ const headerSize = calcLength(buf[8], buf[9], buf[10], buf[11]);
279
+ const zipStartOffset = 12 + headerSize;
280
+
281
+ return buf.slice(zipStartOffset, buf.length);
282
+ }
283
+
284
+ const calcLength = (a, b, c, d) => {
285
+ let length = 0;
286
+
287
+ length += a << 0;
288
+ length += b << 8;
289
+ length += c << 16;
290
+ length += d << 24 >>> 0;
291
+ return length;
292
+ }
293
+
294
+ const getExtMetadata = (extUrl) => (
295
+ request.head(extUrl, {
296
+ maxAttempts: 3,
297
+ retryDelay: 2000,
298
+ timeout: 2 * 1000,
299
+ fullResponse: true,
300
+ })
301
+ );
302
+
303
+ const getExtVersion = (metadata) => {
304
+ const [extFullName = ''] = metadata.split('/').reverse();
305
+ const [extName = ''] = extFullName.split('.');
306
+ const splitExtName = extName.split('_');
307
+ splitExtName.shift();
308
+ return splitExtName.join('_');
309
+ };
310
+
311
+ const composeExtractionPromises = (filteredArchives) => (
312
+ filteredArchives.map((extArchivePath) => {
313
+ const [archiveName = ''] = extArchivePath.split(path.sep).reverse();
314
+ const [destFolder] = archiveName.split('.');
315
+ return ExtensionsExtractor.extractExtension(extArchivePath, path.join(CHROME_EXTENSIONS_PATH, destFolder))
316
+ .then(() => ExtensionsExtractor.deleteExtensionArchive(extArchivePath))
317
+ })
318
+ );
319
+
320
+ module.exports = ExtensionsManager;
321
+
322
+
package/gologin.js CHANGED
@@ -13,12 +13,13 @@ const decompress = require('decompress');
13
13
  const decompressUnzip = require('decompress-unzip');
14
14
  const path = require('path');
15
15
  const zipdir = require('zip-dir');
16
+ const https = require('https');
16
17
 
17
18
  const BrowserChecker = require('./browser-checker');
18
19
  const { BrowserUserDataManager } = require('./browser-user-data-manager');
19
20
  const { CookiesManager } = require('./cookies-manager');
20
21
  const fontsCollection = require('./fonts');
21
- const https = require('https');
22
+ const ExtensionsManager = require('./extensions-manager');
22
23
 
23
24
  const SEPARATOR = path.sep;
24
25
  const API_URL = 'https://api.gologin.com';
@@ -360,13 +361,50 @@ class GoLogin {
360
361
 
361
362
  const prefFileExists = await access(pref_file_name).then(() => true).catch(() => false);
362
363
  if (!prefFileExists) {
363
- debug('Preferences file not exists waiting', pref_file_name);
364
+ debug('Preferences file not exists waiting', pref_file_name, '. Using empty profile');
365
+ profile_folder = await this.emptyProfileFolder();
366
+ await writeFile(this.profile_zip_path, profile_folder);
367
+ await this.extractProfile(profilePath, this.profile_zip_path);
364
368
  }
365
369
 
366
370
  const preferences_raw = await readFile(pref_file_name);
367
371
  let preferences = JSON.parse(preferences_raw.toString());
368
372
  let proxy = _.get(profile, 'proxy');
369
373
  let name = _.get(profile, 'name');
374
+ const chromeExtensions = _.get(profile, 'chromeExtensions');
375
+
376
+ if (chromeExtensions.length) {
377
+ const ExtensionsManagerInst = new ExtensionsManager();
378
+ ExtensionsManagerInst.apiUrl = API_URL;
379
+ await ExtensionsManagerInst.init()
380
+ .then(() => ExtensionsManagerInst.updateExtensions())
381
+ .catch(() => {});
382
+ ExtensionsManagerInst.accessToken = this.access_token;
383
+
384
+ await ExtensionsManagerInst.getExtensionsPolicies();
385
+ let profileExtensionsCheckRes = [];
386
+
387
+ if (ExtensionsManagerInst.useLocalExtStorage) {
388
+ profileExtensionsCheckRes = await ExtensionsManagerInst.checkChromeExtensions(chromeExtensions).catch((e) => {
389
+ console.log('checkChromeExtensions error: ', e);
390
+ return [];
391
+ });
392
+ }
393
+
394
+ let extSettings;
395
+ if (ExtensionsManagerInst.useLocalExtStorage && profileExtensionsCheckRes.length) {
396
+ extSettings = BrowserUserDataManager.setExtPaths(preferences, profileExtensionsCheckRes);
397
+ } else if (!ExtensionsManagerInst.useLocalExtStorage) {
398
+ const originalExtensionsFolder = path.join(profilePath, 'Default', 'Extensions');
399
+ extSettings = await BrowserUserDataManager.setOriginalExtPaths(preferences, originalExtensionsFolder);
400
+ }
401
+
402
+ if (extSettings) {
403
+ const currentExtSettings = preferences.extensions || {};
404
+ currentExtSettings.settings = extSettings
405
+ preferences.extensions = currentExtSettings;
406
+ }
407
+ }
370
408
 
371
409
  if (proxy.mode === 'gologin' || proxy.mode === 'tor') {
372
410
  const autoProxyServer = _.get(profile, 'autoProxyServer');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gologin",
3
- "version": "1.0.40",
3
+ "version": "1.0.43",
4
4
  "description": "A high-level API to control Orbita browser over GoLogin API",
5
5
  "main": "./gologin.js",
6
6
  "repository": {
package/zero_profile.zip CHANGED
Binary file