Skip to content

Commit 1dce7c7

Browse files
committed
Fix a MAJOR bug when loading scripts that call tap into the events of a webpage.
1 parent f425e19 commit 1dce7c7

File tree

9 files changed

+1024
-654
lines changed

9 files changed

+1024
-654
lines changed

src/GM/gm_bridge.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
export class ResourceManager {
2+
constructor(resourceContents = {}, resourceURLs = {}) {
3+
this.contents = new Map(Object.entries(resourceContents));
4+
this.urls = new Map(Object.entries(resourceURLs));
5+
}
6+
7+
getText(resourceName) {
8+
return this.contents.get(resourceName) ?? null;
9+
}
10+
11+
getURL(resourceName) {
12+
return this.urls.get(resourceName) ?? null;
13+
}
14+
15+
static fromScript(script) {
16+
const resourceURLs = {};
17+
18+
if (Array.isArray(script.resources)) {
19+
script.resources.forEach((resource) => {
20+
resourceURLs[resource.name] = resource.url;
21+
});
22+
}
23+
24+
return new ResourceManager(script.resourceContents || {}, resourceURLs);
25+
}
26+
}
27+
28+
export class GMBridge {
29+
static ResourceManager = ResourceManager;
30+
31+
constructor(scriptId, extensionId, worldType = "MAIN") {
32+
this.scriptId = scriptId;
33+
this.extensionId = extensionId;
34+
this.worldType = worldType;
35+
this.messageIdCounter = 0;
36+
this.pendingPromises = new Map();
37+
38+
if (this.worldType === "MAIN") {
39+
this.setupMessageListener();
40+
}
41+
}
42+
43+
setupMessageListener() {
44+
window.addEventListener("message", (event) => {
45+
if (!this.isValidResponse(event)) return;
46+
47+
const { messageId, error, result } = event.data;
48+
const promise = this.pendingPromises.get(messageId);
49+
50+
if (!promise) return;
51+
52+
if (error) {
53+
promise.reject(new Error(error));
54+
} else {
55+
promise.resolve(result);
56+
}
57+
58+
this.pendingPromises.delete(messageId);
59+
});
60+
}
61+
62+
isValidResponse(event) {
63+
return (
64+
event.source === window &&
65+
event.data?.type === "GM_API_RESPONSE" &&
66+
event.data.extensionId === this.extensionId &&
67+
this.pendingPromises.has(event.data.messageId)
68+
);
69+
}
70+
71+
call(action, payload = {}) {
72+
const enrichedPayload = { ...payload, scriptId: this.scriptId };
73+
74+
// Use direct chrome.runtime API if available (ISOLATED world)
75+
if (
76+
this.worldType === "ISOLATED" &&
77+
typeof chrome?.runtime?.sendMessage === "function"
78+
) {
79+
return this.callIsolated(action, enrichedPayload);
80+
}
81+
82+
// Fallback to postMessage (MAIN world)
83+
return this.callMain(action, enrichedPayload);
84+
}
85+
86+
callMain(action, payload = {}) {
87+
return new Promise((resolve, reject) => {
88+
const messageId = `gm_${this.scriptId}_${this.messageIdCounter++}`;
89+
this.pendingPromises.set(messageId, { resolve, reject });
90+
91+
window.postMessage(
92+
{
93+
type: "GM_API_REQUEST",
94+
extensionId: this.extensionId,
95+
messageId,
96+
action,
97+
payload,
98+
},
99+
"*"
100+
);
101+
});
102+
}
103+
104+
callIsolated(action, payload = {}) {
105+
return new Promise((resolve, reject) => {
106+
try {
107+
chrome.runtime.sendMessage(
108+
{
109+
type: "GM_API_REQUEST",
110+
payload: { action, ...payload },
111+
},
112+
(response) => {
113+
if (chrome.runtime.lastError) {
114+
reject(new Error(chrome.runtime.lastError.message));
115+
return;
116+
}
117+
118+
if (response?.error) {
119+
reject(new Error(response.error));
120+
return;
121+
}
122+
123+
resolve(response?.result);
124+
}
125+
);
126+
} catch (error) {
127+
reject(error);
128+
}
129+
});
130+
}
131+
}

0 commit comments

Comments
 (0)