befly-vscode 1.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/configs/obfuscator.config.json +32 -0
- package/configs/tsdown.config.js +23 -0
- package/configs/vite.config.js +54 -0
- package/package.json +37 -0
- package/scripts/release.mjs +32 -0
- package/utils/createHttp.js +106 -0
- package/utils/createReporter.js +91 -0
- package/utils/getVsCode.js +18 -0
- package/utils/reporterSetup.js +144 -0
- package/utils/webviewHtml.js +171 -0
- package/utils/webviewReporter.js +90 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"target": "node",
|
|
3
|
+
|
|
4
|
+
"compact": true,
|
|
5
|
+
"optionsPreset": "default",
|
|
6
|
+
|
|
7
|
+
"sourceMap": false,
|
|
8
|
+
|
|
9
|
+
"identifierNamesGenerator": "hexadecimal",
|
|
10
|
+
"renameGlobals": false,
|
|
11
|
+
"renameProperties": false,
|
|
12
|
+
"transformObjectKeys": false,
|
|
13
|
+
|
|
14
|
+
"ignoreImports": true,
|
|
15
|
+
|
|
16
|
+
"stringArray": true,
|
|
17
|
+
"stringArrayCallsTransform": true,
|
|
18
|
+
"stringArrayRotate": true,
|
|
19
|
+
"stringArrayShuffle": true,
|
|
20
|
+
"splitStrings": true,
|
|
21
|
+
"splitStringsChunkLength": 8,
|
|
22
|
+
"stringArrayEncoding": ["base64"],
|
|
23
|
+
"stringArrayThreshold": 1,
|
|
24
|
+
|
|
25
|
+
"controlFlowFlattening": false,
|
|
26
|
+
"deadCodeInjection": false,
|
|
27
|
+
"selfDefending": false,
|
|
28
|
+
"debugProtection": false,
|
|
29
|
+
"disableConsoleOutput": false,
|
|
30
|
+
|
|
31
|
+
"reservedNames": ["^activate$", "^deactivate$"]
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { defineConfig } from "tsdown";
|
|
4
|
+
|
|
5
|
+
const pkgDir = process.cwd();
|
|
6
|
+
|
|
7
|
+
export default defineConfig(() => ({
|
|
8
|
+
platform: "node",
|
|
9
|
+
target: "node22",
|
|
10
|
+
format: "esm",
|
|
11
|
+
fixedExtension: true,
|
|
12
|
+
clean: true,
|
|
13
|
+
sourcemap: false,
|
|
14
|
+
dts: false,
|
|
15
|
+
deps: {
|
|
16
|
+
onlyBundle: false,
|
|
17
|
+
neverBundle: ["vscode"]
|
|
18
|
+
},
|
|
19
|
+
entry: {
|
|
20
|
+
extension: path.join(pkgDir, "extension", "extension.js")
|
|
21
|
+
},
|
|
22
|
+
outDir: path.join(pkgDir, "dist")
|
|
23
|
+
}));
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import vue from "@vitejs/plugin-vue";
|
|
5
|
+
import analyzer from "vite-bundle-analyzer";
|
|
6
|
+
|
|
7
|
+
const pkgDir = process.cwd();
|
|
8
|
+
const webviewRoot = path.join(pkgDir, "webview");
|
|
9
|
+
const outDir = path.join(pkgDir, "dist");
|
|
10
|
+
const publicDirCandidate = path.join(pkgDir, "public");
|
|
11
|
+
const publicDir = fs.existsSync(publicDirCandidate) ? publicDirCandidate : undefined;
|
|
12
|
+
const isBuild = process.env.NODE_ENV === "production";
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
plugins: [
|
|
16
|
+
vue(),
|
|
17
|
+
analyzer({
|
|
18
|
+
enabled: isBuild,
|
|
19
|
+
analyzerMode: "static",
|
|
20
|
+
openAnalyzer: false,
|
|
21
|
+
fileName: "bundle-analyzer.html",
|
|
22
|
+
reportTitle: "WebView Bundle",
|
|
23
|
+
defaultSizes: "gzip"
|
|
24
|
+
})
|
|
25
|
+
],
|
|
26
|
+
root: webviewRoot,
|
|
27
|
+
base: "./",
|
|
28
|
+
publicDir: publicDir,
|
|
29
|
+
resolve: {
|
|
30
|
+
alias: {
|
|
31
|
+
"@": webviewRoot
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
server: {
|
|
35
|
+
port: 5200,
|
|
36
|
+
origin: "http://localhost:5200",
|
|
37
|
+
cors: true,
|
|
38
|
+
hmr: {
|
|
39
|
+
host: "localhost"
|
|
40
|
+
},
|
|
41
|
+
fs: {
|
|
42
|
+
allow: [webviewRoot, pkgDir]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
build: {
|
|
46
|
+
outDir: outDir,
|
|
47
|
+
emptyOutDir: false,
|
|
48
|
+
assetsDir: "assets",
|
|
49
|
+
sourcemap: false
|
|
50
|
+
},
|
|
51
|
+
define: {
|
|
52
|
+
"process.env": {}
|
|
53
|
+
}
|
|
54
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "befly-vscode",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Befly VSCode - VSCode 扩展构建工具集",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vscode-release": "scripts/release.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"utils/",
|
|
11
|
+
"configs/",
|
|
12
|
+
"scripts/",
|
|
13
|
+
"package.json"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
"./*": {
|
|
18
|
+
"import": "./*.js",
|
|
19
|
+
"default": "./*.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@vitejs/plugin-vue": "^6.0.7",
|
|
24
|
+
"@vscode/vsce": "^3.9.2",
|
|
25
|
+
"@vue/compiler-sfc": "^3.5.35",
|
|
26
|
+
"bumpp": "^11.1.0",
|
|
27
|
+
"javascript-obfuscator": "^5.4.3",
|
|
28
|
+
"sass-embedded": "^1.100.0",
|
|
29
|
+
"tsdown": "^0.22.2",
|
|
30
|
+
"vite": "^8.0.16",
|
|
31
|
+
"vite-bundle-analyzer": "^1.3.8",
|
|
32
|
+
"vue": "^3.5.35"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"bun": ">=1.3.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
const type = process.argv[2];
|
|
6
|
+
const allowed = ["major", "minor", "patch"];
|
|
7
|
+
|
|
8
|
+
if (!allowed.includes(type)) {
|
|
9
|
+
console.error("用法: vscode-release <major|minor|patch>");
|
|
10
|
+
console.error(" major (rx) - 大版本");
|
|
11
|
+
console.error(" minor (ry) - 中版本");
|
|
12
|
+
console.error(" patch (rz) - 小版本");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function run(cmd, args) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const child = spawn(cmd, args, { stdio: "inherit", env: process.env });
|
|
19
|
+
child.on("error", reject);
|
|
20
|
+
child.on("exit", (code) => {
|
|
21
|
+
if (code === 0) {
|
|
22
|
+
resolve();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
reject(new Error(`命令执行失败: ${cmd} ${args.join(" ")}`));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await run("bunx", ["javascript-obfuscator", "./dist/extension.mjs", "--output", "./dist/extension.mjs", "--config", "node_modules/befly-vscode/configs/obfuscator.config.json"]);
|
|
31
|
+
|
|
32
|
+
await run("bunx", ["@vscode/vsce", "publish", type, "--no-dependencies", "--no-git-tag-version", "--allow-missing-repository", "--allow-unused-files-pattern", "--skip-license", "--skip-duplicate", "--baseImagesUrl", ".", "--baseContentUrl", "."]);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export function createHttp(options) {
|
|
2
|
+
const request = async function (url, data, dropValues = [], dropKeyValue = {}) {
|
|
3
|
+
try {
|
|
4
|
+
const fullUrl = /^https?:\/\//i.test(url) ? url : `${options.apiPath}${url}`;
|
|
5
|
+
const isForm = typeof FormData !== "undefined" && data instanceof FormData;
|
|
6
|
+
const dropList = [null, undefined, ...dropValues];
|
|
7
|
+
const dropKeyMap = {
|
|
8
|
+
...dropKeyValue,
|
|
9
|
+
keyword: [""]
|
|
10
|
+
};
|
|
11
|
+
let payloadData = data ?? {};
|
|
12
|
+
if (isForm) {
|
|
13
|
+
const nextForm = new FormData();
|
|
14
|
+
for (const entry of payloadData.entries()) {
|
|
15
|
+
const key = entry[0];
|
|
16
|
+
const value = entry[1];
|
|
17
|
+
let shouldDrop = false;
|
|
18
|
+
for (const dropValue of dropList) {
|
|
19
|
+
if (Object.is(value, dropValue)) {
|
|
20
|
+
shouldDrop = true;
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!shouldDrop) {
|
|
25
|
+
for (const dropValue of dropKeyMap[key] || []) {
|
|
26
|
+
if (Object.is(value, dropValue)) {
|
|
27
|
+
shouldDrop = true;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!shouldDrop) {
|
|
33
|
+
nextForm.append(key, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
payloadData = nextForm;
|
|
37
|
+
} else if (payloadData && typeof payloadData === "object") {
|
|
38
|
+
const nextData = {};
|
|
39
|
+
for (const key of Object.keys(payloadData)) {
|
|
40
|
+
const value = payloadData[key];
|
|
41
|
+
let shouldDrop = false;
|
|
42
|
+
for (const dropValue of dropList) {
|
|
43
|
+
if (Object.is(value, dropValue)) {
|
|
44
|
+
shouldDrop = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!shouldDrop) {
|
|
49
|
+
for (const dropValue of dropKeyMap[key] || []) {
|
|
50
|
+
if (Object.is(value, dropValue)) {
|
|
51
|
+
shouldDrop = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!shouldDrop) {
|
|
57
|
+
nextData[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
payloadData = nextData;
|
|
61
|
+
}
|
|
62
|
+
const headers = new Headers();
|
|
63
|
+
if (!isForm) {
|
|
64
|
+
headers.set("Content-Type", "application/json");
|
|
65
|
+
}
|
|
66
|
+
const tokenValue = options.getToken();
|
|
67
|
+
if (tokenValue) {
|
|
68
|
+
headers.set("Authorization", "Bearer " + tokenValue);
|
|
69
|
+
}
|
|
70
|
+
const init = {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: headers,
|
|
73
|
+
body: isForm ? payloadData : JSON.stringify(payloadData ?? {})
|
|
74
|
+
};
|
|
75
|
+
const res = await fetch(fullUrl, init);
|
|
76
|
+
let payload = {};
|
|
77
|
+
try {
|
|
78
|
+
payload = await res.json();
|
|
79
|
+
} catch {
|
|
80
|
+
if (res.ok) {
|
|
81
|
+
return Promise.reject({ msg: "响应解析失败" });
|
|
82
|
+
}
|
|
83
|
+
payload = {
|
|
84
|
+
code: res.status,
|
|
85
|
+
msg: `请求失败:HTTP ${res.status}`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
payload = await options.onReturn(payload);
|
|
89
|
+
if (payload?.code !== 0) {
|
|
90
|
+
return Promise.reject({
|
|
91
|
+
msg: payload.msg ?? "请求失败",
|
|
92
|
+
code: payload.code,
|
|
93
|
+
detail: payload.detail
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return Promise.resolve(payload);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error && typeof error === "object") {
|
|
99
|
+
return Promise.reject(error);
|
|
100
|
+
}
|
|
101
|
+
return Promise.reject({ msg: "网络连接失败" });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return request;
|
|
106
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createHttp } from "./createHttp.js";
|
|
2
|
+
|
|
3
|
+
const reportRequest = createHttp({
|
|
4
|
+
apiPath: "https://api.yicode.tech/api",
|
|
5
|
+
getToken: () => ""
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const ERROR_REPORT_DEDUP_GAP = 3000;
|
|
9
|
+
|
|
10
|
+
function normalizeDetail(detail) {
|
|
11
|
+
if (detail === null || typeof detail === "undefined") return "";
|
|
12
|
+
if (typeof detail === "string") {
|
|
13
|
+
return detail;
|
|
14
|
+
}
|
|
15
|
+
if (detail instanceof Error) {
|
|
16
|
+
return detail.stack || detail.message || String(detail);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.stringify(detail);
|
|
20
|
+
} catch {
|
|
21
|
+
return String(detail);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createBasePayload(options = {}) {
|
|
26
|
+
return {
|
|
27
|
+
source: options.source || "vscode",
|
|
28
|
+
productName: options.productName,
|
|
29
|
+
productCode: options.productCode,
|
|
30
|
+
productVersion: options.productVersion,
|
|
31
|
+
pagePath: options.pagePath,
|
|
32
|
+
pageName: options.pageName
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mergePagePayload(basePayload, page = {}) {
|
|
37
|
+
return {
|
|
38
|
+
...basePayload,
|
|
39
|
+
pagePath: page.pagePath ?? basePayload.pagePath,
|
|
40
|
+
pageName: page.pageName ?? basePayload.pageName
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function postReport(path, payload) {
|
|
45
|
+
try {
|
|
46
|
+
await reportRequest(path, payload, ["", null, undefined]);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createReporter(options = {}) {
|
|
54
|
+
const basePayload = createBasePayload(options);
|
|
55
|
+
if (!basePayload.productCode) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const requestPost = options.requestPost || postReport;
|
|
60
|
+
const fixedErrorType = basePayload.productCode || "extension";
|
|
61
|
+
let lastErrorKey = "";
|
|
62
|
+
let lastErrorTime = 0;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
reportInfo: function (page = {}) {
|
|
66
|
+
return requestPost("/core/tongJi/infoReport", mergePagePayload(basePayload, page));
|
|
67
|
+
},
|
|
68
|
+
reportError: function (input = {}, page = {}) {
|
|
69
|
+
const message = input.message || "未知错误";
|
|
70
|
+
const detail = normalizeDetail(input.detail);
|
|
71
|
+
const errorKey = `${fixedErrorType}|${message}|${detail}`;
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
if (errorKey === lastErrorKey && now - lastErrorTime < ERROR_REPORT_DEDUP_GAP) {
|
|
74
|
+
return Promise.resolve(false);
|
|
75
|
+
}
|
|
76
|
+
lastErrorKey = errorKey;
|
|
77
|
+
lastErrorTime = now;
|
|
78
|
+
return requestPost("/core/tongJi/errorReport", {
|
|
79
|
+
...mergePagePayload(basePayload, page),
|
|
80
|
+
errorType: input.errorType || fixedErrorType,
|
|
81
|
+
message: message,
|
|
82
|
+
detail: detail
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
reportOnline: function (clientId, page = {}) {
|
|
86
|
+
const payload = mergePagePayload(basePayload, page);
|
|
87
|
+
payload.clientId = clientId || "";
|
|
88
|
+
return requestPost("/core/tongJi/onlineReport", payload);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function getVsCode() {
|
|
2
|
+
const cacheKey = "__YICODE_VSCODE_API";
|
|
3
|
+
const cached = globalThis[cacheKey];
|
|
4
|
+
if (cached) {
|
|
5
|
+
return cached;
|
|
6
|
+
}
|
|
7
|
+
const acquire = globalThis.acquireVsCodeApi;
|
|
8
|
+
if (typeof acquire !== "function") {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const api = acquire();
|
|
13
|
+
globalThis[cacheKey] = api;
|
|
14
|
+
return api;
|
|
15
|
+
} catch {
|
|
16
|
+
return globalThis[cacheKey] ?? null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createReporter } from "./createReporter.js";
|
|
2
|
+
|
|
3
|
+
function getPackageMeta(context) {
|
|
4
|
+
const packageJson = context.extension.packageJSON;
|
|
5
|
+
const productCode = packageJson.name;
|
|
6
|
+
if (!productCode) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
productName: packageJson.displayName || packageJson.name,
|
|
11
|
+
productCode: productCode,
|
|
12
|
+
productVersion: packageJson.version
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createExtensionReporter(context, options = {}) {
|
|
17
|
+
const packageMeta = getPackageMeta(context);
|
|
18
|
+
if (!packageMeta) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const productCode = options.productCode || packageMeta.productCode;
|
|
22
|
+
const pageName = productCode;
|
|
23
|
+
return createReporter({
|
|
24
|
+
...options,
|
|
25
|
+
source: options.source || "vscode",
|
|
26
|
+
productName: options.productName || packageMeta.productName,
|
|
27
|
+
productCode: productCode,
|
|
28
|
+
productVersion: options.productVersion || packageMeta.productVersion,
|
|
29
|
+
pagePath: `/${pageName}`,
|
|
30
|
+
pageName: pageName
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ONLINE_REPORT_HEARTBEAT_INTERVAL = 60 * 1000;
|
|
35
|
+
const ONLINE_REPORT_CLIENT_ID_KEY = "befly-vscode-online-client-id";
|
|
36
|
+
|
|
37
|
+
function generateClientId() {
|
|
38
|
+
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readClientId(context) {
|
|
42
|
+
try {
|
|
43
|
+
const stored = context.globalState.get(ONLINE_REPORT_CLIENT_ID_KEY);
|
|
44
|
+
if (stored) {
|
|
45
|
+
return String(stored);
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// ignore
|
|
49
|
+
}
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeClientId(context, clientId) {
|
|
54
|
+
try {
|
|
55
|
+
context.globalState.update(ONLINE_REPORT_CLIENT_ID_KEY, clientId);
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getClientId(context) {
|
|
62
|
+
const stored = readClientId(context);
|
|
63
|
+
if (stored) {
|
|
64
|
+
return stored;
|
|
65
|
+
}
|
|
66
|
+
const clientId = generateClientId();
|
|
67
|
+
writeClientId(context, clientId);
|
|
68
|
+
return clientId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function startReportHeartbeat(reporter, context) {
|
|
72
|
+
if (!reporter || !reporter.reportOnline) {
|
|
73
|
+
return () => {};
|
|
74
|
+
}
|
|
75
|
+
const clientId = getClientId(context);
|
|
76
|
+
|
|
77
|
+
const timer = setInterval(() => {
|
|
78
|
+
void reporter.reportOnline(clientId);
|
|
79
|
+
}, ONLINE_REPORT_HEARTBEAT_INTERVAL);
|
|
80
|
+
|
|
81
|
+
void reporter.reportOnline(clientId);
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
clearInterval(timer);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function setupWebviewMessageReporter(panel, reporter) {
|
|
89
|
+
if (!panel || !reporter) {
|
|
90
|
+
return () => {};
|
|
91
|
+
}
|
|
92
|
+
const disposable = panel.webview.onDidReceiveMessage((message) => {
|
|
93
|
+
if (!message || typeof message !== "object") {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (message.command === "reportPageView") {
|
|
97
|
+
void reporter.reportInfo({
|
|
98
|
+
pagePath: message.pagePath,
|
|
99
|
+
pageName: message.pageName
|
|
100
|
+
});
|
|
101
|
+
} else if (message.command === "reportError") {
|
|
102
|
+
void reporter.reportError(
|
|
103
|
+
{
|
|
104
|
+
message: message.message,
|
|
105
|
+
detail: message.detail,
|
|
106
|
+
errorType: message.errorType
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
pagePath: message.pagePath,
|
|
110
|
+
pageName: message.pageName
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return () => {
|
|
116
|
+
disposable.dispose();
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function setupExtensionErrorHandlers(reporter) {
|
|
121
|
+
if (!reporter) {
|
|
122
|
+
return () => {};
|
|
123
|
+
}
|
|
124
|
+
const handleError = (error) => {
|
|
125
|
+
void reporter.reportError({
|
|
126
|
+
message: error?.message || "扩展未捕获异常",
|
|
127
|
+
detail: error?.stack || String(error),
|
|
128
|
+
errorType: "extension"
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
const handleRejection = (reason) => {
|
|
132
|
+
void reporter.reportError({
|
|
133
|
+
message: reason?.message || "扩展未处理 Promise 拒绝",
|
|
134
|
+
detail: reason?.stack || String(reason),
|
|
135
|
+
errorType: "extension"
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
process.on("uncaughtException", handleError);
|
|
139
|
+
process.on("unhandledRejection", handleRejection);
|
|
140
|
+
return () => {
|
|
141
|
+
process.off("uncaughtException", handleError);
|
|
142
|
+
process.off("unhandledRejection", handleRejection);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
import * as vscode from "vscode";
|
|
4
|
+
|
|
5
|
+
const DEV_SERVER_ORIGIN = "http://localhost:5200";
|
|
6
|
+
const DEV_SERVER_WS_ORIGIN = "ws://localhost:5200";
|
|
7
|
+
|
|
8
|
+
export function createBootstrapJs(args) {
|
|
9
|
+
const devServerOrigin = args.isDev ? args.httpOrigin : "";
|
|
10
|
+
const bootstrapValues = {
|
|
11
|
+
__YICODE_IS_DEV: args.isDev === true,
|
|
12
|
+
__YICODE_MODE: args.mode || "",
|
|
13
|
+
__YICODE_PAGE_NAME: args.pageName || "main",
|
|
14
|
+
__YICODE_PAGE_PATH: args.pagePath || "/main",
|
|
15
|
+
__YICODE_DEV_SERVER_ORIGIN: devServerOrigin,
|
|
16
|
+
__YICODE_PRODUCT_NAME: args.productName || "",
|
|
17
|
+
__YICODE_PRODUCT_CODE: args.productCode || "",
|
|
18
|
+
__YICODE_PRODUCT_VERSION: args.productVersion || ""
|
|
19
|
+
};
|
|
20
|
+
return Object.entries(bootstrapValues)
|
|
21
|
+
.map(([key, value]) => `globalThis.${key}=${JSON.stringify(value)};`)
|
|
22
|
+
.join("");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getPackageMeta(context) {
|
|
26
|
+
const packageJson = context.extension.packageJSON;
|
|
27
|
+
return {
|
|
28
|
+
productName: packageJson.displayName || packageJson.name || "",
|
|
29
|
+
productCode: packageJson.name || "",
|
|
30
|
+
productVersion: packageJson.version || ""
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isEnvEnabled(name) {
|
|
35
|
+
const raw = process.env[name];
|
|
36
|
+
if (!raw) return false;
|
|
37
|
+
const v = raw.toLowerCase();
|
|
38
|
+
return v === "1" || v === "true" || v === "yes";
|
|
39
|
+
}
|
|
40
|
+
export function createNonce() {
|
|
41
|
+
const timestamp = Date.now();
|
|
42
|
+
const random6 = Math.floor(Math.random() * 1000000)
|
|
43
|
+
.toString()
|
|
44
|
+
.padStart(6, "0");
|
|
45
|
+
return `${timestamp}${random6}`;
|
|
46
|
+
}
|
|
47
|
+
export function createCsp(mode, args) {
|
|
48
|
+
const common = {
|
|
49
|
+
defaultSrc: "default-src 'none'",
|
|
50
|
+
imgSrc: `img-src ${args.cspSource} https: http: data: blob:`,
|
|
51
|
+
styleSrc: `style-src ${args.cspSource} 'unsafe-inline'`,
|
|
52
|
+
fontSrc: `font-src ${args.cspSource} data:`
|
|
53
|
+
};
|
|
54
|
+
if (mode === "dev") {
|
|
55
|
+
return [
|
|
56
|
+
common.defaultSrc,
|
|
57
|
+
`script-src 'nonce-${args.nonce}' 'unsafe-eval' ${args.httpOrigin}`,
|
|
58
|
+
`${common.imgSrc} ${args.httpOrigin}`,
|
|
59
|
+
`${common.styleSrc} ${args.httpOrigin}`,
|
|
60
|
+
`${common.fontSrc} ${args.httpOrigin}`,
|
|
61
|
+
`connect-src ${args.httpOrigin} ${args.wsOrigin} https://api.yicode.tech http://127.0.0.1:3020 http://localhost:3020`
|
|
62
|
+
].join("; ");
|
|
63
|
+
}
|
|
64
|
+
return [common.defaultSrc, common.imgSrc, `script-src 'nonce-${args.nonce}'`, common.styleSrc, common.fontSrc, `connect-src ${args.cspSource} https://api.yicode.tech`].join("; ");
|
|
65
|
+
}
|
|
66
|
+
export function createDevHtml(args) {
|
|
67
|
+
const bootstrap = args.bootstrapJs ? `<script nonce="${args.nonce}">${args.bootstrapJs}</script>` : "";
|
|
68
|
+
return `<!doctype html>
|
|
69
|
+
<html lang="zh-CN">
|
|
70
|
+
<head>
|
|
71
|
+
<meta charset="UTF-8" />
|
|
72
|
+
<meta http-equiv="Content-Security-Policy" content="${args.csp}" />
|
|
73
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
74
|
+
<title>WebView(开发)</title>
|
|
75
|
+
${bootstrap}
|
|
76
|
+
</head>
|
|
77
|
+
<body style="padding: 0">
|
|
78
|
+
<div id="app"></div>
|
|
79
|
+
<script type="module" nonce="${args.nonce}" src="${args.httpOrigin}/@vite/client"></script>
|
|
80
|
+
<script type="module" nonce="${args.nonce}" src="${args.httpOrigin}/main.js"></script>
|
|
81
|
+
</body>
|
|
82
|
+
</html>`;
|
|
83
|
+
}
|
|
84
|
+
export function createBuildHtml(args) {
|
|
85
|
+
let out = args.html.replace(/(src|href)="\.\/([^"]+)"/g, (_match, attr, relPath) => {
|
|
86
|
+
const resourceUri = args.mapAssetUri(relPath);
|
|
87
|
+
return `${attr}="${resourceUri}"`;
|
|
88
|
+
});
|
|
89
|
+
const baseTag = `<base href="${args.baseHref}">`;
|
|
90
|
+
out = out.replace(
|
|
91
|
+
/<head(\s[^>]*)?>/i,
|
|
92
|
+
(m) => `${m}
|
|
93
|
+
${baseTag}`
|
|
94
|
+
);
|
|
95
|
+
if (args.csp) {
|
|
96
|
+
const cspTag = `<meta http-equiv="Content-Security-Policy" content="${args.csp}" />`;
|
|
97
|
+
out = out.replace(
|
|
98
|
+
/<head(\s[^>]*)?>/i,
|
|
99
|
+
(m) => `${m}
|
|
100
|
+
${cspTag}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const bootstrapTag = `<script nonce="${args.nonce}">${args.bootstrapJs}</script>`;
|
|
104
|
+
out = out.replace(
|
|
105
|
+
/<head(\s[^>]*)?>/i,
|
|
106
|
+
(m) => `${m}
|
|
107
|
+
${bootstrapTag}`
|
|
108
|
+
);
|
|
109
|
+
out = out.replace(/<script(?!\b[^>]*\bnonce=)/g, `<script nonce="${args.nonce}"`);
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function getWebviewHtml(context, webview, options = {}) {
|
|
114
|
+
const nonce = createNonce();
|
|
115
|
+
const modeName = options.mode || "main";
|
|
116
|
+
const packageMeta = getPackageMeta(context);
|
|
117
|
+
const pageName = packageMeta.productCode || "main";
|
|
118
|
+
const pagePath = `/${pageName}`;
|
|
119
|
+
const mode = modeName === "main" ? `${context.extension.id}` : `${context.extension.id}.${modeName}`;
|
|
120
|
+
|
|
121
|
+
if (isEnvEnabled("WEBVIEW_DEV_SERVER") && context.extensionMode === vscode.ExtensionMode.Development) {
|
|
122
|
+
const httpOrigin = DEV_SERVER_ORIGIN;
|
|
123
|
+
const wsOrigin = DEV_SERVER_WS_ORIGIN;
|
|
124
|
+
const bootstrapJs = createBootstrapJs({
|
|
125
|
+
isDev: true,
|
|
126
|
+
mode: mode,
|
|
127
|
+
httpOrigin: httpOrigin,
|
|
128
|
+
pageName: pageName,
|
|
129
|
+
pagePath: pagePath,
|
|
130
|
+
productName: packageMeta.productName,
|
|
131
|
+
productCode: packageMeta.productCode,
|
|
132
|
+
productVersion: packageMeta.productVersion
|
|
133
|
+
});
|
|
134
|
+
const csp = createCsp("dev", {
|
|
135
|
+
cspSource: webview.cspSource,
|
|
136
|
+
nonce: nonce,
|
|
137
|
+
httpOrigin: httpOrigin,
|
|
138
|
+
wsOrigin: wsOrigin
|
|
139
|
+
});
|
|
140
|
+
return createDevHtml({
|
|
141
|
+
nonce: nonce,
|
|
142
|
+
csp: csp,
|
|
143
|
+
httpOrigin: httpOrigin,
|
|
144
|
+
bootstrapJs: bootstrapJs
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const htmlPath = vscode.Uri.joinPath(context.extensionUri, "dist", "index.html");
|
|
148
|
+
const htmlContent = fs.readFileSync(htmlPath.fsPath, "utf8");
|
|
149
|
+
const distBaseUri = webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, "dist"));
|
|
150
|
+
const baseHref = `${distBaseUri}`.endsWith("/") ? `${distBaseUri}` : `${distBaseUri}/`;
|
|
151
|
+
const bootstrapJs = createBootstrapJs({
|
|
152
|
+
isDev: false,
|
|
153
|
+
mode: mode,
|
|
154
|
+
httpOrigin: "",
|
|
155
|
+
pageName: pageName,
|
|
156
|
+
pagePath: pagePath,
|
|
157
|
+
productName: packageMeta.productName,
|
|
158
|
+
productCode: packageMeta.productCode,
|
|
159
|
+
productVersion: packageMeta.productVersion
|
|
160
|
+
});
|
|
161
|
+
return createBuildHtml({
|
|
162
|
+
html: htmlContent,
|
|
163
|
+
nonce: nonce,
|
|
164
|
+
baseHref: baseHref,
|
|
165
|
+
bootstrapJs: bootstrapJs,
|
|
166
|
+
mapAssetUri: (relPath) => {
|
|
167
|
+
const resourceUri = webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, "dist", relPath));
|
|
168
|
+
return `${resourceUri}`;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createReporter } from "./createReporter.js";
|
|
2
|
+
|
|
3
|
+
function readGlobalString(name) {
|
|
4
|
+
return globalThis[name] || "";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function createWebviewReporterOptions() {
|
|
8
|
+
const productCode = readGlobalString("__YICODE_PRODUCT_CODE");
|
|
9
|
+
const pageName = productCode || readGlobalString("__YICODE_PAGE_NAME") || "main";
|
|
10
|
+
return {
|
|
11
|
+
source: "vscode",
|
|
12
|
+
productName: readGlobalString("__YICODE_PRODUCT_NAME"),
|
|
13
|
+
productCode: productCode,
|
|
14
|
+
productVersion: readGlobalString("__YICODE_PRODUCT_VERSION"),
|
|
15
|
+
pagePath: `/${pageName}`,
|
|
16
|
+
pageName: pageName
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readRejectionMessage(reason) {
|
|
21
|
+
if (reason instanceof Error && reason.message) {
|
|
22
|
+
return reason.message;
|
|
23
|
+
}
|
|
24
|
+
if (reason?.message) return reason.message;
|
|
25
|
+
if (reason) return String(reason);
|
|
26
|
+
return "WebView Promise 未处理异常";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readWindowErrorDetail(event) {
|
|
30
|
+
if (event?.error) {
|
|
31
|
+
return event.error;
|
|
32
|
+
}
|
|
33
|
+
const location = [event?.filename, event?.lineno, event?.colno].filter(Boolean).join(":");
|
|
34
|
+
if (event?.message && location) {
|
|
35
|
+
return `${event.message} @ ${location}`;
|
|
36
|
+
}
|
|
37
|
+
return event?.message || location || "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getWebviewTongjiReporter() {
|
|
41
|
+
const cacheKey = "__YICODE_WEBVIEW_TONGJI_REPORTER";
|
|
42
|
+
const cached = globalThis?.[cacheKey];
|
|
43
|
+
if (cached) {
|
|
44
|
+
return cached;
|
|
45
|
+
}
|
|
46
|
+
const reporter = createReporter(createWebviewReporterOptions());
|
|
47
|
+
if (reporter) {
|
|
48
|
+
globalThis[cacheKey] = reporter;
|
|
49
|
+
}
|
|
50
|
+
return reporter;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function reportWebviewError(message, detail) {
|
|
54
|
+
const reporter = getWebviewTongjiReporter();
|
|
55
|
+
if (!reporter) {
|
|
56
|
+
return Promise.resolve(false);
|
|
57
|
+
}
|
|
58
|
+
return reporter.reportError({
|
|
59
|
+
message: message || "WebView 异常",
|
|
60
|
+
detail: detail
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function registerWebviewGlobalErrorHandlers(reporter) {
|
|
65
|
+
if (!reporter) {
|
|
66
|
+
return () => {};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const handleError = (event) => {
|
|
70
|
+
void reporter.reportError({
|
|
71
|
+
message: event?.message || "WebView 运行异常",
|
|
72
|
+
detail: readWindowErrorDetail(event)
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleRejection = (event) => {
|
|
77
|
+
void reporter.reportError({
|
|
78
|
+
message: readRejectionMessage(event?.reason),
|
|
79
|
+
detail: event?.reason
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
globalThis.addEventListener("error", handleError);
|
|
84
|
+
globalThis.addEventListener("unhandledrejection", handleRejection);
|
|
85
|
+
|
|
86
|
+
return () => {
|
|
87
|
+
globalThis.removeEventListener("error", handleError);
|
|
88
|
+
globalThis.removeEventListener("unhandledrejection", handleRejection);
|
|
89
|
+
};
|
|
90
|
+
}
|