gologin-commonjs 2.1.1
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/babel.config.json +17 -0
- package/dist/README.md +163 -0
- package/dist/example.js +35 -0
- package/dist/examples/example-amazon-cloud-browser.js +52 -0
- package/dist/examples/example-amazon-headless.js +56 -0
- package/dist/examples/example-amazon.js +53 -0
- package/dist/examples/example-create-custom-profile.js +42 -0
- package/dist/examples/example-create-profile.js +43 -0
- package/dist/examples/example-custom-args.js +34 -0
- package/dist/examples/example-fast-profile-settings.js +59 -0
- package/dist/examples/example-gmail.js +82 -0
- package/dist/examples/example-iphey.js +19 -0
- package/dist/examples/example-local-profile.js +28 -0
- package/dist/examples/example-login-walmart.js +38 -0
- package/dist/examples/example-startremote.js +29 -0
- package/dist/examples/example-stopremote.js +22 -0
- package/dist/examples/example-timezone.js +51 -0
- package/dist/fonts.js +3339 -0
- package/dist/fonts_config +104 -0
- package/dist/gologin-browser-ext.zip +0 -0
- package/dist/gologin_zeroprofile.b64 +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/profile_export_example.csv +2 -0
- package/dist/run.sh +1 -0
- package/dist/src/bookmarks/utils.js +23 -0
- package/dist/src/browser/browser-api.js +106 -0
- package/dist/src/browser/browser-checker.js +336 -0
- package/dist/src/browser/browser-user-data-manager.js +306 -0
- package/dist/src/cookies/cookies-manager.js +164 -0
- package/dist/src/extensions/extensions-extractor.js +50 -0
- package/dist/src/extensions/extensions-manager.js +301 -0
- package/dist/src/extensions/user-extensions-manager.js +246 -0
- package/dist/src/gologin-api.js +103 -0
- package/dist/src/gologin.js +1319 -0
- package/dist/src/profile/profile-archiver.js +68 -0
- package/dist/src/profile/profile-directories-to-remove.js +71 -0
- package/dist/src/utils/browser.js +59 -0
- package/dist/src/utils/common.js +60 -0
- package/dist/src/utils/constants.js +7 -0
- package/dist/src/utils/utils.js +53 -0
- package/dist/test.html +1 -0
- package/dist/zero_profile.zip +0 -0
- package/gologin/.eslintrc.json +290 -0
- package/gologin/.sentry-native/a65389b2-9a7d-41ed-7de5-95c4570f0d3d.run.lock +0 -0
- package/gologin/README.md +163 -0
- package/gologin/example.js +36 -0
- package/gologin/examples/example-amazon-cloud-browser.js +44 -0
- package/gologin/examples/example-amazon-headless.js +50 -0
- package/gologin/examples/example-amazon.js +47 -0
- package/gologin/examples/example-create-custom-profile.js +39 -0
- package/gologin/examples/example-create-profile.js +40 -0
- package/gologin/examples/example-custom-args.js +34 -0
- package/gologin/examples/example-fast-profile-settings.js +69 -0
- package/gologin/examples/example-gmail.js +67 -0
- package/gologin/examples/example-iphey.js +17 -0
- package/gologin/examples/example-local-profile.js +26 -0
- package/gologin/examples/example-login-walmart.js +35 -0
- package/gologin/examples/example-startremote.js +25 -0
- package/gologin/examples/example-stopremote.js +20 -0
- package/gologin/examples/example-timezone.js +44 -0
- package/gologin/fonts.js +3339 -0
- package/gologin/fonts_config +104 -0
- package/gologin/gologin-browser-ext.zip +0 -0
- package/gologin/gologin_zeroprofile.b64 +1 -0
- package/gologin/index.d.ts +61 -0
- package/gologin/package.json +49 -0
- package/gologin/profile_export_example.csv +2 -0
- package/gologin/run.sh +1 -0
- package/gologin/src/bookmarks/utils.js +16 -0
- package/gologin/src/browser/browser-api.js +95 -0
- package/gologin/src/browser/browser-checker.js +392 -0
- package/gologin/src/browser/browser-user-data-manager.js +335 -0
- package/gologin/src/cookies/cookies-manager.js +189 -0
- package/gologin/src/extensions/extensions-extractor.js +56 -0
- package/gologin/src/extensions/extensions-manager.js +384 -0
- package/gologin/src/extensions/user-extensions-manager.js +295 -0
- package/gologin/src/gologin-api.js +110 -0
- package/gologin/src/gologin.js +1553 -0
- package/gologin/src/profile/profile-archiver.js +86 -0
- package/gologin/src/profile/profile-directories-to-remove.js +75 -0
- package/gologin/src/utils/browser.js +62 -0
- package/gologin/src/utils/common.js +76 -0
- package/gologin/src/utils/constants.js +1 -0
- package/gologin/src/utils/utils.js +49 -0
- package/gologin/test.html +1 -0
- package/gologin/zero_profile.zip +0 -0
- package/package.json +46 -0
- package/tes.js +35 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.UserExtensionsManager = void 0;
|
|
7
|
+
var _fs = require("fs");
|
|
8
|
+
var _path = require("path");
|
|
9
|
+
var _requestretry = _interopRequireDefault(require("requestretry"));
|
|
10
|
+
var _common = require("../utils/common.js");
|
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
const {
|
|
13
|
+
readdir,
|
|
14
|
+
readFile,
|
|
15
|
+
stat,
|
|
16
|
+
mkdir,
|
|
17
|
+
copyFile
|
|
18
|
+
} = _fs.promises;
|
|
19
|
+
class UserExtensionsManager {
|
|
20
|
+
#existedUserExtensions = [];
|
|
21
|
+
#API_BASE_URL = '';
|
|
22
|
+
#ACCESS_TOKEN = '';
|
|
23
|
+
#USER_AGENT = '';
|
|
24
|
+
#TWO_FA_KEY = '';
|
|
25
|
+
set userAgent(userAgent) {
|
|
26
|
+
if (!userAgent) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.#USER_AGENT = userAgent;
|
|
30
|
+
}
|
|
31
|
+
set accessToken(accessToken) {
|
|
32
|
+
if (!accessToken) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.#ACCESS_TOKEN = accessToken;
|
|
36
|
+
}
|
|
37
|
+
set twoFaKey(twoFaKey) {
|
|
38
|
+
if (!twoFaKey) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.#TWO_FA_KEY = twoFaKey;
|
|
42
|
+
}
|
|
43
|
+
set apiUrl(apiUrl) {
|
|
44
|
+
if (!apiUrl) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.#API_BASE_URL = apiUrl;
|
|
48
|
+
}
|
|
49
|
+
get apiBaseUrl() {
|
|
50
|
+
return this.#API_BASE_URL;
|
|
51
|
+
}
|
|
52
|
+
get existedUserExtensions() {
|
|
53
|
+
return this.#existedUserExtensions;
|
|
54
|
+
}
|
|
55
|
+
get accessToken() {
|
|
56
|
+
return this.#ACCESS_TOKEN;
|
|
57
|
+
}
|
|
58
|
+
get twoFaKey() {
|
|
59
|
+
return this.#TWO_FA_KEY;
|
|
60
|
+
}
|
|
61
|
+
get userAgent() {
|
|
62
|
+
return this.#USER_AGENT;
|
|
63
|
+
}
|
|
64
|
+
set existedUserExtensions(fileList) {
|
|
65
|
+
if (!fileList) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.#existedUserExtensions = fileList;
|
|
69
|
+
}
|
|
70
|
+
checkLocalUserChromeExtensions = async (userChromeExtensions, profileId) => {
|
|
71
|
+
if (!userChromeExtensions.length) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const extensionsToDownloadPaths = (await _requestretry.default.post(`${this.#API_BASE_URL}/extensions/user_chrome_extensions_paths`, {
|
|
75
|
+
json: true,
|
|
76
|
+
fullResponse: false,
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${this.#ACCESS_TOKEN}`,
|
|
79
|
+
'user-agent': this.#USER_AGENT,
|
|
80
|
+
'x-two-factor-token': this.#TWO_FA_KEY || ''
|
|
81
|
+
},
|
|
82
|
+
body: {
|
|
83
|
+
existedUserChromeExtensions: this.#existedUserExtensions,
|
|
84
|
+
profileId,
|
|
85
|
+
userChromeExtensions
|
|
86
|
+
}
|
|
87
|
+
})) || [];
|
|
88
|
+
const extensionsToDownloadPathsFiltered = extensionsToDownloadPaths.filter(extPath => userChromeExtensions.some(extId => extPath.includes(extId)));
|
|
89
|
+
if (!extensionsToDownloadPathsFiltered.length) {
|
|
90
|
+
return this.getExtensionsStrToIncludeAsOrbitaParam(userChromeExtensions, _common.USER_EXTENSIONS_PATH);
|
|
91
|
+
}
|
|
92
|
+
const promises = extensionsToDownloadPathsFiltered.map(async awsPath => {
|
|
93
|
+
const [basePath] = awsPath.split('?');
|
|
94
|
+
const [extId] = basePath.split('/').reverse();
|
|
95
|
+
const zipPath = `${(0, _path.join)(_common.USER_EXTENSIONS_PATH, extId)}.zip`;
|
|
96
|
+
const archiveZip = (0, _fs.createWriteStream)(zipPath);
|
|
97
|
+
await (0, _requestretry.default)(awsPath, {
|
|
98
|
+
retryDelay: 2 * 1000,
|
|
99
|
+
maxAttempts: 3
|
|
100
|
+
}).pipe(archiveZip);
|
|
101
|
+
await new Promise(r => archiveZip.on('close', () => r()));
|
|
102
|
+
return zipPath;
|
|
103
|
+
});
|
|
104
|
+
const zipPaths = await Promise.all(promises).catch(() => []);
|
|
105
|
+
if (!zipPaths) {
|
|
106
|
+
return this.getExtensionsStrToIncludeAsOrbitaParam(userChromeExtensions, _common.USER_EXTENSIONS_PATH);
|
|
107
|
+
}
|
|
108
|
+
const extractionPromises = (0, _common.composeExtractionPromises)(zipPaths, _common.USER_EXTENSIONS_PATH);
|
|
109
|
+
const isExtensionsExtracted = await Promise.all(extractionPromises).catch(() => 'error');
|
|
110
|
+
if (isExtensionsExtracted !== 'error') {
|
|
111
|
+
const [downloadedFolders] = zipPaths.map(archivePath => archivePath.split(_path.sep).reverse());
|
|
112
|
+
this.#existedUserExtensions = [...this.#existedUserExtensions, ...downloadedFolders];
|
|
113
|
+
}
|
|
114
|
+
return this.getExtensionsStrToIncludeAsOrbitaParam(userChromeExtensions, _common.USER_EXTENSIONS_PATH);
|
|
115
|
+
};
|
|
116
|
+
async getExtensionsStrToIncludeAsOrbitaParam(profileExtensions = [], folderPath = _common.CHROME_EXTENSIONS_PATH) {
|
|
117
|
+
if (!(Array.isArray(profileExtensions) && profileExtensions.length)) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const folders = await readdir(folderPath).then(folderNames => folderNames.map(folderName => (0, _path.join)(folderPath, folderName)));
|
|
121
|
+
if (!folders.length) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const formattedIdsList = folders.map(el => {
|
|
125
|
+
const [folderName] = el.split(_path.sep).reverse();
|
|
126
|
+
const [originalId] = folderName.split('@');
|
|
127
|
+
return {
|
|
128
|
+
originalId,
|
|
129
|
+
path: el
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
return profileExtensions.map(el => {
|
|
133
|
+
const extExisted = formattedIdsList.find(chromeExtPathElem => chromeExtPathElem.originalId === el);
|
|
134
|
+
if (!extExisted) {
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
return extExisted.path;
|
|
138
|
+
}).filter(Boolean);
|
|
139
|
+
}
|
|
140
|
+
async getExtensionsNameAndImage(extensionsIds, pathToExtensions) {
|
|
141
|
+
const isCheckLocalFiles = [_common.CHROME_EXTENSIONS_PATH, _common.USER_EXTENSIONS_PATH].includes(pathToExtensions);
|
|
142
|
+
const extensionFolderNames = await readdir(pathToExtensions).catch(() => {});
|
|
143
|
+
const filteredExtensionFolderNames = extensionFolderNames.filter(extensionFolder => extensionsIds.some(extensionId => !extensionFolder.includes('.zip') && extensionFolder.includes(extensionId)));
|
|
144
|
+
if (!filteredExtensionFolderNames.length) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const namesPromise = extensionsIds.map(async extensionsId => {
|
|
148
|
+
const folderName = filteredExtensionFolderNames.find(folderName => folderName.includes(extensionsId));
|
|
149
|
+
if (!folderName) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
let pathToExtensionsFolder = [pathToExtensions, folderName];
|
|
153
|
+
if (!isCheckLocalFiles) {
|
|
154
|
+
const [extensionVersion] = await readdir((0, _path.join)(pathToExtensions, folderName));
|
|
155
|
+
pathToExtensionsFolder = [pathToExtensions, folderName, extensionVersion];
|
|
156
|
+
}
|
|
157
|
+
const manifestPath = (0, _path.join)(...pathToExtensionsFolder, 'manifest.json');
|
|
158
|
+
const manifestString = await readFile(manifestPath, 'utf8').catch(() => '');
|
|
159
|
+
if (!manifestString) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const manifestObject = JSON.parse(manifestString);
|
|
163
|
+
let name;
|
|
164
|
+
if (manifestObject.name.includes('__MSG')) {
|
|
165
|
+
const manifestName = manifestObject.name || '';
|
|
166
|
+
const fieldNameInLocale = manifestName.replace(/__/g, '').split('MSG_')[1];
|
|
167
|
+
const localePath = (0, _path.join)(...pathToExtensionsFolder, '_locales', manifestObject.default_locale, 'messages.json');
|
|
168
|
+
const localeString = await readFile(localePath, 'utf8').catch(() => {});
|
|
169
|
+
try {
|
|
170
|
+
const parsedLocale = JSON.parse(localeString.trim());
|
|
171
|
+
name = parsedLocale[fieldNameInLocale].message;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.log(e);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
name = manifestObject.name;
|
|
177
|
+
}
|
|
178
|
+
if (!name) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const iconObject = manifestObject.icons;
|
|
182
|
+
let iconPath = manifestObject.browser_action?.default_icon;
|
|
183
|
+
if (iconObject) {
|
|
184
|
+
iconPath = iconObject['128'];
|
|
185
|
+
}
|
|
186
|
+
let iconBSON = '';
|
|
187
|
+
if (iconPath) {
|
|
188
|
+
const iconPathFull = (0, _path.join)(...pathToExtensionsFolder, iconPath);
|
|
189
|
+
iconBSON = await readFile(iconPathFull, 'base64').catch(() => {});
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
name,
|
|
193
|
+
extId: extensionsId,
|
|
194
|
+
iconBinary: iconBSON
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
const extensionsArray = await Promise.all(namesPromise);
|
|
198
|
+
return extensionsArray.filter(Boolean);
|
|
199
|
+
}
|
|
200
|
+
generateExtensionId() {
|
|
201
|
+
let result = '';
|
|
202
|
+
let extensionIdLength = 32;
|
|
203
|
+
const characters = 'abcdefghijklmnopqrstuvwxyz';
|
|
204
|
+
const charactersLength = characters.length;
|
|
205
|
+
while (extensionIdLength--) {
|
|
206
|
+
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.UserExtensionsManager = UserExtensionsManager;
|
|
212
|
+
const checkFileSizeSync = async pathToFile => {
|
|
213
|
+
try {
|
|
214
|
+
const [fileName] = pathToFile.split(_path.sep).reverse();
|
|
215
|
+
if (fileName === '.DS_Store') {
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
const fileStats = await stat(pathToFile);
|
|
219
|
+
if (!fileStats.isDirectory()) {
|
|
220
|
+
return fileStats.size;
|
|
221
|
+
}
|
|
222
|
+
const files = await readdir(pathToFile);
|
|
223
|
+
const promises = files.map(async file => checkFileSizeSync((0, _path.join)(pathToFile, file)));
|
|
224
|
+
return (await Promise.all(promises)).reduce((result, value) => result + value, 0);
|
|
225
|
+
} catch {
|
|
226
|
+
return -1;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const copyFolder = async (fromPath, destPath) => {
|
|
230
|
+
const stats = await stat(fromPath);
|
|
231
|
+
if (!stats.isDirectory()) {
|
|
232
|
+
return copyFile(fromPath, destPath);
|
|
233
|
+
}
|
|
234
|
+
await mkdir(destPath, {
|
|
235
|
+
recursive: true
|
|
236
|
+
}).catch(() => null);
|
|
237
|
+
const files = await readdir(fromPath);
|
|
238
|
+
const promises = files.map(async file => {
|
|
239
|
+
await mkdir(destPath, {
|
|
240
|
+
recursive: true
|
|
241
|
+
}).catch(() => null);
|
|
242
|
+
return copyFolder((0, _path.join)(fromPath, file), (0, _path.join)(destPath, file));
|
|
243
|
+
});
|
|
244
|
+
return Promise.all(promises);
|
|
245
|
+
};
|
|
246
|
+
var _default = exports.default = UserExtensionsManager;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.GologinApi = GologinApi;
|
|
7
|
+
exports.delay = void 0;
|
|
8
|
+
exports.exitAll = exitAll;
|
|
9
|
+
exports.getDefaultParams = getDefaultParams;
|
|
10
|
+
var _puppeteerCore = _interopRequireDefault(require("puppeteer-core"));
|
|
11
|
+
var _gologin = _interopRequireDefault(require("./gologin.js"));
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
function getDefaultParams() {
|
|
14
|
+
return {
|
|
15
|
+
token: process.env.GOLOGIN_API_TOKEN,
|
|
16
|
+
profile_id: process.env.GOLOGIN_PROFILE_ID,
|
|
17
|
+
executablePath: process.env.GOLOGIN_EXECUTABLE_PATH,
|
|
18
|
+
autoUpdateBrowser: true
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const createLegacyGologin = ({
|
|
22
|
+
profileId,
|
|
23
|
+
...params
|
|
24
|
+
}) => {
|
|
25
|
+
const defaults = getDefaultParams();
|
|
26
|
+
const mergedParams = {
|
|
27
|
+
...defaults,
|
|
28
|
+
...params
|
|
29
|
+
};
|
|
30
|
+
mergedParams.profile_id = profileId ?? mergedParams.profile_id;
|
|
31
|
+
console.log({
|
|
32
|
+
mergedParams
|
|
33
|
+
});
|
|
34
|
+
return new _gologin.default(mergedParams);
|
|
35
|
+
};
|
|
36
|
+
const createdApis = [];
|
|
37
|
+
const delay = (ms = 250) => new Promise(res => setTimeout(res, ms));
|
|
38
|
+
exports.delay = delay;
|
|
39
|
+
function GologinApi({
|
|
40
|
+
token
|
|
41
|
+
}) {
|
|
42
|
+
if (!token) {
|
|
43
|
+
throw new Error('GoLogin API token is missing');
|
|
44
|
+
}
|
|
45
|
+
const browsers = [];
|
|
46
|
+
const legacyGls = [];
|
|
47
|
+
const launchLocal = async params => {
|
|
48
|
+
const legacyGologin = createLegacyGologin({
|
|
49
|
+
...params,
|
|
50
|
+
token
|
|
51
|
+
});
|
|
52
|
+
if (!params.profileId) {
|
|
53
|
+
const {
|
|
54
|
+
id
|
|
55
|
+
} = await legacyGologin.quickCreateProfile();
|
|
56
|
+
await legacyGologin.setProfileId(id);
|
|
57
|
+
}
|
|
58
|
+
const started = await legacyGologin.start();
|
|
59
|
+
const browser = await _puppeteerCore.default.connect({
|
|
60
|
+
browserWSEndpoint: started.wsUrl,
|
|
61
|
+
ignoreHTTPSErrors: true
|
|
62
|
+
});
|
|
63
|
+
browsers.push(browser);
|
|
64
|
+
legacyGls.push(legacyGologin);
|
|
65
|
+
return {
|
|
66
|
+
browser
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
const launchCloudProfile = async params => {
|
|
70
|
+
const profileParam = params.profileId ? `&profile=${params.profileId}` : '';
|
|
71
|
+
const geolocationParam = params.geolocation ? `&geolocation=${params.geolocation}` : '';
|
|
72
|
+
const browserWSEndpoint = `https://cloud.gologin.com/connect?token=${token}${profileParam}${geolocationParam}`;
|
|
73
|
+
const browser = await _puppeteerCore.default.connect({
|
|
74
|
+
browserWSEndpoint,
|
|
75
|
+
ignoreHTTPSErrors: true
|
|
76
|
+
});
|
|
77
|
+
browsers.push(browser);
|
|
78
|
+
return {
|
|
79
|
+
browser
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const api = {
|
|
83
|
+
async launch(params = {}) {
|
|
84
|
+
if (params.cloud) {
|
|
85
|
+
return launchCloudProfile(params);
|
|
86
|
+
}
|
|
87
|
+
return launchLocal(params);
|
|
88
|
+
},
|
|
89
|
+
async exit(status = 0) {
|
|
90
|
+
Promise.allSettled(browsers.map(browser => browser.close()));
|
|
91
|
+
Promise.allSettled(legacyGls.map(gl => gl.stopLocal({
|
|
92
|
+
posting: false
|
|
93
|
+
})));
|
|
94
|
+
process.exit(status);
|
|
95
|
+
},
|
|
96
|
+
delay
|
|
97
|
+
};
|
|
98
|
+
createdApis.push(api);
|
|
99
|
+
return api;
|
|
100
|
+
}
|
|
101
|
+
function exitAll() {
|
|
102
|
+
Promise.allSettled(createdApis.map(api => api.exit()));
|
|
103
|
+
}
|