gologin 2.1.12 → 2.1.13

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.
Files changed (49) hide show
  1. package/package.json +1 -1
  2. package/src/gologin.js +2 -1
  3. package/.sentry-native/15e25388-76f1-4a90-cea4-9a33e95e5be5.run/af276dd3-14b9-4b32-df19-69cb16a9a85e.envelope +0 -5
  4. package/.sentry-native/15e25388-76f1-4a90-cea4-9a33e95e5be5.run.lock +0 -0
  5. package/.sentry-native/last_crash +0 -1
  6. package/gologin/.eslintrc.json +0 -290
  7. package/gologin/README.md +0 -163
  8. package/gologin/example.js +0 -36
  9. package/gologin/examples/example-amazon-cloud-browser.js +0 -37
  10. package/gologin/examples/example-amazon-headless.js +0 -50
  11. package/gologin/examples/example-amazon.js +0 -47
  12. package/gologin/examples/example-create-custom-profile.js +0 -39
  13. package/gologin/examples/example-create-profile.js +0 -40
  14. package/gologin/examples/example-custom-args.js +0 -34
  15. package/gologin/examples/example-fast-profile-settings.js +0 -69
  16. package/gologin/examples/example-gmail.js +0 -67
  17. package/gologin/examples/example-iphey.js +0 -17
  18. package/gologin/examples/example-local-profile.js +0 -26
  19. package/gologin/examples/example-login-walmart.js +0 -35
  20. package/gologin/examples/example-stopremote.js +0 -20
  21. package/gologin/examples/example-timezone.js +0 -44
  22. package/gologin/fonts.js +0 -4293
  23. package/gologin/fonts_config +0 -104
  24. package/gologin/gologin-browser-ext.zip +0 -0
  25. package/gologin/gologin_zeroprofile.b64 +0 -1
  26. package/gologin/index.d.ts +0 -61
  27. package/gologin/package.json +0 -49
  28. package/gologin/profile_export_example.csv +0 -2
  29. package/gologin/run.sh +0 -1
  30. package/gologin/src/bookmarks/utils.js +0 -15
  31. package/gologin/src/browser/browser-api.js +0 -95
  32. package/gologin/src/browser/browser-checker.js +0 -392
  33. package/gologin/src/browser/browser-user-data-manager.js +0 -335
  34. package/gologin/src/cookies/cookies-manager.js +0 -189
  35. package/gologin/src/extensions/extensions-extractor.js +0 -56
  36. package/gologin/src/extensions/extensions-manager.js +0 -384
  37. package/gologin/src/extensions/user-extensions-manager.js +0 -295
  38. package/gologin/src/gologin-api.js +0 -110
  39. package/gologin/src/gologin.js +0 -1472
  40. package/gologin/src/profile/profile-archiver.js +0 -86
  41. package/gologin/src/profile/profile-directories-to-remove.js +0 -75
  42. package/gologin/src/utils/browser.js +0 -62
  43. package/gologin/src/utils/common.js +0 -76
  44. package/gologin/src/utils/constants.js +0 -1
  45. package/gologin/src/utils/utils.js +0 -49
  46. package/gologin/zero_profile.zip +0 -0
  47. package/gologin-canvas.zip +0 -0
  48. package/gologin.zip +0 -0
  49. package/test.html +0 -1
@@ -1,335 +0,0 @@
1
- import { createHash } from 'crypto';
2
- import { createWriteStream, promises as _promises, rmdirSync } from 'fs';
3
- import { homedir, tmpdir } from 'os';
4
- import { dirname, join, resolve, sep } from 'path';
5
- import requestretry from 'requestretry';
6
- import { fileURLToPath } from 'url';
7
-
8
- import { fontsCollection } from '../../fonts.js';
9
-
10
- const { access, readFile, writeFile, mkdir, readdir, copyFile, rename } = _promises;
11
-
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = dirname(__filename);
14
-
15
- const FONTS_URL = 'https://fonts.gologin.com/';
16
- const FONTS_DIR_NAME = 'fonts';
17
-
18
- const HOMEDIR = homedir();
19
- const BROWSER_PATH = join(HOMEDIR, '.gologin', 'browser');
20
- const OS_PLATFORM = process.platform;
21
- const DEFAULT_ORBITA_EXTENSIONS_NAMES = ['Google Hangouts', 'Chromium PDF Viewer', 'CryptoTokenExtension', 'Web Store'];
22
- const GOLOGIN_BASE_FOLDER_NAME = '.gologin';
23
- const GOLOGIN_TEST_FOLDER_NAME = '.gologin_test';
24
- const osPlatform = process.platform;
25
-
26
- export const downloadCookies = ({ profileId, ACCESS_TOKEN, API_BASE_URL }) =>
27
- requestretry.get(`${API_BASE_URL}/browser/${profileId}/cookies`, {
28
- headers: {
29
- Authorization: `Bearer ${ACCESS_TOKEN}`,
30
- 'user-agent': 'gologin-api',
31
- },
32
- json: true,
33
- maxAttempts: 3,
34
- retryDelay: 2000,
35
- timeout: 10 * 1000,
36
- }).catch((e) => {
37
- console.log(e);
38
-
39
- return { body: [] };
40
- });
41
-
42
- export const uploadCookies = ({ cookies = [], profileId, ACCESS_TOKEN, API_BASE_URL }) =>
43
- requestretry.post(`${API_BASE_URL}/browser/${profileId}/cookies?encrypted=true`, {
44
- headers: {
45
- Authorization: `Bearer ${ACCESS_TOKEN}`,
46
- 'User-Agent': 'gologin-api',
47
- },
48
- json: cookies,
49
- maxAttempts: 3,
50
- retryDelay: 2000,
51
- timeout: 20 * 1000,
52
- }).catch((e) => {
53
- console.log(e);
54
-
55
- return e;
56
- });
57
-
58
- export const downloadFonts = async (fontsList = [], profilePath) => {
59
- if (!fontsList.length) {
60
- return;
61
- }
62
-
63
- const browserFontsPath = join(BROWSER_PATH, FONTS_DIR_NAME);
64
- await mkdir(browserFontsPath, { recursive: true });
65
-
66
- const files = await readdir(browserFontsPath);
67
- const fontsToDownload = fontsList.filter(font => !files.includes(font));
68
-
69
- let promises = fontsToDownload.map(font => requestretry.get(FONTS_URL + font, {
70
- maxAttempts: 5,
71
- retryDelay: 2000,
72
- timeout: 30 * 1000,
73
- })
74
- .pipe(createWriteStream(join(browserFontsPath, font))),
75
- );
76
-
77
- if (promises.length) {
78
- await Promise.all(promises);
79
- }
80
-
81
- promises = fontsList.map((font) =>
82
- copyFile(join(browserFontsPath, font), join(profilePath, FONTS_DIR_NAME, font)));
83
-
84
- await Promise.all(promises);
85
- };
86
-
87
- export const composeFonts = async (fontsList = [], profilePath, differentOs = false) => {
88
- if (!(fontsList.length && profilePath)) {
89
- return;
90
- }
91
-
92
- const fontsToDownload = fontsCollection
93
- .filter(elem => fontsList.includes(elem.value))
94
- .reduce((res, elem) => res.concat(elem.fileNames || []), []);
95
-
96
- if (differentOs && !fontsToDownload.length) {
97
- throw new Error('No fonts to download found. Use getAvailableFonts() method and set some fonts from this list');
98
- }
99
-
100
- fontsToDownload.push('LICENSE.txt');
101
- fontsToDownload.push('OFL.txt');
102
-
103
- const pathToFontsDir = join(profilePath, FONTS_DIR_NAME);
104
- const fontsDirExists = await access(pathToFontsDir).then(() => true, () => false);
105
- if (fontsDirExists) {
106
- rmdirSync(pathToFontsDir, { recursive: true });
107
- }
108
-
109
- await mkdir(pathToFontsDir, { recursive: true });
110
- await downloadFonts(fontsToDownload, profilePath);
111
-
112
- if (OS_PLATFORM === 'linux') {
113
- await copyFontsConfigFile(profilePath);
114
- }
115
- };
116
-
117
- export const copyFontsConfigFile = async (profilePath) => {
118
- if (!profilePath) {
119
- return;
120
- }
121
-
122
- const fileContent = await readFile(resolve(__dirname, '..', '..', 'fonts_config'), 'utf-8');
123
- const result = fileContent.replace(/\$\$GOLOGIN_FONTS\$\$/g, join(profilePath, FONTS_DIR_NAME));
124
-
125
- const defaultFolderPath = join(profilePath, 'Default');
126
- await mkdir(defaultFolderPath, { recursive: true });
127
- await writeFile(join(defaultFolderPath, 'fonts_config'), result);
128
- };
129
-
130
- export const setExtPathsAndRemoveDeleted = (settings = {}, profileExtensionsCheckRes = [], profileId = '') => {
131
- const formattedLocalExtArray = profileExtensionsCheckRes.map((el) => {
132
- const [extFolderName = ''] = el.split(sep).reverse();
133
- const [originalId] = extFolderName.split('@');
134
- if (!originalId) {
135
- return null;
136
- }
137
-
138
- return {
139
- path: el,
140
- originalId,
141
- };
142
- }).filter(Boolean);
143
-
144
- const extensionsSettings = settings.extensions?.settings || {};
145
- const extensionsEntries = Object.entries(extensionsSettings);
146
-
147
- const promises = extensionsEntries.map(async (extensionObj) => {
148
- let [extensionId, currentExtSettings = {}] = extensionObj;
149
- const extName = currentExtSettings.manifest?.name || '';
150
- let extPath = currentExtSettings.path || '';
151
- let originalId = '';
152
-
153
- const isExtensionToBeDeleted = ['resources', 'passwords-ext', 'cookies-ext'].some(substring => extPath.includes(substring))
154
- && [GOLOGIN_BASE_FOLDER_NAME, GOLOGIN_TEST_FOLDER_NAME].some(substring => extPath.includes(substring))
155
- || DEFAULT_ORBITA_EXTENSIONS_NAMES.includes(extName)
156
- && [GOLOGIN_BASE_FOLDER_NAME, GOLOGIN_TEST_FOLDER_NAME].some(substring => extPath.includes(substring));
157
-
158
- if (isExtensionToBeDeleted) {
159
- delete extensionsSettings[extensionId];
160
-
161
- return;
162
- }
163
-
164
- if (osPlatform === 'win32') {
165
- extPath = extPath.replace(/\//g, '\\');
166
- } else {
167
- extPath = extPath.replace(/\\/g, '/');
168
- }
169
-
170
- extensionsSettings[extensionId].path = extPath;
171
-
172
- const splittedPath = extPath.split(sep);
173
- const isExtensionManageable = ['chrome-extensions', 'user-extensions'].some(substring => extPath.includes(substring))
174
- && [GOLOGIN_BASE_FOLDER_NAME, GOLOGIN_TEST_FOLDER_NAME].some(substring => extPath.includes(substring));
175
-
176
- if (isExtensionManageable) {
177
- const [extFolderName] = extPath.split(sep).reverse();
178
- [originalId] = extFolderName.split('@');
179
- } else if (splittedPath.length === 2) {
180
- [originalId] = splittedPath;
181
- }
182
-
183
- if (isExtensionManageable || splittedPath.length === 2) {
184
- const isExtensionInProfileSettings = formattedLocalExtArray.find(el => el.path.includes(originalId));
185
- if (!isExtensionInProfileSettings) {
186
- delete extensionsSettings[extensionId];
187
-
188
- return;
189
- }
190
- }
191
-
192
- const localExtObj = originalId && formattedLocalExtArray.find(el => el.path.includes(originalId));
193
- if (!localExtObj) {
194
- return;
195
- }
196
-
197
- const initialExtName = extensionId;
198
-
199
- extensionId = await recalculateId({
200
- localExtObj, extensionId, extensionsSettings, currentExtSettings,
201
- });
202
-
203
- if (initialExtName !== extensionId) {
204
- const profilePath = join(tmpdir(), `gologin_profile_${profileId}`);
205
- const extSyncFolder = join(profilePath, 'Default', 'Sync Extension Settings', initialExtName);
206
- const newSyncFolder = join(profilePath, 'Default', 'Sync Extension Settings', extensionId);
207
-
208
- await rename(extSyncFolder, newSyncFolder).catch(() => null);
209
- }
210
-
211
- if (localExtObj.path.endsWith('.zip')) {
212
- localExtObj.path = localExtObj.path.replace('.zip', '');
213
- }
214
-
215
- extensionsSettings[extensionId].path = localExtObj.path || '';
216
- });
217
-
218
- return Promise.all(promises).then(() => extensionsSettings);
219
- };
220
-
221
- export const setOriginalExtPaths = async (settings = {}, originalExtensionsFolder = '') => {
222
- if (!originalExtensionsFolder) {
223
- return null;
224
- }
225
-
226
- const extensionsSettings = settings.extensions?.settings || {};
227
- const extensionsEntries = Object.entries(extensionsSettings);
228
-
229
- const originalExtensionsList = await readdir(originalExtensionsFolder).catch(() => []);
230
- if (!originalExtensionsList.length) {
231
- return null;
232
- }
233
-
234
- const promises = originalExtensionsList.map(async (originalId) => {
235
- const extFolderPath = join(originalExtensionsFolder, originalId);
236
- const extFolderContent = await readdir(extFolderPath);
237
- if (!extFolderPath.length) {
238
- return {};
239
- }
240
-
241
- if (extFolderContent.includes('manifest.json')) {
242
- return {
243
- originalId,
244
- path: join(originalExtensionsFolder, originalId),
245
- };
246
- }
247
-
248
- const [version] = extFolderContent;
249
-
250
- return {
251
- originalId,
252
- path: join(originalExtensionsFolder, originalId, version),
253
- };
254
- });
255
-
256
- const originalExtPaths = await Promise.all(promises);
257
-
258
- extensionsEntries.forEach((extensionObj) => {
259
- const [extensionsId] = extensionObj;
260
- const extPath = extensionsSettings[extensionsId].path;
261
- if (!/chrome-extensions/.test(extPath)) {
262
- return;
263
- }
264
-
265
- const originalExtPath = originalExtPaths.find(el => el.originalId === extensionsId);
266
- if (!originalExtPath) {
267
- return;
268
- }
269
-
270
- extensionsSettings[extensionsId].path = originalExtPath.path || '';
271
- });
272
-
273
- return extensionsSettings;
274
- };
275
-
276
- export const recalculateId = async ({ localExtObj, extensionId, extensionsSettings, currentExtSettings }) => {
277
- if (currentExtSettings.manifest?.key) {
278
- return extensionId;
279
- }
280
-
281
- const manifestFilePath = join(localExtObj.path, 'manifest.json');
282
- const manifestString = await readFile(manifestFilePath, { encoding: 'utf8' }).catch(() => ({}));
283
-
284
- if (!manifestString) {
285
- return extensionId;
286
- }
287
-
288
- let manifestObject;
289
- try {
290
- manifestObject = JSON.parse(manifestString);
291
- } catch {
292
- return extensionId;
293
- }
294
-
295
- if (manifestObject.key) {
296
- return extensionId;
297
- }
298
-
299
- let encoding = 'utf8';
300
- if (osPlatform === 'win32') {
301
- encoding = 'utf16le';
302
- }
303
-
304
- const extPathToEncode = Buffer.from(localExtObj.path, encoding);
305
-
306
- const hexEncodedPath = createHash('sha256').update(extPathToEncode).digest('hex');
307
- const newId = hexEncodedPath.split('').slice(0, 32).map(symbol => extIdEncoding[symbol]).join('');
308
- if (extensionId !== newId) {
309
- delete extensionsSettings[extensionId];
310
-
311
- extensionsSettings[newId] = currentExtSettings;
312
- extensionId = newId;
313
- }
314
-
315
- return extensionId;
316
- };
317
-
318
- const extIdEncoding = {
319
- 0: 'a',
320
- 1: 'b',
321
- 2: 'c',
322
- 3: 'd',
323
- 4: 'e',
324
- 5: 'f',
325
- 6: 'g',
326
- 7: 'h',
327
- 8: 'i',
328
- 9: 'j',
329
- a: 'k',
330
- b: 'l',
331
- c: 'm',
332
- d: 'n',
333
- e: 'o',
334
- f: 'p',
335
- };
@@ -1,189 +0,0 @@
1
- import { open } from 'sqlite';
2
- import sqlite3 from 'sqlite3';
3
- import { promises as fsPromises } from 'fs';
4
- import { join } from 'path';
5
-
6
- const { access } = fsPromises;
7
- const { Database, OPEN_READONLY } = sqlite3;
8
-
9
- const MAX_SQLITE_VARIABLES = 76;
10
-
11
- const SAME_SITE = {
12
- '-1': 'unspecified',
13
- 0: 'no_restriction',
14
- 1: 'lax',
15
- 2: 'strict',
16
- };
17
-
18
- export const getDB = (filePath, readOnly = true) => {
19
- const connectionOpts = {
20
- filename: filePath,
21
- driver: Database,
22
- };
23
-
24
- if (readOnly) {
25
- connectionOpts.mode = OPEN_READONLY;
26
- }
27
-
28
- return open(connectionOpts);
29
- }
30
-
31
- export const getChunckedInsertValues = (cookiesArr) => {
32
- const todayUnix = Math.floor(new Date().getTime() / 1000.0);
33
- const chunckedCookiesArr = chunk(cookiesArr, MAX_SQLITE_VARIABLES);
34
-
35
- return chunckedCookiesArr.map((cookies) => {
36
- const queryPlaceholders = cookies.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').join(', ');
37
- 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}`;
38
- const queryParams = cookies.flatMap((cookie) => {
39
- const creationDate = cookie.creationDate ? cookie.creationDate : unixToLDAP(todayUnix);
40
- let expirationDate = cookie.session ? 0 : unixToLDAP(cookie.expirationDate);
41
- const encryptedValue = cookie.value;
42
- const samesite = Object.keys(SAME_SITE).find((key) => SAME_SITE[key] === (cookie.sameSite || '-1'));
43
- const isSecure =
44
- cookie.name.startsWith('__Host-') || cookie.name.startsWith('__Secure-') ? 1 : Number(cookie.secure);
45
-
46
- const sourceScheme = isSecure === 1 ? 2 : 1;
47
- const sourcePort = isSecure === 1 ? 443 : 80;
48
- // eslint-disable-next-line no-undefined
49
- let isPersistent = [undefined, null].includes(cookie.session)
50
- ? Number(expirationDate !== 0)
51
- : Number(!cookie.session);
52
-
53
- if (/^(\.)?mail.google.com$/.test(cookie.domain) && cookie.name === 'COMPASS') {
54
- expirationDate = 0;
55
- isPersistent = 0;
56
- }
57
-
58
- return [
59
- creationDate,
60
- cookie.domain,
61
- '', // top_frame_site_key
62
- cookie.name,
63
- '', // value
64
- encryptedValue,
65
- cookie.path,
66
- expirationDate,
67
- isSecure,
68
- Number(cookie.httpOnly),
69
- 0, // last_access_utc
70
- expirationDate === 0 ? 0 : 1, // has_expires
71
- isPersistent,
72
- 1, // default priority value (https://github.com/chromium/chromium/blob/main/net/cookies/cookie_constants.h)
73
- samesite,
74
- sourceScheme,
75
- sourcePort,
76
- 0, // is_same_party
77
- 0, // last_update_utc
78
- ];
79
- });
80
-
81
- return [query, queryParams];
82
- });
83
- }
84
-
85
- export const loadCookiesFromFile = async (filePath) => {
86
- let db;
87
- const cookies = [];
88
-
89
- try {
90
- db = await getDB(filePath);
91
- const cookiesRows = await db.all('select * from cookies');
92
- for (const row of cookiesRows) {
93
- const {
94
- host_key,
95
- name,
96
- encrypted_value,
97
- path,
98
- is_secure,
99
- is_httponly,
100
- expires_utc,
101
- is_persistent,
102
- samesite,
103
- creation_utc,
104
- } = row;
105
-
106
- cookies.push({
107
- url: buildCookieURL(host_key, is_secure, path),
108
- domain: host_key,
109
- name,
110
- value: encrypted_value,
111
- path,
112
- sameSite: SAME_SITE[samesite],
113
- secure: Boolean(is_secure),
114
- httpOnly: Boolean(is_httponly),
115
- hostOnly: !host_key.startsWith('.'),
116
- session: !is_persistent,
117
- expirationDate: ldapToUnix(expires_utc),
118
- creationDate: ldapToUnix(creation_utc),
119
- });
120
- }
121
- } catch (error) {
122
- console.log(error);
123
- } finally {
124
- db && await db.close();
125
- }
126
-
127
- return cookies;
128
- }
129
-
130
- export const unixToLDAP = (unixtime) => {
131
- if (unixtime === 0) {
132
- return unixtime;
133
- }
134
-
135
- const win32filetime = new Date(Date.UTC(1601, 0, 1)).getTime() / 1000;
136
- const sum = unixtime - win32filetime;
137
-
138
- return sum * 1000000;
139
- }
140
-
141
- export const ldapToUnix = (ldap) => {
142
- const ldapLength = ldap.toString().length;
143
- if (ldap === 0 || ldapLength > 18) {
144
- return ldap;
145
- }
146
-
147
- let _ldap = ldap;
148
- if (ldapLength < 18) {
149
- _ldap = Number(_ldap + '0'.repeat(18 - ldapLength));
150
- }
151
-
152
- const win32filetime = new Date(Date.UTC(1601, 0, 1)).getTime();
153
-
154
- return (_ldap / 10000 + win32filetime) / 1000;
155
- }
156
-
157
- export const buildCookieURL = (domain, secure, path) => {
158
- let domainWithoutDot = domain;
159
- if (domain.startsWith('.')) {
160
- domainWithoutDot = domain.substr(1);
161
- }
162
-
163
- return 'http' + (secure ? 's' : '') + '://' + domainWithoutDot + path;
164
- }
165
-
166
- export const chunk = (arr, chunkSize = 1, cache = []) => {
167
- const tmp = [...arr];
168
- if (chunkSize <= 0) {
169
- return cache;
170
- }
171
-
172
- while (tmp.length) {
173
- cache.push(tmp.splice(0, chunkSize));
174
- }
175
-
176
- return cache;
177
- }
178
-
179
- export const getCookiesFilePath = async (profileId, tmpdir) => {
180
- const baseCookiesFilePath = join(tmpdir, `gologin_profile_${profileId}`, 'Default', 'Cookies');
181
- const bypassCookiesFilePath = join(tmpdir, `gologin_profile_${profileId}`, 'Default', 'Network', 'Cookies');
182
-
183
- return access(baseCookiesFilePath)
184
- .then(() => baseCookiesFilePath)
185
- .catch(() => access(bypassCookiesFilePath)
186
- .then(() => bypassCookiesFilePath)
187
- .catch(() => baseCookiesFilePath)
188
- );
189
- }
@@ -1,56 +0,0 @@
1
- import decompress from 'decompress';
2
- import decompressUnzip from 'decompress-unzip';
3
- import { promises } from 'fs';
4
-
5
- const { access, unlink } = promises;
6
-
7
- export const extractExtension = (source, dest) => {
8
- if (!(source && dest)) {
9
- throw new Error('Missing parameter');
10
- }
11
-
12
- return access(source)
13
- .then(() =>
14
- withRetry({
15
- fn() {
16
- return decompress(source, dest, {
17
- plugins: [decompressUnzip()],
18
- filter: file => !file.path.endsWith('/'),
19
- });
20
- },
21
- }),
22
- );
23
- }
24
-
25
- export const deleteExtensionArchive = (dest) => {
26
- if (!dest) {
27
- throw new Error('Missing parameter');
28
- }
29
-
30
- return access(dest)
31
- .then(
32
- () => unlink(dest),
33
- () => Promise.resolve(),
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
-
44
- return fnToProducePromise(opts).catch(err => {
45
- console.error(err);
46
- if (callCounter >= callLimit) {
47
- return Promise.reject(err);
48
- }
49
-
50
- opts.callCounter = callCounter + 1;
51
-
52
- return new Promise(resolve => process.nextTick(resolve)).then(() =>
53
- withRetry(opts),
54
- );
55
- });
56
- };