cloak22 2.2.0
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/.githooks/pre-commit +12 -0
- package/.githooks/pre-push +37 -0
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/README.org +187 -0
- package/bin/cloak.js +24 -0
- package/dist/app-paths.js +26 -0
- package/dist/chrome-cookies.js +420 -0
- package/dist/chrome-profile-sites.js +155 -0
- package/dist/chrome-profiles.js +71 -0
- package/dist/cli.js +627 -0
- package/dist/cookies.js +95 -0
- package/dist/daemon.js +93 -0
- package/dist/extension.js +133 -0
- package/dist/install-extension.js +13 -0
- package/dist/main.js +688 -0
- package/dist/output.js +26 -0
- package/dist/state-db.js +232 -0
- package/docs/assets/cloak-logo-readme-centered.png +0 -0
- package/package.json +66 -0
- package/scripts/postinstall.cjs +55 -0
- package/scripts/render-readme.cjs +54 -0
- package/scripts/setup-git-hooks.cjs +21 -0
- package/src/app-paths.ts +39 -0
- package/src/chrome-cookies.ts +681 -0
- package/src/chrome-profile-sites.ts +274 -0
- package/src/chrome-profiles.ts +92 -0
- package/src/cli.ts +815 -0
- package/src/cookies.ts +149 -0
- package/src/daemon.ts +143 -0
- package/src/extension.ts +201 -0
- package/src/install-extension.ts +13 -0
- package/src/main.ts +1085 -0
- package/src/output.ts +21 -0
- package/src/state-db.ts +320 -0
package/dist/cookies.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normalizeCookie = normalizeCookie;
|
|
7
|
+
exports.parseCookieFile = parseCookieFile;
|
|
8
|
+
exports.readCookiesFromFile = readCookiesFromFile;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
function normalizeSameSite(sameSite) {
|
|
11
|
+
if (!sameSite)
|
|
12
|
+
return undefined;
|
|
13
|
+
switch (sameSite.toLowerCase()) {
|
|
14
|
+
case "strict":
|
|
15
|
+
return "Strict";
|
|
16
|
+
case "lax":
|
|
17
|
+
return "Lax";
|
|
18
|
+
case "none":
|
|
19
|
+
case "no_restriction":
|
|
20
|
+
return "None";
|
|
21
|
+
case "unspecified":
|
|
22
|
+
return undefined;
|
|
23
|
+
default:
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function normalizeCookie(raw) {
|
|
28
|
+
const cookie = {
|
|
29
|
+
name: raw.name,
|
|
30
|
+
value: raw.value,
|
|
31
|
+
domain: raw.domain,
|
|
32
|
+
path: raw.path,
|
|
33
|
+
};
|
|
34
|
+
if (typeof raw.httpOnly === "boolean") {
|
|
35
|
+
cookie.httpOnly = raw.httpOnly;
|
|
36
|
+
}
|
|
37
|
+
if (typeof raw.secure === "boolean") {
|
|
38
|
+
cookie.secure = raw.secure;
|
|
39
|
+
}
|
|
40
|
+
if ("expires" in raw && typeof raw.expires === "number") {
|
|
41
|
+
cookie.expires = Math.trunc(raw.expires);
|
|
42
|
+
}
|
|
43
|
+
else if ("expirationDate" in raw &&
|
|
44
|
+
typeof raw.expirationDate === "number") {
|
|
45
|
+
cookie.expires = Math.trunc(raw.expirationDate);
|
|
46
|
+
}
|
|
47
|
+
const sameSite = normalizeSameSite(raw.sameSite);
|
|
48
|
+
if (sameSite) {
|
|
49
|
+
cookie.sameSite = sameSite;
|
|
50
|
+
}
|
|
51
|
+
return cookie;
|
|
52
|
+
}
|
|
53
|
+
function isCookieEntry(value) {
|
|
54
|
+
return (Boolean(value) &&
|
|
55
|
+
typeof value === "object" &&
|
|
56
|
+
typeof value.name === "string" &&
|
|
57
|
+
typeof value.value === "string" &&
|
|
58
|
+
typeof value.domain === "string" &&
|
|
59
|
+
typeof value.path === "string");
|
|
60
|
+
}
|
|
61
|
+
function parseCookieFileContents(value, sourceLabel) {
|
|
62
|
+
let entries;
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
entries = value;
|
|
65
|
+
}
|
|
66
|
+
else if (value &&
|
|
67
|
+
typeof value === "object" &&
|
|
68
|
+
"cookies" in value &&
|
|
69
|
+
Array.isArray(value.cookies)) {
|
|
70
|
+
entries = value.cookies;
|
|
71
|
+
}
|
|
72
|
+
if (!entries) {
|
|
73
|
+
throw new Error(`${sourceLabel} must contain a JSON array of cookies or a JSON object with a cookies array`);
|
|
74
|
+
}
|
|
75
|
+
return entries.map((entry, index) => {
|
|
76
|
+
if (!isCookieEntry(entry)) {
|
|
77
|
+
throw new Error(`${sourceLabel} contains an invalid cookie at index ${index}`);
|
|
78
|
+
}
|
|
79
|
+
return entry;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function parseCookieFile(contents, sourceLabel = "cookie file") {
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = JSON.parse(contents);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
throw new Error(`${sourceLabel} must contain valid JSON`);
|
|
89
|
+
}
|
|
90
|
+
return parseCookieFileContents(parsed, sourceLabel).map(normalizeCookie);
|
|
91
|
+
}
|
|
92
|
+
function readCookiesFromFile(filePath, readFile = node_fs_1.default.readFileSync) {
|
|
93
|
+
const contents = readFile(filePath, "utf8");
|
|
94
|
+
return parseCookieFile(contents, filePath);
|
|
95
|
+
}
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildRunArguments = buildRunArguments;
|
|
7
|
+
exports.spawnDaemonProcess = spawnDaemonProcess;
|
|
8
|
+
exports.isProcessRunning = isProcessRunning;
|
|
9
|
+
exports.stopProcess = stopProcess;
|
|
10
|
+
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
function buildRunArguments(command) {
|
|
13
|
+
const args = ["run"];
|
|
14
|
+
if (!command.headless) {
|
|
15
|
+
args.push("--window");
|
|
16
|
+
}
|
|
17
|
+
if (command.profile) {
|
|
18
|
+
args.push("--profile", command.profile);
|
|
19
|
+
}
|
|
20
|
+
if (command.cookieFile) {
|
|
21
|
+
args.push("--cookie-file", command.cookieFile);
|
|
22
|
+
}
|
|
23
|
+
for (const url of command.cookieUrls) {
|
|
24
|
+
args.push("--cookie-url", url);
|
|
25
|
+
}
|
|
26
|
+
return args;
|
|
27
|
+
}
|
|
28
|
+
function spawnDaemonProcess(options) {
|
|
29
|
+
const spawnProcess = options.spawnProcess ?? node_child_process_1.default.spawn;
|
|
30
|
+
const openFile = options.openFile ?? node_fs_1.default.openSync;
|
|
31
|
+
const logFd = openFile(options.logPath, "a");
|
|
32
|
+
const child = spawnProcess(options.execPath, [
|
|
33
|
+
...options.execArgv,
|
|
34
|
+
options.scriptPath,
|
|
35
|
+
...buildRunArguments(options.command),
|
|
36
|
+
], {
|
|
37
|
+
detached: true,
|
|
38
|
+
stdio: ["ignore", logFd, logFd],
|
|
39
|
+
});
|
|
40
|
+
child.unref();
|
|
41
|
+
if (typeof child.pid !== "number") {
|
|
42
|
+
throw new Error("Failed to start cloak daemon.");
|
|
43
|
+
}
|
|
44
|
+
return child.pid;
|
|
45
|
+
}
|
|
46
|
+
function isProcessRunning(pid, killProcess = process.kill) {
|
|
47
|
+
try {
|
|
48
|
+
killProcess(pid, 0);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error &&
|
|
53
|
+
typeof error === "object" &&
|
|
54
|
+
"code" in error &&
|
|
55
|
+
error.code === "ESRCH") {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function stopProcess(pid, dependencies = {}) {
|
|
62
|
+
const killProcess = dependencies.killProcess ?? process.kill;
|
|
63
|
+
const processIsRunning = dependencies.isProcessRunning ?? ((candidatePid) => isProcessRunning(candidatePid, killProcess));
|
|
64
|
+
const sleep = dependencies.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
65
|
+
const timeoutMs = dependencies.timeoutMs ?? 5000;
|
|
66
|
+
const intervalMs = dependencies.intervalMs ?? 100;
|
|
67
|
+
if (!processIsRunning(pid)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
killProcess(pid, "SIGTERM");
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error &&
|
|
75
|
+
typeof error === "object" &&
|
|
76
|
+
"code" in error &&
|
|
77
|
+
error.code === "ESRCH") {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
const deadline = Date.now() + timeoutMs;
|
|
83
|
+
while (Date.now() < deadline) {
|
|
84
|
+
if (!processIsRunning(pid)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
await sleep(intervalMs);
|
|
88
|
+
}
|
|
89
|
+
if (processIsRunning(pid)) {
|
|
90
|
+
killProcess(pid, "SIGKILL");
|
|
91
|
+
}
|
|
92
|
+
return !processIsRunning(pid);
|
|
93
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.REQUIRED_EXTENSION_SHA256 = exports.REQUIRED_EXTENSION_ARCHIVE_NAME = exports.REQUIRED_EXTENSION_URL = void 0;
|
|
7
|
+
exports.validateExtensionArchive = validateExtensionArchive;
|
|
8
|
+
exports.downloadRequiredExtensionArchive = downloadRequiredExtensionArchive;
|
|
9
|
+
exports.ensureRequiredExtensionArchive = ensureRequiredExtensionArchive;
|
|
10
|
+
exports.prepareRequiredExtension = prepareRequiredExtension;
|
|
11
|
+
exports.installRequiredExtension = installRequiredExtension;
|
|
12
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const node_crypto_1 = require("node:crypto");
|
|
16
|
+
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
17
|
+
const app_paths_js_1 = require("./app-paths.js");
|
|
18
|
+
const output_js_1 = require("./output.js");
|
|
19
|
+
exports.REQUIRED_EXTENSION_URL = "https://github.com/jackwener/opencli/releases/download/v1.6.8/opencli-extension.zip";
|
|
20
|
+
exports.REQUIRED_EXTENSION_ARCHIVE_NAME = "opencli-extension.zip";
|
|
21
|
+
exports.REQUIRED_EXTENSION_SHA256 = "a5f51d111e49e7215191a80c2dc822d4ea94950c9e239dc077862a8044bc8d2e";
|
|
22
|
+
function validateExtensionArchive(archivePath) {
|
|
23
|
+
let zip;
|
|
24
|
+
try {
|
|
25
|
+
zip = new adm_zip_1.default(archivePath);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new Error(`Invalid required extension archive at ${archivePath}`, {
|
|
29
|
+
cause: error,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const entryNames = zip
|
|
33
|
+
.getEntries()
|
|
34
|
+
.filter((entry) => !entry.isDirectory)
|
|
35
|
+
.map((entry) => normalizeZipEntryName(entry.entryName));
|
|
36
|
+
if (entryNames.length === 0) {
|
|
37
|
+
throw new Error(`Required extension archive is empty: ${archivePath}`);
|
|
38
|
+
}
|
|
39
|
+
if (!findManifestEntry(entryNames)) {
|
|
40
|
+
throw new Error(`Required extension archive must contain manifest.json at the root or one directory deep: ${archivePath}`);
|
|
41
|
+
}
|
|
42
|
+
const archiveDigest = (0, node_crypto_1.createHash)("sha256")
|
|
43
|
+
.update(node_fs_1.default.readFileSync(archivePath))
|
|
44
|
+
.digest("hex");
|
|
45
|
+
if (archiveDigest !== exports.REQUIRED_EXTENSION_SHA256) {
|
|
46
|
+
throw new Error(`Required extension archive digest mismatch at ${archivePath}: expected ${exports.REQUIRED_EXTENSION_SHA256}, got ${archiveDigest}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function downloadRequiredExtensionArchive(extensionsDir, dependencies = {}) {
|
|
50
|
+
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
51
|
+
const validateArchive = dependencies.validateExtensionArchive ?? validateExtensionArchive;
|
|
52
|
+
const log = dependencies.log ?? ((message) => console.log((0, output_js_1.formatInfo)(message)));
|
|
53
|
+
const archivePath = node_path_1.default.join(extensionsDir, exports.REQUIRED_EXTENSION_ARCHIVE_NAME);
|
|
54
|
+
const tempArchivePath = node_path_1.default.join(extensionsDir, `${exports.REQUIRED_EXTENSION_ARCHIVE_NAME}.download-${process.pid}-${Date.now()}`);
|
|
55
|
+
node_fs_1.default.mkdirSync(extensionsDir, { recursive: true });
|
|
56
|
+
const response = await fetchImpl(exports.REQUIRED_EXTENSION_URL);
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(`Failed to download required extension: ${response.status} ${response.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
const archiveBody = Buffer.from(await response.arrayBuffer());
|
|
61
|
+
node_fs_1.default.writeFileSync(tempArchivePath, archiveBody);
|
|
62
|
+
try {
|
|
63
|
+
validateArchive(tempArchivePath);
|
|
64
|
+
node_fs_1.default.rmSync(archivePath, { force: true });
|
|
65
|
+
node_fs_1.default.renameSync(tempArchivePath, archivePath);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
node_fs_1.default.rmSync(tempArchivePath, { force: true });
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
log(`Prepared required extension archive at ${archivePath}`);
|
|
72
|
+
return archivePath;
|
|
73
|
+
}
|
|
74
|
+
async function ensureRequiredExtensionArchive(extensionsDir, dependencies = {}) {
|
|
75
|
+
const validateArchive = dependencies.validateExtensionArchive ?? validateExtensionArchive;
|
|
76
|
+
const downloadRequiredExtension = dependencies.downloadRequiredExtension ?? downloadRequiredExtensionArchive;
|
|
77
|
+
const log = dependencies.log ?? ((message) => console.log((0, output_js_1.formatInfo)(message)));
|
|
78
|
+
const archivePath = node_path_1.default.join(extensionsDir, exports.REQUIRED_EXTENSION_ARCHIVE_NAME);
|
|
79
|
+
if (node_fs_1.default.existsSync(archivePath)) {
|
|
80
|
+
try {
|
|
81
|
+
validateArchive(archivePath);
|
|
82
|
+
return archivePath;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
log(`Repairing required extension archive at ${archivePath}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
log(`Downloading required extension archive to ${archivePath}`);
|
|
90
|
+
}
|
|
91
|
+
const refreshedArchivePath = await downloadRequiredExtension(extensionsDir, {
|
|
92
|
+
validateExtensionArchive: validateArchive,
|
|
93
|
+
log,
|
|
94
|
+
});
|
|
95
|
+
validateArchive(refreshedArchivePath);
|
|
96
|
+
return refreshedArchivePath;
|
|
97
|
+
}
|
|
98
|
+
async function prepareRequiredExtension(extensionsDir, dependencies = {}) {
|
|
99
|
+
const ensureArchive = dependencies.ensureRequiredExtensionArchive ?? ensureRequiredExtensionArchive;
|
|
100
|
+
const makeTempDir = dependencies.makeTempDir ?? node_fs_1.default.mkdtempSync;
|
|
101
|
+
const archivePath = await ensureArchive(extensionsDir);
|
|
102
|
+
const tempDir = makeTempDir(node_path_1.default.join(node_os_1.default.tmpdir(), "cloak-ext-"));
|
|
103
|
+
new adm_zip_1.default(archivePath).extractAllTo(tempDir, true);
|
|
104
|
+
return resolveExtractedExtensionRoot(tempDir);
|
|
105
|
+
}
|
|
106
|
+
async function installRequiredExtension(extensionsDir = (0, app_paths_js_1.defaultAppRootDir)(), dependencies = {}) {
|
|
107
|
+
return ensureRequiredExtensionArchive(extensionsDir, dependencies);
|
|
108
|
+
}
|
|
109
|
+
function normalizeZipEntryName(entryName) {
|
|
110
|
+
return entryName.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
111
|
+
}
|
|
112
|
+
function findManifestEntry(entryNames) {
|
|
113
|
+
return entryNames.find((entryName) => {
|
|
114
|
+
if (entryName === "manifest.json") {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
const segments = entryName.split("/");
|
|
118
|
+
return segments.length === 2 && segments[1] === "manifest.json";
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function resolveExtractedExtensionRoot(extractedDir) {
|
|
122
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(extractedDir, "manifest.json"))) {
|
|
123
|
+
return extractedDir;
|
|
124
|
+
}
|
|
125
|
+
const nestedExtension = node_fs_1.default
|
|
126
|
+
.readdirSync(extractedDir, { withFileTypes: true })
|
|
127
|
+
.filter((entry) => entry.isDirectory())
|
|
128
|
+
.find((entry) => node_fs_1.default.existsSync(node_path_1.default.join(extractedDir, entry.name, "manifest.json")));
|
|
129
|
+
if (!nestedExtension) {
|
|
130
|
+
throw new Error(`Required extension archive did not extract a manifest.json: ${extractedDir}`);
|
|
131
|
+
}
|
|
132
|
+
return node_path_1.default.join(extractedDir, nestedExtension.name);
|
|
133
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const extension_js_1 = require("./extension.js");
|
|
5
|
+
const output_js_1 = require("./output.js");
|
|
6
|
+
async function run() {
|
|
7
|
+
const archivePath = await (0, extension_js_1.installRequiredExtension)();
|
|
8
|
+
console.log((0, output_js_1.formatSuccess)(`Required extension ready at ${archivePath}`));
|
|
9
|
+
}
|
|
10
|
+
run().catch((error) => {
|
|
11
|
+
console.error((0, output_js_1.formatError)(String(error)));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|