Add dedicated injector
This commit is contained in:
Vendored
+77211
-890
File diff suppressed because one or more lines are too long
@@ -8,10 +8,10 @@ import { MovableWindow } from "./movableWindow";
|
||||
import { Minimap } from "./minimap";
|
||||
import { Overlay } from "./overlay";
|
||||
import { debounce } from "lodash";
|
||||
import { TC_LOGO } from "../../constants";
|
||||
import styled from "styled-components";
|
||||
import { MuralEx } from "../../interfaces";
|
||||
import { PieCharts } from "./pixelCharts";
|
||||
import { TC_LOGO } from "../../assets";
|
||||
|
||||
const Img = styled.img`
|
||||
width: 25px;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from "react";
|
||||
import { PixelPlaced } from "../../lib/pixelPlaced";
|
||||
import styled from "styled-components";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSquare } from "@fortawesome/free-solid-svg-icons";
|
||||
import { formatNumber } from "../../lib/utils";
|
||||
import { StoreEvents } from "../../lib/store";
|
||||
import { Store, StoreEvents } from "../../lib/store";
|
||||
|
||||
const CountBox = styled.div<{width: number}>`
|
||||
position: fixed;
|
||||
@@ -31,7 +30,7 @@ const Icon = styled.span`
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
pixelCount: PixelPlaced;
|
||||
store: Store;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -50,15 +49,15 @@ export class PixelCount extends React.Component<Props, State> {
|
||||
}
|
||||
componentDidMount() {
|
||||
this.updateCount();
|
||||
this.props.pixelCount.store.on(StoreEvents.PixelLog, this.updateCount);
|
||||
this.props.store.on(StoreEvents.PixelLog, this.updateCount);
|
||||
window.addEventListener("resize", this.onResize);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.props.pixelCount.store.off(StoreEvents.PixelLog, this.updateCount);
|
||||
this.props.store.off(StoreEvents.PixelLog, this.updateCount);
|
||||
window.removeEventListener("resize", this.onResize);
|
||||
}
|
||||
updateCount = async () => {
|
||||
this.setState({count: await this.props.pixelCount.getCount()});
|
||||
this.setState({count: await this.props.store.getPixelCount()});
|
||||
};
|
||||
onResize = () => {
|
||||
this.setState({ width: window.innerWidth });
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
|
||||
import React from "react";
|
||||
import { ImportOutput, MuralOld, RGB } from "../interfaces";
|
||||
import { canvasToImageData, convertOldMuralToNewMural, createClosestIndexColor, createMuralExtended, getColorScore, getExtension,
|
||||
import { ImportOutput, MuralOld } from "../interfaces";
|
||||
import {
|
||||
canvasToImageData, convertOldMuralToNewMural,
|
||||
createClosestIndexColor, createMuralExtended, getExtension,
|
||||
imageDataToPaletteIndices, imageToCanvas, loadImageSource, processNumberEvent,
|
||||
readAsArrayBuffer,
|
||||
readAsDataUrl, readAsString, resize, rgb } from "../lib/utils";
|
||||
readAsDataUrl, readAsString, resize } from "../lib/utils";
|
||||
import styled from "styled-components";
|
||||
import { Popup } from "./components/Popup";
|
||||
import RgbQuant, { DitheringKernel, RGBQuantOptions } from "rgbquant";
|
||||
|
||||
+2
-3
@@ -5,14 +5,13 @@ import { Coordinates } from "../lib/coordinates";
|
||||
import { Storage } from "../lib/storage";
|
||||
import { Main } from "./components/main";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { PixelPlaced } from "../lib/pixelPlaced";
|
||||
import { PixelCount } from "./components/pixelCount";
|
||||
|
||||
export function createUI(
|
||||
store: Store, storage: Storage, cords: Coordinates, palette: Palette, pixels: PixelPlaced
|
||||
store: Store, storage: Storage, cords: Coordinates, palette: Palette
|
||||
) {
|
||||
const unmounts = [
|
||||
appendWindow(<PixelCount pixelCount={pixels} />),
|
||||
appendWindow(<PixelCount store={store} />),
|
||||
appendWindow(<Main cords={cords} store={store} storage={storage} palette={palette}/>),
|
||||
];
|
||||
return () => {
|
||||
|
||||
File diff suppressed because one or more lines are too long
+6
-2
File diff suppressed because one or more lines are too long
+4
-3
@@ -5,8 +5,8 @@ import { createUI } from "./UI/ui";
|
||||
import { waitForDraw } from "./lib/utils";
|
||||
import { Storage } from "./lib/storage";
|
||||
import { Store } from "./lib/store";
|
||||
import { PixelPlaced } from "./lib/pixelPlaced";
|
||||
import process from "process";
|
||||
import { Injector } from "./lib/injector";
|
||||
|
||||
async function main() {
|
||||
globalThis.process = process;
|
||||
@@ -16,11 +16,12 @@ async function main() {
|
||||
const storage = new Storage(ENVIRONMENT === "browser-extension");
|
||||
const store = new Store(storage, palette);
|
||||
await store.load();
|
||||
const pixelPlaced = PixelPlaced.create(store);
|
||||
const injector = new Injector(store);
|
||||
await injector.inject();
|
||||
const coordinates = new Coordinates();
|
||||
await coordinates.init();
|
||||
|
||||
createUI(store,storage, coordinates, palette, pixelPlaced);
|
||||
createUI(store,storage, coordinates, palette);
|
||||
|
||||
console.log(
|
||||
"%cOverlay by 0xa663",
|
||||
|
||||
+13
-88
@@ -1,99 +1,15 @@
|
||||
// Creating artificial API between two worlds
|
||||
|
||||
export type InterceptBefore = (requestInfo: string, init?: RequestInit) => Promise<void> | void;
|
||||
export type InterceptAfter = (requestInfo: string, response: Response, init?: RequestInit) => Promise<void> | void;
|
||||
|
||||
export class Interceptor {
|
||||
|
||||
private static fetch = window.fetch;
|
||||
private static overwritten = false;
|
||||
private static before = new Map<string, InterceptBefore[]>();
|
||||
private static after = new Map<string, InterceptAfter[]>();
|
||||
|
||||
private constructor() {}
|
||||
|
||||
private static overrideFetch() {
|
||||
if (this.overwritten) {
|
||||
return;
|
||||
}
|
||||
this.overwritten = true;
|
||||
(window as any).fetch = async (...args: any) => {
|
||||
const url = ((args[0] as RequestInfo | URL) || "").toString();
|
||||
const init = args[1];
|
||||
|
||||
const befores = [...(this.before.get(url) || []), ...(this.before.get("*") || [])];
|
||||
if (befores) {
|
||||
for (const before of befores) {
|
||||
await before(url, init);
|
||||
}
|
||||
}
|
||||
|
||||
const response = (this.fetch as any)(...args);
|
||||
const afters = [...(this.after.get(url) || []), ...(this.after.get("*") || [])];
|
||||
if (afters) {
|
||||
const result = await response;
|
||||
for (const after of afters) {
|
||||
try {
|
||||
await after(url, result, init);
|
||||
} catch (_) {}
|
||||
}
|
||||
return response;
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static onAfter(url: string, cb: InterceptAfter) {
|
||||
Interceptor.overrideFetch();
|
||||
const arr = Interceptor.after.get(url) || [];
|
||||
if (!arr.includes(cb)) {
|
||||
arr.push(cb);
|
||||
}
|
||||
Interceptor.after.set(url, arr);
|
||||
}
|
||||
static offAfter(url: string, cb: InterceptAfter) {
|
||||
const arr = Interceptor.after.get(url) || [];
|
||||
const index = arr.indexOf(cb);
|
||||
if (index !== -1) {
|
||||
arr.splice(index, 1);
|
||||
}
|
||||
if (arr.length) {
|
||||
Interceptor.after.set(url, arr);
|
||||
} else {
|
||||
Interceptor.after.delete(url);
|
||||
}
|
||||
}
|
||||
|
||||
static onBefore(url: string, cb: InterceptBefore) {
|
||||
Interceptor.overrideFetch();
|
||||
const arr = Interceptor.before.get(url) || [];
|
||||
if (!arr.includes(cb)) {
|
||||
arr.push(cb);
|
||||
}
|
||||
Interceptor.before.set(url, arr);
|
||||
}
|
||||
|
||||
static offBefore(url: string, cb: InterceptBefore) {
|
||||
const arr = Interceptor.before.get(url) || [];
|
||||
const index = arr.indexOf(cb);
|
||||
if (index !== -1) {
|
||||
arr.splice(index, 1);
|
||||
}
|
||||
if (arr.length) {
|
||||
Interceptor.before.set(url, arr);
|
||||
} else {
|
||||
Interceptor.before.delete(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
import { EVENT_CROSS_WORLD_INJECTED, EVENT_CROSS_WORLD_PIXEL_PLACED, EVENT_CROSS_WORLD_URL_UPDATE } from "./constants";
|
||||
import { Interceptor } from "./lib/interceptor";
|
||||
import { HistoryPushStateSpy } from "./lib/urlHistorySpy.ts";
|
||||
|
||||
Interceptor.onAfter("/api/pixel", async (_, response, init) => {
|
||||
try {
|
||||
if (response.status === 200 && init && "body" in init) {
|
||||
const body = JSON.parse(init.body as any);
|
||||
if ("x" in body && "y" in body && "color" in body) {
|
||||
const pixelsPlaced = new CustomEvent("__pixelsPlaced", {
|
||||
const pixelsPlaced = new CustomEvent(EVENT_CROSS_WORLD_PIXEL_PLACED, {
|
||||
detail: { color: body.color, x: body.x, y: body.y },
|
||||
});
|
||||
document.dispatchEvent(pixelsPlaced);
|
||||
@@ -103,3 +19,12 @@ Interceptor.onAfter("/api/pixel", async (_, response, init) => {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
HistoryPushStateSpy.onPushState((_, __, url) => {
|
||||
const pixelsPlaced = new CustomEvent(EVENT_CROSS_WORLD_URL_UPDATE, {
|
||||
detail: url,
|
||||
});
|
||||
document.dispatchEvent(pixelsPlaced);
|
||||
});
|
||||
|
||||
document.dispatchEvent(new CustomEvent(EVENT_CROSS_WORLD_INJECTED));
|
||||
@@ -1,9 +1,10 @@
|
||||
import saveAs from "file-saver";
|
||||
import { createCanvas, CHUNK_SIZE, fetchTile } from "./utils";
|
||||
import { createCanvas, fetchTile } from "./utils";
|
||||
import { Coordinates } from "./coordinates";
|
||||
import { Rect } from "../interfaces";
|
||||
import { Popup } from "../UI/components/Popup";
|
||||
import React from "react";
|
||||
import { CHUNK_SIZE } from "../constants";
|
||||
|
||||
export function getSelectionArea() {
|
||||
return new Promise<Rect>(resolve => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CHUNK_SIZE } from "../constants";
|
||||
import { Listener, BasicEventEmitter } from "./eventEmitter";
|
||||
import { CHUNK_SIZE, waitForDraw } from "./utils";
|
||||
import { waitForDraw } from "./utils";
|
||||
|
||||
export enum CordType {
|
||||
Div,
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
EVENT_CROSS_WORLD_INJECTED,
|
||||
EVENT_CROSS_WORLD_PIXEL_PLACED,
|
||||
EVENT_CROSS_WORLD_URL_UPDATE,
|
||||
INJECT_SCRIPT_PIXEL_OBSERVER
|
||||
} from "../constants";
|
||||
import { BasicEventEmitter } from "./eventEmitter";
|
||||
import { Store } from "./store";
|
||||
|
||||
interface Pixel {
|
||||
x: number;
|
||||
y: number;
|
||||
color: number;
|
||||
}
|
||||
export enum InjectEvents {
|
||||
PixelPlaced = 1,
|
||||
UrlChange
|
||||
}
|
||||
|
||||
|
||||
export class Injector {
|
||||
private eventEmitter = new BasicEventEmitter();
|
||||
|
||||
constructor(private store: Store) {}
|
||||
|
||||
inject() {
|
||||
const api = document.createElement("script");
|
||||
api.addEventListener("load", () => {
|
||||
document.body.removeChild(api);
|
||||
}, { once: true });
|
||||
api.addEventListener("error", error => {
|
||||
console.error(error);
|
||||
});
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const d = document as any;
|
||||
api.addEventListener("error", (error) => {
|
||||
reject(error.error);
|
||||
}, { once: true });
|
||||
d.addEventListener(EVENT_CROSS_WORLD_INJECTED, () => {
|
||||
resolve();
|
||||
}, { once: true});
|
||||
|
||||
if (ENVIRONMENT === "browser-extension") {
|
||||
api.src = chrome.runtime.getURL("/assets/scripts/inject.js");
|
||||
} else {
|
||||
api.textContent = INJECT_SCRIPT_PIXEL_OBSERVER;
|
||||
}
|
||||
|
||||
|
||||
document.body.appendChild(api);
|
||||
d.addEventListener(EVENT_CROSS_WORLD_PIXEL_PLACED, (event: CustomEvent<Pixel>) => {
|
||||
const body = event.detail;
|
||||
if (typeof body === "object" && "x" in body && "y" in body && "color" in body
|
||||
&& typeof body.color === "number" && typeof body.x === "number"
|
||||
&& typeof body.y === "number"
|
||||
) {
|
||||
this.store.addPixelLog(body.x, body.y, body.color);
|
||||
this.eventEmitter.emit(InjectEvents.PixelPlaced, body);
|
||||
}
|
||||
});
|
||||
d.addEventListener(EVENT_CROSS_WORLD_URL_UPDATE, (event: CustomEvent<string>) => {
|
||||
const body = event.detail;
|
||||
if (typeof body === "string") {
|
||||
this.eventEmitter.emit(InjectEvents.UrlChange, body);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
on(event: InjectEvents.UrlChange, cb: (pixel: Pixel) => any): this;
|
||||
on(event: InjectEvents.UrlChange, cb: (url: string) => any): this;
|
||||
on(event: InjectEvents, cb: any) {
|
||||
this.eventEmitter.on(event, cb);
|
||||
return this;
|
||||
}
|
||||
off(event: InjectEvents.UrlChange, cb: (pixel: Pixel) => any): this;
|
||||
off(event: InjectEvents.UrlChange, cb: (url: string) => any): this;
|
||||
off(event: InjectEvents, cb: any) {
|
||||
this.eventEmitter.off(event, cb);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { INJECT_SCRIPT_PIXEL_OBSERVER } from "../constants";
|
||||
import { BasicEventEmitter } from "./eventEmitter";
|
||||
import { Store } from "./store";
|
||||
|
||||
interface Pixel {
|
||||
x: number;
|
||||
y: number;
|
||||
color: number;
|
||||
}
|
||||
|
||||
export class PixelPlaced {
|
||||
private static instance: PixelPlaced;
|
||||
private constructor(public readonly store: Store) {
|
||||
// The website is logging pixels as object onto pixelsPlaced onto console with .log.
|
||||
// With extension we need to cross 2 worlds. Adding artificial api to document
|
||||
|
||||
const api = document.createElement("script");
|
||||
api.addEventListener("load", () => {
|
||||
document.body.removeChild(api);
|
||||
}, { once: true });
|
||||
api.addEventListener("error", error => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
if (ENVIRONMENT === "browser-extension") {
|
||||
api.src = chrome.runtime.getURL("/assets/scripts/inject.js");
|
||||
} else {
|
||||
api.textContent = INJECT_SCRIPT_PIXEL_OBSERVER;
|
||||
}
|
||||
|
||||
document.body.appendChild(api);
|
||||
(document as any)
|
||||
.addEventListener("__pixelsPlaced", (event: CustomEvent<Pixel>) => {
|
||||
const body = event.detail;
|
||||
console.log(body);
|
||||
if (typeof body === "object" && "x" in body && "y" in body && "color" in body
|
||||
&& typeof body.color === "number" && typeof body.x === "number"
|
||||
&& typeof body.y === "number"
|
||||
) {
|
||||
const data = event.detail;
|
||||
this.store.addPixelLog(data.x, data.y, data.color);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static create(store: Store) {
|
||||
if (!this.instance) {
|
||||
this.instance = new PixelPlaced(store);
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
getCount() {
|
||||
return this.store.getPixelCount();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
export type SpyHistoryPushStateSpy = (data: any, unused: string, url?: string | URL | null) => Promise<void> | void;
|
||||
|
||||
export class HistoryPushStateSpy {
|
||||
private static pushState = window.history.pushState;
|
||||
private static overwritten = false;
|
||||
private static listeners: SpyHistoryPushStateSpy[] = [];
|
||||
|
||||
private static overridePushState() {
|
||||
if (this.overwritten) {
|
||||
return;
|
||||
}
|
||||
this.overwritten = true;
|
||||
const listeners = this.listeners;
|
||||
const pushState = this.pushState;
|
||||
(window as any).history.pushState = function (...args: any) {
|
||||
for (const listener of listeners) {
|
||||
(listener as any).apply(this, args);
|
||||
}
|
||||
return (pushState as any).apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
static onPushState(cb: SpyHistoryPushStateSpy) {
|
||||
this.overridePushState();
|
||||
if (!this.listeners.includes(cb)) {
|
||||
this.listeners.push(cb);
|
||||
}
|
||||
}
|
||||
static offPushState(cb: SpyHistoryPushStateSpy) {
|
||||
const index = this.listeners.indexOf(cb);
|
||||
if (index !== -1) {
|
||||
this.listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -2,8 +2,7 @@ import { clone, isInteger } from "lodash";
|
||||
import { MuralEx, MuralOld, MuralStatus, RGB } from "../interfaces";
|
||||
import { fetchCombineTiledImage } from "./canvasShot";
|
||||
import { Mural } from "./mural";
|
||||
|
||||
export const CHUNK_SIZE = 512;
|
||||
import { CHUNK_SIZE } from "../constants";
|
||||
|
||||
export function pushUnique<T>(items: T[], item: T) {
|
||||
const index = items.indexOf(item);
|
||||
|
||||
Reference in New Issue
Block a user