From 7a50bd2cf62d7fde49cc6b714ce7aa2fe13439a8 Mon Sep 17 00:00:00 2001 From: Miorel-Lucian Palii Date: Sun, 13 Oct 2024 23:13:48 -0700 Subject: [PATCH] Process `__NEXT_DATA__` in Chrome extension The LeetCode study plans (e.g. https://leetcode.com/studyplan/top-100-liked/) get their data in this way, so we inject middleware here for this use case as well. --- .../extension/injectJsonScriptMiddleware.ts | 53 +++++++++++++++++++ .../leetcode-zen-mode/src/extension/main.ts | 6 +++ workspaces/util/src/jsonParseSafe.ts | 11 ++++ 3 files changed, 70 insertions(+) create mode 100644 workspaces/leetcode-zen-mode/src/extension/injectJsonScriptMiddleware.ts create mode 100644 workspaces/util/src/jsonParseSafe.ts diff --git a/workspaces/leetcode-zen-mode/src/extension/injectJsonScriptMiddleware.ts b/workspaces/leetcode-zen-mode/src/extension/injectJsonScriptMiddleware.ts new file mode 100644 index 00000000..2d0f34e8 --- /dev/null +++ b/workspaces/leetcode-zen-mode/src/extension/injectJsonScriptMiddleware.ts @@ -0,0 +1,53 @@ +import invariant from "invariant"; +import type { JsonValue } from "type-fest"; + +import { jsonParseSafe } from "@code-chronicles/util/jsonParseSafe"; + +type ProxiedPropertyKey = "innerHTML" | "innerText" | "textContent"; + +type MiddlewareFn = ( + data: JsonValue, + script: HTMLScriptElement, + property: ProxiedPropertyKey, +) => JsonValue; + +function inject( + proto: TProto, + property: ProxiedPropertyKey, + middlewareFn: MiddlewareFn, +): void { + const prevDescriptor = Object.getOwnPropertyDescriptor(proto, property); + + invariant( + prevDescriptor && prevDescriptor.get, + `\`${proto.constructor.name}.prototype.${property}\` property descriptor didn't have the expected form!`, + ); + const prevDescriptorGet = prevDescriptor.get; + + Object.defineProperty(HTMLScriptElement.prototype, property, { + ...prevDescriptor, + get(this: HTMLScriptElement) { + const data = prevDescriptorGet.call(this); + + // If the data doesn't parse as JSON, we pass it through unchanged. + // If it does parse as JSON, we'll run the middleware. + const parsedData = jsonParseSafe(data); + if (parsedData) { + return JSON.stringify(middlewareFn(parsedData.data, this, property)); + } + + return data; + }, + }); +} + +/** + * Injects a function to process and possibly replace JSON data stored within + *