okx-onchain-console 0.1.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/COMPANION.md +42 -0
- package/README.md +240 -0
- package/companion.mjs +826 -0
- package/dist/assets/data-YT0JVIX1.js +1 -0
- package/dist/assets/sidepanel-Bl_Aq3uM.css +1 -0
- package/dist/background.js +1 -0
- package/dist/content.js +1 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-32.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/index.html +15 -0
- package/dist/manifest.json +33 -0
- package/dist/product-icon.png +0 -0
- package/dist/screenshots/console-overview.png +0 -0
- package/dist/sidepanel.js +164 -0
- package/dist-web/assets/sidepanel-Bl_Aq3uM.css +1 -0
- package/dist-web/icons/icon-128.png +0 -0
- package/dist-web/icons/icon-16.png +0 -0
- package/dist-web/icons/icon-32.png +0 -0
- package/dist-web/icons/icon-48.png +0 -0
- package/dist-web/index.html +14 -0
- package/dist-web/manifest.json +33 -0
- package/dist-web/product-icon.png +0 -0
- package/dist-web/screenshots/console-overview.png +0 -0
- package/dist-web/sidepanel.js +164 -0
- package/package.json +49 -0
- package/public/manifest.json +33 -0
- package/web.mjs +161 -0
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "okx-onchain-console",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local read-only OnchainOS console for your browser, with optional Chrome extension support",
|
|
5
|
+
"bin": {
|
|
6
|
+
"okx-onchain-console": "./web.mjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"web.mjs",
|
|
10
|
+
"companion.mjs",
|
|
11
|
+
"dist",
|
|
12
|
+
"dist-web",
|
|
13
|
+
"public/manifest.json",
|
|
14
|
+
"COMPANION.md"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.14.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"start": "npm run start:web --",
|
|
22
|
+
"start:web": "npm run build:web && node web.mjs",
|
|
23
|
+
"start:extension": "npm run build:extension && npm run companion:install && open -a 'Google Chrome' chrome://extensions https://www.okx.ai/_onchain-os-console",
|
|
24
|
+
"prepack": "npm run build",
|
|
25
|
+
"dev": "vite --host 127.0.0.1",
|
|
26
|
+
"companion:install": "node companion.mjs --install",
|
|
27
|
+
"companion:probe": "node companion.mjs --snapshot",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"build": "npm run build:web && npm run build:extension",
|
|
30
|
+
"build:web": "npm run typecheck && vite build --mode web",
|
|
31
|
+
"build:extension": "npm run typecheck && vite build && vite build --config vite.content.config.ts",
|
|
32
|
+
"test": "npm run build && vitest run",
|
|
33
|
+
"format": "prettier --write src test *.ts *.mjs *.json *.html *.md public/manifest.json"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"lucide-react": "^0.468.0",
|
|
37
|
+
"react": "^19.1.0",
|
|
38
|
+
"react-dom": "^19.1.0",
|
|
39
|
+
"@types/chrome": "^0.0.287",
|
|
40
|
+
"@types/node": "^22.0.0",
|
|
41
|
+
"@types/react": "^19.0.0",
|
|
42
|
+
"@types/react-dom": "^19.0.0",
|
|
43
|
+
"jsdom": "^26.1.0",
|
|
44
|
+
"prettier": "^3.9.6",
|
|
45
|
+
"typescript": "^5.8.0",
|
|
46
|
+
"vite": "^6.3.0",
|
|
47
|
+
"vitest": "^3.2.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Onchain OS Console",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0PLRnak4oaeN5zem/ZgobfvCEEvq3kvaAQtzcEMd8pS68UQ/j68wsSS0jUa3L2hmwRkBF19j4Qtr5FHr31ZmcgC268hf6Bn75ZfT+6rPGXx+MeDMo/EesdETcf3LuHKDaeqyAZivGu85O5YlP2aOAhEi4Z4HNHbBGcHd2xFK9cvxgPu/QlURBwINZbKa9wb1xp/ChKrAgL4OtHgJWlqPxVZGnjHiXkz21h1bq5kyECUGMWtLn3btXvfyXU/n7/WQmOE568HXGLL9E4okEU8JK+HfOcarAmKyIBBbCkSDpxVPCsV95hCZ+2es5SupK+D9NfnicXRgocqrsM76qH5AwIDAQAB",
|
|
6
|
+
"minimum_chrome_version": "116",
|
|
7
|
+
"description": "A local, read-only personal console for OnchainOS data inside OKX.AI.",
|
|
8
|
+
"icons": {
|
|
9
|
+
"16": "icons/icon-16.png",
|
|
10
|
+
"32": "icons/icon-32.png",
|
|
11
|
+
"48": "icons/icon-48.png",
|
|
12
|
+
"128": "icons/icon-128.png"
|
|
13
|
+
},
|
|
14
|
+
"permissions": ["nativeMessaging", "storage"],
|
|
15
|
+
"host_permissions": ["https://okx.ai/*", "https://www.okx.ai/*"],
|
|
16
|
+
"background": { "service_worker": "background.js", "type": "module" },
|
|
17
|
+
"web_accessible_resources": [
|
|
18
|
+
{
|
|
19
|
+
"resources": ["index.html"],
|
|
20
|
+
"matches": ["https://okx.ai/*", "https://www.okx.ai/*"]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"content_scripts": [
|
|
24
|
+
{
|
|
25
|
+
"matches": ["https://okx.ai/*", "https://www.okx.ai/*"],
|
|
26
|
+
"js": ["content.js"],
|
|
27
|
+
"run_at": "document_idle"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"content_security_policy": {
|
|
31
|
+
"extension_pages": "script-src 'self'; object-src 'none'; connect-src 'none'"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/web.mjs
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
5
|
+
import { dirname, join, extname, resolve, sep } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { snapshot } from "./companion.mjs";
|
|
9
|
+
|
|
10
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "dist-web");
|
|
11
|
+
const mime = {
|
|
12
|
+
".html": "text/html; charset=utf-8",
|
|
13
|
+
".js": "text/javascript; charset=utf-8",
|
|
14
|
+
".css": "text/css; charset=utf-8",
|
|
15
|
+
".png": "image/png",
|
|
16
|
+
".svg": "image/svg+xml",
|
|
17
|
+
".woff2": "font/woff2",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function startServer({ port = 0, getSnapshot = snapshot } = {}) {
|
|
21
|
+
const html = (await readFile(join(root, "index.html"), "utf8"))
|
|
22
|
+
.replace("<head>", '<head><meta name="onchain-mode" content="local-web">')
|
|
23
|
+
.replace("Onchain OS Console · 演示", "Onchain OS Console");
|
|
24
|
+
const token = randomBytes(32).toString("hex");
|
|
25
|
+
let origin;
|
|
26
|
+
let pending;
|
|
27
|
+
const server = createServer(async (req, res) => {
|
|
28
|
+
res.setHeader("Cache-Control", "no-store");
|
|
29
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
30
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
31
|
+
res.setHeader(
|
|
32
|
+
"Content-Security-Policy",
|
|
33
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",
|
|
34
|
+
);
|
|
35
|
+
const reply = (status, body, type = "application/json; charset=utf-8") => {
|
|
36
|
+
res.writeHead(status, { "Content-Type": type });
|
|
37
|
+
res.end(
|
|
38
|
+
typeof body === "object" && !Buffer.isBuffer(body)
|
|
39
|
+
? JSON.stringify(body)
|
|
40
|
+
: body,
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
if (
|
|
44
|
+
req.headers.host !== new URL(origin).host ||
|
|
45
|
+
(req.headers.origin && req.headers.origin !== origin) ||
|
|
46
|
+
req.headers["sec-fetch-site"] === "cross-site"
|
|
47
|
+
) {
|
|
48
|
+
return reply(403, { ok: false, error: "禁止跨站访问" });
|
|
49
|
+
}
|
|
50
|
+
if (req.method !== "GET")
|
|
51
|
+
return reply(405, { ok: false, error: "仅支持只读请求" });
|
|
52
|
+
try {
|
|
53
|
+
const url = new URL(req.url, origin);
|
|
54
|
+
if (url.pathname.startsWith("/api/")) {
|
|
55
|
+
if (req.headers.authorization !== `Bearer ${token}`)
|
|
56
|
+
return reply(401, {
|
|
57
|
+
ok: false,
|
|
58
|
+
error: "访问凭证已失效,请打开终端提供的完整地址。",
|
|
59
|
+
});
|
|
60
|
+
if (url.pathname !== "/api/snapshot" || url.search)
|
|
61
|
+
return reply(404, { ok: false, error: "不支持的请求" });
|
|
62
|
+
pending ??= Promise.resolve()
|
|
63
|
+
.then(getSnapshot)
|
|
64
|
+
.finally(() => {
|
|
65
|
+
pending = undefined;
|
|
66
|
+
});
|
|
67
|
+
return reply(200, { ok: true, data: await pending });
|
|
68
|
+
}
|
|
69
|
+
if (url.pathname === "/" || url.pathname === "/index.html")
|
|
70
|
+
return reply(200, html, mime[".html"]);
|
|
71
|
+
const pathname = decodeURIComponent(url.pathname);
|
|
72
|
+
if (
|
|
73
|
+
!/^\/(assets|icons)\//.test(pathname) &&
|
|
74
|
+
!["/product-icon.png", "/sidepanel.js"].includes(pathname)
|
|
75
|
+
)
|
|
76
|
+
return reply(404, "Not found", "text/plain");
|
|
77
|
+
const path = await realpath(resolve(root, `.${pathname}`));
|
|
78
|
+
if (!path.startsWith(root + sep) || !mime[extname(path)])
|
|
79
|
+
return reply(404, "Not found", "text/plain");
|
|
80
|
+
return reply(200, await readFile(path), mime[extname(path)]);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error.code === "ENOENT" || error instanceof URIError)
|
|
83
|
+
return reply(404, "Not found", "text/plain");
|
|
84
|
+
return reply(500, {
|
|
85
|
+
ok: false,
|
|
86
|
+
error: "本机数据查询失败,请检查 OnchainOS CLI 后重试。",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
await new Promise((resolve, reject) => {
|
|
91
|
+
server.once("error", reject);
|
|
92
|
+
server.listen(port, "127.0.0.1", resolve);
|
|
93
|
+
});
|
|
94
|
+
origin = `http://127.0.0.1:${server.address().port}`;
|
|
95
|
+
return { server, url: `${origin}/#token=${token}` };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function main() {
|
|
99
|
+
const args = process.argv.slice(2);
|
|
100
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
101
|
+
console.log(
|
|
102
|
+
"Usage: okx-onchain-console [--port 0-65535] [--no-open]\n默认自动选择空闲端口并打开浏览器。需要 Node.js 22.14+ 和已登录的 OnchainOS CLI。",
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
let port = 0;
|
|
107
|
+
let open = true;
|
|
108
|
+
for (let i = 0; i < args.length; i++) {
|
|
109
|
+
if (args[i] === "--no-open") open = false;
|
|
110
|
+
else if (args[i] === "--port" && /^\d+$/.test(args[i + 1] ?? "")) {
|
|
111
|
+
port = Number(args[++i]);
|
|
112
|
+
if (port > 65535) throw new Error("端口必须在 0–65535 之间");
|
|
113
|
+
} else throw new Error(`不支持的参数:${args[i]},使用 --help 查看帮助`);
|
|
114
|
+
}
|
|
115
|
+
const { server, url } = await startServer({ port });
|
|
116
|
+
console.log(
|
|
117
|
+
`Onchain OS Console\n${url}\n仅本机访问 · 只读模式 · Ctrl+C 停止服务`,
|
|
118
|
+
);
|
|
119
|
+
if (open) {
|
|
120
|
+
const command =
|
|
121
|
+
process.platform === "darwin"
|
|
122
|
+
? "open"
|
|
123
|
+
: process.platform === "win32"
|
|
124
|
+
? "rundll32.exe"
|
|
125
|
+
: "xdg-open";
|
|
126
|
+
const child = spawn(
|
|
127
|
+
command,
|
|
128
|
+
process.platform === "win32"
|
|
129
|
+
? ["url.dll,FileProtocolHandler", url]
|
|
130
|
+
: [url],
|
|
131
|
+
{ stdio: "ignore" },
|
|
132
|
+
);
|
|
133
|
+
child.on("error", () =>
|
|
134
|
+
console.error("无法自动打开浏览器,请手动打开上方地址。"),
|
|
135
|
+
);
|
|
136
|
+
child.on("exit", (code) => {
|
|
137
|
+
if (code) console.error("请手动打开上方地址。");
|
|
138
|
+
});
|
|
139
|
+
child.unref();
|
|
140
|
+
}
|
|
141
|
+
const stop = () => {
|
|
142
|
+
server.close();
|
|
143
|
+
server.closeAllConnections();
|
|
144
|
+
};
|
|
145
|
+
process.once("SIGINT", stop);
|
|
146
|
+
process.once("SIGTERM", stop);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
process.argv[1] &&
|
|
151
|
+
(await realpath(process.argv[1])) === fileURLToPath(import.meta.url)
|
|
152
|
+
) {
|
|
153
|
+
main().catch((error) => {
|
|
154
|
+
console.error(
|
|
155
|
+
error.code === "ENOENT"
|
|
156
|
+
? "缺少 Web 构建文件,请先运行 npm run build:web。"
|
|
157
|
+
: error.message,
|
|
158
|
+
);
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
});
|
|
161
|
+
}
|