gologin 2.0.1 → 2.0.2
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 +4 -4
- package/browser-user-data-manager.js +1 -1
- package/cookies-manager.js +2 -2
- package/examples/example-amazon-goless.js +2 -2
- package/examples/example-amazon-headless.js +2 -2
- package/examples/example-amazon.js +2 -2
- package/examples/example-gmail.js +2 -2
- package/examples/example-local-profile.js +2 -2
- package/examples/example-login-walmart.js +2 -2
- package/examples/example-timezone.js +2 -2
- package/gologin.js +4 -17
- package/package.json +3 -3
- package/profile-archiver.js +81 -0
- package/profile-directories-to-remove.js +55 -0
- package/user-extensions-manager.js +1 -126
package/README.md
CHANGED
|
@@ -23,11 +23,11 @@ To have an access to the page below you need <a href="https://app.gologin.com/#/
|
|
|
23
23
|
### Example
|
|
24
24
|
|
|
25
25
|
```js
|
|
26
|
-
import
|
|
26
|
+
import puppeteer from 'puppeteer-core';
|
|
27
27
|
|
|
28
28
|
import GoLogin from './gologin.js';
|
|
29
29
|
|
|
30
|
-
const { connect } =
|
|
30
|
+
const { connect } = puppeteer;
|
|
31
31
|
|
|
32
32
|
(async () => {
|
|
33
33
|
const GL = new GoLogin({
|
|
@@ -128,11 +128,11 @@ stop current browser without removing archived profile
|
|
|
128
128
|
### example-local-profile.js
|
|
129
129
|
|
|
130
130
|
```js
|
|
131
|
-
import
|
|
131
|
+
import puppeteer from 'puppeteer-core';
|
|
132
132
|
|
|
133
133
|
import GoLogin from './gologin.js';
|
|
134
134
|
|
|
135
|
-
const { connect } =
|
|
135
|
+
const { connect } = puppeteer;
|
|
136
136
|
|
|
137
137
|
(async () => {
|
|
138
138
|
const GL = new GoLogin({
|
|
@@ -62,7 +62,7 @@ export const downloadFonts = async (fontsList = [], profilePath) => {
|
|
|
62
62
|
const files = await readdir(browserFontsPath);
|
|
63
63
|
const fontsToDownload = fontsList.filter(font => !files.includes(font));
|
|
64
64
|
|
|
65
|
-
let promises = fontsToDownload.map(font => get(FONTS_URL + font, {
|
|
65
|
+
let promises = fontsToDownload.map(font => requestretry.get(FONTS_URL + font, {
|
|
66
66
|
maxAttempts: 5,
|
|
67
67
|
retryDelay: 2000,
|
|
68
68
|
timeout: 30 * 1000,
|
package/cookies-manager.js
CHANGED
package/gologin.js
CHANGED
|
@@ -4,25 +4,25 @@ import decompress from 'decompress';
|
|
|
4
4
|
import decompressUnzip from 'decompress-unzip';
|
|
5
5
|
import { existsSync, mkdirSync, promises as _promises } from 'fs';
|
|
6
6
|
import { get as _get } from 'https';
|
|
7
|
-
import
|
|
7
|
+
import lodash from 'lodash';
|
|
8
8
|
import { tmpdir } from 'os';
|
|
9
9
|
import { join, resolve as _resolve,sep } from 'path';
|
|
10
10
|
import requests from 'requestretry';
|
|
11
11
|
import rimraf from 'rimraf';
|
|
12
12
|
import ProxyAgent from 'simple-proxy-agent';
|
|
13
13
|
import util from 'util';
|
|
14
|
-
import zipdir from 'zip-dir';
|
|
15
14
|
|
|
16
15
|
import BrowserChecker from './browser-checker.js';
|
|
17
16
|
import { composeFonts, downloadCookies, setExtPathsAndRemoveDeleted, setOriginalExtPaths, uploadCookies } from './browser-user-data-manager.js';
|
|
18
17
|
import { getChunckedInsertValues, getDB, loadCookiesFromFile } from './cookies-manager.js';
|
|
19
18
|
import ExtensionsManager from './extensions-manager.js';
|
|
20
19
|
import { fontsCollection } from './fonts.js';
|
|
20
|
+
import { archiveProfile } from './profile-archiver.js';
|
|
21
21
|
|
|
22
22
|
const exec = util.promisify(execNonPromise);
|
|
23
23
|
|
|
24
24
|
const { access, unlink, writeFile, readFile } = _promises;
|
|
25
|
-
const { get, merge } =
|
|
25
|
+
const { get, merge } = lodash;
|
|
26
26
|
|
|
27
27
|
const SEPARATOR = sep;
|
|
28
28
|
const API_URL = 'https://api.gologin.com';
|
|
@@ -1014,20 +1014,7 @@ export class GoLogin {
|
|
|
1014
1014
|
debug('profile sanitized');
|
|
1015
1015
|
|
|
1016
1016
|
const profilePath = this.profilePath();
|
|
1017
|
-
const fileBuff = await
|
|
1018
|
-
{
|
|
1019
|
-
saveTo: zipPath,
|
|
1020
|
-
filter: (path) => !/RunningChromeVersion/.test(path),
|
|
1021
|
-
}, (err, buffer) => {
|
|
1022
|
-
if (err) {
|
|
1023
|
-
reject(err);
|
|
1024
|
-
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
resolve(buffer);
|
|
1029
|
-
}),
|
|
1030
|
-
);
|
|
1017
|
+
const fileBuff = await archiveProfile(profilePath);
|
|
1031
1018
|
|
|
1032
1019
|
debug('PROFILE ZIP CREATED', profilePath, zipPath);
|
|
1033
1020
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gologin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A high-level API to control Orbita browser over GoLogin API",
|
|
5
5
|
"main": "./gologin.js",
|
|
6
6
|
"repository": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"license": "GPL-3.0",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"archiver": "^3.1.1",
|
|
18
|
+
"adm-zip": "^0.5.1",
|
|
18
19
|
"child_process": "^1.0.2",
|
|
19
20
|
"decompress": "^4.2.1",
|
|
20
21
|
"decompress-unzip": "^4.0.1",
|
|
@@ -27,8 +28,7 @@
|
|
|
27
28
|
"rimraf": "^3.0.2",
|
|
28
29
|
"simple-proxy-agent": "^1.1.0",
|
|
29
30
|
"sqlite": "^4.0.23",
|
|
30
|
-
"sqlite3": "^5.0.2"
|
|
31
|
-
"zip-dir": "^2.0.0"
|
|
31
|
+
"sqlite3": "^5.0.2"
|
|
32
32
|
},
|
|
33
33
|
"bugs": {
|
|
34
34
|
"url": "https://github.com/gologinapp/gologin/issues"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import AdmZip from 'adm-zip';
|
|
2
|
+
import { promises as _promises } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
import { getDirectoriesForArchiver } from './profile-directories-to-remove.js';
|
|
6
|
+
|
|
7
|
+
const { access } = _promises;
|
|
8
|
+
|
|
9
|
+
export const archiveProfile = async (profileFolder = '', tryAgain = true) => {
|
|
10
|
+
const folderExists = await access(profileFolder).then(() => true, () => false);
|
|
11
|
+
if (!folderExists) {
|
|
12
|
+
throw new Error('Invalid profile folder path: ' + profileFolder);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const archive = new AdmZip();
|
|
16
|
+
archive.addLocalFolder(path.join(profileFolder, 'Default'), 'Default');
|
|
17
|
+
archive.addLocalFile(path.join(profileFolder, 'First Run'));
|
|
18
|
+
const dirsToRemove = getDirectoriesForArchiver();
|
|
19
|
+
dirsToRemove.forEach(entry => archive.deleteFile(entry));
|
|
20
|
+
|
|
21
|
+
const archiveIsValid = checkProfileArchiveIsValid(archive);
|
|
22
|
+
if (tryAgain && !archiveIsValid) {
|
|
23
|
+
await new Promise(r => setTimeout(() => r(), 300));
|
|
24
|
+
|
|
25
|
+
return archiveProfile(profileFolder, false);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return new Promise((resolve, reject) => archive.toBuffer(resolve, reject));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const decompressProfile = async (zipPath = '', profileFolder = '') => {
|
|
32
|
+
const zipExists = await access(zipPath).then(() => true, () => false);
|
|
33
|
+
if (!zipExists) {
|
|
34
|
+
throw new Error('Invalid zip path: ' + zipPath);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const archive = new AdmZip(zipPath);
|
|
38
|
+
archive
|
|
39
|
+
.getEntries()
|
|
40
|
+
.forEach((elem) => {
|
|
41
|
+
if (
|
|
42
|
+
!elem.isDirectory &&
|
|
43
|
+
(
|
|
44
|
+
elem.entryName.includes('RunningChromeVersion') ||
|
|
45
|
+
elem.entryName.includes('SingletonLock') ||
|
|
46
|
+
elem.entryName.includes('SingletonSocket') ||
|
|
47
|
+
elem.entryName.includes('SingletonCookie')
|
|
48
|
+
)
|
|
49
|
+
) {
|
|
50
|
+
archive.deleteFile(elem);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
archive.extractAllTo(profileFolder, true);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const checkProfileArchiveIsValid = (zipObject) => {
|
|
58
|
+
if (!zipObject) {
|
|
59
|
+
throw new Error('No zip object provided');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return zipObject
|
|
63
|
+
.getEntries()
|
|
64
|
+
.map(elem => {
|
|
65
|
+
if (elem.isDirectory) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return elem.entryName.includes('Preferences') || elem.entryName.includes('Cookies');
|
|
70
|
+
})
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.length >= 2;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const flatArray = (array = []) => array.map((elem) => {
|
|
76
|
+
if (Array.isArray(elem)) {
|
|
77
|
+
return flatArray(elem).flat();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return elem;
|
|
81
|
+
}).flat().filter(Boolean);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const DEFAULT_FOLDER_USELESS_FILE = [
|
|
2
|
+
{
|
|
3
|
+
name: 'Cache',
|
|
4
|
+
subs: [],
|
|
5
|
+
isDirectory: true,
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
name: 'fonts_config',
|
|
9
|
+
subs: [],
|
|
10
|
+
isDirectory: false,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'Service Worker',
|
|
14
|
+
subs: ['CacheStorage'],
|
|
15
|
+
isDirectory: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Code Cache',
|
|
19
|
+
subs: [],
|
|
20
|
+
isDirectory: true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'GPUCache',
|
|
24
|
+
subs: [],
|
|
25
|
+
isDirectory: true,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export const getDirectoriesToDeleteForNode = (routerSlash = '/') =>
|
|
30
|
+
DEFAULT_FOLDER_USELESS_FILE.reduce((res, el) => {
|
|
31
|
+
const basePath = routerSlash + 'Default' + routerSlash + el.name;
|
|
32
|
+
if (el.subs.length) {
|
|
33
|
+
el.subs.forEach(sub => res.push(basePath + routerSlash + sub));
|
|
34
|
+
} else {
|
|
35
|
+
res.push(basePath);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return res;
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
export const getDirectoriesForArchiver = () => DEFAULT_FOLDER_USELESS_FILE.reduce((res, el) => {
|
|
42
|
+
const { name, subs, isDirectory } = el;
|
|
43
|
+
const basePath = 'Default/' + name;
|
|
44
|
+
|
|
45
|
+
if (subs.length) {
|
|
46
|
+
subs.forEach((sub) => {
|
|
47
|
+
const resPath = basePath + '/' + (isDirectory ? sub + '/' : sub);
|
|
48
|
+
res.push(resPath);
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
res.push(basePath + (isDirectory ? '/' : ''));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return res;
|
|
55
|
+
}, []);
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { createWriteStream, promises as _promises } from 'fs';
|
|
2
2
|
import { join, sep } from 'path';
|
|
3
3
|
import request from 'requestretry';
|
|
4
|
-
import zipdir from 'zip-dir';
|
|
5
4
|
|
|
6
5
|
import { CHROME_EXTENSIONS_PATH, composeExtractionPromises, USER_EXTENSIONS_PATH } from './common.js';
|
|
7
|
-
import { extractExtension } from './extensions-extractor.js';
|
|
8
6
|
|
|
9
|
-
const { readdir,
|
|
10
|
-
|
|
11
|
-
const MAX_FILE_SIZE = 80 * 1024 * 1024;
|
|
12
|
-
const MAX_FILE_SIZE_MB = MAX_FILE_SIZE / 1024 / 1024;
|
|
7
|
+
const { readdir, readFile, stat, mkdir, copyFile } = _promises;
|
|
13
8
|
|
|
14
9
|
export class UserExtensionsManager {
|
|
15
10
|
#existedUserExtensions = [];
|
|
@@ -78,126 +73,6 @@ export class UserExtensionsManager {
|
|
|
78
73
|
this.#existedUserExtensions = fileList;
|
|
79
74
|
}
|
|
80
75
|
|
|
81
|
-
async addCustomExtension(pathToFiles) {
|
|
82
|
-
try {
|
|
83
|
-
const filesSize = await checkFileSizeSync(pathToFiles);
|
|
84
|
-
const isZip = pathToFiles.endsWith('.zip');
|
|
85
|
-
|
|
86
|
-
if (filesSize > MAX_FILE_SIZE) {
|
|
87
|
-
throw new Error(`The maximum file size is ${MAX_FILE_SIZE_MB}MB`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const customId = this.generateExtensionId();
|
|
91
|
-
|
|
92
|
-
if (isZip) {
|
|
93
|
-
const pathToExtract = join(USER_EXTENSIONS_PATH, customId);
|
|
94
|
-
await extractExtension(pathToFiles, pathToExtract);
|
|
95
|
-
pathToFiles = pathToExtract;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
let fileList = (await readdir(pathToFiles).catch(() => ['cantReadError']))
|
|
99
|
-
.filter(folderContent => folderContent !== '.DS_Store');
|
|
100
|
-
|
|
101
|
-
if (fileList.length === 1 && !fileList.includes('cantReadError')) {
|
|
102
|
-
const isFolder = (await stat(pathToFiles)).isDirectory();
|
|
103
|
-
if (isFolder) {
|
|
104
|
-
const [folderName] = fileList;
|
|
105
|
-
pathToFiles = join(pathToFiles, folderName);
|
|
106
|
-
fileList = await readdir(pathToFiles).catch(() => ['cantReadError']);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (fileList.includes('cantReadError')) {
|
|
111
|
-
throw new Error('Can\'t access folder');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!fileList.includes('manifest.json')) {
|
|
115
|
-
if (isZip) {
|
|
116
|
-
rmdir(pathToFiles);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
throw new Error('There is no manifest.json in the extension folder');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!isZip) {
|
|
123
|
-
const destPath = join(USER_EXTENSIONS_PATH, customId);
|
|
124
|
-
await copyFolder(pathToFiles, destPath).catch(() => {
|
|
125
|
-
throw new Error('Something went wrong coping your folder');
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const [nameIconId] = await this.getExtensionsNameAndImage([customId], USER_EXTENSIONS_PATH);
|
|
130
|
-
|
|
131
|
-
if (!nameIconId) {
|
|
132
|
-
throw new Error('Something went wrong. Please try again later');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const dbResult = await request(`${this.#API_BASE_URL}/extensions/create_user_extension`, {
|
|
136
|
-
method: 'POST',
|
|
137
|
-
headers: {
|
|
138
|
-
Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
|
|
139
|
-
'user-agent': this.#USER_AGENT,
|
|
140
|
-
'x-two-factor-token': this.#TWO_FA_KEY || '',
|
|
141
|
-
},
|
|
142
|
-
body: {
|
|
143
|
-
extensionInfo: nameIconId,
|
|
144
|
-
},
|
|
145
|
-
json: true,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// if success - there is no body
|
|
149
|
-
if (dbResult.body) {
|
|
150
|
-
throw new Error('Something went wrong inserting your data to database');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const fileBuffer = await zipdir(pathToFiles).catch(() => null);
|
|
154
|
-
if (!fileBuffer) {
|
|
155
|
-
throw new Error('Something went wrong. Please try again later');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const signedUrl = await request.get(`${this.#API_BASE_URL}/extensions/upload_url?extId=${customId}`, {
|
|
159
|
-
headers: {
|
|
160
|
-
Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
|
|
161
|
-
'user-agent': this.#USER_AGENT,
|
|
162
|
-
'x-two-factor-token': this.#TWO_FA_KEY || '',
|
|
163
|
-
},
|
|
164
|
-
maxAttempts: 3,
|
|
165
|
-
retryDelay: 2000,
|
|
166
|
-
timeout: 10 * 1000,
|
|
167
|
-
fullResponse: false,
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
const uploadResponse = await request.put(signedUrl, {
|
|
171
|
-
headers: {
|
|
172
|
-
'Content-Type': 'application/zip',
|
|
173
|
-
'Content-Length': Buffer.byteLength(fileBuffer),
|
|
174
|
-
},
|
|
175
|
-
body: fileBuffer,
|
|
176
|
-
maxBodyLength: Infinity,
|
|
177
|
-
maxContentLength: Infinity,
|
|
178
|
-
maxAttempts: 3,
|
|
179
|
-
retryDelay: 2000,
|
|
180
|
-
timeout: 30 * 1000,
|
|
181
|
-
fullResponse: true,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// if success - there is no body, in case of error - there will be an error in the body
|
|
185
|
-
if (uploadResponse.body) {
|
|
186
|
-
throw new Error('Your extension is added locally but we couldn\'t upload it to the cloud');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
status: 'success',
|
|
191
|
-
message: nameIconId,
|
|
192
|
-
};
|
|
193
|
-
} catch (e) {
|
|
194
|
-
return {
|
|
195
|
-
status: 'error',
|
|
196
|
-
message: e.message,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
76
|
checkLocalUserChromeExtensions = async (userChromeExtensions) => {
|
|
202
77
|
if (!userChromeExtensions.length) {
|
|
203
78
|
return;
|