Skip to content

Commit 8e46d2c

Browse files
committed
refactor: extract GM resource handler
1 parent a5479c5 commit 8e46d2c

13 files changed

Lines changed: 657 additions & 211 deletions

‎background.core.js‎

Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -7879,76 +7879,12 @@ async function handleMessage(message, sender) {
78797879
case 'fetchResource':
78807880
return await ResourceCache.fetchResource(data.url);
78817881

7882-
case 'GM_getResourceText': {
7883-
const script = await ScriptStorage.get(data.scriptId);
7884-
if (!script || !script.meta.resource) return null;
7885-
const url = script.meta.resource[data.name];
7886-
if (!url) return null;
7887-
try {
7888-
return await ResourceCache.fetchResource(url);
7889-
} catch (e) {
7890-
return null;
7891-
}
7892-
}
7893-
7894-
case 'GM_getResourceURL': {
7895-
const script2 = await ScriptStorage.get(data.scriptId);
7896-
if (!script2 || !script2.meta.resource) return null;
7897-
const url2 = script2.meta.resource[data.name];
7898-
if (!url2) return null;
7899-
try {
7900-
return await ResourceCache.getDataUri(url2);
7901-
} catch (e) {
7902-
return null;
7903-
}
7904-
}
7905-
7906-
// GM_loadScript - Fetch a script URL and return its source code
7907-
// Allows userscripts to dynamically load libraries at runtime
7908-
case 'GM_loadScript': {
7909-
try {
7910-
if (!data.url) return { error: 'No URL provided' };
7911-
if (!data.scriptId) return { error: 'Missing script context' };
7912-
const lsScript = await ScriptStorage.get(data.scriptId);
7913-
if (!lsScript) return { error: 'Script context not found' };
7914-
7915-
// Enforce @connect for GM_loadScript (same rules as GM_xmlhttpRequest)
7916-
const lsPolicy = evaluateConnectPolicy(lsScript, data.url);
7917-
if (!lsPolicy.allowed) {
7918-
return { error: lsPolicy.error };
7919-
}
7920-
const lsPreCheck = InternalHostGuard.classifyFetchUrl(data.url, ['http:', 'https:']);
7921-
if (!lsPreCheck.ok) {
7922-
return { error: 'GM_loadScript URL rejected: ' + lsPreCheck.message };
7923-
}
7924-
7925-
const controller = new AbortController();
7926-
const timeoutId = setTimeout(() => controller.abort(), data.timeout || 30000);
7927-
let code;
7928-
try {
7929-
const response = await fetch(data.url, { signal: controller.signal });
7930-
if (!response.ok) return { error: `HTTP ${response.status}` };
7931-
const lsPostCheck = InternalHostGuard.classifyResponseUrl(response, ['http:', 'https:']);
7932-
if (!lsPostCheck.ok) {
7933-
return { error: 'GM_loadScript URL redirected to ' + lsPostCheck.message };
7934-
}
7935-
// Stream-bounded read so a remote script source can't OOM us
7936-
// by serving an unbounded body. See _fetchTextBounded for
7937-
// rationale.
7938-
try {
7939-
code = await _fetchTextBounded(response, MAX_SCRIPT_SIZE, 'Script');
7940-
} catch (sizeErr) {
7941-
return { error: sizeErr?.message || String(sizeErr) };
7942-
}
7943-
} finally {
7944-
clearTimeout(timeoutId);
7945-
}
7946-
if (!code || code.length === 0) return { error: 'Empty response' };
7947-
return { code };
7948-
} catch (e) {
7949-
return { error: e?.message || 'Fetch failed' };
7950-
}
7951-
}
7882+
// GM resources and GM_loadScript dynamic library loading
7883+
case 'GM_getResourceText':
7884+
case 'GM_getResourceURL':
7885+
case 'GM_loadScript':
7886+
if (typeof GMResourceHandler === 'undefined') return { error: 'GMResourceHandler not available' };
7887+
return await GMResourceHandler.handleGMResourceMessage(action, data);
79527888

79537889
// XHR - Using fetch() since XMLHttpRequest is not available in Service Workers
79547890
// Provides abort support via AbortController and simulates events

‎background.js‎

Lines changed: 136 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -18600,6 +18600,136 @@ if (typeof self !== 'undefined') {
1860018600
self.GMNotificationHandler = GMNotificationHandler;
1860118601
}
1860218602

18603+
// ============================================================================
18604+
// Generated from src/background/gm-resource-handler.ts; do not edit by hand.
18605+
// Run `node scripts/generate-ts-runtime-modules.mjs` or `npm run build:bg`.
18606+
// ============================================================================
18607+
18608+
const GMResourceHandler = (() => {
18609+
const module = { exports: {} };
18610+
const exports = module.exports;
18611+
"use strict";
18612+
var __defProp = Object.defineProperty;
18613+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
18614+
var __getOwnPropNames = Object.getOwnPropertyNames;
18615+
var __hasOwnProp = Object.prototype.hasOwnProperty;
18616+
var __export = (target, all) => {
18617+
for (var name in all)
18618+
__defProp(target, name, { get: all[name], enumerable: true });
18619+
};
18620+
var __copyProps = (to, from, except, desc) => {
18621+
if (from && typeof from === "object" || typeof from === "function") {
18622+
for (let key of __getOwnPropNames(from))
18623+
if (!__hasOwnProp.call(to, key) && key !== except)
18624+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18625+
}
18626+
return to;
18627+
};
18628+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18629+
18630+
// src/background/gm-resource-handler.ts
18631+
var gm_resource_handler_exports = {};
18632+
__export(gm_resource_handler_exports, {
18633+
GMResourceHandler: () => GMResourceHandler,
18634+
GM_RESOURCE_ACTIONS: () => GM_RESOURCE_ACTIONS,
18635+
default: () => gm_resource_handler_default,
18636+
handleGMResourceMessage: () => handleGMResourceMessage,
18637+
isGMResourceAction: () => isGMResourceAction
18638+
});
18639+
module.exports = __toCommonJS(gm_resource_handler_exports);
18640+
var GM_RESOURCE_ACTIONS = [
18641+
"GM_getResourceText",
18642+
"GM_getResourceURL",
18643+
"GM_loadScript"
18644+
];
18645+
var GM_RESOURCE_ACTION_SET = new Set(GM_RESOURCE_ACTIONS);
18646+
function errorMessage(error, fallback) {
18647+
if (error && typeof error === "object" && "message" in error) {
18648+
const message = error.message;
18649+
if (typeof message === "string" && message) return message;
18650+
}
18651+
return fallback;
18652+
}
18653+
function isGMResourceAction(action) {
18654+
return typeof action === "string" && GM_RESOURCE_ACTION_SET.has(action);
18655+
}
18656+
async function handleGMResourceMessage(action, data = {}) {
18657+
switch (action) {
18658+
case "GM_getResourceText": {
18659+
const script = await ScriptStorage.get(data.scriptId);
18660+
if (!script || !script.meta?.resource) return null;
18661+
const url = data.name ? script.meta.resource[data.name] : void 0;
18662+
if (!url) return null;
18663+
try {
18664+
return await ResourceCache.fetchResource(url);
18665+
} catch (_) {
18666+
return null;
18667+
}
18668+
}
18669+
case "GM_getResourceURL": {
18670+
const script = await ScriptStorage.get(data.scriptId);
18671+
if (!script || !script.meta?.resource) return null;
18672+
const url = data.name ? script.meta.resource[data.name] : void 0;
18673+
if (!url) return null;
18674+
try {
18675+
return await ResourceCache.getDataUri(url);
18676+
} catch (_) {
18677+
return null;
18678+
}
18679+
}
18680+
case "GM_loadScript": {
18681+
try {
18682+
if (!data.url) return { error: "No URL provided" };
18683+
if (!data.scriptId) return { error: "Missing script context" };
18684+
const script = await ScriptStorage.get(data.scriptId);
18685+
if (!script) return { error: "Script context not found" };
18686+
const policy = evaluateConnectPolicy(script, data.url);
18687+
if (!policy.allowed) return { error: policy.error };
18688+
const preCheck = InternalHostGuard.classifyFetchUrl(data.url, ["http:", "https:"]);
18689+
if (!preCheck.ok) {
18690+
return { error: "GM_loadScript URL rejected: " + preCheck.message };
18691+
}
18692+
const controller = new AbortController();
18693+
const timeoutId = setTimeout(() => controller.abort(), data.timeout || 3e4);
18694+
let code;
18695+
try {
18696+
const response = await fetch(data.url, { signal: controller.signal });
18697+
if (!response.ok) return { error: `HTTP ${response.status}` };
18698+
const postCheck = InternalHostGuard.classifyResponseUrl(response, ["http:", "https:"]);
18699+
if (!postCheck.ok) {
18700+
return { error: "GM_loadScript URL redirected to " + postCheck.message };
18701+
}
18702+
try {
18703+
code = await _fetchTextBounded(response, MAX_SCRIPT_SIZE, "Script");
18704+
} catch (sizeError) {
18705+
return { error: errorMessage(sizeError, String(sizeError)) };
18706+
}
18707+
} finally {
18708+
clearTimeout(timeoutId);
18709+
}
18710+
if (!code || code.length === 0) return { error: "Empty response" };
18711+
return { code };
18712+
} catch (error) {
18713+
return { error: errorMessage(error, "Fetch failed") };
18714+
}
18715+
}
18716+
default:
18717+
return { error: `Unsupported GM resource action: ${action}` };
18718+
}
18719+
}
18720+
var GMResourceHandler = Object.freeze({
18721+
GM_RESOURCE_ACTIONS,
18722+
handleGMResourceMessage,
18723+
isGMResourceAction
18724+
});
18725+
var gm_resource_handler_default = GMResourceHandler;
18726+
return module.exports.default || module.exports.GMResourceHandler || module.exports;
18727+
})();
18728+
18729+
if (typeof self !== 'undefined') {
18730+
self.GMResourceHandler = GMResourceHandler;
18731+
}
18732+
1860318733
// ============================================================================
1860418734
// Generated from src/background/connect-policy.ts; do not edit by hand.
1860518735
// Run `node scripts/generate-ts-runtime-modules.mjs` or `npm run build:bg`.
@@ -37270,76 +37400,12 @@ async function handleMessage(message, sender) {
3727037400
case 'fetchResource':
3727137401
return await ResourceCache.fetchResource(data.url);
3727237402

37273-
case 'GM_getResourceText': {
37274-
const script = await ScriptStorage.get(data.scriptId);
37275-
if (!script || !script.meta.resource) return null;
37276-
const url = script.meta.resource[data.name];
37277-
if (!url) return null;
37278-
try {
37279-
return await ResourceCache.fetchResource(url);
37280-
} catch (e) {
37281-
return null;
37282-
}
37283-
}
37284-
37285-
case 'GM_getResourceURL': {
37286-
const script2 = await ScriptStorage.get(data.scriptId);
37287-
if (!script2 || !script2.meta.resource) return null;
37288-
const url2 = script2.meta.resource[data.name];
37289-
if (!url2) return null;
37290-
try {
37291-
return await ResourceCache.getDataUri(url2);
37292-
} catch (e) {
37293-
return null;
37294-
}
37295-
}
37296-
37297-
// GM_loadScript - Fetch a script URL and return its source code
37298-
// Allows userscripts to dynamically load libraries at runtime
37299-
case 'GM_loadScript': {
37300-
try {
37301-
if (!data.url) return { error: 'No URL provided' };
37302-
if (!data.scriptId) return { error: 'Missing script context' };
37303-
const lsScript = await ScriptStorage.get(data.scriptId);
37304-
if (!lsScript) return { error: 'Script context not found' };
37305-
37306-
// Enforce @connect for GM_loadScript (same rules as GM_xmlhttpRequest)
37307-
const lsPolicy = evaluateConnectPolicy(lsScript, data.url);
37308-
if (!lsPolicy.allowed) {
37309-
return { error: lsPolicy.error };
37310-
}
37311-
const lsPreCheck = InternalHostGuard.classifyFetchUrl(data.url, ['http:', 'https:']);
37312-
if (!lsPreCheck.ok) {
37313-
return { error: 'GM_loadScript URL rejected: ' + lsPreCheck.message };
37314-
}
37315-
37316-
const controller = new AbortController();
37317-
const timeoutId = setTimeout(() => controller.abort(), data.timeout || 30000);
37318-
let code;
37319-
try {
37320-
const response = await fetch(data.url, { signal: controller.signal });
37321-
if (!response.ok) return { error: `HTTP ${response.status}` };
37322-
const lsPostCheck = InternalHostGuard.classifyResponseUrl(response, ['http:', 'https:']);
37323-
if (!lsPostCheck.ok) {
37324-
return { error: 'GM_loadScript URL redirected to ' + lsPostCheck.message };
37325-
}
37326-
// Stream-bounded read so a remote script source can't OOM us
37327-
// by serving an unbounded body. See _fetchTextBounded for
37328-
// rationale.
37329-
try {
37330-
code = await _fetchTextBounded(response, MAX_SCRIPT_SIZE, 'Script');
37331-
} catch (sizeErr) {
37332-
return { error: sizeErr?.message || String(sizeErr) };
37333-
}
37334-
} finally {
37335-
clearTimeout(timeoutId);
37336-
}
37337-
if (!code || code.length === 0) return { error: 'Empty response' };
37338-
return { code };
37339-
} catch (e) {
37340-
return { error: e?.message || 'Fetch failed' };
37341-
}
37342-
}
37403+
// GM resources and GM_loadScript dynamic library loading
37404+
case 'GM_getResourceText':
37405+
case 'GM_getResourceURL':
37406+
case 'GM_loadScript':
37407+
if (typeof GMResourceHandler === 'undefined') return { error: 'GMResourceHandler not available' };
37408+
return await GMResourceHandler.handleGMResourceMessage(action, data);
3734337409

3734437410
// XHR - Using fetch() since XMLHttpRequest is not available in Service Workers
3734537411
// Provides abort support via AbortController and simulates events

‎esbuild.config.mjs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ async function buildBackground() {
112112
readFile("modules/gm-tabs-handler.js"),
113113
readFile("modules/gm-values-handler.js"),
114114
readFile("modules/gm-notification-handler.js"),
115+
readFile("modules/gm-resource-handler.js"),
115116
readFile("modules/connect-policy.js"),
116117
readFile("modules/resources.js"),
117118

0 commit comments

Comments
 (0)