diff options
Diffstat (limited to 'src')
| -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 | 155 | ||||
| -rw-r--r-- | src/shared/script-injector.ts | 2 |
6 files changed, 164 insertions, 21 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 03ea5e0..3215c43 100644 --- a/src/shared/intercept-jquery-ajax.ts +++ b/src/shared/intercept-jquery-ajax.ts @@ -13,7 +13,7 @@ const postInterceptors: InterceptorRegistration[] = []; const getInterceptors: InterceptorRegistration[] = []; /** Data a responder wants to resolve the "request" with, instead of hitting the network. */ -type JQueryAjaxResponse = { +export type JQueryAjaxResponse = { data: unknown; textStatus?: string }; @@ -23,7 +23,7 @@ type JQueryAjaxResponse = { * Returning `undefined` means "not handled, let the real request (or the next * responder) proceed." */ -type JQueryAjaxResponder = (args: JQueryAjaxArgs) => JQueryAjaxResponse | undefined; +type JQueryAjaxResponder = (args: JQueryAjaxArgs) => JQueryAjaxResponse | undefined | null; type ResponderRegistration = { urlSuffix: string; @@ -32,6 +32,30 @@ type ResponderRegistration = { 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; @@ -49,13 +73,40 @@ function extractUrl(args: unknown[]): string | 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, and returns it so the real ajax call can be skipped. - * Returns `null` if no responder handled the request. + * 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[], args: unknown[]): JQuery.jqXHR | null { +function tryRespond( + responders: ResponderRegistration[], + listeners: ListenerRegistration[], + args: unknown[] +): JQuery.jqXHR | null { const requestUrl = extractUrl(args); if (typeof requestUrl !== "string") return null; @@ -63,7 +114,7 @@ function tryRespond(responders: ResponderRegistration[], args: unknown[]): JQuer if (!requestUrl.endsWith(urlSuffix)) continue; const response = responder(args as unknown as JQueryAjaxArgs); - if (response === undefined) continue; + if (!response) continue; const { data, textStatus = "success" } = response; const deferred = $.Deferred(); @@ -75,6 +126,8 @@ function tryRespond(responders: ResponderRegistration[], args: unknown[]): JQuer } deferred.resolve(data, textStatus, jqXHR); + runListeners(listeners, args, { data, textStatus, jqXHR }, /* fromResponder */ true); + return jqXHR; } return null; @@ -82,9 +135,11 @@ function tryRespond(responders: ResponderRegistration[], args: unknown[]): JQuer /** * On DOMContentLoaded, proxies jQuery's `$.post` and `$.get` so that - * registered interceptors are called before the real request, and - * registered responders get a chance to short-circuit the request entirely - * by resolving it with synthetic data. + * 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 @@ -93,8 +148,9 @@ document.addEventListener("DOMContentLoaded", function() { /** * Proxied `$.post` that invokes registered interceptors before the real - * call, then checks registered responders for a short-circuit before - * falling back to the real `$.post`. + * 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) { @@ -106,17 +162,30 @@ document.addEventListener("DOMContentLoaded", function() { } } - const shortCircuited = tryRespond(postResponders, args); + 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, then checks registered responders for a short-circuit before - * falling back to the real `$.get`. + * 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) { @@ -126,10 +195,22 @@ document.addEventListener("DOMContentLoaded", function() { } } - const shortCircuited = tryRespond(getResponders, args); + const shortCircuited = tryRespond(getResponders, getListeners, args); if (shortCircuited) return shortCircuited; - return (originalGet as unknown as OriginalJQueryAjaxMethod)(...args); + 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; }); @@ -186,3 +267,43 @@ export function addPostResponder(urlSuffix: string, responder: JQueryAjaxRespond 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); }; |
