Skip to content

Commit 3bf890d

Browse files
committed
不依赖外部网站访问进行安装
1 parent 080a859 commit 3bf890d

File tree

5 files changed

+181
-19
lines changed

5 files changed

+181
-19
lines changed

rspack.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default defineConfig({
4444
"editor.worker": "monaco-editor/esm/vs/editor/editor.worker.js",
4545
"ts.worker": "monaco-editor/esm/vs/language/typescript/ts.worker.js",
4646
"linter.worker": `${src}/linter.worker.ts`,
47+
script_installation_page: `${src}/script_installation_page.ts`,
4748
},
4849
output: {
4950
path: `${dist}/ext/src`,
@@ -205,6 +206,13 @@ export default defineConfig({
205206
minify: true,
206207
chunks: ["sandbox"],
207208
}),
209+
new rspack.HtmlRspackPlugin({
210+
filename: `${dist}/ext/src/script_installation_page.html`,
211+
template: `${src}/pages/script_installation_page.html`,
212+
inject: "head",
213+
minify: true,
214+
chunks: ["script_installation_page"],
215+
}),
208216
].filter(Boolean),
209217
optimization: {
210218
minimizer: [

src/app/service/service_worker/script.ts

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ import { type ResourceService } from "./resource";
2828
import { type ValueService } from "./value";
2929
import { compileScriptCode, isEarlyStartScript } from "../content/utils";
3030
import { type SystemConfig } from "@App/pkg/config/config";
31-
import { localePath } from "@App/locales/locales";
3231
import { arrayMove } from "@dnd-kit/sortable";
33-
import { DocumentationSite } from "@App/app/const";
3432
import type {
3533
TScriptRunStatus,
3634
TDeleteScript,
@@ -80,13 +78,35 @@ export class ScriptService {
8078
}
8179

8280
listenerScriptInstall() {
81+
const handleIncomingUrl = async (targetUrl: string) => {
82+
const url = await this.getInstallPageUrl(targetUrl, "user");
83+
if (!url) throw new Error("getInstallPageUrl failed");
84+
return url;
85+
};
86+
87+
// Runs in MV3 background (service worker)
88+
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
89+
switch (msg.type) {
90+
case "INSTALL_PAGE:SUBMIT_URL": {
91+
// Do work (can be async)
92+
handleIncomingUrl(msg.url)
93+
.then((result) => sendResponse({ ok: true, result }))
94+
.catch((err) => sendResponse({ ok: false, error: String(err) }));
95+
return true; // <-- keep the message channel open for async sendResponse
96+
}
97+
default:
98+
// ignore
99+
}
100+
});
101+
83102
// 初始化脚本安装监听
84-
chrome.webRequest.onBeforeRequest.addListener(
85-
(req: chrome.webRequest.OnBeforeRequestDetails) => {
86-
// 处理url, 实现安装脚本
87-
if (req.method !== "GET") {
88-
return undefined;
103+
chrome.webNavigation.onBeforeNavigate.addListener(
104+
(req: chrome.webNavigation.WebNavigationParentedCallbackDetails) => {
105+
const lastError = chrome.runtime.lastError;
106+
if (lastError) {
107+
console.error(lastError.message);
89108
}
109+
// 处理url, 实现安装脚本
90110
let targetUrl: string | null = null;
91111
// 判断是否为 file:///*/*.user.js
92112
if (req.url.startsWith("file://") && req.url.endsWith(".user.js")) {
@@ -150,13 +170,16 @@ export class ScriptService {
150170
});
151171
},
152172
{
153-
urls: [
154-
`${DocumentationSite}/docs/script_installation/*`,
155-
`${DocumentationSite}/en/docs/script_installation/*`,
156-
"https://www.tampermonkey.net/script_installation.php*",
157-
"file:///*/*.user.js*",
173+
url: [
174+
// { hostEquals: "docs.scriptcat.org", pathPrefix: "/docs/script_installation/" },
175+
// { hostEquals: "docs.scriptcat.org", pathPrefix: "/en/docs/script_installation/" },
176+
{ hostEquals: "www.tampermonkey.net", pathPrefix: "/script_installation.php" },
177+
{ schemes: ["file"], pathSuffix: ".user.js" },
178+
{
179+
hostEquals: chrome.runtime.id,
180+
pathPrefix: "/src/script_installation_page.html",
181+
},
158182
],
159-
types: ["main_frame"],
160183
}
161184
);
162185
// 兼容 chrome 内核 < 128 处理
@@ -187,7 +210,7 @@ export class ScriptService {
187210
action: {
188211
type: "redirect" as chrome.declarativeNetRequest.RuleActionType,
189212
redirect: {
190-
regexSubstitution: `${DocumentationSite}${localePath}/docs/script_installation/#url=\\0`,
213+
regexSubstitution: `chrome-extension://${chrome.runtime.id}/src/script_installation_page.html?url=\\0`,
191214
},
192215
},
193216
condition: condition,
@@ -206,6 +229,18 @@ export class ScriptService {
206229
}
207230

208231
public async openInstallPageByUrl(url: string, source: InstallSource): Promise<{ success: boolean; msg: string }> {
232+
try {
233+
const installPageUrl = await this.getInstallPageUrl(url, source);
234+
if (!installPageUrl) throw new Error("getInstallPageUrl failed");
235+
await openInCurrentTab(installPageUrl);
236+
return { success: true, msg: "" };
237+
} catch (err: any) {
238+
console.error(err);
239+
return { success: false, msg: err.message };
240+
}
241+
}
242+
243+
public async getInstallPageUrl(url: string, source: InstallSource): Promise<string> {
209244
const uuid = uuidv4();
210245
try {
211246
await this.openUpdateOrInstallPage(uuid, url, source, false);
@@ -217,11 +252,10 @@ export class ScriptService {
217252
},
218253
30 * 1000
219254
);
220-
await openInCurrentTab(`/src/install.html?uuid=${uuid}`);
221-
return { success: true, msg: "" };
255+
return `/src/install.html?uuid=${uuid}`;
222256
} catch (err: any) {
223257
console.error(err);
224-
return { success: false, msg: err.message };
258+
return "";
225259
}
226260
}
227261

src/manifest.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"notifications",
4343
"clipboardWrite",
4444
"unlimitedStorage",
45-
"declarativeNetRequest"
45+
"declarativeNetRequest",
46+
"webNavigation"
4647
],
4748
"optional_permissions": [
4849
"userScripts"
@@ -54,5 +55,11 @@
5455
"pages": [
5556
"src/sandbox.html"
5657
]
57-
}
58+
},
59+
"web_accessible_resources": [
60+
{
61+
"resources": ["/src/script_installation_page.html"],
62+
"matches": ["<all_urls>"]
63+
}
64+
]
5865
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Script Installation</title>
7+
<style>
8+
.downloading {
9+
display: flex;
10+
flex-direction: row;
11+
flex-wrap: wrap;
12+
column-gap: 4px;
13+
align-items: center;
14+
}
15+
.error-message {
16+
color: red;
17+
}
18+
.error-message:empty {
19+
display:none;
20+
}
21+
.error-message::before {
22+
content: "ERROR: ";
23+
}
24+
25+
/* https://css-loaders.com/dots/ */
26+
.loader {
27+
width: 60px;
28+
aspect-ratio: 2;
29+
--_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000);
30+
background:
31+
var(--_g) 0% 50%,
32+
var(--_g) 50% 50%,
33+
var(--_g) 100% 50%;
34+
background-size: calc(100%/3) 50%;
35+
animation: l3 1s infinite linear;
36+
transform: scale(0.5);
37+
}
38+
@keyframes l3 {
39+
20%{background-position:0% 0%, 50% 50%,100% 50%}
40+
40%{background-position:0% 100%, 50% 0%,100% 50%}
41+
60%{background-position:0% 50%, 50% 100%,100% 0%}
42+
80%{background-position:0% 50%, 50% 50%,100% 100%}
43+
}
44+
</style>
45+
</head>
46+
<body>
47+
48+
<h1>Script Installation</h1>
49+
<p>This page is used as an intermediate step to install a new user script in ScriptCat.</p>
50+
51+
<h2>Script Resources</h2>
52+
<h3 class="downloading"><div>Downloading</div><div class="loader"></div></h3>
53+
<h3 class="error-message"></h3>
54+
55+
<h2>Remarks</h2>
56+
57+
<p>After the Script Resources Downloading is complete, this page should be automatically redirected.</p>
58+
<p>This page is not suitable for direct viewing. If necessary, please visit Installation Script.</p>
59+
60+
</body>
61+
</html>

src/script_installation_page.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// read original URL from the hash and send to SW
2+
3+
const showError = (errorMessage: string) => {
4+
const errorMessageElement = document.querySelector(".error-message");
5+
if (errorMessageElement) {
6+
errorMessageElement.textContent = `${errorMessage}`;
7+
}
8+
const downloadingElement = document.querySelector(".downloading");
9+
if (downloadingElement) {
10+
downloadingElement.textContent = ``;
11+
}
12+
console.error("Service worker error:", errorMessage);
13+
};
14+
15+
try {
16+
const urlParam = new URLSearchParams(location.search).get("url");
17+
// const urlParam = new URLSearchParams(location.hash.slice(1)).get("url");
18+
const isValidURL = (url: string) => {
19+
try {
20+
new URL(url);
21+
return true;
22+
} catch {
23+
return false;
24+
}
25+
};
26+
27+
if (!urlParam || !isValidURL(urlParam)) {
28+
throw new Error("a valid URL shall be specified in the hash #url=....");
29+
}
30+
31+
chrome.runtime.sendMessage(
32+
{
33+
type: "INSTALL_PAGE:SUBMIT_URL",
34+
url: urlParam,
35+
},
36+
(resp) => {
37+
const lastError = chrome.runtime.lastError;
38+
if (lastError) {
39+
showError(lastError.message || "chrome.runtime.lastError");
40+
return;
41+
}
42+
43+
if (!resp?.ok) {
44+
showError(resp.error);
45+
return;
46+
}
47+
location.replace(resp.result);
48+
}
49+
);
50+
} catch (e: any) {
51+
showError(e?.message);
52+
}

0 commit comments

Comments
 (0)