@gxp-dev/tools 2.0.6 → 2.0.8
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/bin/lib/commands/build.js +18 -12
- package/browser-extensions/README.md +1 -0
- package/browser-extensions/chrome/background.js +857 -0
- package/browser-extensions/chrome/content.js +51 -0
- package/browser-extensions/chrome/devtools.html +9 -0
- package/browser-extensions/chrome/devtools.js +23 -0
- package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
- package/browser-extensions/chrome/inspector.js +1087 -0
- package/browser-extensions/chrome/manifest.json +70 -0
- package/browser-extensions/chrome/panel.html +638 -0
- package/browser-extensions/chrome/panel.js +862 -0
- package/browser-extensions/chrome/popup.html +399 -0
- package/browser-extensions/chrome/popup.js +515 -0
- package/browser-extensions/chrome/rules.json +1 -0
- package/browser-extensions/chrome/test-chrome.html +145 -0
- package/browser-extensions/chrome/test-mixed-content.html +190 -0
- package/browser-extensions/chrome/test-uri-pattern.html +199 -0
- package/browser-extensions/firefox/README.md +134 -0
- package/browser-extensions/firefox/background.js +804 -0
- package/browser-extensions/firefox/content.js +120 -0
- package/browser-extensions/firefox/debug-errors.html +229 -0
- package/browser-extensions/firefox/debug-https.html +113 -0
- package/browser-extensions/firefox/devtools.html +9 -0
- package/browser-extensions/firefox/devtools.js +24 -0
- package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
- package/browser-extensions/firefox/inspector.js +1087 -0
- package/browser-extensions/firefox/manifest.json +67 -0
- package/browser-extensions/firefox/panel.html +638 -0
- package/browser-extensions/firefox/panel.js +862 -0
- package/browser-extensions/firefox/popup.html +525 -0
- package/browser-extensions/firefox/popup.js +536 -0
- package/browser-extensions/firefox/test-gramercy.html +126 -0
- package/browser-extensions/firefox/test-imports.html +58 -0
- package/browser-extensions/firefox/test-masking.html +147 -0
- package/browser-extensions/firefox/test-uri-pattern.html +199 -0
- package/package.json +7 -2
- package/runtime/PortalContainer.vue +326 -0
- package/runtime/dev-tools/DevToolsModal.vue +217 -0
- package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
- package/runtime/dev-tools/MockDataEditor.vue +621 -0
- package/runtime/dev-tools/SocketSimulator.vue +562 -0
- package/runtime/dev-tools/StoreInspector.vue +644 -0
- package/runtime/dev-tools/index.js +6 -0
- package/runtime/gxpStringsPlugin.js +428 -0
- package/runtime/index.html +22 -0
- package/runtime/main.js +32 -0
- package/runtime/mock-api/auth-middleware.js +97 -0
- package/runtime/mock-api/image-generator.js +221 -0
- package/runtime/mock-api/index.js +197 -0
- package/runtime/mock-api/response-generator.js +394 -0
- package/runtime/mock-api/route-generator.js +323 -0
- package/runtime/mock-api/socket-triggers.js +371 -0
- package/runtime/mock-api/spec-loader.js +300 -0
- package/runtime/server.js +180 -0
- package/runtime/stores/gxpPortalConfigStore.js +554 -0
- package/runtime/stores/index.js +6 -0
- package/runtime/vite-inspector-plugin.js +749 -0
- package/runtime/vite-source-tracker-plugin.js +232 -0
- package/runtime/vite.config.js +402 -0
- package/scripts/launch-chrome.js +90 -0
- package/scripts/pack-chrome.js +91 -0
- package/socket-events/AiSessionMessageCreated.json +18 -0
- package/socket-events/SocialStreamPostCreated.json +24 -0
- package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
- package/template/README.md +332 -0
- package/template/app-manifest.json +32 -0
- package/template/dev-assets/images/avatar-placeholder.png +0 -0
- package/template/dev-assets/images/background-placeholder.jpg +0 -0
- package/template/dev-assets/images/banner-placeholder.jpg +0 -0
- package/template/dev-assets/images/icon-placeholder.png +0 -0
- package/template/dev-assets/images/logo-placeholder.png +0 -0
- package/template/dev-assets/images/product-placeholder.jpg +0 -0
- package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
- package/template/env.example +51 -0
- package/template/gitignore +53 -0
- package/template/index.html +22 -0
- package/template/main.js +28 -0
- package/template/src/DemoPage.vue +459 -0
- package/template/src/Plugin.vue +38 -0
- package/template/src/stores/index.js +9 -0
- package/template/src/stores/test-data.json +173 -0
- package/template/theme-layouts/AdditionalStyling.css +0 -0
- package/template/theme-layouts/PrivateLayout.vue +39 -0
- package/template/theme-layouts/PublicLayout.vue +39 -0
- package/template/theme-layouts/SystemLayout.vue +39 -0
- package/template/vite.config.js +333 -0
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
// Extension state
|
|
2
|
+
let config = {
|
|
3
|
+
enabled: false,
|
|
4
|
+
// Legacy fields for backward compatibility
|
|
5
|
+
redirectUrl: "https://localhost:3060/src/Plugin.vue",
|
|
6
|
+
urlPattern: "uploads\\/plugin-version\\/\\d+\\/file_name\\/.*\\.js(\\?.*)?",
|
|
7
|
+
useCustomPattern: false,
|
|
8
|
+
// New rules-based configuration
|
|
9
|
+
rules: {
|
|
10
|
+
js: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
pattern: "uploads\\/plugin-version\\/\\d+\\/file_name\\/.*\\.js(\\?.*)?",
|
|
13
|
+
redirectUrl: "https://localhost:3060/src/Plugin.vue",
|
|
14
|
+
useCustomPattern: false,
|
|
15
|
+
},
|
|
16
|
+
css: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
pattern:
|
|
19
|
+
"uploads\\/plugin-version\\/\\d+\\/style_file_name\\/.*\\.css(\\?.*)?",
|
|
20
|
+
redirectUrl: "",
|
|
21
|
+
returnBlank: true,
|
|
22
|
+
useCustomPattern: false,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
maskingMode: false,
|
|
26
|
+
clearCacheOnEnable: true,
|
|
27
|
+
disableCacheForRedirects: true,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let notificationTimeouts = new Map();
|
|
31
|
+
let cacheBlacklist = new Set();
|
|
32
|
+
|
|
33
|
+
// Initialize extension
|
|
34
|
+
chrome.runtime.onStartup.addListener(initializeExtension);
|
|
35
|
+
chrome.runtime.onInstalled.addListener(initializeExtension);
|
|
36
|
+
|
|
37
|
+
async function initializeExtension() {
|
|
38
|
+
console.log("[JavaScript Proxy] Initializing extension...");
|
|
39
|
+
try {
|
|
40
|
+
// Load saved configuration
|
|
41
|
+
const defaultConfig = {
|
|
42
|
+
enabled: false,
|
|
43
|
+
// Legacy fields for backward compatibility
|
|
44
|
+
redirectUrl: "https://localhost:3060/src/Plugin.vue",
|
|
45
|
+
urlPattern:
|
|
46
|
+
"uploads\\/plugin-version\\/\\d+\\/file_name\\/.*\\.js(\\?.*)?",
|
|
47
|
+
useCustomPattern: false,
|
|
48
|
+
// New rules-based configuration
|
|
49
|
+
rules: {
|
|
50
|
+
js: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
pattern:
|
|
53
|
+
"uploads\\/plugin-version\\/\\d+\\/file_name\\/.*\\.js(\\?.*)?",
|
|
54
|
+
redirectUrl: "https://localhost:3060/src/Plugin.vue",
|
|
55
|
+
useCustomPattern: false,
|
|
56
|
+
},
|
|
57
|
+
css: {
|
|
58
|
+
enabled: true,
|
|
59
|
+
pattern:
|
|
60
|
+
"uploads\\/plugin-version\\/\\d+\\/style_file_name\\/.*\\.css(\\?.*)?",
|
|
61
|
+
redirectUrl: "",
|
|
62
|
+
returnBlank: true,
|
|
63
|
+
useCustomPattern: false,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
maskingMode: false,
|
|
67
|
+
clearCacheOnEnable: true,
|
|
68
|
+
disableCacheForRedirects: true,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const result = await chrome.storage.sync.get(defaultConfig);
|
|
72
|
+
config = result;
|
|
73
|
+
|
|
74
|
+
// Migrate legacy configuration to new rules format
|
|
75
|
+
config = migrateConfig(config);
|
|
76
|
+
|
|
77
|
+
console.log("[JavaScript Proxy] Loaded configuration:", config);
|
|
78
|
+
|
|
79
|
+
updateIcon();
|
|
80
|
+
updateRequestListener();
|
|
81
|
+
|
|
82
|
+
console.log("[JavaScript Proxy] Extension initialized successfully");
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("[JavaScript Proxy] Failed to initialize:", error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Migrate legacy configuration to new rules format
|
|
89
|
+
function migrateConfig(config) {
|
|
90
|
+
// If rules don't exist, create them from legacy config
|
|
91
|
+
if (!config.rules) {
|
|
92
|
+
config.rules = {
|
|
93
|
+
js: {
|
|
94
|
+
enabled: true,
|
|
95
|
+
pattern:
|
|
96
|
+
config.urlPattern ||
|
|
97
|
+
"uploads\\/plugin-version\\/\\d+\\/file_name\\/.*\\.js(\\?.*)?",
|
|
98
|
+
redirectUrl:
|
|
99
|
+
config.redirectUrl || "https://localhost:3060/src/Plugin.vue",
|
|
100
|
+
useCustomPattern: config.useCustomPattern || false,
|
|
101
|
+
},
|
|
102
|
+
css: {
|
|
103
|
+
enabled: false,
|
|
104
|
+
pattern:
|
|
105
|
+
"uploads\\/plugin-version\\/\\d+\\/style_file_name\\/.*\\.css(\\?.*)?",
|
|
106
|
+
redirectUrl: "",
|
|
107
|
+
returnBlank: false,
|
|
108
|
+
useCustomPattern: false,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
} else {
|
|
112
|
+
// Ensure all required fields exist
|
|
113
|
+
if (!config.rules.js) {
|
|
114
|
+
config.rules.js = {
|
|
115
|
+
enabled: true,
|
|
116
|
+
pattern:
|
|
117
|
+
config.urlPattern ||
|
|
118
|
+
"uploads\\/plugin-version\\/\\d+\\/file_name\\/.*\\.js(\\?.*)?",
|
|
119
|
+
redirectUrl:
|
|
120
|
+
config.redirectUrl || "https://localhost:3060/src/Plugin.vue",
|
|
121
|
+
useCustomPattern: config.useCustomPattern || false,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (!config.rules.css) {
|
|
125
|
+
config.rules.css = {
|
|
126
|
+
enabled: true,
|
|
127
|
+
pattern:
|
|
128
|
+
"uploads\\/plugin-version\\/\\d+\\/style_file_name\\/.*\\.css(\\?.*)?",
|
|
129
|
+
redirectUrl: "",
|
|
130
|
+
returnBlank: true,
|
|
131
|
+
useCustomPattern: false,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return config;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Update toolbar icon based on state
|
|
140
|
+
function updateIcon() {
|
|
141
|
+
const iconPath = config.enabled ? "icons/gx_on" : "icons/gx_off";
|
|
142
|
+
chrome.action.setIcon({
|
|
143
|
+
path: {
|
|
144
|
+
16: `${iconPath}_16.png`,
|
|
145
|
+
32: `${iconPath}_32.png`,
|
|
146
|
+
64: `${iconPath}_64.png`,
|
|
147
|
+
128: `${iconPath}_128.png`,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const title = config.enabled
|
|
152
|
+
? "JavaScript Proxy (ON)"
|
|
153
|
+
: "JavaScript Proxy (OFF)";
|
|
154
|
+
chrome.action.setTitle({ title });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Cache management functions
|
|
158
|
+
async function clearCacheForPattern(urlPattern) {
|
|
159
|
+
try {
|
|
160
|
+
console.log(`[JavaScript Proxy] Clearing cache for pattern: ${urlPattern}`);
|
|
161
|
+
|
|
162
|
+
// Clear browser cache
|
|
163
|
+
await chrome.browsingData.removeCache({
|
|
164
|
+
origins: [], // Empty array means all origins
|
|
165
|
+
since: 0, // Clear all cache entries
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Clear service worker caches by injecting script into active tabs
|
|
169
|
+
await clearServiceWorkerCaches(urlPattern);
|
|
170
|
+
|
|
171
|
+
console.log("[JavaScript Proxy] Cache cleared successfully");
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error("[JavaScript Proxy] Error clearing cache:", error);
|
|
174
|
+
throw error; // Re-throw to let the caller handle the error
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function clearServiceWorkerCaches(urlPattern) {
|
|
179
|
+
try {
|
|
180
|
+
// Get all active tabs
|
|
181
|
+
const tabs = await chrome.tabs.query({});
|
|
182
|
+
|
|
183
|
+
// Inject the script into each tab
|
|
184
|
+
for (const tab of tabs) {
|
|
185
|
+
try {
|
|
186
|
+
if (
|
|
187
|
+
tab.url &&
|
|
188
|
+
!tab.url.startsWith("chrome://") &&
|
|
189
|
+
!tab.url.startsWith("chrome-extension://")
|
|
190
|
+
) {
|
|
191
|
+
await chrome.scripting.executeScript({
|
|
192
|
+
target: { tabId: tab.id },
|
|
193
|
+
func: async function (pattern) {
|
|
194
|
+
try {
|
|
195
|
+
if ("caches" in window) {
|
|
196
|
+
const cacheNames = await caches.keys();
|
|
197
|
+
const regex = new RegExp(pattern, "i");
|
|
198
|
+
|
|
199
|
+
for (const cacheName of cacheNames) {
|
|
200
|
+
const cache = await caches.open(cacheName);
|
|
201
|
+
const requests = await cache.keys();
|
|
202
|
+
|
|
203
|
+
for (const request of requests) {
|
|
204
|
+
if (regex.test(request.url)) {
|
|
205
|
+
await cache.delete(request);
|
|
206
|
+
console.log(
|
|
207
|
+
"[JavaScript Proxy] Deleted from cache:",
|
|
208
|
+
request.url
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Also try to update service worker registration
|
|
215
|
+
if (
|
|
216
|
+
"serviceWorker" in navigator &&
|
|
217
|
+
navigator.serviceWorker.controller
|
|
218
|
+
) {
|
|
219
|
+
navigator.serviceWorker.controller.postMessage({
|
|
220
|
+
type: "CLEAR_CACHE_FOR_PATTERN",
|
|
221
|
+
pattern: pattern,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error(
|
|
227
|
+
"[JavaScript Proxy] Error clearing service worker cache:",
|
|
228
|
+
error
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
args: [urlPattern.replace(/\\/g, "\\\\")],
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
// Tab might not allow script injection, continue with others
|
|
237
|
+
console.debug(
|
|
238
|
+
`[JavaScript Proxy] Could not inject cache clear script into tab ${tab.id}:`,
|
|
239
|
+
error
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log("[JavaScript Proxy] Service worker cache clearing attempted");
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error(
|
|
247
|
+
"[JavaScript Proxy] Error clearing service worker caches:",
|
|
248
|
+
error
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function addToCacheBlacklist(url) {
|
|
254
|
+
try {
|
|
255
|
+
const urlObj = new URL(url);
|
|
256
|
+
const origin = urlObj.origin;
|
|
257
|
+
cacheBlacklist.add(origin);
|
|
258
|
+
console.log(`[JavaScript Proxy] Added ${origin} to cache blacklist`);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error("[JavaScript Proxy] Error adding to cache blacklist:", error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function shouldDisableCache(url) {
|
|
265
|
+
if (!config.disableCacheForRedirects) return false;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// Check against all enabled rules
|
|
269
|
+
for (const [ruleType, rule] of Object.entries(config.rules || {})) {
|
|
270
|
+
if (rule.enabled) {
|
|
271
|
+
const pattern =
|
|
272
|
+
rule.useCustomPattern && rule.pattern ? rule.pattern : rule.pattern;
|
|
273
|
+
const regex = new RegExp(pattern, "i");
|
|
274
|
+
if (regex.test(url)) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Fallback to legacy pattern for backward compatibility
|
|
281
|
+
if (config.urlPattern) {
|
|
282
|
+
const regex = new RegExp(config.urlPattern, "i");
|
|
283
|
+
return regex.test(url);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return false;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error(
|
|
289
|
+
"[JavaScript Proxy] Error checking cache disable pattern:",
|
|
290
|
+
error
|
|
291
|
+
);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Handle web requests (Chrome Manifest V3 uses declarativeNetRequest)
|
|
297
|
+
async function updateRequestListener() {
|
|
298
|
+
// Remove all existing rules
|
|
299
|
+
try {
|
|
300
|
+
await chrome.declarativeNetRequest.updateDynamicRules({
|
|
301
|
+
removeRuleIds: [1, 2, 3, 4, 5], // Remove up to 5 rules
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
// Rules might not exist, that's fine
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (config.enabled && !config.maskingMode) {
|
|
308
|
+
const rulesToAdd = [];
|
|
309
|
+
let ruleId = 1;
|
|
310
|
+
|
|
311
|
+
// Process each rule type
|
|
312
|
+
for (const [ruleType, rule] of Object.entries(config.rules || {})) {
|
|
313
|
+
if (rule.enabled && rule.pattern) {
|
|
314
|
+
if (rule.returnBlank) {
|
|
315
|
+
// For blank returns, we'll handle in webRequest (can't return blank with declarativeNetRequest)
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (rule.redirectUrl) {
|
|
320
|
+
// Clear cache if enabled
|
|
321
|
+
if (config.clearCacheOnEnable) {
|
|
322
|
+
await clearCacheForPattern(rule.pattern);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Add redirect rule using declarativeNetRequest
|
|
326
|
+
const redirectRule = {
|
|
327
|
+
id: ruleId++,
|
|
328
|
+
priority: 1,
|
|
329
|
+
action: {
|
|
330
|
+
type: "redirect",
|
|
331
|
+
redirect: {
|
|
332
|
+
url: rule.redirectUrl,
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
condition: {
|
|
336
|
+
regexFilter: rule.pattern,
|
|
337
|
+
resourceTypes: ruleType === "js" ? ["script"] : ["stylesheet"],
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
rulesToAdd.push(redirectRule);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Add legacy rule for backward compatibility
|
|
347
|
+
if (config.urlPattern && config.redirectUrl && rulesToAdd.length === 0) {
|
|
348
|
+
if (config.clearCacheOnEnable) {
|
|
349
|
+
await clearCacheForPattern(config.urlPattern);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const legacyRule = {
|
|
353
|
+
id: ruleId++,
|
|
354
|
+
priority: 1,
|
|
355
|
+
action: {
|
|
356
|
+
type: "redirect",
|
|
357
|
+
redirect: {
|
|
358
|
+
url: config.redirectUrl,
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
condition: {
|
|
362
|
+
regexFilter: config.urlPattern,
|
|
363
|
+
resourceTypes: ["script"],
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
rulesToAdd.push(legacyRule);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (rulesToAdd.length > 0) {
|
|
371
|
+
try {
|
|
372
|
+
await chrome.declarativeNetRequest.updateDynamicRules({
|
|
373
|
+
addRules: rulesToAdd,
|
|
374
|
+
});
|
|
375
|
+
console.log("[JavaScript Proxy] Added redirect rules:", rulesToAdd);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.error("[JavaScript Proxy] Error adding redirect rules:", error);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// For masking mode or blank returns, we need webRequest API
|
|
383
|
+
updateWebRequestListener();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Handle web requests for masking mode and blank returns (fallback to webRequest for complex logic)
|
|
387
|
+
function handleRequest(details) {
|
|
388
|
+
if (!config.enabled) return {};
|
|
389
|
+
|
|
390
|
+
// Skip data URLs and blob URLs
|
|
391
|
+
if (
|
|
392
|
+
details.url.startsWith("data:") ||
|
|
393
|
+
details.url.startsWith("blob:") ||
|
|
394
|
+
details.url.startsWith("chrome:") ||
|
|
395
|
+
details.url.startsWith("chrome-extension:")
|
|
396
|
+
) {
|
|
397
|
+
return {};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
// Check each rule type
|
|
402
|
+
for (const [ruleType, rule] of Object.entries(config.rules || {})) {
|
|
403
|
+
if (!rule.enabled || !rule.pattern) continue;
|
|
404
|
+
|
|
405
|
+
// Check if this rule type matches the resource type
|
|
406
|
+
if (ruleType === "js" && details.type !== "script") continue;
|
|
407
|
+
if (ruleType === "css" && details.type !== "stylesheet") continue;
|
|
408
|
+
|
|
409
|
+
const regex = new RegExp(rule.pattern, "i");
|
|
410
|
+
|
|
411
|
+
// Test the full URL against the pattern
|
|
412
|
+
if (regex.test(details.url)) {
|
|
413
|
+
// Handle blank return for CSS
|
|
414
|
+
if (rule.returnBlank) {
|
|
415
|
+
console.log(
|
|
416
|
+
`[JavaScript Proxy] Returning blank for ${ruleType}: ${details.url}`
|
|
417
|
+
);
|
|
418
|
+
// Return a data URL with empty content
|
|
419
|
+
const blankUrl =
|
|
420
|
+
ruleType === "css"
|
|
421
|
+
? "data:text/css;charset=utf-8,"
|
|
422
|
+
: "data:text/javascript;charset=utf-8,";
|
|
423
|
+
return { redirectUrl: blankUrl };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Handle masking mode or rules without declarativeNetRequest redirect
|
|
427
|
+
if (config.maskingMode || !rule.redirectUrl) {
|
|
428
|
+
if (rule.redirectUrl) {
|
|
429
|
+
let newUrl = rule.redirectUrl;
|
|
430
|
+
|
|
431
|
+
// Ensure URL has protocol
|
|
432
|
+
if (!newUrl.includes("://")) {
|
|
433
|
+
newUrl = "https://" + newUrl;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Check if this would create a redirect loop
|
|
437
|
+
if (details.url === newUrl || details.url.includes("localhost")) {
|
|
438
|
+
console.log(
|
|
439
|
+
`[JavaScript Proxy] Skipping redirect loop: ${details.url}`
|
|
440
|
+
);
|
|
441
|
+
return {};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Store original URL for header modification
|
|
445
|
+
storeOriginalUrl(details.requestId, details.url, newUrl);
|
|
446
|
+
|
|
447
|
+
console.log(
|
|
448
|
+
`[JavaScript Proxy] Masking ${ruleType}: ${details.url} (routing to ${newUrl})`
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
return { redirectUrl: newUrl };
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Fallback to legacy pattern for backward compatibility
|
|
458
|
+
if (config.maskingMode && config.urlPattern) {
|
|
459
|
+
const regex = new RegExp(config.urlPattern, "i");
|
|
460
|
+
|
|
461
|
+
if (regex.test(details.url) && details.type === "script") {
|
|
462
|
+
let newUrl = config.redirectUrl;
|
|
463
|
+
|
|
464
|
+
if (!newUrl.includes("://")) {
|
|
465
|
+
newUrl = "https://" + newUrl;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (details.url === newUrl || details.url.includes("localhost")) {
|
|
469
|
+
console.log(
|
|
470
|
+
`[JavaScript Proxy] Skipping redirect loop: ${details.url}`
|
|
471
|
+
);
|
|
472
|
+
return {};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
storeOriginalUrl(details.requestId, details.url, newUrl);
|
|
476
|
+
|
|
477
|
+
console.log(
|
|
478
|
+
`[JavaScript Proxy] Legacy masking: ${details.url} (routing to ${newUrl})`
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
return { redirectUrl: newUrl };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error("[JavaScript Proxy] Error processing request:", error);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Store mapping of request IDs to proxy info for header modification
|
|
492
|
+
const pendingProxyRequests = new Map();
|
|
493
|
+
|
|
494
|
+
function storeOriginalUrl(requestId, originalUrl, redirectUrl) {
|
|
495
|
+
pendingProxyRequests.set(requestId, {
|
|
496
|
+
originalUrl,
|
|
497
|
+
redirectUrl,
|
|
498
|
+
timestamp: Date.now(),
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Clean up old entries after 30 seconds
|
|
502
|
+
setTimeout(() => {
|
|
503
|
+
pendingProxyRequests.delete(requestId);
|
|
504
|
+
}, 30000);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Update webRequest listener for masking mode
|
|
508
|
+
function updateWebRequestListener() {
|
|
509
|
+
// Remove existing listeners
|
|
510
|
+
if (
|
|
511
|
+
chrome.webRequest &&
|
|
512
|
+
chrome.webRequest.onBeforeRequest.hasListener(handleRequest)
|
|
513
|
+
) {
|
|
514
|
+
chrome.webRequest.onBeforeRequest.removeListener(handleRequest);
|
|
515
|
+
}
|
|
516
|
+
if (
|
|
517
|
+
chrome.webRequest &&
|
|
518
|
+
chrome.webRequest.onBeforeSendHeaders.hasListener(handleRequestHeaders)
|
|
519
|
+
) {
|
|
520
|
+
chrome.webRequest.onBeforeSendHeaders.removeListener(handleRequestHeaders);
|
|
521
|
+
}
|
|
522
|
+
if (
|
|
523
|
+
chrome.webRequest &&
|
|
524
|
+
chrome.webRequest.onHeadersReceived.hasListener(handleResponseHeaders)
|
|
525
|
+
) {
|
|
526
|
+
chrome.webRequest.onHeadersReceived.removeListener(handleResponseHeaders);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Check if we need webRequest for any features
|
|
530
|
+
const needsWebRequest =
|
|
531
|
+
config.maskingMode ||
|
|
532
|
+
config.disableCacheForRedirects ||
|
|
533
|
+
Object.values(config.rules || {}).some(
|
|
534
|
+
(rule) => rule.enabled && rule.returnBlank
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
// Add webRequest listeners for masking mode, cache control, or blank returns
|
|
538
|
+
if (config.enabled && chrome.webRequest && needsWebRequest) {
|
|
539
|
+
chrome.webRequest.onBeforeRequest.addListener(
|
|
540
|
+
handleRequest,
|
|
541
|
+
{ urls: ["<all_urls>"] },
|
|
542
|
+
["blocking"]
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
chrome.webRequest.onBeforeSendHeaders.addListener(
|
|
546
|
+
handleRequestHeaders,
|
|
547
|
+
{ urls: ["<all_urls>"] },
|
|
548
|
+
["blocking", "requestHeaders"]
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
chrome.webRequest.onHeadersReceived.addListener(
|
|
552
|
+
handleResponseHeaders,
|
|
553
|
+
{ urls: ["<all_urls>"] },
|
|
554
|
+
["blocking", "responseHeaders"]
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
console.log(
|
|
558
|
+
"[JavaScript Proxy] WebRequest listeners enabled for masking mode"
|
|
559
|
+
);
|
|
560
|
+
} else {
|
|
561
|
+
console.log("[JavaScript Proxy] WebRequest listeners disabled");
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Handle request headers for masking mode and cache control
|
|
566
|
+
function handleRequestHeaders(details) {
|
|
567
|
+
if (!config.enabled) return {};
|
|
568
|
+
|
|
569
|
+
// Initialize modifications
|
|
570
|
+
const modifications = { requestHeaders: details.requestHeaders };
|
|
571
|
+
let hasModifications = false;
|
|
572
|
+
|
|
573
|
+
// Handle masking mode
|
|
574
|
+
if (config.maskingMode) {
|
|
575
|
+
const proxyInfo = pendingProxyRequests.get(details.requestId);
|
|
576
|
+
if (proxyInfo) {
|
|
577
|
+
// Add/modify headers to properly route to localhost
|
|
578
|
+
const hostHeader = modifications.requestHeaders.find(
|
|
579
|
+
(h) => h.name.toLowerCase() === "host"
|
|
580
|
+
);
|
|
581
|
+
if (hostHeader) {
|
|
582
|
+
try {
|
|
583
|
+
const proxyUrl = new URL(proxyInfo.redirectUrl);
|
|
584
|
+
hostHeader.value = proxyUrl.host;
|
|
585
|
+
hasModifications = true;
|
|
586
|
+
} catch (error) {
|
|
587
|
+
console.error("[JavaScript Proxy] Error parsing proxy URL:", error);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Handle cache control for URLs matching our pattern
|
|
594
|
+
if (config.disableCacheForRedirects && shouldDisableCache(details.url)) {
|
|
595
|
+
// Add cache-busting headers
|
|
596
|
+
const cacheHeaders = [
|
|
597
|
+
{
|
|
598
|
+
name: "Cache-Control",
|
|
599
|
+
value: "no-cache, no-store, must-revalidate, max-age=0",
|
|
600
|
+
},
|
|
601
|
+
{ name: "Pragma", value: "no-cache" },
|
|
602
|
+
{ name: "Expires", value: "0" },
|
|
603
|
+
{ name: "If-None-Match", value: "*" },
|
|
604
|
+
{ name: "If-Modified-Since", value: "Thu, 01 Jan 1970 00:00:00 GMT" },
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
cacheHeaders.forEach((cacheHeader) => {
|
|
608
|
+
const existingHeader = modifications.requestHeaders.find(
|
|
609
|
+
(h) => h.name.toLowerCase() === cacheHeader.name.toLowerCase()
|
|
610
|
+
);
|
|
611
|
+
if (existingHeader) {
|
|
612
|
+
existingHeader.value = cacheHeader.value;
|
|
613
|
+
} else {
|
|
614
|
+
modifications.requestHeaders.push(cacheHeader);
|
|
615
|
+
}
|
|
616
|
+
hasModifications = true;
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
console.log(
|
|
620
|
+
`[JavaScript Proxy] Added cache-busting headers for: ${details.url}`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return hasModifications ? modifications : {};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Handle response headers for masking mode and cache control
|
|
628
|
+
function handleResponseHeaders(details) {
|
|
629
|
+
if (!config.enabled) return {};
|
|
630
|
+
|
|
631
|
+
// Initialize modifications
|
|
632
|
+
const modifications = { responseHeaders: details.responseHeaders };
|
|
633
|
+
let hasModifications = false;
|
|
634
|
+
|
|
635
|
+
// Handle masking mode
|
|
636
|
+
if (config.maskingMode) {
|
|
637
|
+
const proxyInfo = pendingProxyRequests.get(details.requestId);
|
|
638
|
+
if (proxyInfo) {
|
|
639
|
+
// Add CORS headers if needed
|
|
640
|
+
const corsHeaders = [
|
|
641
|
+
{ name: "Access-Control-Allow-Origin", value: "*" },
|
|
642
|
+
{
|
|
643
|
+
name: "Access-Control-Allow-Methods",
|
|
644
|
+
value: "GET, POST, PUT, DELETE, OPTIONS",
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
name: "Access-Control-Allow-Headers",
|
|
648
|
+
value: "Content-Type, Authorization",
|
|
649
|
+
},
|
|
650
|
+
];
|
|
651
|
+
|
|
652
|
+
corsHeaders.forEach((corsHeader) => {
|
|
653
|
+
const existingHeader = modifications.responseHeaders.find(
|
|
654
|
+
(h) => h.name.toLowerCase() === corsHeader.name.toLowerCase()
|
|
655
|
+
);
|
|
656
|
+
if (existingHeader) {
|
|
657
|
+
existingHeader.value = corsHeader.value;
|
|
658
|
+
} else {
|
|
659
|
+
modifications.responseHeaders.push(corsHeader);
|
|
660
|
+
}
|
|
661
|
+
hasModifications = true;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Handle cache control for URLs matching our pattern
|
|
667
|
+
if (config.disableCacheForRedirects && shouldDisableCache(details.url)) {
|
|
668
|
+
// Add aggressive response cache-busting headers
|
|
669
|
+
const cacheHeaders = [
|
|
670
|
+
{
|
|
671
|
+
name: "Cache-Control",
|
|
672
|
+
value: "no-cache, no-store, must-revalidate, max-age=0, s-maxage=0",
|
|
673
|
+
},
|
|
674
|
+
{ name: "Pragma", value: "no-cache" },
|
|
675
|
+
{ name: "Expires", value: "Thu, 01 Jan 1970 00:00:00 GMT" },
|
|
676
|
+
{ name: "Last-Modified", value: new Date().toUTCString() },
|
|
677
|
+
{ name: "ETag", value: `"${Date.now()}-${Math.random()}"` },
|
|
678
|
+
{ name: "Vary", value: "*" },
|
|
679
|
+
{ name: "X-Cache-Control", value: "no-cache" },
|
|
680
|
+
];
|
|
681
|
+
|
|
682
|
+
cacheHeaders.forEach((cacheHeader) => {
|
|
683
|
+
const existingHeader = modifications.responseHeaders.find(
|
|
684
|
+
(h) => h.name.toLowerCase() === cacheHeader.name.toLowerCase()
|
|
685
|
+
);
|
|
686
|
+
if (existingHeader) {
|
|
687
|
+
existingHeader.value = cacheHeader.value;
|
|
688
|
+
} else {
|
|
689
|
+
modifications.responseHeaders.push(cacheHeader);
|
|
690
|
+
}
|
|
691
|
+
hasModifications = true;
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
console.log(
|
|
695
|
+
`[JavaScript Proxy] Added response cache-busting headers for: ${details.url}`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (hasModifications) {
|
|
700
|
+
console.log(
|
|
701
|
+
`[JavaScript Proxy] Modified response headers for: ${details.url}`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return hasModifications ? modifications : {};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Show desktop notification for proxy activity (when using masking mode)
|
|
709
|
+
function showProxyNotification(from, to, requestType = "") {
|
|
710
|
+
const shortFrom = from.length > 50 ? from.substring(0, 47) + "..." : from;
|
|
711
|
+
const shortTo = to.length > 30 ? to.substring(0, 27) + "..." : to;
|
|
712
|
+
|
|
713
|
+
const notificationId = `proxy-${Date.now()}`;
|
|
714
|
+
|
|
715
|
+
chrome.notifications.create(notificationId, {
|
|
716
|
+
type: "basic",
|
|
717
|
+
iconUrl: "icons/gx_on_64.png",
|
|
718
|
+
title: "JavaScript Proxy Active",
|
|
719
|
+
message: `${requestType}\n${shortFrom}\n→ ${shortTo}`,
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
// Clear existing timeout for this type
|
|
723
|
+
if (notificationTimeouts.has("proxy")) {
|
|
724
|
+
clearTimeout(notificationTimeouts.get("proxy"));
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Auto-dismiss after 3 seconds
|
|
728
|
+
const timeoutId = setTimeout(() => {
|
|
729
|
+
chrome.notifications.clear(notificationId);
|
|
730
|
+
notificationTimeouts.delete("proxy");
|
|
731
|
+
}, 3000);
|
|
732
|
+
|
|
733
|
+
notificationTimeouts.set("proxy", timeoutId);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// ============================================================
|
|
737
|
+
// DevTools Panel Communication
|
|
738
|
+
// ============================================================
|
|
739
|
+
|
|
740
|
+
const devtoolsConnections = new Map();
|
|
741
|
+
|
|
742
|
+
chrome.runtime.onConnect.addListener((port) => {
|
|
743
|
+
if (port.name === 'gxp-devtools-panel') {
|
|
744
|
+
const extensionListener = (message, port) => {
|
|
745
|
+
if (message.name === 'init') {
|
|
746
|
+
devtoolsConnections.set(message.tabId, port);
|
|
747
|
+
console.log('[GxP DevTools] Panel connected for tab:', message.tabId);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Forward messages to content script
|
|
751
|
+
if (message.tabId && message.action) {
|
|
752
|
+
chrome.tabs.sendMessage(message.tabId, message, (response) => {
|
|
753
|
+
port.postMessage(response);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
port.onMessage.addListener(extensionListener);
|
|
759
|
+
|
|
760
|
+
port.onDisconnect.addListener(() => {
|
|
761
|
+
// Remove the connection when panel is closed
|
|
762
|
+
for (const [tabId, connectedPort] of devtoolsConnections.entries()) {
|
|
763
|
+
if (connectedPort === port) {
|
|
764
|
+
devtoolsConnections.delete(tabId);
|
|
765
|
+
console.log('[GxP DevTools] Panel disconnected for tab:', tabId);
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// Forward messages from content script to DevTools panel
|
|
774
|
+
function forwardToDevTools(tabId, message) {
|
|
775
|
+
const port = devtoolsConnections.get(tabId);
|
|
776
|
+
if (port) {
|
|
777
|
+
try {
|
|
778
|
+
port.postMessage(message);
|
|
779
|
+
} catch (error) {
|
|
780
|
+
console.error('[GxP DevTools] Error forwarding to panel:', error);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Handle messages from popup
|
|
786
|
+
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
787
|
+
console.log("[JavaScript Proxy] Received message:", request);
|
|
788
|
+
|
|
789
|
+
// Handle messages from content script about element selection
|
|
790
|
+
if (request.type === 'elementSelected' && sender.tab) {
|
|
791
|
+
forwardToDevTools(sender.tab.id, request);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
switch (request.action) {
|
|
796
|
+
case "toggleProxy":
|
|
797
|
+
config.enabled = request.enabled;
|
|
798
|
+
chrome.storage.sync.set({ enabled: config.enabled });
|
|
799
|
+
updateIcon();
|
|
800
|
+
updateRequestListener();
|
|
801
|
+
// Clear cache when enabling the proxy
|
|
802
|
+
if (config.enabled && config.clearCacheOnEnable) {
|
|
803
|
+
clearCacheForPattern(config.urlPattern);
|
|
804
|
+
}
|
|
805
|
+
sendResponse({ success: true, enabled: config.enabled });
|
|
806
|
+
return false; // Synchronous response
|
|
807
|
+
|
|
808
|
+
case "updateConfig":
|
|
809
|
+
config = { ...config, ...request.config };
|
|
810
|
+
chrome.storage.sync.set(config);
|
|
811
|
+
updateIcon();
|
|
812
|
+
updateRequestListener();
|
|
813
|
+
sendResponse({ success: true });
|
|
814
|
+
return false; // Synchronous response
|
|
815
|
+
|
|
816
|
+
case "getConfig":
|
|
817
|
+
sendResponse(config);
|
|
818
|
+
return false; // Synchronous response
|
|
819
|
+
|
|
820
|
+
case "clearCache":
|
|
821
|
+
console.log(
|
|
822
|
+
"[JavaScript Proxy] Starting cache clear for pattern:",
|
|
823
|
+
config.urlPattern
|
|
824
|
+
);
|
|
825
|
+
clearCacheForPattern(config.urlPattern)
|
|
826
|
+
.then(() => {
|
|
827
|
+
console.log("[JavaScript Proxy] Cache clear completed successfully");
|
|
828
|
+
sendResponse({ success: true });
|
|
829
|
+
})
|
|
830
|
+
.catch((error) => {
|
|
831
|
+
console.error("[JavaScript Proxy] Cache clear error:", error);
|
|
832
|
+
sendResponse({
|
|
833
|
+
success: false,
|
|
834
|
+
error: error.message || error.toString(),
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
return true; // Keep message channel open for async response
|
|
838
|
+
|
|
839
|
+
default:
|
|
840
|
+
console.warn("[JavaScript Proxy] Unknown action:", request.action);
|
|
841
|
+
sendResponse({ success: false, error: "Unknown action" });
|
|
842
|
+
return false; // Synchronous response
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// Clear expired proxy requests periodically
|
|
847
|
+
setInterval(() => {
|
|
848
|
+
const now = Date.now();
|
|
849
|
+
for (const [requestId, info] of pendingProxyRequests.entries()) {
|
|
850
|
+
if (now - info.timestamp > 30000) {
|
|
851
|
+
pendingProxyRequests.delete(requestId);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}, 10000);
|
|
855
|
+
|
|
856
|
+
// Initialize on startup
|
|
857
|
+
initializeExtension();
|