gologin 1.0.52 → 1.0.56

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.
@@ -2,7 +2,7 @@ const path = require('path');
2
2
  const os = require('os');
3
3
  const request = require('requestretry');
4
4
  const { rmdirSync, createWriteStream } = require('fs');
5
- const { access, readFile, writeFile, mkdir, readdir, copyFile } = require('fs').promises;
5
+ const { access, readFile, writeFile, mkdir, readdir, copyFile, rename } = require('fs').promises;
6
6
  const crypto = require('crypto');
7
7
 
8
8
  const fontsCollection = require('./fonts');
@@ -14,6 +14,9 @@ const HOMEDIR = os.homedir();
14
14
  const BROWSER_PATH = path.join(HOMEDIR, '.gologin', 'browser');
15
15
  const OS_PLATFORM = process.platform;
16
16
  const DEFAULT_ORBITA_EXTENSIONS_NAMES = ['Google Hangouts', 'Chromium PDF Viewer', 'CryptoTokenExtension', 'Web Store'];
17
+ const GOLOGIN_BASE_FOLDER_NAME = '.gologin';
18
+ const GOLOGIN_TEST_FOLDER_NAME = '.gologin_test';
19
+ const osPlatform = process.platform;
17
20
 
18
21
  class BrowserUserDataManager {
19
22
  static downloadCookies({ profileId, ACCESS_TOKEN, API_BASE_URL }) {
@@ -119,7 +122,7 @@ class BrowserUserDataManager {
119
122
  await writeFile(path.join(defaultFolderPath, 'fonts_config'), result);
120
123
  }
121
124
 
122
- static setExtPathsAndRemoveDeleted(settings = {}, profileExtensionsCheckRes = []) {
125
+ static setExtPathsAndRemoveDeleted(settings = {}, profileExtensionsCheckRes = [], profileId = '') {
123
126
  const formattedLocalExtArray = profileExtensionsCheckRes.map((el) => {
124
127
  const [extFolderName = ''] = el.split(path.sep).reverse();
125
128
  const [originalId] = extFolderName.split('@');
@@ -130,54 +133,54 @@ class BrowserUserDataManager {
130
133
  return {
131
134
  path: el,
132
135
  originalId,
133
- }
136
+ };
134
137
  }).filter(Boolean);
135
138
 
136
139
  const extensionsSettings = settings.extensions?.settings || {};
137
140
  const extensionsEntries = Object.entries(extensionsSettings);
138
141
 
139
- extensionsEntries.forEach((extensionObj) => {
142
+ const promises = extensionsEntries.map(async (extensionObj) => {
140
143
  let [extensionId, currentExtSettings = {}] = extensionObj;
141
144
  const extName = currentExtSettings.manifest?.name || '';
142
145
  let extPath = currentExtSettings.path || '';
143
146
  let originalId = '';
144
147
 
145
148
  const isExtensionToBeDeleted = ['resources', 'passwords-ext', 'cookies-ext'].some(substring => extPath.includes(substring))
146
- || DEFAULT_ORBITA_EXTENSIONS_NAMES.includes(extName);
149
+ && [GOLOGIN_BASE_FOLDER_NAME, GOLOGIN_TEST_FOLDER_NAME].some(substring => extPath.includes(substring))
150
+ || DEFAULT_ORBITA_EXTENSIONS_NAMES.includes(extName)
151
+ && [GOLOGIN_BASE_FOLDER_NAME, GOLOGIN_TEST_FOLDER_NAME].some(substring => extPath.includes(substring));
152
+
147
153
  if (isExtensionToBeDeleted) {
148
154
  delete extensionsSettings[extensionId];
155
+
149
156
  return;
150
157
  }
151
158
 
152
- if (os.platform() === 'win32') {
159
+ if (osPlatform === 'win32') {
153
160
  extPath = extPath.replace(/\//g, '\\');
154
161
  } else {
155
162
  extPath = extPath.replace(/\\/g, '/');
156
163
  }
164
+
157
165
  extensionsSettings[extensionId].path = extPath;
158
166
 
159
- const isExtensionManageable = ['chrome-extensions', 'user-extensions'].some(substring => extPath.includes(substring));
167
+ const splittedPath = extPath.split(path.sep);
168
+ const isExtensionManageable = ['chrome-extensions', 'user-extensions'].some(substring => extPath.includes(substring))
169
+ && [GOLOGIN_BASE_FOLDER_NAME, GOLOGIN_TEST_FOLDER_NAME].some(substring => extPath.includes(substring));
170
+
160
171
  if (isExtensionManageable) {
161
172
  const [extFolderName] = extPath.split(path.sep).reverse();
162
173
  [originalId] = extFolderName.split('@');
174
+ } else if (splittedPath.length === 2) {
175
+ [originalId] = splittedPath;
176
+ }
177
+
178
+ if (isExtensionManageable || splittedPath.length === 2) {
163
179
  const isExtensionInProfileSettings = formattedLocalExtArray.find(el => el.path.includes(originalId));
164
180
  if (!isExtensionInProfileSettings) {
165
181
  delete extensionsSettings[extensionId];
166
- return;
167
- }
168
182
 
169
- if (!currentExtSettings.manifest?.key) {
170
- const hexEncodedPath = crypto.createHash('sha256').update(extPath).digest('hex');
171
- const newId = hexEncodedPath.split('').slice(0, 32).map(symbol => extIdEncoding[symbol]).join('');
172
- delete extensionsSettings[extensionId];
173
-
174
- extensionsSettings[newId] = currentExtSettings;
175
- extensionId = newId;
176
- }
177
- } else {
178
- const splittedPath = extPath.split(path.sep);
179
- if (splittedPath.length === 2) {
180
- [originalId] = splittedPath
183
+ return;
181
184
  }
182
185
  }
183
186
 
@@ -186,10 +189,28 @@ class BrowserUserDataManager {
186
189
  return;
187
190
  }
188
191
 
189
- extensionsSettings[extensionId].path = localExtObj?.path || '';
192
+ const initialExtName = extensionId;
193
+
194
+ extensionId = await this.recalculateId({
195
+ localExtObj, extensionId, extensionsSettings, currentExtSettings,
196
+ });
197
+
198
+ if (initialExtName !== extensionId) {
199
+ const profilePath = path.join(os.tmpdir(), `gologin_profile_${profileId}`);
200
+ const extSyncFolder = path.join(profilePath, 'Default', 'Sync Extension Settings', initialExtName);
201
+ const newSyncFolder = path.join(profilePath, 'Default', 'Sync Extension Settings', extensionId);
202
+
203
+ await rename(extSyncFolder, newSyncFolder).catch(() => null);
204
+ }
205
+
206
+ if (localExtObj.path.endsWith('.zip')) {
207
+ localExtObj.path = localExtObj.path.replace('.zip', '');
208
+ }
209
+
210
+ extensionsSettings[extensionId].path = localExtObj.path || '';
190
211
  });
191
212
 
192
- return extensionsSettings;
213
+ return Promise.all(promises).then(() => extensionsSettings);
193
214
  }
194
215
 
195
216
  static async setOriginalExtPaths(settings = {}, originalExtensionsFolder = '') {
@@ -244,6 +265,48 @@ class BrowserUserDataManager {
244
265
 
245
266
  return extensionsSettings;
246
267
  }
268
+
269
+ static async recalculateId({ localExtObj, extensionId, extensionsSettings, currentExtSettings }) {
270
+ if (currentExtSettings.manifest?.key) {
271
+ return extensionId;
272
+ }
273
+
274
+ const manifestFilePath = path.join(localExtObj.path, 'manifest.json');
275
+ const manifestString = await readFile(manifestFilePath, { encoding: 'utf8' }).catch(() => ({}));
276
+
277
+ if (!manifestString) {
278
+ return extensionId;
279
+ }
280
+
281
+ let manifestObject;
282
+ try {
283
+ manifestObject = JSON.parse(manifestString);
284
+ } catch {
285
+ return extensionId;
286
+ }
287
+
288
+ if (manifestObject.key) {
289
+ return extensionId;
290
+ }
291
+
292
+ let encoding = 'utf8';
293
+ if (osPlatform === 'win32') {
294
+ encoding = 'utf16le';
295
+ }
296
+
297
+ const extPathToEncode = Buffer.from(localExtObj.path, encoding);
298
+
299
+ const hexEncodedPath = crypto.createHash('sha256').update(extPathToEncode).digest('hex');
300
+ const newId = hexEncodedPath.split('').slice(0, 32).map(symbol => extIdEncoding[symbol]).join('');
301
+ if (extensionId !== newId) {
302
+ delete extensionsSettings[extensionId];
303
+
304
+ extensionsSettings[newId] = currentExtSettings;
305
+ extensionId = newId;
306
+ }
307
+
308
+ return extensionId;
309
+ }
247
310
  }
248
311
 
249
312
  const extIdEncoding = {
package/common.js ADDED
@@ -0,0 +1,23 @@
1
+ const path = require('path');
2
+
3
+ const ExtensionsExtractor = require('./extensions-extractor');
4
+ const os = require('os');
5
+
6
+ const HOMEDIR = os.homedir();
7
+ const CHROME_EXT_DIR_NAME = 'chrome-extensions';
8
+ const EXTENSIONS_PATH = path.join(HOMEDIR, '.gologin', 'extensions');
9
+ const CHROME_EXTENSIONS_PATH = path.join(EXTENSIONS_PATH, CHROME_EXT_DIR_NAME);
10
+ const USER_EXTENSIONS_PATH = path.join(HOMEDIR, '.gologin', 'extensions', 'user-extensions');
11
+
12
+ const composeExtractionPromises = (filteredArchives, destPath = CHROME_EXTENSIONS_PATH) => (
13
+ filteredArchives.map((extArchivePath) => {
14
+ const [archiveName = ''] = extArchivePath.split(path.sep).reverse();
15
+ const [destFolder] = archiveName.split('.');
16
+ return ExtensionsExtractor.extractExtension(extArchivePath, path.join(destPath, destFolder))
17
+ .then(() => ExtensionsExtractor.deleteExtensionArchive(extArchivePath))
18
+ })
19
+ );
20
+
21
+ module.exports.composeExtractionPromises = composeExtractionPromises;
22
+ module.exports.USER_EXTENSIONS_PATH = USER_EXTENSIONS_PATH;
23
+ module.exports.CHROME_EXTENSIONS_PATH = CHROME_EXTENSIONS_PATH;
@@ -1,28 +1,23 @@
1
1
  const path = require('path');
2
2
  const request = require('requestretry').defaults({ timeout: 60000 });
3
3
  const fs = require('fs');
4
- const { mkdir, readdir, rmdir } = require('fs').promises;
5
- const os = require('os');
4
+ const { mkdir, readdir, rmdir, unlink } = require('fs').promises;
6
5
 
7
- const ExtensionsExtractor = require('./extensions-extractor');
6
+ const UserExtensionsManager = require('./user-extensions-manager');
7
+ const { composeExtractionPromises, CHROME_EXTENSIONS_PATH, USER_EXTENSIONS_PATH } = require('./common');
8
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
9
  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
10
 
15
- class ExtensionsManager {
16
- #USER_AGENT = '';
17
- #API_BASE_URL = '';
18
- #ACCESS_TOKEN = '';
11
+ class ExtensionsManager extends UserExtensionsManager {
19
12
  #existedChromeExtensions = [];
20
13
  #inited = false;
21
14
  #useLocalExtStorage = false;
15
+ #useCookiesExt = false;
22
16
  #deleteProfileExtFolders = false;
23
- #deleteWidevineCdmFolder = false;
17
+ #extensionsUpdating = true;
24
18
 
25
19
  constructor() {
20
+ super();
26
21
  if (!ExtensionsManager.instance) {
27
22
  ExtensionsManager.instance = this;
28
23
  }
@@ -30,49 +25,42 @@ class ExtensionsManager {
30
25
  return ExtensionsManager.instance;
31
26
  }
32
27
 
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() {
28
+ async init() {
63
29
  if (this.#inited) {
64
30
  return Promise.resolve();
65
31
  }
66
32
 
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));
33
+ const promises = [
34
+ mkdir(CHROME_EXTENSIONS_PATH, { recursive: true })
35
+ .then(() => readdir(CHROME_EXTENSIONS_PATH))
36
+ .then(filesList => {
37
+ this.#existedChromeExtensions = filesList.filter(extPath => !extPath.includes('.zip'));
38
+ return filesList.map(fileName => fileName.includes('.zip') ?
39
+ unlink(path.join(CHROME_EXTENSIONS_PATH, fileName)) :
40
+ Promise.resolve());
41
+ })
42
+ .then(promisesToDelete => Promise.all(promisesToDelete))
43
+ .catch((e) => console.log('ExtensionsManager init error:', e)),
44
+ mkdir(USER_EXTENSIONS_PATH, { recursive: true })
45
+ .then(() => readdir(USER_EXTENSIONS_PATH))
46
+ .then(filesList => {
47
+ this.existedUserExtensions = filesList.filter(extPath => !extPath.includes('.zip'));
48
+ return filesList.map(fileName => fileName.includes('.zip') ?
49
+ unlink(path.join(USER_EXTENSIONS_PATH, fileName)) :
50
+ Promise.resolve());
51
+ })
52
+ .then((promisesToDelete) => Promise.all(promisesToDelete))
53
+ .catch((e) => console.log('error creating user extensions folder:', e)),
54
+ ]
55
+
56
+ return Promise.all(promises).then(() => this.#inited = true);
74
57
  }
75
58
 
59
+ get isInited() { return this.#inited }
60
+ get useLocalExtStorage() { return this.#useLocalExtStorage }
61
+ get deleteProfileExtFolders() { return this.#deleteProfileExtFolders }
62
+ get useCookiesExt() { return this.#useCookiesExt }
63
+
76
64
  get existedChromeExtensionsList() {
77
65
  return this.#existedChromeExtensions;
78
66
  }
@@ -83,20 +71,25 @@ class ExtensionsManager {
83
71
  }
84
72
 
85
73
  const extensionsToDownload = this.#getExtensionsToDownload(profileExtensions);
86
- if (!extensionsToDownload) {
87
- return [];
88
- }
89
74
 
90
75
  const downloadedArchives = await this.downloadChromeExtensions(extensionsToDownload);
91
76
  const filteredArchives = downloadedArchives.filter(Boolean);
92
- const promises = composeExtractionPromises(filteredArchives);
93
77
 
94
- await Promise.all(promises);
78
+ if (filteredArchives.length) {
79
+ const [downloadedFolders] = filteredArchives.map(archivePath => archivePath.split(path.sep).reverse());
80
+ this.#existedChromeExtensions = [...this.#existedChromeExtensions, ...downloadedFolders];
81
+
82
+ const promises = composeExtractionPromises(filteredArchives);
83
+
84
+ await Promise.all(promises);
85
+ }
86
+
95
87
  return this.getExtensionsStrToIncludeAsOrbitaParam(profileExtensions);
96
88
  }
97
89
 
98
90
  #getExtensionsToDownload(profileExtensions) {
99
- const existedOriginalIds = this.#existedChromeExtensions.map((val) => {
91
+ const existedExtensionsFolders = [...this.#existedChromeExtensions, ...this.existedUserExtensions]
92
+ const existedOriginalIds = existedExtensionsFolders.map((val) => {
100
93
  const [originalId] = val.split('@');
101
94
  return originalId;
102
95
  });
@@ -159,10 +152,11 @@ class ExtensionsManager {
159
152
  }
160
153
 
161
154
  async getExtensionsPolicies() {
162
- const globalExtConfig = await request.get(`${this.#API_BASE_URL}/gologin-settings/chrome_ext_policies`, {
155
+ const globalExtConfig = await request.get(`${this.apiBaseUrl}/gologin-settings/chrome_ext_policies`, {
163
156
  headers: {
164
- Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
165
- 'user-agent': this.#USER_AGENT,
157
+ Authorization: `Bearer ${this.accessToken}`,
158
+ 'user-agent': this.userAgent,
159
+ 'x-two-factor-token': this.twoFaKey || '',
166
160
  },
167
161
  json: true,
168
162
  maxAttempts: 2,
@@ -175,40 +169,12 @@ class ExtensionsManager {
175
169
  const {
176
170
  useLocalExtStorage = false,
177
171
  deleteProfileExtFolders = false,
178
- deleteWidevineCdmFolder = false,
172
+ useCookiesExt = true,
179
173
  } = chromeExtPolicies;
180
174
 
181
175
  this.#useLocalExtStorage = useLocalExtStorage;
182
176
  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);
177
+ this.#useCookiesExt = useCookiesExt;
212
178
  }
213
179
 
214
180
  async updateExtensions() {
@@ -220,7 +186,7 @@ class ExtensionsManager {
220
186
  const oldFolders = [];
221
187
 
222
188
  const versionCheckPromises = fileList.map(async (extension) => {
223
- if (!extension.includes('@')) {
189
+ if (!extension.includes('@') || extension.includes('.zip')) {
224
190
  return '';
225
191
  }
226
192
 
@@ -247,7 +213,70 @@ class ExtensionsManager {
247
213
  rmdir(folder, { recursive: true, maxRetries: 3 }).catch(() => {})
248
214
  ));
249
215
 
250
- return Promise.all(removeFoldersPromises);
216
+ await Promise.all(removeFoldersPromises).then(() => this.#extensionsUpdating = false);
217
+ }
218
+
219
+
220
+ async checkLocalExtensions() {
221
+ if (this.#extensionsUpdating || !this.accessToken) {
222
+ return;
223
+ }
224
+
225
+ const fileList = await readdir(CHROME_EXTENSIONS_PATH).catch(() => []);
226
+ if (!fileList.length) {
227
+ return;
228
+ }
229
+
230
+ const extensionsIds = fileList.filter(folderName => folderName.includes('@') && !folderName.includes('.zip'))
231
+ .map(folderName => {
232
+ const [name] = folderName.split('@');
233
+ return name;
234
+ });
235
+
236
+ if (!extensionsIds.length) {
237
+ return;
238
+ }
239
+
240
+ this.insertExtensionsToDb(extensionsIds);
241
+ }
242
+
243
+ async insertExtensionsToDb(extensionsIds, pathToExtensions = CHROME_EXTENSIONS_PATH) {
244
+ if (!extensionsIds?.length) {
245
+ return;
246
+ }
247
+
248
+ const checkResponse = await request(`${this.apiBaseUrl}/extensions/check`, {
249
+ method: 'POST',
250
+ headers: {
251
+ Authorization: `Bearer ${this.accessToken}`,
252
+ 'user-agent': this.userAgent,
253
+ 'x-two-factor-token': this.twoFaKey || '',
254
+ },
255
+ body: {
256
+ extensionsIds,
257
+ },
258
+ json: true,
259
+ });
260
+ const { extensionsToAdd = [] } = checkResponse.body;
261
+
262
+ if (!extensionsToAdd.length) {
263
+ return;
264
+ }
265
+
266
+ const extensionsToUpdate = await this.getExtensionsNameAndImage(extensionsToAdd, pathToExtensions);
267
+
268
+ request(`${this.apiBaseUrl}/extensions/create`, {
269
+ method: 'POST',
270
+ headers: {
271
+ Authorization: `Bearer ${this.accessToken}`,
272
+ 'user-agent': this.userAgent,
273
+ 'x-two-factor-token': this.twoFaKey || '',
274
+ },
275
+ body: {
276
+ extensionsInfo: extensionsToUpdate,
277
+ },
278
+ json: true,
279
+ });
251
280
  }
252
281
 
253
282
  getExtensionsToInstall(extensionsFromPref, extensionsFromDB) {
@@ -330,15 +359,6 @@ const getExtVersion = (metadata) => {
330
359
  return splitExtName.join('_');
331
360
  };
332
361
 
333
- const composeExtractionPromises = (filteredArchives) => (
334
- filteredArchives.map((extArchivePath) => {
335
- const [archiveName = ''] = extArchivePath.split(path.sep).reverse();
336
- const [destFolder] = archiveName.split('.');
337
- return ExtensionsExtractor.extractExtension(extArchivePath, path.join(CHROME_EXTENSIONS_PATH, destFolder))
338
- .then(() => ExtensionsExtractor.deleteExtensionArchive(extArchivePath))
339
- })
340
- );
341
-
342
362
  module.exports = ExtensionsManager;
343
363
 
344
364
 
package/gologin.js CHANGED
@@ -23,6 +23,7 @@ const ExtensionsManager = require('./extensions-manager');
23
23
 
24
24
  const SEPARATOR = path.sep;
25
25
  const API_URL = 'https://api.gologin.com';
26
+ // const API_URL = 'http://localhost:3002';
26
27
  const OS_PLATFORM = process.platform;
27
28
 
28
29
  // process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
@@ -402,9 +403,11 @@ class GoLogin {
402
403
  let preferences = JSON.parse(preferences_raw.toString());
403
404
  let proxy = _.get(profile, 'proxy');
404
405
  let name = _.get(profile, 'name');
405
- const chromeExtensions = _.get(profile, 'chromeExtensions');
406
+ const chromeExtensions = _.get(profile, 'chromeExtensions') || [];
407
+ const userChromeExtensions = _.get(profile, 'userChromeExtensions') || [];
408
+ const allExtensions = [...chromeExtensions, ...userChromeExtensions];
406
409
 
407
- if (chromeExtensions && chromeExtensions.length) {
410
+ if (allExtensions.length) {
408
411
  const ExtensionsManagerInst = new ExtensionsManager();
409
412
  ExtensionsManagerInst.apiUrl = API_URL;
410
413
  await ExtensionsManagerInst.init()
@@ -416,15 +419,31 @@ class GoLogin {
416
419
  let profileExtensionsCheckRes = [];
417
420
 
418
421
  if (ExtensionsManagerInst.useLocalExtStorage) {
419
- profileExtensionsCheckRes = await ExtensionsManagerInst.checkChromeExtensions(chromeExtensions).catch((e) => {
420
- console.log('checkChromeExtensions error: ', e);
421
- return [];
422
- });
422
+ const promises = [
423
+ ExtensionsManagerInst.checkChromeExtensions(allExtensions)
424
+ .then(res => ({ profileExtensionsCheckRes: res }))
425
+ .catch((e) => {
426
+ console.log('checkChromeExtensions error: ', e);
427
+ return { profileExtensionsCheckRes: [] };
428
+ }),
429
+ ExtensionsManagerInst.checkLocalUserChromeExtensions(userChromeExtensions)
430
+ .then(res => ({ profileUserExtensionsCheckRes: res }))
431
+ .catch((error) => {
432
+ console.log('checkUserChromeExtensions error: ', error);
433
+ return null;
434
+ }),
435
+ ];
436
+ const extensionsResult = await Promise.all(promises);
437
+
438
+ const profileExtensionPathRes = extensionsResult.find(el => 'profileExtensionsCheckRes' in el) || {};
439
+ const profileUserExtensionPathRes = extensionsResult.find(el => 'profileUserExtensionsCheckRes' in el);
440
+ profileExtensionsCheckRes =
441
+ (profileExtensionPathRes?.profileExtensionsCheckRes || []).concat(profileUserExtensionPathRes?.profileUserExtensionsCheckRes || []);
423
442
  }
424
443
 
425
444
  let extSettings;
426
445
  if (ExtensionsManagerInst.useLocalExtStorage) {
427
- extSettings = BrowserUserDataManager.setExtPathsAndRemoveDeleted(preferences, profileExtensionsCheckRes);
446
+ extSettings = await BrowserUserDataManager.setExtPathsAndRemoveDeleted(preferences, profileExtensionsCheckRes, this.profile_id);
428
447
  } else {
429
448
  const originalExtensionsFolder = path.join(profilePath, 'Default', 'Extensions');
430
449
  extSettings = await BrowserUserDataManager.setOriginalExtPaths(preferences, originalExtensionsFolder);
@@ -484,6 +503,7 @@ class GoLogin {
484
503
  };
485
504
  profile.geoLocation = this.getGeolocationParams(profileGeolocation, tzGeoLocation);
486
505
  profile.name = name;
506
+ profile.name_base64 = Buffer.from(name).toString('base64');
487
507
  profile.profile_id = this.profile_id;
488
508
 
489
509
  profile.webRtc = {
@@ -821,7 +841,7 @@ class GoLogin {
821
841
  if (Array.isArray(this.extra_params) && this.extra_params.length) {
822
842
  params = params.concat(this.extra_params);
823
843
  }
824
-
844
+ console.log(params)
825
845
  const child = execFile(ORBITA_BROWSER, params, {env});
826
846
  // const child = spawn(ORBITA_BROWSER, params, { env, shell: true });
827
847
  child.stdout.on('data', (data) => debug(data.toString()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gologin",
3
- "version": "1.0.52",
3
+ "version": "1.0.56",
4
4
  "description": "A high-level API to control Orbita browser over GoLogin API",
5
5
  "main": "./gologin.js",
6
6
  "repository": {
@@ -0,0 +1,408 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const { readdir, rmdir, readFile, stat, mkdir, copyFile } = require('fs').promises;
4
+ const request = require('requestretry').defaults({ timeout: 60000 });
5
+ const zipdir = require('zip-dir');
6
+
7
+ const ExtensionsExtractor = require('./extensions-extractor');
8
+ const { composeExtractionPromises, CHROME_EXTENSIONS_PATH, USER_EXTENSIONS_PATH } = require('./common');
9
+
10
+ const MAX_FILE_SIZE = 80 * 1024 * 1024;
11
+ const MAX_FILE_SIZE_MB = MAX_FILE_SIZE / 1024 / 1024;
12
+
13
+ class UserExtensionsManager {
14
+ #existedUserExtensions = [];
15
+ #API_BASE_URL = '';
16
+ #ACCESS_TOKEN = '';
17
+ #USER_AGENT = '';
18
+ #TWO_FA_KEY = '';
19
+
20
+ set userAgent(userAgent) {
21
+ if (!userAgent) {
22
+ return;
23
+ }
24
+
25
+ this.#USER_AGENT = userAgent;
26
+ }
27
+
28
+ set accessToken(accessToken) {
29
+ if (!accessToken) {
30
+ return;
31
+ }
32
+
33
+ this.#ACCESS_TOKEN = accessToken;
34
+ }
35
+
36
+ set twoFaKey(twoFaKey) {
37
+ if (!twoFaKey) {
38
+ return;
39
+ }
40
+
41
+ this.#TWO_FA_KEY = twoFaKey;
42
+ }
43
+
44
+ set apiUrl(apiUrl) {
45
+ if (!apiUrl) {
46
+ return;
47
+ }
48
+
49
+ this.#API_BASE_URL = apiUrl;
50
+ }
51
+
52
+ get apiBaseUrl() {
53
+ return this.#API_BASE_URL;
54
+ }
55
+
56
+ get existedUserExtensions() {
57
+ return this.#existedUserExtensions;
58
+ }
59
+
60
+ get accessToken() {
61
+ return this.#ACCESS_TOKEN;
62
+ }
63
+
64
+ get twoFaKey() {
65
+ return this.#TWO_FA_KEY;
66
+ }
67
+
68
+ get userAgent() {
69
+ return this.#USER_AGENT;
70
+ }
71
+
72
+ set existedUserExtensions(fileList) {
73
+ if (!fileList) {
74
+ return;
75
+ }
76
+
77
+ this.#existedUserExtensions = fileList;
78
+ }
79
+
80
+ async addCustomExtension(pathToFiles) {
81
+ try {
82
+ const filesSize = await checkFileSizeSync(pathToFiles);
83
+ const isZip = pathToFiles.endsWith('.zip');
84
+
85
+ if (filesSize > MAX_FILE_SIZE) {
86
+ throw new Error(`The maximum file size is ${MAX_FILE_SIZE_MB}MB`);
87
+ }
88
+
89
+ const customId = this.generateExtensionId();
90
+
91
+ if (isZip) {
92
+ const pathToExtract = path.join(USER_EXTENSIONS_PATH, customId);
93
+ await ExtensionsExtractor.extractExtension(pathToFiles, pathToExtract);
94
+ pathToFiles = pathToExtract;
95
+ }
96
+
97
+ let fileList = (await readdir(pathToFiles).catch(() => ['cantReadError']))
98
+ .filter(folderContent => folderContent !== '.DS_Store');
99
+
100
+ if (fileList.length === 1 && !fileList.includes('cantReadError')) {
101
+ const isFolder = (await stat(pathToFiles)).isDirectory();
102
+ if (isFolder) {
103
+ const [folderName] = fileList;
104
+ pathToFiles = path.join(pathToFiles, folderName);
105
+ fileList = await readdir(pathToFiles).catch(() => ['cantReadError']);
106
+ }
107
+ }
108
+
109
+ if (fileList.includes('cantReadError')) {
110
+ throw new Error('Can\'t access folder');
111
+ }
112
+
113
+ if (!fileList.includes('manifest.json')) {
114
+ if (isZip) {
115
+ rmdir(pathToFiles);
116
+ }
117
+ throw new Error('There is no manifest.json in the extension folder');
118
+ }
119
+
120
+ if (!isZip) {
121
+ const destPath = path.join(USER_EXTENSIONS_PATH, customId)
122
+ await copyFolder(pathToFiles, destPath).catch(() => {
123
+ throw new Error('Something went wrong coping your folder');
124
+ });
125
+ }
126
+
127
+ const [nameIconId] = await this.getExtensionsNameAndImage([customId], USER_EXTENSIONS_PATH);
128
+
129
+ if (!nameIconId) {
130
+ throw new Error('Something went wrong. Please try again later');
131
+ }
132
+
133
+ const dbResult = await request(`${this.#API_BASE_URL}/extensions/create_user_extension`, {
134
+ method: 'POST',
135
+ headers: {
136
+ Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
137
+ 'user-agent': this.#USER_AGENT,
138
+ 'x-two-factor-token': this.#TWO_FA_KEY || '',
139
+ },
140
+ body: {
141
+ extensionInfo: nameIconId,
142
+ },
143
+ json: true,
144
+ });
145
+ // if success - there is no body
146
+ if (dbResult.body) {
147
+ throw new Error('Something went wrong inserting your data to database');
148
+ }
149
+
150
+ const fileBuffer = await zipdir(pathToFiles).catch(() => null);
151
+ if (!fileBuffer) {
152
+ throw new Error('Something went wrong. Please try again later');
153
+ }
154
+
155
+ const signedUrl = await request.get(`${this.#API_BASE_URL}/extensions/upload_url?extId=${customId}`, {
156
+ headers: {
157
+ Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
158
+ 'user-agent': this.#USER_AGENT,
159
+ 'x-two-factor-token': this.#TWO_FA_KEY || '',
160
+ },
161
+ maxAttempts: 3,
162
+ retryDelay: 2000,
163
+ timeout: 10 * 1000,
164
+ fullResponse: false,
165
+ });
166
+
167
+ const uploadResponse = await request.put(signedUrl, {
168
+ headers: {
169
+ 'Content-Type': 'application/zip',
170
+ 'Content-Length': Buffer.byteLength(fileBuffer),
171
+ },
172
+ body: fileBuffer,
173
+ maxBodyLength: Infinity,
174
+ maxContentLength: Infinity,
175
+ maxAttempts: 3,
176
+ retryDelay: 2000,
177
+ timeout: 30 * 1000,
178
+ fullResponse: true,
179
+ });
180
+ // if success - there is no body, in case of error - there will be an error in the body
181
+ if (uploadResponse.body) {
182
+ throw new Error('Your extension is added locally but we couldn\'t upload it to the cloud');
183
+ }
184
+
185
+ return {
186
+ status: 'success',
187
+ message: nameIconId,
188
+ }
189
+ } catch (e) {
190
+ return {
191
+ status: 'error',
192
+ message: e.message,
193
+ }
194
+ }
195
+ }
196
+
197
+ checkLocalUserChromeExtensions = async (userChromeExtensions) => {
198
+ if (!userChromeExtensions.length) {
199
+ return;
200
+ }
201
+
202
+ const extensionsToDownloadPaths = await request.post(`${this.#API_BASE_URL}/extensions/user_chrome_extensions_paths`, {
203
+ json: true,
204
+ fullResponse: false,
205
+ headers: {
206
+ Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
207
+ 'user-agent': this.#USER_AGENT,
208
+ 'x-two-factor-token': this.#TWO_FA_KEY || '',
209
+ },
210
+ body: {
211
+ existedUserChromeExtensions: this.#existedUserExtensions,
212
+ }
213
+ }) || [];
214
+
215
+ const extensionsToDownloadPathsFiltered =
216
+ extensionsToDownloadPaths.filter(extPath => userChromeExtensions.some(extId => extPath.includes(extId)));
217
+
218
+ if (!extensionsToDownloadPathsFiltered.length) {
219
+ return this.getExtensionsStrToIncludeAsOrbitaParam(userChromeExtensions, USER_EXTENSIONS_PATH);
220
+ }
221
+
222
+ const promises = extensionsToDownloadPathsFiltered.map(async awsPath => {
223
+ const [basePath] = awsPath.split('?');
224
+ const [extId] = basePath.split('/').reverse();
225
+ const zipPath = `${path.join(USER_EXTENSIONS_PATH, extId)}.zip`;
226
+ const archiveZip = fs.createWriteStream(zipPath);
227
+
228
+ await request(awsPath, {
229
+ retryDelay: 2 * 1000,
230
+ maxAttempts: 3,
231
+ }).pipe(archiveZip);
232
+
233
+ await new Promise(r => archiveZip.on('close', () => r()));
234
+ return zipPath;
235
+ });
236
+
237
+ const zipPaths = await Promise.all(promises).catch(() => []);
238
+
239
+ if (!zipPaths) {
240
+ return this.getExtensionsStrToIncludeAsOrbitaParam(userChromeExtensions, USER_EXTENSIONS_PATH);
241
+ }
242
+
243
+ const extractionPromises = composeExtractionPromises(zipPaths, USER_EXTENSIONS_PATH);
244
+ const isExtensionsExtracted = await Promise.all(extractionPromises).catch(() => 'error');
245
+
246
+ if (isExtensionsExtracted !== 'error') {
247
+ const [downloadedFolders] = zipPaths.map(archivePath => archivePath.split(path.sep).reverse());
248
+ this.#existedUserExtensions = [...this.#existedUserExtensions, ...downloadedFolders];
249
+ }
250
+
251
+ return this.getExtensionsStrToIncludeAsOrbitaParam(userChromeExtensions, USER_EXTENSIONS_PATH);
252
+ }
253
+
254
+ async getExtensionsStrToIncludeAsOrbitaParam(profileExtensions = [], folderPath = CHROME_EXTENSIONS_PATH) {
255
+ if (!(Array.isArray(profileExtensions) && profileExtensions.length)) {
256
+ return [];
257
+ }
258
+
259
+ const folders = await readdir(folderPath).then(folderNames => folderNames.map(folderName => path.join(folderPath, folderName)));
260
+
261
+ if (!folders.length) {
262
+ return [];
263
+ }
264
+
265
+ const formattedIdsList = folders.map((el) => {
266
+ const [folderName] = el.split(path.sep).reverse();
267
+ const [originalId] = folderName.split('@');
268
+ return {
269
+ originalId,
270
+ path: el,
271
+ };
272
+ });
273
+
274
+ return profileExtensions.map((el) => {
275
+ const extExisted = formattedIdsList.find(chromeExtPathElem => chromeExtPathElem.originalId === el);
276
+
277
+ if (!extExisted) {
278
+ return '';
279
+ }
280
+
281
+ return extExisted.path;
282
+ }).filter(Boolean);
283
+ }
284
+
285
+ async getExtensionsNameAndImage(extensionsIds, pathToExtensions) {
286
+ const isCheckLocalFiles = [CHROME_EXTENSIONS_PATH, USER_EXTENSIONS_PATH].includes(pathToExtensions);
287
+ const extensionFolderNames = await readdir(pathToExtensions).catch(() => {});
288
+ const filteredExtensionFolderNames = extensionFolderNames.filter(extensionFolder => extensionsIds.some(extensionId => !extensionFolder.includes('.zip') && extensionFolder.includes(extensionId)));
289
+
290
+ if (!filteredExtensionFolderNames.length) {
291
+ return;
292
+ }
293
+
294
+ const namesPromise = extensionsIds.map(async (extensionsId) => {
295
+ const folderName = filteredExtensionFolderNames.find(folderName => folderName.includes(extensionsId));
296
+
297
+ if (!folderName) {
298
+ return;
299
+ }
300
+
301
+ let pathToExtensionsFolder = [pathToExtensions, folderName];
302
+ if (!isCheckLocalFiles) {
303
+ const [extensionVersion] = await readdir(path.join(pathToExtensions, folderName));
304
+ pathToExtensionsFolder = [pathToExtensions, folderName, extensionVersion];
305
+ }
306
+
307
+ const manifestPath = path.join(...pathToExtensionsFolder, 'manifest.json');
308
+ const manifestString = await readFile(manifestPath, 'utf8').catch(() => '');
309
+ if (!manifestString) {
310
+ return;
311
+ }
312
+
313
+ const manifestObject = JSON.parse(manifestString);
314
+ let name;
315
+ if (manifestObject.name.includes('__MSG')) {
316
+ const manifestName = manifestObject.name || '';
317
+ const fieldNameInLocale = manifestName.replace(/__/g, '').split('MSG_')[1];
318
+ const localePath = path.join(...pathToExtensionsFolder, '_locales', manifestObject.default_locale, 'messages.json');
319
+ const localeString = await readFile(localePath, 'utf8').catch(() => {});
320
+
321
+ try {
322
+ const parsedLocale = JSON.parse(localeString.trim());
323
+ name = parsedLocale[fieldNameInLocale].message;
324
+ } catch (e) {}
325
+ } else {
326
+ name = manifestObject.name;
327
+ }
328
+
329
+ if (!name) {
330
+ return;
331
+ }
332
+
333
+ const iconObject = manifestObject.icons;
334
+ let iconPath = manifestObject.browser_action?.default_icon;
335
+ if (iconObject) {
336
+ iconPath = iconObject['128'];
337
+ }
338
+
339
+ let iconBSON = '';
340
+ if (iconPath) {
341
+ const iconPathFull = path.join(...pathToExtensionsFolder, iconPath);
342
+ iconBSON = await readFile(iconPathFull, 'base64').catch(() => {});
343
+ }
344
+
345
+ return {
346
+ name,
347
+ extId: extensionsId,
348
+ iconBinary: iconBSON,
349
+ };
350
+ });
351
+
352
+ const extensionsArray = await Promise.all(namesPromise);
353
+ return extensionsArray.filter(Boolean);
354
+ }
355
+
356
+ generateExtensionId() {
357
+ let result = '';
358
+ let extensionIdLength = 32;
359
+ const characters = 'abcdefghijklmnopqrstuvwxyz';
360
+ const charactersLength = characters.length;
361
+ while (extensionIdLength--) {
362
+ result += characters.charAt(Math.floor(Math.random() *
363
+ charactersLength));
364
+ }
365
+ return result;
366
+ }
367
+ }
368
+
369
+ const checkFileSizeSync = async (pathToFile) => {
370
+ try {
371
+ const [fileName] = pathToFile.split(path.sep).reverse();
372
+ if (fileName === '.DS_Store') {
373
+ return 0;
374
+ }
375
+
376
+ const fileStats = await stat(pathToFile);
377
+ if (!fileStats.isDirectory()) {
378
+ return fileStats.size;
379
+ }
380
+
381
+ const files = await readdir(pathToFile);
382
+ const promises = files.map(async file => checkFileSizeSync(path.join(pathToFile, file)));
383
+
384
+ return (await Promise.all(promises)).reduce((result, value) => result + value, 0);
385
+ } catch {
386
+ return -1;
387
+ }
388
+ };
389
+
390
+ const copyFolder = async (fromPath, destPath) => {
391
+ const stats = await stat(fromPath);
392
+
393
+ if (!stats.isDirectory()) {
394
+ return copyFile(fromPath, destPath);
395
+ }
396
+
397
+ await mkdir(destPath, { recursive: true }).catch(() => null);
398
+ const files = await readdir(fromPath);
399
+ const promises = files.map(async file => {
400
+ await mkdir(destPath, { recursive: true }).catch(() => null);
401
+
402
+ return copyFolder(path.join(fromPath, file), path.join(destPath, file));
403
+ });
404
+
405
+ return Promise.all(promises);
406
+ };
407
+
408
+ module.exports = UserExtensionsManager;