gologin 1.0.50 → 1.0.53

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/README.md CHANGED
@@ -103,7 +103,10 @@ For debugging use `DEBUG=* node example.js` command
103
103
  To use GoLogin with Selenium see `selenium/example.js`
104
104
 
105
105
  ## Full GoLogin API
106
- <a href="https://api.gologin.com/docs" target="_blank">API link here</a>
106
+ **Swagger:** <a href="https://api.gologin.com/docs" target="_blank">link here</a>
107
+
108
+ **Postman:** <a href="https://documenter.getpostman.com/view/21126834/Uz5GnvaL" target="_blank">link here</a>
109
+
107
110
 
108
111
  ## For local profiles
109
112
 
@@ -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 }) {
@@ -113,10 +116,13 @@ class BrowserUserDataManager {
113
116
 
114
117
  const fileContent = await readFile(path.resolve(__dirname, 'fonts_config'), 'utf-8');
115
118
  const result = fileContent.replace(/\$\$GOLOGIN_FONTS\$\$/g, path.join(profilePath, FONTS_DIR_NAME));
116
- await writeFile(path.join(profilePath, 'Default', 'fonts_config'), result);
119
+
120
+ const defaultFolderPath = path.join(profilePath, 'Default');
121
+ await mkdir(defaultFolderPath, { recursive: true });
122
+ await writeFile(path.join(defaultFolderPath, 'fonts_config'), result);
117
123
  }
118
124
 
119
- static setExtPathsAndRemoveDeleted(settings = {}, profileExtensionsCheckRes = []) {
125
+ static setExtPathsAndRemoveDeleted(settings = {}, profileExtensionsCheckRes = [], profileId = '') {
120
126
  const formattedLocalExtArray = profileExtensionsCheckRes.map((el) => {
121
127
  const [extFolderName = ''] = el.split(path.sep).reverse();
122
128
  const [originalId] = extFolderName.split('@');
@@ -127,54 +133,54 @@ class BrowserUserDataManager {
127
133
  return {
128
134
  path: el,
129
135
  originalId,
130
- }
136
+ };
131
137
  }).filter(Boolean);
132
138
 
133
139
  const extensionsSettings = settings.extensions?.settings || {};
134
140
  const extensionsEntries = Object.entries(extensionsSettings);
135
141
 
136
- extensionsEntries.forEach((extensionObj) => {
142
+ const promises = extensionsEntries.map(async (extensionObj) => {
137
143
  let [extensionId, currentExtSettings = {}] = extensionObj;
138
144
  const extName = currentExtSettings.manifest?.name || '';
139
145
  let extPath = currentExtSettings.path || '';
140
146
  let originalId = '';
141
147
 
142
148
  const isExtensionToBeDeleted = ['resources', 'passwords-ext', 'cookies-ext'].some(substring => extPath.includes(substring))
143
- || 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
+
144
153
  if (isExtensionToBeDeleted) {
145
154
  delete extensionsSettings[extensionId];
155
+
146
156
  return;
147
157
  }
148
158
 
149
- if (os.platform() === 'win32') {
159
+ if (osPlatform === 'win32') {
150
160
  extPath = extPath.replace(/\//g, '\\');
151
161
  } else {
152
162
  extPath = extPath.replace(/\\/g, '/');
153
163
  }
164
+
154
165
  extensionsSettings[extensionId].path = extPath;
155
166
 
156
- 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
+
157
171
  if (isExtensionManageable) {
158
172
  const [extFolderName] = extPath.split(path.sep).reverse();
159
173
  [originalId] = extFolderName.split('@');
174
+ } else if (splittedPath.length === 2) {
175
+ [originalId] = splittedPath;
176
+ }
177
+
178
+ if (isExtensionManageable || splittedPath.length === 2) {
160
179
  const isExtensionInProfileSettings = formattedLocalExtArray.find(el => el.path.includes(originalId));
161
180
  if (!isExtensionInProfileSettings) {
162
181
  delete extensionsSettings[extensionId];
163
- return;
164
- }
165
-
166
- if (!currentExtSettings.manifest?.key) {
167
- const hexEncodedPath = crypto.createHash('sha256').update(extPath).digest('hex');
168
- const newId = hexEncodedPath.split('').slice(0, 32).map(symbol => extIdEncoding[symbol]).join('');
169
- delete extensionsSettings[extensionId];
170
182
 
171
- extensionsSettings[newId] = currentExtSettings;
172
- extensionId = newId;
173
- }
174
- } else {
175
- const splittedPath = extPath.split(path.sep);
176
- if (splittedPath.length === 2) {
177
- [originalId] = splittedPath
183
+ return;
178
184
  }
179
185
  }
180
186
 
@@ -183,10 +189,28 @@ class BrowserUserDataManager {
183
189
  return;
184
190
  }
185
191
 
186
- 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 || '';
187
211
  });
188
212
 
189
- return extensionsSettings;
213
+ return Promise.all(promises).then(() => extensionsSettings);
190
214
  }
191
215
 
192
216
  static async setOriginalExtPaths(settings = {}, originalExtensionsFolder = '') {
@@ -241,6 +265,48 @@ class BrowserUserDataManager {
241
265
 
242
266
  return extensionsSettings;
243
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
+ }
244
310
  }
245
311
 
246
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;
@@ -29,8 +29,8 @@ class CookiesManager {
29
29
  const chunckedCookiesArr = this.chunk(cookiesArr, MAX_SQLITE_VARIABLES);
30
30
 
31
31
  return chunckedCookiesArr.map((cookies) => {
32
- const queryPlaceholders = cookies.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').join(', ');
33
- const query = `insert or replace into cookies (creation_utc, top_frame_site_key, host_key, name, value, path, expires_utc, is_secure, is_httponly, last_access_utc, is_persistent, encrypted_value, samesite, has_expires) values ${queryPlaceholders}`;
32
+ const queryPlaceholders = cookies.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').join(', ');
33
+ const query = `insert or replace into cookies (creation_utc, host_key, top_frame_site_key, name, value, encrypted_value, path, expires_utc, is_secure, is_httponly, last_access_utc, has_expires, is_persistent, priority, samesite, source_scheme, source_port, is_same_party, last_update_utc) values ${queryPlaceholders}`;
34
34
  const queryParams = cookies.flatMap((cookie) => {
35
35
  const creationDate = cookie.creationDate ? cookie.creationDate : this.unixToLDAP(todayUnix);
36
36
  let expirationDate = cookie.session ? 0 : this.unixToLDAP(cookie.expirationDate);
@@ -38,6 +38,10 @@ class CookiesManager {
38
38
  const samesite = Object.keys(SAME_SITE).find((key) => SAME_SITE[key] === (cookie.sameSite || '-1'));
39
39
  const isSecure =
40
40
  cookie.name.startsWith('__Host-') || cookie.name.startsWith('__Secure-') ? 1 : Number(cookie.secure);
41
+
42
+ const sourceScheme = isSecure === 1 ? 2 : 1;
43
+ const sourcePort = isSecure === 1 ? 443 : 80;
44
+ // eslint-disable-next-line no-undefined
41
45
  let isPersistent = [undefined, null].includes(cookie.session)
42
46
  ? Number(expirationDate !== 0)
43
47
  : Number(!cookie.session);
@@ -49,19 +53,24 @@ class CookiesManager {
49
53
 
50
54
  return [
51
55
  creationDate,
52
- '', // top_frame_site_key
53
56
  cookie.domain,
57
+ '', // top_frame_site_key
54
58
  cookie.name,
55
59
  '', // value
60
+ encryptedValue,
56
61
  cookie.path,
57
62
  expirationDate,
58
63
  isSecure,
59
64
  Number(cookie.httpOnly),
60
65
  0, // last_access_utc
66
+ expirationDate === 0 ? 0 : 1, // has_expires
61
67
  isPersistent,
62
- encryptedValue,
68
+ 1, // default priority value (https://github.com/chromium/chromium/blob/main/net/cookies/cookie_constants.h)
63
69
  samesite,
64
- expirationDate === 0 ? 0 : 1, // has_expires
70
+ sourceScheme,
71
+ sourcePort,
72
+ 0, // is_same_party
73
+ 0, // last_update_utc
65
74
  ];
66
75
  });
67
76
 
@@ -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,12 +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;
177
+ this.#useCookiesExt = useCookiesExt;
184
178
  }
185
179
 
186
180
  async getExtensionsStrToIncludeAsOrbitaParam(profileExtensions = []) {
@@ -188,16 +182,23 @@ class ExtensionsManager {
188
182
  return [];
189
183
  }
190
184
 
191
- const chromeExtList = await readdir(CHROME_EXTENSIONS_PATH);
192
- if (!chromeExtList) {
185
+ const folders = await Promise.all([
186
+ readdir(CHROME_EXTENSIONS_PATH).then(folderNames => folderNames.map(folderName => path.join(CHROME_EXTENSIONS_PATH, folderName))),
187
+ readdir(USER_EXTENSIONS_PATH).then(folderNames => folderNames.map(folderName => path.join(USER_EXTENSIONS_PATH, folderName))),
188
+ ]);
189
+
190
+ const chromeExtList = [].concat.apply([], folders).filter(Boolean);
191
+
192
+ if (!chromeExtList.length) {
193
193
  return [];
194
194
  }
195
195
 
196
196
  const formattedIdsList = chromeExtList.map((el) => {
197
- const [originalId] = el.split('@');
197
+ const [folderName] = el.split(path.sep).reverse();
198
+ const [originalId] = folderName.split('@');
198
199
  return {
199
200
  originalId,
200
- folderName: el,
201
+ path: el,
201
202
  };
202
203
  });
203
204
 
@@ -207,7 +208,7 @@ class ExtensionsManager {
207
208
  return '';
208
209
  }
209
210
 
210
- return path.join(CHROME_EXTENSIONS_PATH, extExisted.folderName);
211
+ return extExisted.path;
211
212
  }).filter(Boolean);
212
213
  }
213
214
 
@@ -220,7 +221,7 @@ class ExtensionsManager {
220
221
  const oldFolders = [];
221
222
 
222
223
  const versionCheckPromises = fileList.map(async (extension) => {
223
- if (!extension.includes('@')) {
224
+ if (!extension.includes('@') || extension.includes('.zip')) {
224
225
  return '';
225
226
  }
226
227
 
@@ -247,7 +248,70 @@ class ExtensionsManager {
247
248
  rmdir(folder, { recursive: true, maxRetries: 3 }).catch(() => {})
248
249
  ));
249
250
 
250
- return Promise.all(removeFoldersPromises);
251
+ await Promise.all(removeFoldersPromises).then(() => this.#extensionsUpdating = false);
252
+ }
253
+
254
+
255
+ async checkLocalExtensions() {
256
+ if (this.#extensionsUpdating || !this.accessToken) {
257
+ return;
258
+ }
259
+
260
+ const fileList = await readdir(CHROME_EXTENSIONS_PATH).catch(() => []);
261
+ if (!fileList.length) {
262
+ return;
263
+ }
264
+
265
+ const extensionsIds = fileList.filter(folderName => folderName.includes('@') && !folderName.includes('.zip'))
266
+ .map(folderName => {
267
+ const [name] = folderName.split('@');
268
+ return name;
269
+ });
270
+
271
+ if (!extensionsIds.length) {
272
+ return;
273
+ }
274
+
275
+ this.insertExtensionsToDb(extensionsIds);
276
+ }
277
+
278
+ async insertExtensionsToDb(extensionsIds, pathToExtensions = CHROME_EXTENSIONS_PATH) {
279
+ if (!extensionsIds?.length) {
280
+ return;
281
+ }
282
+
283
+ const checkResponse = await request(`${this.apiBaseUrl}/extensions/check`, {
284
+ method: 'POST',
285
+ headers: {
286
+ Authorization: `Bearer ${this.accessToken}`,
287
+ 'user-agent': this.userAgent,
288
+ 'x-two-factor-token': this.twoFaKey || '',
289
+ },
290
+ body: {
291
+ extensionsIds,
292
+ },
293
+ json: true,
294
+ });
295
+ const { extensionsToAdd = [] } = checkResponse.body;
296
+
297
+ if (!extensionsToAdd.length) {
298
+ return;
299
+ }
300
+
301
+ const extensionsToUpdate = await this.getExtensionsNameAndImage(extensionsToAdd, pathToExtensions);
302
+
303
+ request(`${this.apiBaseUrl}/extensions/create`, {
304
+ method: 'POST',
305
+ headers: {
306
+ Authorization: `Bearer ${this.accessToken}`,
307
+ 'user-agent': this.userAgent,
308
+ 'x-two-factor-token': this.twoFaKey || '',
309
+ },
310
+ body: {
311
+ extensionsInfo: extensionsToUpdate,
312
+ },
313
+ json: true,
314
+ });
251
315
  }
252
316
 
253
317
  getExtensionsToInstall(extensionsFromPref, extensionsFromDB) {
@@ -330,15 +394,6 @@ const getExtVersion = (metadata) => {
330
394
  return splitExtName.join('_');
331
395
  };
332
396
 
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
397
  module.exports = ExtensionsManager;
343
398
 
344
399
 
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,27 @@ 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().catch((error) => {
430
+ console.log('checkUserChromeExtensions error: ', error);
431
+ return null;
432
+ }),
433
+ ];
434
+ const extensionsResult = await Promise.all(promises);
435
+
436
+ const profileExtensionsPathRes = extensionsResult.find(el => 'profileExtensionsCheckRes' in el) || {};
437
+ profileExtensionsCheckRes = profileExtensionsPathRes.profileExtensionsCheckRes;
423
438
  }
424
439
 
425
440
  let extSettings;
426
441
  if (ExtensionsManagerInst.useLocalExtStorage) {
427
- extSettings = BrowserUserDataManager.setExtPathsAndRemoveDeleted(preferences, profileExtensionsCheckRes);
442
+ extSettings = await BrowserUserDataManager.setExtPathsAndRemoveDeleted(preferences, profileExtensionsCheckRes, this.profile_id);
428
443
  } else {
429
444
  const originalExtensionsFolder = path.join(profilePath, 'Default', 'Extensions');
430
445
  extSettings = await BrowserUserDataManager.setOriginalExtPaths(preferences, originalExtensionsFolder);
@@ -484,6 +499,7 @@ class GoLogin {
484
499
  };
485
500
  profile.geoLocation = this.getGeolocationParams(profileGeolocation, tzGeoLocation);
486
501
  profile.name = name;
502
+ profile.profile_id = this.profile_id;
487
503
 
488
504
  profile.webRtc = {
489
505
  mode: _.get(profile, 'webRTC.mode') === 'alerted' ? 'public' : _.get(profile, 'webRTC.mode'),
@@ -820,7 +836,7 @@ class GoLogin {
820
836
  if (Array.isArray(this.extra_params) && this.extra_params.length) {
821
837
  params = params.concat(this.extra_params);
822
838
  }
823
-
839
+ console.log(params)
824
840
  const child = execFile(ORBITA_BROWSER, params, {env});
825
841
  // const child = spawn(ORBITA_BROWSER, params, { env, shell: true });
826
842
  child.stdout.on('data', (data) => debug(data.toString()));
@@ -1045,11 +1061,11 @@ class GoLogin {
1045
1061
  });
1046
1062
 
1047
1063
  if (response.body.statusCode === 400) {
1048
- throw new Error(`gologin failed account creation with status code, ${data.statusCode} DATA ${JSON.stringify(response.body.message)}`);
1064
+ throw new Error(`gologin failed account creation with status code, ${response.statusCode} DATA ${JSON.stringify(response.body.message)}`);
1049
1065
  }
1050
1066
 
1051
1067
  if (response.body.statusCode === 500) {
1052
- throw new Error(`gologin failed account creation with status code, ${data.statusCode}`);
1068
+ throw new Error(`gologin failed account creation with status code, ${response.statusCode}`);
1053
1069
  }
1054
1070
  debug(JSON.stringify(response.body));
1055
1071
  return response.body.id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gologin",
3
- "version": "1.0.50",
3
+ "version": "1.0.53",
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,2 @@
1
+ _id,name,notes,proxy,os,isM1,timezone.enabled,timezone.fillBasedOnIp,timezone.timezone,geolocation.accuracy,geolocation.customize,geolocation.enabled,geolocation.fillBasedOnIp,geolocation.latitude,geolocation.longitude,geolocation.mode,cookies,webGLMetadata.renderer,webGLMetadata.vendor,proxy.autoProxyRegion,proxy.torProxyRegion,audioContext.mode,audioContext.noise,browserType,canvas.mode,canvas.noise,chromeExtensions,clientRects.mode,clientRects.noise,devicePixelRatio,dns,extensions.enabled,extensions.preloadCustom,fonts.enableDomRect,fonts.enableMasking,fonts.families,googleServicesEnabled,lockEnabled,mediaDevices.audioInputs,mediaDevices.audioOutputs,mediaDevices.enableMasking,mediaDevices.uid,mediaDevices.videoInputs,navigator.deviceMemory,navigator.hardwareConcurrency,navigator.language,navigator.maxTouchPoints,navigator.platform,navigator.resolution,navigator.userAgent,plugins.enableFlash,plugins.enableVulnerable,proxyEnabled,startUrl,storage.bookmarks,storage.extensions,storage.history,storage.local,storage.passwords,storage.session,updateUALastChosenBrowserV,userChromeExtensions,webGL.getClientRectsNoise,webGL.mode,webGL.noise,webGLMetadata.mode,webRTC.customize,webRTC.enabled,webRTC.fillBasedOnIp,webRTC.localIpMasking,webRTC.localIps,webRTC.mode,webRTC.publicIp,webglParams.antialiasing,webglParams.extensions,webglParams.glCanvas,webglParams.glParamValues,webglParams.shaiderPrecisionFormat,webglParams.supportedFunctions,webglParams.textureMaxAnisotropyExt
2
+ "62ceac1ebf5c65e7b9737e90",divine-moon,,gologin://us,lin,,true,true,,10,true,true,true,0,0,prompt,"[{""sameSite"":""unspecified"",""url"":""http://accounts.youtube.com/accounts"",""domain"":""accounts.youtube.com"",""name"":""CheckConnectionTempCookie418"",""value"":""583900"",""path"":""/accounts"",""secure"":false,""httpOnly"":false,""hostOnly"":true,""session"":false,""expirationDate"":1657711797},{""sameSite"":""no_restriction"",""url"":""https://google.com/"",""domain"":"".google.com"",""name"":""NID"",""value"":""511=pmrVWL6S6C04qFVhNbPwFk7VuJS3Q4GIbuYcwLeIBx8qxFPkraAGDr89L_9v1OxfCdvukGBBXLZzQ-Zx17qqxmKesRDAwk91GzT2uXWM6n_62DBXD0K5Dt2vBPtnVV15UA-AkE1ujldxEDI5FJjzN6RJ_VLYK3YaJax_Drdhp_I"",""path"":""/"",""secure"":true,""httpOnly"":true,""hostOnly"":false,""session"":false,""expirationDate"":1673522989.3169844},{""sameSite"":""unspecified"",""url"":""https://accounts.google.com/"",""domain"":""accounts.google.com"",""name"":""__Host-GAPS"",""value"":""1:XwbHhqGMbhJNRMbV1-WjwIE3WM3iqA:SGVT-tDrpcVPTnu3"",""path"":""/"",""secure"":true,""httpOnly"":true,""hostOnly"":true,""session"":false,""expirationDate"":1720783788.086744}]",,Google Inc.,us,us,noise,2.992842533846e-8,chrome,off,0.3554128,[],noise,5.40273,1.34375,,true,true,true,true,"[""AIGDT"",""AMGDT"",""Alef"",""Ani"",""AnjaliOldLipi"",""Caladea"",""Chilanka"",""David Libre"",""DejaVu Sans"",""DejaVu Sans Condensed"",""DejaVu Sans Light"",""DejaVu Serif"",""Droid Sans"",""Frank Ruehl"",""Frank Ruehl Libre"",""Frank Ruehl Libre Black"",""Frank Ruehl Libre Light"",""FreeMono"",""FreeSans"",""FreeSerif"",""Gargi"",""Gubbi"",""Jamrul"",""KacstBook"",""KacstLetter"",""KacstNaskh"",""Kalapi"",""Kalimati"",""Karumbi"",""Khmer OS"",""Khmer UI"",""Kinnari"",""Liberation Mono"",""Liberation Sans"",""Liberation Sans Narrow"",""Liberation Serif"",""Lohit Devanagari"",""Loma"",""Meera"",""Meera Inimai"",""Miriam"",""Miriam Fixed"",""Miriam Libre"",""Mitra Mono"",""Mukti Narrow"",""Nakula"",""Navuli"",""Nimbus Roman"",""Nimbus Sans"",""Nimbus Sans Narrow"",""Norasi"",""Noto Sans"",""Noto Sans Arabic UI"",""Noto Sans CJK JP"",""Noto Sans CJK KR"",""Noto Sans CJK SC"",""Noto Sans CJK TC"",""Noto Sans Mono CJK HK"",""Noto Sans Mono CJK KR"",""Noto Sans Mono CJK SC"",""Noto Sans Mono CJK TC"",""Noto Serif"",""Noto Serif CJK JP"",""Noto Serif CJK KR"",""Noto Serif CJK SC"",""Noto Serif Georgian"",""Oswald"",""Padauk"",""Pagul"",""Phetsarath OT"",""Pothana2000"",""Purisa"",""Roboto Light"",""Roboto Medium"",""Roboto Thin"",""Rubik"",""Sahadeva"",""Samyak Devanagari"",""Samyak Gujarati"",""Source Code Pro"",""Source Code Pro Black"",""Source Code Pro Extra Light"",""Source Code Pro Light"",""Source Code Pro Medium"",""Source Code Pro Semibold"",""Source Sans Pro"",""Source Sans Pro Black"",""Source Sans Pro Extra Light"",""Source Sans Pro Semibold"",""Source Serif Pro"",""Source Serif Pro Light"",""Source Serif Pro Semibold"",""Suruma"",""Tibetan Machine Uni"",""Tlwg Mono"",""Tlwg Typist"",""URW Gothic L"",""Uroob"",""Vemana2000""]",false,false,1,1,true,b04187cb002a40aeb4382c7c2d9cc3ccb395aee9613241af81df212fd0,0,8,8,"en-US,en;q=0.9",0,Linux x86_64,1600x900,"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36",true,true,true,https://iphey.com,true,true,true,true,true,true,,[],5.40273,noise,35.999,mask,true,true,true,true,[],alerted,,false,"[""ANGLE_instanced_arrays"",""EXT_blend_minmax"",""EXT_color_buffer_half_float"",""EXT_float_blend"",""EXT_frag_depth"",""EXT_sRGB"",""EXT_shader_texture_lod"",""EXT_texture_compression_rgtc"",""EXT_texture_filter_anisotropic"",""KHR_parallel_shader_compile"",""OES_element_index_uint"",""OES_fbo_render_mipmap"",""OES_standard_derivatives"",""OES_texture_float"",""OES_texture_float_linear"",""OES_texture_half_float"",""OES_texture_half_float_linear"",""OES_vertex_array_object"",""WEBGL_color_buffer_float"",""WEBGL_compressed_texture_astc"",""WEBGL_compressed_texture_s3tc"",""WEBGL_compressed_texture_s3tc_srgb"",""WEBGL_debug_renderer_info"",""WEBGL_debug_shaders"",""WEBGL_depth_texture"",""WEBGL_draw_buffers"",""WEBGL_lose_context"",""WEBGL_multi_draw"",""WEBKIT_EXT_texture_filter_anisotropic"",""WEBKIT_WEBGL_compressed_texture_s3tc"",""WEBKIT_WEBGL_depth_texture"",""WEBKIT_WEBGL_lose_context""]",webgl,"[{""name"":""ALIASED_LINE_WIDTH_RANGE"",""value"":{""0"":1,""1"":7}},{""name"":""ALIASED_POINT_SIZE_RANGE"",""value"":{""0"":1,""1"":255}},{""name"":[""DEPTH_BITS"",""STENCIL_BITS""],""value"":""n/a""},{""name"":""MAX_3D_TEXTURE_SIZE"",""value"":""n/a""},{""name"":""MAX_ARRAY_TEXTURE_LAYERS"",""value"":""n/a""},{""name"":""MAX_COLOR_ATTACHMENTS"",""value"":""n/a""},{""name"":""MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_COMBINED_TEXTURE_IMAGE_UNITS"",""value"":32},{""name"":""MAX_COMBINED_UNIFORM_BLOCKS"",""value"":""n/a""},{""name"":""MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_CUBE_MAP_TEXTURE_SIZE"",""value"":8192},{""name"":""MAX_DRAW_BUFFERS"",""value"":""n/a""},{""name"":""MAX_FRAGMENT_INPUT_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_FRAGMENT_UNIFORM_BLOCKS"",""value"":""n/a""},{""name"":""MAX_FRAGMENT_UNIFORM_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_FRAGMENT_UNIFORM_VECTORS"",""value"":1024},{""name"":""MAX_PROGRAM_TEXEL_OFFSET"",""value"":""n/a""},{""name"":""MAX_RENDERBUFFER_SIZE"",""value"":8192},{""name"":""MAX_SAMPLES"",""value"":""n/a""},{""name"":""MAX_TEXTURE_IMAGE_UNITS"",""value"":16},{""name"":""MAX_TEXTURE_LOD_BIAS"",""value"":""n/a""},{""name"":""MAX_TEXTURE_SIZE"",""value"":8192},{""name"":""MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS"",""value"":""n/a""},{""name"":""MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_UNIFORM_BLOCK_SIZE"",""value"":""n/a""},{""name"":""MAX_UNIFORM_BUFFER_BINDINGS"",""value"":""n/a""},{""name"":""MAX_VARYING_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_VARYING_VECTORS"",""value"":16},{""name"":""MAX_VERTEX_ATTRIBS"",""value"":16},{""name"":""MAX_VERTEX_OUTPUT_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_VERTEX_TEXTURE_IMAGE_UNITS"",""value"":16},{""name"":""MAX_VERTEX_UNIFORM_BLOCKS"",""value"":""n/a""},{""name"":""MAX_VERTEX_UNIFORM_COMPONENTS"",""value"":""n/a""},{""name"":""MAX_VERTEX_UNIFORM_VECTORS"",""value"":1024},{""name"":""MAX_VIEWPORT_DIMS"",""value"":{""0"":8192,""1"":8192}},{""name"":""MIN_PROGRAM_TEXEL_OFFSET"",""value"":""n/a""},{""name"":[""RED_BITS"",""GREEN_BITS"",""BLUE_BITS"",""ALPHA_BITS""],""value"":""n/a""},{""name"":""RENDERER"",""value"":""WebKit WebGL""},{""name"":""SHADING_LANGUAGE_VERSION"",""value"":""WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)""},{""name"":""UNIFORM_BUFFER_OFFSET_ALIGNMENT"",""value"":""n/a""},{""name"":""VENDOR"",""value"":""WebKit""},{""name"":""VERSION"",""value"":""WebGL 1.0 (OpenGL ES 2.0 Chromium)""}]",highp/highp,"[{""name"":""beginQuery"",""supported"":false},{""name"":""beginTransformFeedback"",""supported"":false},{""name"":""bindBufferBase"",""supported"":false},{""name"":""bindBufferRange"",""supported"":false},{""name"":""bindSampler"",""supported"":false},{""name"":""bindTransformFeedback"",""supported"":false},{""name"":""bindVertexArray"",""supported"":false},{""name"":""blitFramebuffer"",""supported"":false},{""name"":""clearBufferfi"",""supported"":false},{""name"":""clearBufferfv"",""supported"":false},{""name"":""clearBufferiv"",""supported"":false},{""name"":""clearBufferuiv"",""supported"":false},{""name"":""clientWaitSync"",""supported"":false},{""name"":""compressedTexImage3D"",""supported"":false},{""name"":""compressedTexSubImage3D"",""supported"":false},{""name"":""copyBufferSubData"",""supported"":false},{""name"":""copyTexSubImage3D"",""supported"":false},{""name"":""createQuery"",""supported"":false},{""name"":""createSampler"",""supported"":false},{""name"":""createTransformFeedback"",""supported"":false},{""name"":""createVertexArray"",""supported"":false},{""name"":""deleteQuery"",""supported"":false},{""name"":""deleteSampler"",""supported"":false},{""name"":""deleteSync"",""supported"":false},{""name"":""deleteTransformFeedback"",""supported"":false},{""name"":""deleteVertexArray"",""supported"":false},{""name"":""drawArraysInstanced"",""supported"":false},{""name"":""drawBuffers"",""supported"":false},{""name"":""drawElementsInstanced"",""supported"":false},{""name"":""drawRangeElements"",""supported"":false},{""name"":""endQuery"",""supported"":false},{""name"":""endTransformFeedback"",""supported"":false},{""name"":""fenceSync"",""supported"":false},{""name"":""framebufferTextureLayer"",""supported"":false},{""name"":""getActiveUniformBlockName"",""supported"":false},{""name"":""getActiveUniformBlockParameter"",""supported"":false},{""name"":""getActiveUniforms"",""supported"":false},{""name"":""getBufferSubData"",""supported"":false},{""name"":""getFragDataLocation"",""supported"":false},{""name"":""getIndexedParameter"",""supported"":false},{""name"":""getInternalformatParameter"",""supported"":false},{""name"":""getQuery"",""supported"":false},{""name"":""getQueryParameter"",""supported"":false},{""name"":""getSamplerParameter"",""supported"":false},{""name"":""getSyncParameter"",""supported"":false},{""name"":""getTransformFeedbackVarying"",""supported"":false},{""name"":""getUniformBlockIndex"",""supported"":false},{""name"":""getUniformIndices"",""supported"":false},{""name"":""invalidateFramebuffer"",""supported"":false},{""name"":""invalidateSubFramebuffer"",""supported"":false},{""name"":""isQuery"",""supported"":false},{""name"":""isSampler"",""supported"":false},{""name"":""isSync"",""supported"":false},{""name"":""isTransformFeedback"",""supported"":false},{""name"":""isVertexArray"",""supported"":false},{""name"":""pauseTransformFeedback"",""supported"":false},{""name"":""readBuffer"",""supported"":false},{""name"":""renderbufferStorageMultisample"",""supported"":false},{""name"":""resumeTransformFeedback"",""supported"":false},{""name"":""samplerParameterf"",""supported"":false},{""name"":""samplerParameteri"",""supported"":false},{""name"":""texImage3D"",""supported"":false},{""name"":""texStorage2D"",""supported"":false},{""name"":""texStorage3D"",""supported"":false},{""name"":""texSubImage3D"",""supported"":false},{""name"":""transformFeedbackVaryings"",""supported"":false},{""name"":""uniform1ui"",""supported"":false},{""name"":""uniform1uiv"",""supported"":false},{""name"":""uniform2ui"",""supported"":false},{""name"":""uniform2uiv"",""supported"":false},{""name"":""uniform3ui"",""supported"":false},{""name"":""uniform3uiv"",""supported"":false},{""name"":""uniform4ui"",""supported"":false},{""name"":""uniform4uiv"",""supported"":false},{""name"":""uniformBlockBinding"",""supported"":false},{""name"":""uniformMatrix2x3fv"",""supported"":false},{""name"":""uniformMatrix2x4fv"",""supported"":false},{""name"":""uniformMatrix3x2fv"",""supported"":false},{""name"":""uniformMatrix3x4fv"",""supported"":false},{""name"":""uniformMatrix4x2fv"",""supported"":false},{""name"":""uniformMatrix4x3fv"",""supported"":false},{""name"":""vertexAttribDivisor"",""supported"":false},{""name"":""vertexAttribI4i"",""supported"":false},{""name"":""vertexAttribI4iv"",""supported"":false},{""name"":""vertexAttribI4ui"",""supported"":false},{""name"":""vertexAttribI4uiv"",""supported"":false},{""name"":""vertexAttribIPointer"",""supported"":false},{""name"":""waitSync"",""supported"":false}]",16
@@ -0,0 +1,368 @@
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 () => {
198
+ const extensionsToDownloadPaths = await request.post(`${this.#API_BASE_URL}/extensions/user_chrome_extensions_paths`, {
199
+ json: true,
200
+ fullResponse: false,
201
+ headers: {
202
+ Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
203
+ 'user-agent': this.#USER_AGENT,
204
+ 'x-two-factor-token': this.#TWO_FA_KEY || '',
205
+ },
206
+ body: {
207
+ existedUserChromeExtensions: this.#existedUserExtensions,
208
+ }
209
+ }) || [];
210
+
211
+ if (!extensionsToDownloadPaths.length) {
212
+ return;
213
+ }
214
+
215
+ const promises = extensionsToDownloadPaths.map(async awsPath => {
216
+ const [basePath] = awsPath.split('?');
217
+ const [extId] = basePath.split('/').reverse();
218
+ const zipPath = `${path.join(USER_EXTENSIONS_PATH, extId)}.zip`;
219
+ const archiveZip = fs.createWriteStream(zipPath);
220
+
221
+ await request(awsPath, {
222
+ retryDelay: 2 * 1000,
223
+ maxAttempts: 3,
224
+ }).pipe(archiveZip);
225
+
226
+ await new Promise(r => archiveZip.on('close', () => r()));
227
+ return zipPath;
228
+ });
229
+
230
+ const zipPaths = await Promise.all(promises).catch(() => []);
231
+
232
+ if (!zipPaths) {
233
+ return;
234
+ }
235
+
236
+ const extractionPromises = composeExtractionPromises(zipPaths, USER_EXTENSIONS_PATH);
237
+ const isExtensionsExtracted = await Promise.all(extractionPromises).catch(() => 'error');
238
+
239
+ if (isExtensionsExtracted !== 'error') {
240
+ const [downloadedFolders] = zipPaths.map(archivePath => archivePath.split(path.sep).reverse());
241
+ this.#existedUserExtensions = [...this.#existedUserExtensions, ...downloadedFolders];
242
+ }
243
+ }
244
+
245
+ async getExtensionsNameAndImage(extensionsIds, pathToExtensions) {
246
+ const isCheckLocalFiles = [CHROME_EXTENSIONS_PATH, USER_EXTENSIONS_PATH].includes(pathToExtensions);
247
+ const extensionFolderNames = await readdir(pathToExtensions).catch(() => {});
248
+ const filteredExtensionFolderNames = extensionFolderNames.filter(extensionFolder => extensionsIds.some(extensionId => !extensionFolder.includes('.zip') && extensionFolder.includes(extensionId)));
249
+
250
+ if (!filteredExtensionFolderNames.length) {
251
+ return;
252
+ }
253
+
254
+ const namesPromise = extensionsIds.map(async (extensionsId) => {
255
+ const folderName = filteredExtensionFolderNames.find(folderName => folderName.includes(extensionsId));
256
+
257
+ if (!folderName) {
258
+ return;
259
+ }
260
+
261
+ let pathToExtensionsFolder = [pathToExtensions, folderName];
262
+ if (!isCheckLocalFiles) {
263
+ const [extensionVersion] = await readdir(path.join(pathToExtensions, folderName));
264
+ pathToExtensionsFolder = [pathToExtensions, folderName, extensionVersion];
265
+ }
266
+
267
+ const manifestPath = path.join(...pathToExtensionsFolder, 'manifest.json');
268
+ const manifestString = await readFile(manifestPath, 'utf8').catch(() => '');
269
+ if (!manifestString) {
270
+ return;
271
+ }
272
+
273
+ const manifestObject = JSON.parse(manifestString);
274
+ let name;
275
+ if (manifestObject.name.includes('__MSG')) {
276
+ const manifestName = manifestObject.name || '';
277
+ const fieldNameInLocale = manifestName.replace(/__/g, '').split('MSG_')[1];
278
+ const localePath = path.join(...pathToExtensionsFolder, '_locales', manifestObject.default_locale, 'messages.json');
279
+ const localeString = await readFile(localePath, 'utf8').catch(() => {});
280
+
281
+ try {
282
+ const parsedLocale = JSON.parse(localeString.trim());
283
+ name = parsedLocale[fieldNameInLocale].message;
284
+ } catch (e) {}
285
+ } else {
286
+ name = manifestObject.name;
287
+ }
288
+
289
+ if (!name) {
290
+ return;
291
+ }
292
+
293
+ const iconObject = manifestObject.icons;
294
+ let iconPath = manifestObject.browser_action?.default_icon;
295
+ if (iconObject) {
296
+ iconPath = iconObject['128'];
297
+ }
298
+
299
+ let iconBSON = '';
300
+ if (iconPath) {
301
+ const iconPathFull = path.join(...pathToExtensionsFolder, iconPath);
302
+ iconBSON = await readFile(iconPathFull, 'base64').catch(() => {});
303
+ }
304
+
305
+ return {
306
+ name,
307
+ extId: extensionsId,
308
+ iconBinary: iconBSON,
309
+ };
310
+ });
311
+
312
+ const extensionsArray = await Promise.all(namesPromise);
313
+ return extensionsArray.filter(Boolean);
314
+ }
315
+
316
+ generateExtensionId() {
317
+ let result = '';
318
+ let extensionIdLength = 32;
319
+ const characters = 'abcdefghijklmnopqrstuvwxyz';
320
+ const charactersLength = characters.length;
321
+ while (extensionIdLength--) {
322
+ result += characters.charAt(Math.floor(Math.random() *
323
+ charactersLength));
324
+ }
325
+ return result;
326
+ }
327
+ }
328
+
329
+ const checkFileSizeSync = async (pathToFile) => {
330
+ try {
331
+ const [fileName] = pathToFile.split(path.sep).reverse();
332
+ if (fileName === '.DS_Store') {
333
+ return 0;
334
+ }
335
+
336
+ const fileStats = await stat(pathToFile);
337
+ if (!fileStats.isDirectory()) {
338
+ return fileStats.size;
339
+ }
340
+
341
+ const files = await readdir(pathToFile);
342
+ const promises = files.map(async file => checkFileSizeSync(path.join(pathToFile, file)));
343
+
344
+ return (await Promise.all(promises)).reduce((result, value) => result + value, 0);
345
+ } catch {
346
+ return -1;
347
+ }
348
+ };
349
+
350
+ const copyFolder = async (fromPath, destPath) => {
351
+ const stats = await stat(fromPath);
352
+
353
+ if (!stats.isDirectory()) {
354
+ return copyFile(fromPath, destPath);
355
+ }
356
+
357
+ await mkdir(destPath, { recursive: true }).catch(() => null);
358
+ const files = await readdir(fromPath);
359
+ const promises = files.map(async file => {
360
+ await mkdir(destPath, { recursive: true }).catch(() => null);
361
+
362
+ return copyFolder(path.join(fromPath, file), path.join(destPath, file));
363
+ });
364
+
365
+ return Promise.all(promises);
366
+ };
367
+
368
+ module.exports = UserExtensionsManager;