aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.envrc1
-rw-r--r--.gitignore1
-rw-r--r--config/webpack/plugins/create-manifest-webpack-plugin/index.js6
-rw-r--r--config/webpack/plugins/create-rules-webpack-plugin.js23
-rw-r--r--flake.lock47
-rw-r--r--flake.nix23
-rw-r--r--src/rules/block-scripts.json9
-rw-r--r--src/rules/redirect-jquery-script.json12
-rw-r--r--src/scripts/common/login/form-caching.ts22
-rw-r--r--src/shared/intercept-jquery-ajax.ts59
10 files changed, 114 insertions, 89 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
index c38a4bd..b3f911f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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) {
diff --git a/flake.lock b/flake.lock
index 21fd074..5ce7b1d 100644
--- a/flake.lock
+++ b/flake.lock
@@ -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"
}
}
},
diff --git a/flake.nix b/flake.nix
index 76423fa..b64172c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -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,