mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
feat: complete react fiber control loop
This commit is contained in:
+13
-17
@@ -92,25 +92,21 @@ async function init() {
|
|||||||
console.info('[BetterSEQTA+] Successfully initalised BetterSEQTA+, starting to load assets.')
|
console.info('[BetterSEQTA+] Successfully initalised BetterSEQTA+, starting to load assets.')
|
||||||
main()
|
main()
|
||||||
|
|
||||||
waitForElm('.Viewer__Viewer___32BH-', true).then(() => {
|
/* waitForElm('.Viewer__Viewer___32BH-', true).then(async () => {
|
||||||
console.log('Element exists')
|
console.log('Element exists')
|
||||||
|
|
||||||
const button = document.createElement('button')
|
await browser.runtime.sendMessage({ type: 'injectMainScript' })
|
||||||
button.innerHTML = 'Click me'
|
|
||||||
button.onclick = () => {
|
const nice = ReactFiber.find(".Viewer__Viewer___32BH-", { debug: true })
|
||||||
// function call to run onclick
|
|
||||||
ReactFiber.find(".Viewer__Viewer___32BH-", { debug: true })
|
|
||||||
.setState(prev => ({ ...prev, selected: new Set([999999]) }))
|
|
||||||
}
|
console.log(nice.getState())
|
||||||
button.style.position = 'fixed'
|
nice.setState({ selected: new Set([999431]) })
|
||||||
button.style.top = '0'
|
|
||||||
button.style.right = '0'
|
|
||||||
button.style.padding = '10px'
|
//console.log(nice)
|
||||||
button.style.zIndex = '9999'
|
}) */
|
||||||
button.style.backgroundColor = 'red'
|
|
||||||
button.style.color = 'white'
|
|
||||||
document.body.appendChild(button)
|
|
||||||
})
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-31
@@ -13,13 +13,7 @@ function reloadSeqtaPages() {
|
|||||||
result.then(open, console.error)
|
result.then(open, console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const injectedTabs = new Set<number>(); // Keep track of injected tabs
|
|
||||||
|
|
||||||
async function injectPageState(tabId: number) {
|
async function injectPageState(tabId: number) {
|
||||||
if (injectedTabs.has(tabId)) {
|
|
||||||
return; // Already injected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await browser.scripting.executeScript({
|
await browser.scripting.executeScript({
|
||||||
target: { tabId },
|
target: { tabId },
|
||||||
@@ -27,18 +21,12 @@ async function injectPageState(tabId: number) {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
world: "MAIN",
|
world: "MAIN",
|
||||||
});
|
});
|
||||||
injectedTabs.add(tabId);
|
console.info(`[background] Injected pageState.js into tab ${tabId}`);
|
||||||
console.log(`[background] Injected pageState.js into tab ${tabId}`);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[background] Failed to inject pageState.js into tab ${tabId}:`, err);
|
console.error(`[background] Failed to inject pageState.js into tab ${tabId}:`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the script when a tab is closed
|
|
||||||
browser.tabs.onRemoved.addListener((tabId) => {
|
|
||||||
injectedTabs.delete(tabId);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Main message listener
|
// Main message listener
|
||||||
browser.runtime.onMessage.addListener((request: any, sender: any, sendResponse: (response?: any) => void) => {
|
browser.runtime.onMessage.addListener((request: any, sender: any, sendResponse: (response?: any) => void) => {
|
||||||
|
|
||||||
@@ -88,26 +76,13 @@ browser.runtime.onMessage.addListener((request: any, sender: any, sendResponse:
|
|||||||
GetNews(sendResponse, url);
|
GetNews(sendResponse, url);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 'reactFiberRequest': {
|
|
||||||
//const { tabId, /* selector, action, payload, debug */ } = request;
|
|
||||||
const tabId = sender.tab.id;
|
|
||||||
|
|
||||||
// Ensure pageState.js is injected
|
case 'injectMainScript': {
|
||||||
injectPageState(tabId).then(() => {
|
const senderTab = sender.tab ? sender.tab.id : undefined;
|
||||||
// Forward the request to the injected script
|
|
||||||
browser.tabs.sendMessage(tabId, { ...request, type: "reactFiberAction" }, { frameId: 0 }) // Target the main world
|
injectPageState(senderTab!)
|
||||||
.then(response => {
|
return;
|
||||||
sendResponse(response); // Send the response back to the content script
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(`[background] Error communicating with injected script in tab ${tabId}:`, err);
|
|
||||||
});
|
|
||||||
}).catch(err => {
|
|
||||||
console.error("[background] Failed to inject", err);
|
|
||||||
});
|
|
||||||
return true; // Keep the message channel open for the async response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log('Unknown request type');
|
console.log('Unknown request type');
|
||||||
|
|||||||
+81
-39
@@ -1,3 +1,4 @@
|
|||||||
|
// pageState.ts
|
||||||
class ReactFiber {
|
class ReactFiber {
|
||||||
constructor(selector, options = {}) {
|
constructor(selector, options = {}) {
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
@@ -36,37 +37,62 @@ class ReactFiber {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getState(key = null) {
|
getState(key) {
|
||||||
if (!this.components.length) return null;
|
if (!this.components.length) return null;
|
||||||
const state = this.components[0]?.state || null;
|
const state = this.components[0]?.state || null;
|
||||||
return key ? state?.[key] : state;
|
|
||||||
|
if (key === undefined) {
|
||||||
|
return state; // Return entire state
|
||||||
|
} else if (typeof key === 'string') {
|
||||||
|
return state?.[key]; // Return single key
|
||||||
|
} else if (Array.isArray(key)) {
|
||||||
|
// Return object with only specified keys
|
||||||
|
const filteredState = {};
|
||||||
|
for (const k of key) {
|
||||||
|
if (state && Object.hasOwn(state, k)) { // Use Object.hasOwn for safety
|
||||||
|
filteredState[k] = state[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredState;
|
||||||
|
}
|
||||||
|
return null; // Invalid key type
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(updateFn) {
|
setState(update) {
|
||||||
this.components.forEach(component => {
|
this.components.forEach(component => {
|
||||||
if (component?.setState) {
|
if (component?.setState) {
|
||||||
component.setState(prevState => {
|
if (typeof update === 'function') {
|
||||||
const newState = updateFn(prevState);
|
// Functional update
|
||||||
if (this.debug) console.log("✅ Updated State:", newState);
|
component.setState(prevState => {
|
||||||
return newState;
|
const newState = update(prevState);
|
||||||
});
|
if (this.debug) console.log("✅ Updated State (Functional):", newState);
|
||||||
}
|
return newState;
|
||||||
});
|
});
|
||||||
return this; // Enable chaining
|
} else {
|
||||||
|
// Object update (merge with existing state)
|
||||||
|
component.setState(prevState => {
|
||||||
|
const newState = { ...prevState, ...update }; // Merge here!
|
||||||
|
if (this.debug) console.log("✅ Updated State (Object Merge):", newState);
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProp(propName) {
|
getProp(propName) {
|
||||||
if (!this.fibers.length) return null;
|
if (!this.fibers.length) return null;
|
||||||
return this.fibers[0]?.memoizedProps?.[propName] || null;
|
return this.fibers[0]?.memoizedProps?.[propName];
|
||||||
}
|
}
|
||||||
|
|
||||||
setProp(propName, value) {
|
setProp(propName) {
|
||||||
this.fibers.forEach(fiber => {
|
this.fibers.forEach(fiber => {
|
||||||
if (fiber?.memoizedProps) {
|
if (fiber?.memoizedProps) {
|
||||||
fiber.memoizedProps[propName] = value;
|
fiber.memoizedProps[propName] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return this.forceUpdate(); // Apply the change and return this for chaining
|
return this; // Enable chaining
|
||||||
}
|
}
|
||||||
|
|
||||||
forceUpdate() {
|
forceUpdate() {
|
||||||
@@ -80,37 +106,53 @@ class ReactFiber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message listener for communication with the background script
|
console.log("Window cat: ", window.cat);
|
||||||
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
||||||
if (request.type === "reactFiberAction") {
|
|
||||||
const { selector, action, payload, debug } = request;
|
|
||||||
const fiberInstance = ReactFiber.find(selector, {debug}); // Use the class
|
|
||||||
|
|
||||||
|
// Listen for messages from the background script (via window.postMessage)
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
console.log(event)
|
||||||
|
|
||||||
|
if (event.data.type === "reactFiberRequest") {
|
||||||
|
const { selector, action, payload, debug, messageId } = event.data;
|
||||||
|
const fiberInstance = ReactFiber.find(selector, { debug });
|
||||||
|
|
||||||
|
let response;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "getState":
|
case "getState":
|
||||||
sendResponse(fiberInstance.getState(payload.key));
|
response = fiberInstance.getState(payload.key);
|
||||||
break;
|
break;
|
||||||
case "setState":
|
case "setState":
|
||||||
// Very important: Eval the function string in the context of the page
|
// Handle both function and object updates
|
||||||
const updateFn = eval(`(${payload.updateFn})`);
|
if (payload.updateFn) {
|
||||||
fiberInstance.setState(updateFn);
|
const updateFn = eval(`(${payload.updateFn})`);
|
||||||
sendResponse({}); // Send acknowledgement
|
fiberInstance.setState(updateFn);
|
||||||
|
} else {
|
||||||
|
fiberInstance.setState(payload.updateObject);
|
||||||
|
}
|
||||||
|
response = {}; // Acknowledge
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "getProp":
|
case "getProp":
|
||||||
sendResponse(fiberInstance.getProp(payload.propName));
|
response = fiberInstance.getProp(payload.propName);
|
||||||
break;
|
break;
|
||||||
case "setProp":
|
case "setProp":
|
||||||
fiberInstance.setProp(payload.propName, payload.value);
|
fiberInstance.setProp(payload.propName, payload.value);
|
||||||
sendResponse({}); // Send acknowledgement
|
response = {}; // Acknowledge
|
||||||
break;
|
break;
|
||||||
case "forceUpdate":
|
case "forceUpdate":
|
||||||
fiberInstance.forceUpdate();
|
fiberInstance.forceUpdate();
|
||||||
sendResponse({}); // Send acknowledgement
|
response = {}; // Acknowledge
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`[pageState] Unknown action: ${action}`);
|
console.warn(`[pageState] Unknown action: ${action}`);
|
||||||
sendResponse(null);
|
response = null;
|
||||||
}
|
}
|
||||||
return true; // Keep message channel open (for consistency, even if not always needed)
|
|
||||||
|
// Send the response back to the background script using window.postMessage
|
||||||
|
window.postMessage({
|
||||||
|
type: "reactFiberResponse",
|
||||||
|
response,
|
||||||
|
messageId,
|
||||||
|
}, "*");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import browser from 'webextension-polyfill';
|
import browser from 'webextension-polyfill';
|
||||||
|
|
||||||
class ReactFiber {
|
class ReactFiber {
|
||||||
private selector: string;
|
private selector: string;
|
||||||
private debug: boolean;
|
private debug: boolean;
|
||||||
|
private messageIdCounter: number = 0; // Counter for unique message IDs
|
||||||
|
|
||||||
constructor(selector: string, options: { debug?: boolean } = {}) {
|
constructor(selector: string, options: { debug?: boolean } = {}) {
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
@@ -14,54 +15,61 @@ private selector: string;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async sendMessage(action: string, payload: any = {}): Promise<any> {
|
private async sendMessage(action: string, payload: any = {}): Promise<any> {
|
||||||
const message = {
|
return new Promise((resolve, _) => {
|
||||||
type: "reactFiberRequest",
|
const messageId = this.messageIdCounter++;
|
||||||
selector: this.selector,
|
const message = {
|
||||||
action,
|
type: "reactFiberRequest",
|
||||||
payload,
|
selector: this.selector,
|
||||||
debug: this.debug
|
action,
|
||||||
};
|
payload,
|
||||||
|
debug: this.debug,
|
||||||
|
messageId, // Include the unique message ID
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
const listener = (response: any) => {
|
||||||
const response = await browser.runtime.sendMessage(message);
|
if (response.data?.type === 'reactFiberResponse' && response.data?.messageId === messageId) {
|
||||||
|
if (this.debug) {
|
||||||
if (this.debug) {
|
console.log("Content Received Response:", response.data.response);
|
||||||
console.log("Content Received Response:", response);
|
}
|
||||||
}
|
resolve(response.data.response);
|
||||||
return response;
|
window.removeEventListener("message", listener)
|
||||||
} catch (error) {
|
}
|
||||||
console.error("Error sending message:", error);
|
};
|
||||||
return null; // or throw error, depending on desired behavior
|
window.addEventListener('message', listener);
|
||||||
}
|
window.postMessage(message, "*");
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async getState(key?: string): Promise<any> {
|
async getState(key?: string | string[]): Promise<any> { // Type change: allow string or string[]
|
||||||
return this.sendMessage("getState", { key });
|
return this.sendMessage("getState", { key });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setState(updateFn: (prevState: any) => any): Promise<ReactFiber> {
|
async setState(update: any | ((prevState: any) => any)): Promise<ReactFiber> {
|
||||||
// Serialize the update function to a string. This is the tricky part.
|
// Now async again.
|
||||||
// We can only send strings/JSON-serializable data through messages.
|
const updateFnString = typeof update === 'function' ? update.toString() : null;
|
||||||
const updateFnString = updateFn.toString();
|
const updateObject = typeof update !== 'function' ? update : null;
|
||||||
await this.sendMessage("setState", { updateFn: updateFnString });
|
|
||||||
|
await this.sendMessage("setState", { updateFn: updateFnString, updateObject });
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async getProp(propName: string): Promise<any> {
|
async getProp(propName: string): Promise<any> {
|
||||||
return this.sendMessage("getProp", { propName });
|
return this.sendMessage("getProp", { propName });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setProp(propName: string, value: any): Promise<ReactFiber> {
|
async setProp(propName: string, value: any): Promise<ReactFiber> {
|
||||||
|
// Now async again
|
||||||
await this.sendMessage("setProp", { propName, value });
|
await this.sendMessage("setProp", { propName, value });
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async forceUpdate(): Promise<ReactFiber> {
|
async forceUpdate(): Promise<ReactFiber> {
|
||||||
|
// Now async again
|
||||||
await this.sendMessage("forceUpdate");
|
await this.sendMessage("forceUpdate");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ReactFiber;
|
||||||
export default ReactFiber
|
|
||||||
Reference in New Issue
Block a user