diff options
| -rw-r--r-- | .envrc | 1 | ||||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | config/webpack/plugins/create-manifest-webpack-plugin/index.js | 6 | ||||
| -rw-r--r-- | config/webpack/plugins/create-rules-webpack-plugin.js | 23 | ||||
| -rw-r--r-- | flake.lock | 47 | ||||
| -rw-r--r-- | flake.nix | 23 | ||||
| -rw-r--r-- | src/rules/block-scripts.json | 9 | ||||
| -rw-r--r-- | src/rules/redirect-jquery-script.json | 12 | ||||
| -rw-r--r-- | src/scripts/common/login/form-caching.ts | 22 | ||||
| -rw-r--r-- | src/shared/intercept-jquery-ajax.ts | 59 |
10 files changed, 114 insertions, 89 deletions
@@ -0,0 +1 @@ +use flake @@ -1,3 +1,4 @@ dist node_modules **/*.log +.direnv diff --git a/config/webpack/plugins/create-manifest-webpack-plugin/index.js b/config/webpack/plugins/create-manifest-webpack-plugin/index.js index f2e28b2..e28c695 100644 --- a/config/webpack/plugins/create-manifest-webpack-plugin/index.js +++ b/config/webpack/plugins/create-manifest-webpack-plugin/index.js @@ -104,6 +104,12 @@ class CreateManifestPlugin { } } + // `CreateRulesPlugin` skips emitting `rules.json` when `src/rules/` + // holds no rule files, so drop the ruleset reference with it. + if (!("rules.json" in assets)) { + delete manifest.declarative_net_request; + } + const injectedSharedModules = this.sortedSharedModules .filter((sharedModule) => this.injectedSharedModulesImportedCount[sharedModule] > 0) .map((sharedModule) => `shared/${getFileName(sharedModule, "shared")}.js`); diff --git a/config/webpack/plugins/create-rules-webpack-plugin.js b/config/webpack/plugins/create-rules-webpack-plugin.js index ce2fbef..e2a2568 100644 --- a/config/webpack/plugins/create-rules-webpack-plugin.js +++ b/config/webpack/plugins/create-rules-webpack-plugin.js @@ -7,6 +7,12 @@ import webpack from "webpack"; * Webpack plugin that reads all JSON rule files from `src/rules/`, * assigns sequential IDs to each rule, and emits a single `rules.json` * asset used by `declarativeNetRequest`. + * + * If `src/rules/` is missing or contains no JSON rule files, no asset is + * emitted and a build warning is raised. `CreateManifestPlugin` relies on + * that: it drops the `declarative_net_request` manifest key when + * `rules.json` is absent, so the extension never ships a dangling ruleset + * reference. */ class CreateRulesPlugin { /** @@ -21,12 +27,25 @@ class CreateRulesPlugin { stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL }, (_assets) => { - const ruleFileNames = fs.readdirSync(path.resolve(SOURCE_DIR, "rules")); + const rulesDirectoryPath = path.resolve(SOURCE_DIR, "rules"); + const ruleFileNames = fs.existsSync(rulesDirectoryPath) + ? fs.readdirSync(rulesDirectoryPath).filter((ruleFileName) => ruleFileName.endsWith(".json")) + : []; const rules = []; let ruleId = 1; + if (ruleFileNames.length === 0) { + compilation.warnings.push( + new Error( + `No *.json rule files found in ${rulesDirectoryPath}; skipping the rules.json asset and the declarative_net_request manifest key.` + ) + ); + + return; + } + for (const ruleFileName of ruleFileNames) { - const rule = JSON.parse(fs.readFileSync(path.resolve("src/rules", ruleFileName))); + const rule = JSON.parse(fs.readFileSync(path.resolve(rulesDirectoryPath, ruleFileName))); if (rule instanceof Array) { for (const currentRule of rule) { @@ -2,44 +2,51 @@ "nodes": { "home-manager": { "inputs": { - "nixpkgs": [ - "nixpkgs" - ] + "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1784588016, - "narHash": "sha256-ouZe80aWEhMLVMkqICFDN+JUw+0FJtCr/bh+hHtRtMg=", + "lastModified": 1790365885, + "narHash": "sha256-iQ0ebhiVo64NktTnwft9hNxlFu4j5cvljVxyJaEVIP4=", "owner": "nix-community", "repo": "home-manager", - "rev": "deeb6b7eb7e0c44ae1819c051ce175bd92a85100", + "rev": "7b4c5ec4bedaf1e062bbc1bcaeddbc6bd242aa1b", "type": "github" }, "original": { - "owner": "nix-community", - "repo": "home-manager", - "type": "github" + "id": "home-manager", + "type": "indirect" } }, "nixpkgs": { "locked": { - "lastModified": 1784497964, - "narHash": "sha256-vlHUuqAcbcH2RKmHbPiuQzbv1pnzzavXnI62RD0bqCU=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "241313f4e8e508cb9b13278c2b0fa25b9ca27163", - "type": "github" + "lastModified": 1789632929, + "narHash": "sha256-j08lBbqaYwjsW0xy6Dh4MNZpQ/oSvLU1MLAo38OXphM=", + "rev": "a32edd7654519351e48e80372a928df336394670", + "type": "tarball", + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1075288.a32edd765451/nixexprs.tar.zst" }, "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" + "type": "tarball", + "url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.zst" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1790600678, + "narHash": "sha256-k0ouG9zZIYS9D2B6yADfOoMbcUDSBrFg+tfHuUgbHDU=", + "rev": "f45c6f04c2f013f004bf94e284e95d72898d9393", + "type": "tarball", + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1081052.f45c6f04c2f0/nixexprs.tar.zst" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" } }, "root": { "inputs": { "home-manager": "home-manager", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs_2" } } }, @@ -1,27 +1,14 @@ -# Flake for development environment for better-containers -# Provides devShell for the browser extension and home-manager configuration - { - description = "Flake for development environment for better-containers."; + description = "Flake for development environment for banglar-bhumi-utils."; inputs = { - # Nixpkgs from nixos-unstable channel - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; - # Home-manager for user environment configuration - home-manager = { - url = "github:nix-community/home-manager"; - # Use the same nixpkgs as the main input - inputs.nixpkgs.follows = "nixpkgs"; - }; + nixpkgs.url = "nixpkgs"; }; outputs = { self, nixpkgs, home-manager }: let pkgs = nixpkgs.legacyPackages."x86_64-linux"; - # Import all home-manager modules from ./modules - modules = import ./modules; in { - # Development shell for the browser extension project devShells."x86_64-linux".default = pkgs.mkShell { packages = with pkgs; [ nodejs_22 @@ -30,11 +17,5 @@ vtsls ]; }; - - # Home-manager configuration for user arpit - homeConfigurations."arpit" = home-manager.lib.homeManagerConfiguration { - inherit pkgs; - modules = modules; - }; }; } diff --git a/src/rules/block-scripts.json b/src/rules/block-scripts.json deleted file mode 100644 index 0837f7d..0000000 --- a/src/rules/block-scripts.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "priority": 1, - "action": { - "type": "block" - }, - "condition": { - "regexFilter": "^https:\/\/banglarbhumi.gov.in\/BanglarBhumi\/(js\/(jquery-1.12.4|bootstrap-dropdownhover).js|images\/Bhumi2.gif)$" - } -} diff --git a/src/rules/redirect-jquery-script.json b/src/rules/redirect-jquery-script.json deleted file mode 100644 index b54a7ed..0000000 --- a/src/rules/redirect-jquery-script.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "priority": 1, - "action": { - "type": "redirect", - "redirect": { - "url": "https://code.jquery.com/jquery-1.12.4.min.js" - } - }, - "condition": { - "urlFilter": "https://banglarbhumi.gov.in/BanglarBhumi/js/jquery-1.12.4.min.js" - } -} diff --git a/src/scripts/common/login/form-caching.ts b/src/scripts/common/login/form-caching.ts index bde6442..656de6e 100644 --- a/src/scripts/common/login/form-caching.ts +++ b/src/scripts/common/login/form-caching.ts @@ -1,22 +1,28 @@ import { addPostListener, addPostResponder, JQueryAjaxResponse } from "@/shared/intercept-jquery-ajax"; -let loginAreaHTML: string | null = null; +/** Caches the login area HTML to avoid re-fetching it on subsequent requests. */ +let loginAreaHTML: JQueryAjaxResponse | null = null; +/** + * Intercepts `viewLoginAreaAction` AJAX requests and serves the cached HTML + * if available, avoiding an unnecessary network round-trip. + */ addPostResponder( "viewLoginAreaAction", - function(_args): JQueryAjaxResponse | undefined | null { - return loginAreaHTML - ? { - data: loginAreaHTML - } - : null; + function(_args): JQueryAjaxResponse | null { + return loginAreaHTML; } ); +/** + * Listens for successful `viewLoginAreaAction` responses and stores the + * returned HTML so the responder above can serve it from cache later. + */ addPostListener( "viewLoginAreaAction", function(_args, result): void { - loginAreaHTML = result.data?.toString() ?? ""; + if (result.textStatus === "success") + loginAreaHTML = result; }, false ); diff --git a/src/shared/intercept-jquery-ajax.ts b/src/shared/intercept-jquery-ajax.ts index 3215c43..f526189 100644 --- a/src/shared/intercept-jquery-ajax.ts +++ b/src/shared/intercept-jquery-ajax.ts @@ -15,7 +15,8 @@ const getInterceptors: InterceptorRegistration[] = []; /** Data a responder wants to resolve the "request" with, instead of hitting the network. */ export type JQueryAjaxResponse = { data: unknown; - textStatus?: string + textStatus?: string; + jqXHR?: JQuery.jqXHR }; /** @@ -73,6 +74,22 @@ function extractUrl(args: unknown[]): string | null { } /** + * Finds the success callback in `$.post`/`$.get` args, regardless of which + * overload was used: + * $.post(url, success) + * $.post(url, data, success) + * $.post(url, data, success, dataType) + * The callback is always the first function-typed positional argument — + * it is NOT reliably at a fixed index, since `data` is optional. + */ +function extractCallback(args: unknown[]): JQueryAjaxArgs[2] | undefined { + for (const arg of args) { + if (typeof arg === "function") return arg as JQueryAjaxArgs[2]; + } + return undefined; +} + +/** * Invokes any registered listeners whose `urlSuffix` matches the request URL. * `fromResponder` indicates whether this result came from a short-circuited * responder rather than a genuine network response, so listeners registered @@ -96,11 +113,14 @@ function runListeners( /** * Checks registered responders for a match. If one matches and returns a - * response, synthesizes a resolved jqXHR-like object, invokes the caller's - * own success callback (3rd positional arg) to preserve `$.post`/`$.get` - * shorthand semantics, fires any matching listeners, and returns the jqXHR - * so the real ajax call can be skipped. Returns `null` if no responder - * handled the request. + * response, synthesizes a resolved jqXHR-like object and — asynchronously, + * to mirror real `$.post`/`$.get` semantics where the success callback + * never fires synchronously — invokes the caller's own success callback + * (found via `extractCallback`, since its positional index varies by + * overload), resolves the deferred, and fires any matching listeners. + * Returns the jqXHR immediately (still pending) so the caller gets a real + * promise-like object to attach `.done`/`.fail` to, and the real ajax call + * can be skipped. Returns `null` if no responder handled the request. */ function tryRespond( responders: ResponderRegistration[], @@ -120,13 +140,18 @@ function tryRespond( const deferred = $.Deferred(); const jqXHR = deferred.promise() as unknown as JQuery.jqXHR; - const callback = args[2]; - if (typeof callback === "function") { - (callback as JQueryAjaxArgs[2])(data, textStatus, jqXHR); - } - deferred.resolve(data, textStatus, jqXHR); + // Defer to the next tick so callers can rely on the same + // "callback never fires before $.post/$.get returns" guarantee + // that the real jQuery methods provide. + setTimeout(function() { + const callback = extractCallback(args); + if (callback) { + callback(data, textStatus, jqXHR); + } + deferred.resolve(data, textStatus, jqXHR); - runListeners(listeners, args, { data, textStatus, jqXHR }, /* fromResponder */ true); + runListeners(listeners, args, { data, textStatus, jqXHR }, /* fromResponder */ true); + }, 0); return jqXHR; } @@ -167,15 +192,15 @@ document.addEventListener("DOMContentLoaded", function() { // Use our strict signature instead of `Function` to satisfy ESLint const jqXHR = (originalPost as unknown as OriginalJQueryAjaxMethod)(...args); - jqXHR.done((data: unknown, textStatus: string, doneJqXHR: JQuery.jqXHR) => { + jqXHR.done(function(data: unknown, textStatus: string, doneJqXHR: JQuery.jqXHR): void { runListeners(postListeners, args, { data, textStatus, jqXHR: doneJqXHR }, /* fromResponder */ false); }); - jqXHR.fail((failJqXHR: JQuery.jqXHR, textStatus: string) => { + jqXHR.fail(function(failJqXHR: JQuery.jqXHR, textStatus: string): void { runListeners( postListeners, args, { data: failJqXHR.responseJSON ?? failJqXHR.responseText, textStatus, jqXHR: failJqXHR }, - /* fromResponder */ false + false ); }); return jqXHR; @@ -199,10 +224,10 @@ document.addEventListener("DOMContentLoaded", function() { if (shortCircuited) return shortCircuited; const jqXHR = (originalGet as unknown as OriginalJQueryAjaxMethod)(...args); - jqXHR.done((data: unknown, textStatus: string, doneJqXHR: JQuery.jqXHR) => { + jqXHR.done(function(data: unknown, textStatus: string, doneJqXHR: JQuery.jqXHR): void { runListeners(getListeners, args, { data, textStatus, jqXHR: doneJqXHR }, /* fromResponder */ false); }); - jqXHR.fail((failJqXHR: JQuery.jqXHR, textStatus: string) => { + jqXHR.fail(function(failJqXHR: JQuery.jqXHR, textStatus: string): void { runListeners( getListeners, args, |
