@gxp-dev/tools 2.0.5

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.
Files changed (145) hide show
  1. package/.github/workflows/npm-publish.yml +48 -0
  2. package/CLAUDE.md +400 -0
  3. package/README.md +247 -0
  4. package/REFACTOR_PLAN.md +194 -0
  5. package/bin/gx-devtools.js +87 -0
  6. package/bin/lib/cli.js +251 -0
  7. package/bin/lib/commands/assets.js +337 -0
  8. package/bin/lib/commands/build.js +259 -0
  9. package/bin/lib/commands/datastore.js +433 -0
  10. package/bin/lib/commands/dev.js +328 -0
  11. package/bin/lib/commands/extensions.js +298 -0
  12. package/bin/lib/commands/index.js +35 -0
  13. package/bin/lib/commands/init.js +307 -0
  14. package/bin/lib/commands/publish.js +189 -0
  15. package/bin/lib/commands/socket.js +158 -0
  16. package/bin/lib/commands/ssl.js +47 -0
  17. package/bin/lib/constants.js +120 -0
  18. package/bin/lib/tui/App.tsx +600 -0
  19. package/bin/lib/tui/components/CommandInput.tsx +278 -0
  20. package/bin/lib/tui/components/GeminiPanel.tsx +161 -0
  21. package/bin/lib/tui/components/Header.tsx +27 -0
  22. package/bin/lib/tui/components/LogPanel.tsx +122 -0
  23. package/bin/lib/tui/components/TabBar.tsx +56 -0
  24. package/bin/lib/tui/components/WelcomeScreen.tsx +80 -0
  25. package/bin/lib/tui/index.tsx +63 -0
  26. package/bin/lib/tui/services/ExtensionService.ts +122 -0
  27. package/bin/lib/tui/services/GeminiService.ts +395 -0
  28. package/bin/lib/tui/services/ServiceManager.ts +336 -0
  29. package/bin/lib/tui/services/SocketService.ts +204 -0
  30. package/bin/lib/tui/services/ViteService.ts +107 -0
  31. package/bin/lib/tui/services/index.ts +13 -0
  32. package/bin/lib/utils/files.js +180 -0
  33. package/bin/lib/utils/index.js +17 -0
  34. package/bin/lib/utils/paths.js +138 -0
  35. package/bin/lib/utils/prompts.js +71 -0
  36. package/bin/lib/utils/ssl.js +233 -0
  37. package/browser-extensions/README.md +1 -0
  38. package/browser-extensions/chrome/background.js +857 -0
  39. package/browser-extensions/chrome/content.js +51 -0
  40. package/browser-extensions/chrome/devtools.html +9 -0
  41. package/browser-extensions/chrome/devtools.js +23 -0
  42. package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
  43. package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
  44. package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
  45. package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
  46. package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
  47. package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
  48. package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
  49. package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
  50. package/browser-extensions/chrome/inspector.js +1087 -0
  51. package/browser-extensions/chrome/manifest.json +70 -0
  52. package/browser-extensions/chrome/panel.html +638 -0
  53. package/browser-extensions/chrome/panel.js +862 -0
  54. package/browser-extensions/chrome/popup.html +399 -0
  55. package/browser-extensions/chrome/popup.js +515 -0
  56. package/browser-extensions/chrome/rules.json +1 -0
  57. package/browser-extensions/chrome/test-chrome.html +145 -0
  58. package/browser-extensions/chrome/test-mixed-content.html +190 -0
  59. package/browser-extensions/chrome/test-uri-pattern.html +199 -0
  60. package/browser-extensions/firefox/README.md +134 -0
  61. package/browser-extensions/firefox/background.js +804 -0
  62. package/browser-extensions/firefox/content.js +120 -0
  63. package/browser-extensions/firefox/debug-errors.html +229 -0
  64. package/browser-extensions/firefox/debug-https.html +113 -0
  65. package/browser-extensions/firefox/devtools.html +9 -0
  66. package/browser-extensions/firefox/devtools.js +24 -0
  67. package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
  68. package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
  69. package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
  70. package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
  71. package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
  72. package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
  73. package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
  74. package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
  75. package/browser-extensions/firefox/inspector.js +1087 -0
  76. package/browser-extensions/firefox/manifest.json +67 -0
  77. package/browser-extensions/firefox/panel.html +638 -0
  78. package/browser-extensions/firefox/panel.js +862 -0
  79. package/browser-extensions/firefox/popup.html +525 -0
  80. package/browser-extensions/firefox/popup.js +536 -0
  81. package/browser-extensions/firefox/test-gramercy.html +126 -0
  82. package/browser-extensions/firefox/test-imports.html +58 -0
  83. package/browser-extensions/firefox/test-masking.html +147 -0
  84. package/browser-extensions/firefox/test-uri-pattern.html +199 -0
  85. package/docs/DOCUSAURUS_IMPORT.md +378 -0
  86. package/docs/_category_.json +8 -0
  87. package/docs/app-manifest.md +272 -0
  88. package/docs/building-for-platform.md +315 -0
  89. package/docs/dev-tools.md +291 -0
  90. package/docs/getting-started.md +180 -0
  91. package/docs/gxp-store.md +305 -0
  92. package/docs/index.md +44 -0
  93. package/package.json +77 -0
  94. package/runtime/PortalContainer.vue +326 -0
  95. package/runtime/dev-tools/DevToolsModal.vue +217 -0
  96. package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
  97. package/runtime/dev-tools/MockDataEditor.vue +621 -0
  98. package/runtime/dev-tools/SocketSimulator.vue +562 -0
  99. package/runtime/dev-tools/StoreInspector.vue +644 -0
  100. package/runtime/dev-tools/index.js +6 -0
  101. package/runtime/gxpStringsPlugin.js +428 -0
  102. package/runtime/index.html +22 -0
  103. package/runtime/main.js +32 -0
  104. package/runtime/mock-api/auth-middleware.js +97 -0
  105. package/runtime/mock-api/image-generator.js +221 -0
  106. package/runtime/mock-api/index.js +197 -0
  107. package/runtime/mock-api/response-generator.js +394 -0
  108. package/runtime/mock-api/route-generator.js +323 -0
  109. package/runtime/mock-api/socket-triggers.js +371 -0
  110. package/runtime/mock-api/spec-loader.js +300 -0
  111. package/runtime/server.js +180 -0
  112. package/runtime/stores/gxpPortalConfigStore.js +554 -0
  113. package/runtime/stores/index.js +6 -0
  114. package/runtime/vite-inspector-plugin.js +749 -0
  115. package/runtime/vite-source-tracker-plugin.js +232 -0
  116. package/runtime/vite.config.js +402 -0
  117. package/scripts/launch-chrome.js +90 -0
  118. package/scripts/pack-chrome.js +91 -0
  119. package/socket-events/AiSessionMessageCreated.json +18 -0
  120. package/socket-events/SocialStreamPostCreated.json +24 -0
  121. package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
  122. package/template/README.md +332 -0
  123. package/template/app-manifest.json +32 -0
  124. package/template/dev-assets/images/avatar-placeholder.png +0 -0
  125. package/template/dev-assets/images/background-placeholder.jpg +0 -0
  126. package/template/dev-assets/images/banner-placeholder.jpg +0 -0
  127. package/template/dev-assets/images/icon-placeholder.png +0 -0
  128. package/template/dev-assets/images/logo-placeholder.png +0 -0
  129. package/template/dev-assets/images/product-placeholder.jpg +0 -0
  130. package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
  131. package/template/env.example +51 -0
  132. package/template/gitignore +53 -0
  133. package/template/index.html +22 -0
  134. package/template/main.js +28 -0
  135. package/template/src/DemoPage.vue +459 -0
  136. package/template/src/Plugin.vue +38 -0
  137. package/template/src/stores/index.js +9 -0
  138. package/template/src/stores/test-data.json +173 -0
  139. package/template/theme-layouts/AdditionalStyling.css +0 -0
  140. package/template/theme-layouts/PrivateLayout.vue +39 -0
  141. package/template/theme-layouts/PublicLayout.vue +39 -0
  142. package/template/theme-layouts/SystemLayout.vue +39 -0
  143. package/template/vite.config.js +333 -0
  144. package/tsconfig.tui.json +21 -0
  145. package/vite.config.js +164 -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();