gologin 1.0.42 → 1.0.45

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,25 @@
1
+ // Usage example: in the terminal enter
2
+ // node example-startremote.js yU0token yU0Pr0f1leiD
3
+
4
+ // your token api (located in the settings, api)
5
+ // https://github.com/gologinapp/gologin#usage
6
+ const GOLOGIN_API_TOKEN = process.argv[2];
7
+ // your profile id
8
+ const GOLOGIN_PROFILE_ID = process.argv[3];
9
+
10
+ const GoLogin = require('../gologin');
11
+
12
+ (async () =>{
13
+ const GL = new GoLogin({
14
+ token: GOLOGIN_API_TOKEN,
15
+ profile_id: GOLOGIN_PROFILE_ID,
16
+ });
17
+ // connection of the remote work method
18
+ const {status, wsUrl} = await GL.startRemote();
19
+
20
+
21
+ GOLOGIN_PROFILE_CLOUD_URL = wsUrl.split('/')[2]
22
+ console.log('Done! Launch web browser and navigate to URL:', GOLOGIN_PROFILE_CLOUD_URL);
23
+ })();
24
+
25
+ // after running the script, the url will appear on the terminal
@@ -0,0 +1,20 @@
1
+ // Usage example: in the terminal enter
2
+ // node example-stopremote.js yU0token yU0Pr0f1leiD
3
+
4
+ // your token api (located in the settings, api)
5
+ // https://github.com/gologinapp/gologin#usage
6
+ const GOLOGIN_API_TOKEN = process.argv[2];
7
+ // your profile id
8
+ const GOLOGIN_PROFILE_ID = process.argv[3];
9
+
10
+ const GoLogin = require('../gologin');
11
+
12
+ (async () =>{
13
+ const GL = new GoLogin({
14
+ token: GOLOGIN_API_TOKEN,
15
+ profile_id: GOLOGIN_PROFILE_ID,
16
+ });
17
+
18
+ // stop profile
19
+ await GL.stopRemote();
20
+ })();
@@ -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';
@@ -247,6 +248,10 @@ class GoLogin {
247
248
  preferences.language = _.get(preferences, 'navigator.language');
248
249
  }
249
250
 
251
+ if (_.get(preferences, 'isM1')) {
252
+ preferences.is_m1 = _.get(preferences, 'navigator.language');
253
+ }
254
+
250
255
  return preferences;
251
256
  }
252
257
 
@@ -370,6 +375,40 @@ class GoLogin {
370
375
  let preferences = JSON.parse(preferences_raw.toString());
371
376
  let proxy = _.get(profile, 'proxy');
372
377
  let name = _.get(profile, 'name');
378
+ const chromeExtensions = _.get(profile, 'chromeExtensions');
379
+
380
+ if (chromeExtensions && chromeExtensions.length) {
381
+ const ExtensionsManagerInst = new ExtensionsManager();
382
+ ExtensionsManagerInst.apiUrl = API_URL;
383
+ await ExtensionsManagerInst.init()
384
+ .then(() => ExtensionsManagerInst.updateExtensions())
385
+ .catch(() => {});
386
+ ExtensionsManagerInst.accessToken = this.access_token;
387
+
388
+ await ExtensionsManagerInst.getExtensionsPolicies();
389
+ let profileExtensionsCheckRes = [];
390
+
391
+ if (ExtensionsManagerInst.useLocalExtStorage) {
392
+ profileExtensionsCheckRes = await ExtensionsManagerInst.checkChromeExtensions(chromeExtensions).catch((e) => {
393
+ console.log('checkChromeExtensions error: ', e);
394
+ return [];
395
+ });
396
+ }
397
+
398
+ let extSettings;
399
+ if (ExtensionsManagerInst.useLocalExtStorage && profileExtensionsCheckRes.length) {
400
+ extSettings = BrowserUserDataManager.setExtPaths(preferences, profileExtensionsCheckRes);
401
+ } else if (!ExtensionsManagerInst.useLocalExtStorage) {
402
+ const originalExtensionsFolder = path.join(profilePath, 'Default', 'Extensions');
403
+ extSettings = await BrowserUserDataManager.setOriginalExtPaths(preferences, originalExtensionsFolder);
404
+ }
405
+
406
+ if (extSettings) {
407
+ const currentExtSettings = preferences.extensions || {};
408
+ currentExtSettings.settings = extSettings
409
+ preferences.extensions = currentExtSettings;
410
+ }
411
+ }
373
412
 
374
413
  if (proxy.mode === 'gologin' || proxy.mode === 'tor') {
375
414
  const autoProxyServer = _.get(profile, 'autoProxyServer');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gologin",
3
- "version": "1.0.42",
3
+ "version": "1.0.45",
4
4
  "description": "A high-level API to control Orbita browser over GoLogin API",
5
5
  "main": "./gologin.js",
6
6
  "repository": {