diff options
| author | Arpit Chakladar <arpitchakladar+git@gmail.com> | 2026-07-14 00:10:53 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-14 00:10:53 +0530 |
| commit | 89566df399853bff5405ec53c87efa581f0808b0 (patch) | |
| tree | c2474af957002c19decfd1a7938e7d817671036c | |
| parent | ce401d03e75bcbfd1cdfa963954e0807219fb80d (diff) | |
| parent | 5c0cf8a22a04fae0fc6700a01007f10e833bc2cf (diff) | |
| download | banglar-bhumi-utils-89566df399853bff5405ec53c87efa581f0808b0.tar.gz banglar-bhumi-utils-89566df399853bff5405ec53c87efa581f0808b0.zip | |
Merge pull request #7 from arpitchakladar/login-form-caching
Login form caching
| -rw-r--r-- | src/rules/block-scripts.json | 2 | ||||
| -rw-r--r-- | src/scripts.json | 3 | ||||
| -rw-r--r-- | src/scripts/common/login/form-caching.ts | 22 | ||||
| -rw-r--r-- | src/scripts/common/login/index.ts | 1 | ||||
| -rw-r--r-- | src/shared/intercept-jquery-ajax.ts | 259 | ||||
| -rw-r--r-- | src/shared/script-injector.ts | 2 |
6 files changed, 267 insertions, 22 deletions
diff --git a/src/rules/block-scripts.json b/src/rules/block-scripts.json index c0f6c02..0837f7d 100644 --- a/src/rules/block-scripts.json +++ b/src/rules/block-scripts.json @@ -4,6 +4,6 @@ "type": "block" }, "condition": { - "regexFilter": "^https:\/\/banglarbhumi.gov.in\/BanglarBhumi\/js\/(jquery-1.12.4|bootstrap-dropdownhover).js$" + "regexFilter": "^https:\/\/banglarbhumi.gov.in\/BanglarBhumi\/(js\/(jquery-1.12.4|bootstrap-dropdownhover).js|images\/Bhumi2.gif)$" } } diff --git a/src/scripts.json b/src/scripts.json index fb6a0ee..9b4f76b 100644 --- a/src/scripts.json +++ b/src/scripts.json @@ -1,6 +1,7 @@ { "injected": { "common/stop-blocking.ts": "*", + "common/login/form-caching.ts": "*", "pages/know-your-property/index.ts": "KnowYourProperty.action", "pages/application-receipt/index.ts": "Apprecrep.action", "pages/mutation-application/index.ts": "MuteApplication.action", @@ -8,6 +9,6 @@ "pages/sheet-map/index.ts": "SheetMapViewAction.action" }, "after": { - "common/login/index.ts": "*" + "common/login/captcha.ts": "*" } } diff --git a/src/scripts/common/login/form-caching.ts b/src/scripts/common/login/form-caching.ts new file mode 100644 index 0000000..bde6442 --- /dev/null +++ b/src/scripts/common/login/form-caching.ts @@ -0,0 +1,22 @@ +import { addPostListener, addPostResponder, JQueryAjaxResponse } from "@/shared/intercept-jquery-ajax"; + +let loginAreaHTML: string | null = null; + +addPostResponder( + "viewLoginAreaAction", + function(_args): JQueryAjaxResponse | undefined | null { + return loginAreaHTML + ? { + data: loginAreaHTML + } + : null; + } +); + +addPostListener( + "viewLoginAreaAction", + function(_args, result): void { + loginAreaHTML = result.data?.toString() ?? ""; + }, + false +); diff --git a/src/scripts/common/login/index.ts b/src/scripts/common/login/index.ts deleted file mode 100644 index 95b9c5b..0000000 --- a/src/scripts/common/login/index.ts +++ /dev/null @@ -1 +0,0 @@ -import "@/scripts/common/login/captcha"; diff --git a/src/shared/intercept-jquery-ajax.ts b/src/shared/intercept-jquery-ajax.ts index b627d0e..3215c43 100644 --- a/src/shared/intercept-jquery-ajax.ts +++ b/src/shared/intercept-jquery-ajax.ts @@ -4,62 +4,213 @@ export type JQueryAjaxArgs = [ callback: (data: unknown, textStatus: string, jqXHR: JQuery.jqXHR) => void, dataType?: string ]; - type JQueryAjaxInterceptor = (args: JQueryAjaxArgs) => void; - type InterceptorRegistration = { urlSuffix: string; interceptor: JQueryAjaxInterceptor }; - const postInterceptors: InterceptorRegistration[] = []; const getInterceptors: InterceptorRegistration[] = []; +/** Data a responder wants to resolve the "request" with, instead of hitting the network. */ +export type JQueryAjaxResponse = { + data: unknown; + textStatus?: string +}; + +/** + * A responder inspects the outgoing args and optionally returns a response. + * Returning `undefined` means "not handled, let the real request (or the next + * responder) proceed." + */ +type JQueryAjaxResponder = (args: JQueryAjaxArgs) => JQueryAjaxResponse | undefined | null; + +type ResponderRegistration = { + urlSuffix: string; + responder: JQueryAjaxResponder +}; +const postResponders: ResponderRegistration[] = []; +const getResponders: ResponderRegistration[] = []; + +/** The finalized outcome of a request, whether it came from the network (success or failure) or a responder. */ +export type JQueryAjaxResult = { + data: unknown; + textStatus: string; + jqXHR: JQuery.jqXHR +}; + +/** + * A listener fires once a request settles — on success or failure alike — + * and always receives whatever response data is available. Unlike + * interceptors (which see the outgoing request) and responders (which can + * short-circuit it), listeners only observe the final result. + */ +type JQueryAjaxListener = (args: JQueryAjaxArgs, result: JQueryAjaxResult) => void; + +type ListenerRegistration = { + urlSuffix: string; + listener: JQueryAjaxListener; + /** Whether a responder-short-circuited response should also trigger this listener. */ + includeResponderResponses: boolean +}; +const postListeners: ListenerRegistration[] = []; +const getListeners: ListenerRegistration[] = []; + // Define a strict signature to replace the unsafe `Function` type type OriginalJQueryAjaxMethod = (...args: unknown[]) => JQuery.jqXHR; /** + * Extracts the request URL from `$.post`/`$.get` args, whether called with a + * plain string URL or a settings object. + */ +function extractUrl(args: unknown[]): string | null { + const firstArg = args[0]; + if (typeof firstArg === "string") return firstArg; + if (firstArg && typeof firstArg === "object" && "url" in firstArg && typeof firstArg.url === "string") { + return firstArg.url; + } + return null; +} + +/** + * 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 + * with `includeResponderResponses: false` can be skipped. + */ +function runListeners( + listeners: ListenerRegistration[], + args: unknown[], + result: JQueryAjaxResult, + fromResponder: boolean +): void { + const requestUrl = extractUrl(args); + if (typeof requestUrl !== "string") return; + + for (const { urlSuffix, listener, includeResponderResponses } of listeners) { + if (!requestUrl.endsWith(urlSuffix)) continue; + if (fromResponder && !includeResponderResponses) continue; + listener(args as unknown as JQueryAjaxArgs, result); + } +} + +/** + * 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. + */ +function tryRespond( + responders: ResponderRegistration[], + listeners: ListenerRegistration[], + args: unknown[] +): JQuery.jqXHR | null { + const requestUrl = extractUrl(args); + if (typeof requestUrl !== "string") return null; + + for (const { urlSuffix, responder } of responders) { + if (!requestUrl.endsWith(urlSuffix)) continue; + + const response = responder(args as unknown as JQueryAjaxArgs); + if (!response) continue; + + const { data, textStatus = "success" } = response; + 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); + + runListeners(listeners, args, { data, textStatus, jqXHR }, /* fromResponder */ true); + + return jqXHR; + } + return null; +} + +/** * On DOMContentLoaded, proxies jQuery's `$.post` and `$.get` so that - * registered interceptors are called before the real request. + * registered interceptors are called before the real request, registered + * responders get a chance to short-circuit the request entirely by + * resolving it with synthetic data, and registered listeners are notified + * once the request settles (whether real or synthesized, success or + * failure). */ document.addEventListener("DOMContentLoaded", function() { // 1. Keep the original methods bound to the jQuery ($) object const originalPost = $.post.bind($); const originalGet = $.get.bind($); - /** Proxied `$.post` that invokes registered interceptors before the real call. */ + /** + * Proxied `$.post` that invokes registered interceptors before the real + * call, checks registered responders for a short-circuit, and otherwise + * falls back to the real `$.post`, notifying listeners once the request + * settles either way. + */ $.post = function(this: typeof $, ...args: unknown[]): JQuery.jqXHR { for (const { urlSuffix, interceptor } of postInterceptors) { // Safely inspect jQuery settings objects without using "any" - const firstArg = args[0]; - const requestUrl = typeof firstArg === "string" - ? firstArg - : (firstArg && typeof firstArg === "object" && "url" in firstArg && typeof firstArg.url === "string" ? firstArg.url : null); - + const requestUrl = extractUrl(args); if (typeof requestUrl === "string" && requestUrl.endsWith(urlSuffix)) { // Cast the unknown array to the expected tuple type interceptor(args as unknown as JQueryAjaxArgs); } } + const shortCircuited = tryRespond(postResponders, postListeners, args); + if (shortCircuited) return shortCircuited; + // Use our strict signature instead of `Function` to satisfy ESLint - return (originalPost as unknown as OriginalJQueryAjaxMethod)(...args); + const jqXHR = (originalPost as unknown as OriginalJQueryAjaxMethod)(...args); + jqXHR.done((data: unknown, textStatus: string, doneJqXHR: JQuery.jqXHR) => { + runListeners(postListeners, args, { data, textStatus, jqXHR: doneJqXHR }, /* fromResponder */ false); + }); + jqXHR.fail((failJqXHR: JQuery.jqXHR, textStatus: string) => { + runListeners( + postListeners, + args, + { data: failJqXHR.responseJSON ?? failJqXHR.responseText, textStatus, jqXHR: failJqXHR }, + /* fromResponder */ false + ); + }); + return jqXHR; } as typeof $.post; // Reassert the original type so external consumers see the normal signature - /** Proxied `$.get` that invokes registered interceptors before the real call. */ + /** + * Proxied `$.get` that invokes registered interceptors before the real + * call, checks registered responders for a short-circuit, and otherwise + * falls back to the real `$.get`, notifying listeners once the request + * settles either way. + */ $.get = function(this: typeof $, ...args: unknown[]): JQuery.jqXHR { for (const { urlSuffix, interceptor } of getInterceptors) { - const firstArg = args[0]; - const requestUrl = typeof firstArg === "string" - ? firstArg - : (firstArg && typeof firstArg === "object" && "url" in firstArg && typeof firstArg.url === "string" ? firstArg.url : null); - + const requestUrl = extractUrl(args); if (typeof requestUrl === "string" && requestUrl.endsWith(urlSuffix)) { interceptor(args as unknown as JQueryAjaxArgs); } } - return (originalGet as unknown as OriginalJQueryAjaxMethod)(...args); + const shortCircuited = tryRespond(getResponders, getListeners, args); + if (shortCircuited) return shortCircuited; + + const jqXHR = (originalGet as unknown as OriginalJQueryAjaxMethod)(...args); + jqXHR.done((data: unknown, textStatus: string, doneJqXHR: JQuery.jqXHR) => { + runListeners(getListeners, args, { data, textStatus, jqXHR: doneJqXHR }, /* fromResponder */ false); + }); + jqXHR.fail((failJqXHR: JQuery.jqXHR, textStatus: string) => { + runListeners( + getListeners, + args, + { data: failJqXHR.responseJSON ?? failJqXHR.responseText, textStatus, jqXHR: failJqXHR }, + /* fromResponder */ false + ); + }); + return jqXHR; } as typeof $.get; }); @@ -84,3 +235,75 @@ export function addPostInterceptor(urlSuffix: string, interceptor: JQueryAjaxInt export function addGetInterceptor(urlSuffix: string, interceptor: JQueryAjaxInterceptor): void { getInterceptors.push({ urlSuffix, interceptor }); } + +/** + * Registers a responder that can conditionally short-circuit a matching + * `$.post` call, resolving it with synthetic data instead of making the + * real network request. + * + * @param urlSuffix - The URL suffix to match (checked via `endsWith`). + * @param responder - Inspects the original `arguments` from `$.post` and + * either returns a `{ data, textStatus? }` response to + * short-circuit the request, or `undefined` to let the + * request proceed (checking the next responder, or + * falling through to the real `$.post`). + */ +export function addPostResponder(urlSuffix: string, responder: JQueryAjaxResponder): void { + postResponders.push({ urlSuffix, responder }); +} + +/** + * Registers a responder that can conditionally short-circuit a matching + * `$.get` call, resolving it with synthetic data instead of making the + * real network request. + * + * @param urlSuffix - The URL suffix to match (checked via `endsWith`). + * @param responder - Inspects the original `arguments` from `$.get` and + * either returns a `{ data, textStatus? }` response to + * short-circuit the request, or `undefined` to let the + * request proceed (checking the next responder, or + * falling through to the real `$.get`). + */ +export function addGetResponder(urlSuffix: string, responder: JQueryAjaxResponder): void { + getResponders.push({ urlSuffix, responder }); +} + +/** + * Registers a listener that fires once a matching `$.post` request settles + * — success or failure alike — receiving the original request args plus + * whatever response data, `textStatus`, and `jqXHR` are available. + * + * @param urlSuffix - The URL suffix to match (checked via `endsWith`). + * @param listener - Receives the original `arguments` from `$.post` + * plus the result. + * @param includeResponderResponses - Whether a responder-short-circuited response + * should also trigger this listener, as opposed + * to only genuine network responses. Defaults to `true`. + */ +export function addPostListener( + urlSuffix: string, + listener: JQueryAjaxListener, + includeResponderResponses = true +): void { + postListeners.push({ urlSuffix, listener, includeResponderResponses }); +} + +/** + * Registers a listener that fires once a matching `$.get` request settles + * — success or failure alike — receiving the original request args plus + * whatever response data, `textStatus`, and `jqXHR` are available. + * + * @param urlSuffix - The URL suffix to match (checked via `endsWith`). + * @param listener - Receives the original `arguments` from `$.get` + * plus the result. + * @param includeResponderResponses - Whether a responder-short-circuited response + * should also trigger this listener, as opposed + * to only genuine network responses. Defaults to `true`. + */ +export function addGetListener( + urlSuffix: string, + listener: JQueryAjaxListener, + includeResponderResponses = true +): void { + getListeners.push({ urlSuffix, listener, includeResponderResponses }); +} diff --git a/src/shared/script-injector.ts b/src/shared/script-injector.ts index 3422bab..bfdad1d 100644 --- a/src/shared/script-injector.ts +++ b/src/shared/script-injector.ts @@ -15,5 +15,5 @@ export function injectScriptHead(src: string, data: DataType = {}): void { for (const i in data) { s.setAttribute(`data-${i}`, data[i]); } - document.head.append(s); + document.documentElement.append(s); }; |
