From cc257533148419b1c94a1cd257e756d2688a403c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 4 Oct 2022 00:52:42 +0200 Subject: [PATCH] feat: Experimental browser support --- browser/Vencord.ts | 3 ++ browser/VencordNativeStub.ts | 39 ++++++++++++++++ browser/background.js | 1 + browser/content.js | 10 +++++ browser/manifest.json | 30 +++++++++++++ build.mjs | 3 +- buildWeb.mjs | 86 ++++++++++++++++++++++++++++++++++++ package.json | 5 ++- pnpm-lock.yaml | 45 +++++++------------ src/components/Settings.tsx | 13 +++--- src/plugins/settings.ts | 12 +++-- src/utils/isWeb.ts | 1 + src/utils/types.ts | 4 ++ src/webpack/patchWebpack.ts | 1 + 14 files changed, 212 insertions(+), 41 deletions(-) create mode 100644 browser/Vencord.ts create mode 100644 browser/VencordNativeStub.ts create mode 100644 browser/background.js create mode 100644 browser/content.js create mode 100644 browser/manifest.json create mode 100644 buildWeb.mjs create mode 100644 src/utils/isWeb.ts diff --git a/browser/Vencord.ts b/browser/Vencord.ts new file mode 100644 index 000000000..2d4315df0 --- /dev/null +++ b/browser/Vencord.ts @@ -0,0 +1,3 @@ +import "./VencordNativeStub"; + +export * from "../src/Vencord"; diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts new file mode 100644 index 000000000..bdcae4e18 --- /dev/null +++ b/browser/VencordNativeStub.ts @@ -0,0 +1,39 @@ +import IpcEvents from "../src/utils/IpcEvents"; + +// Discord deletes this so need to store in variable +var localStorage = window.localStorage; + +const handlers = { + [IpcEvents.GET_REPO]: () => "", // TODO + [IpcEvents.GET_SETTINGS_DIR]: () => "LocalStorage", + + [IpcEvents.GET_QUICK_CSS]: () => localStorage.getItem("VencordQuickCss"), + [IpcEvents.GET_SETTINGS]: () => localStorage.getItem("VencordSettings") || "{}", + [IpcEvents.SET_SETTINGS]: (s: string) => localStorage.setItem("VencordSettings", s), + + [IpcEvents.GET_UPDATES]: () => ({ ok: true, value: [] }), + + [IpcEvents.OPEN_EXTERNAL]: (url: string) => open(url, "_blank"), + [IpcEvents.OPEN_QUICKCSS]: () => { } // TODO +}; + +function onEvent(event: string, ...args: any[]) { + const handler = handlers[event]; + if (!handler) throw new Error(`Event ${event} not implemented.`); + return handler(...args); +} + +window.VencordNative = { + getVersions: () => ({}), + ipc: { + send: (event: string, ...args: any[]) => void onEvent(event, ...args), + sendSync: onEvent, + on(event: string, listener: () => {}) { + // TODO quickCss + }, + off(event: string, listener: () => {}) { + // not used for now + }, + invoke: (event: string, ...args: any[]) => Promise.resolve(onEvent(event, ...args)) + }, +}; diff --git a/browser/background.js b/browser/background.js new file mode 100644 index 000000000..872134b7f --- /dev/null +++ b/browser/background.js @@ -0,0 +1 @@ +// could use this in the future diff --git a/browser/content.js b/browser/content.js new file mode 100644 index 000000000..5922e8fad --- /dev/null +++ b/browser/content.js @@ -0,0 +1,10 @@ +// This is just the bootstrap script + +if (typeof browser === "undefined") { + var browser = chrome; +} + +var script = document.createElement("script"); +script.src = browser.runtime.getURL("dist/Vencord.js"); +// documentElement because we load before body/head are ready +document.documentElement.appendChild(script); diff --git a/browser/manifest.json b/browser/manifest.json new file mode 100644 index 000000000..c01bc4496 --- /dev/null +++ b/browser/manifest.json @@ -0,0 +1,30 @@ +{ + "manifest_version": 2, + "name": "Vencord Web", + "description": "Yeee", + "version": "1.0.0", + "author": "Vendicated", + "homepage_url": "https://github.com/Vendicated/Vencord", + "background": { + "scripts": [ + "background.js" + ] + }, + "content_scripts": [ + { + "run_at": "document_start", + "matches": [ + "*://*.discord.com/*" + ], + "js": [ + "content.js" + ] + } + ], + "permissions": [ + "*://*.discord.com/*" + ], + "web_accessible_resources": [ + "dist/Vencord.js" + ] +} diff --git a/build.mjs b/build.mjs index d6de0d2df..5b0b1c09e 100755 --- a/build.mjs +++ b/build.mjs @@ -2,7 +2,6 @@ import { execSync } from "child_process"; import esbuild from "esbuild"; import { readdirSync } from "fs"; -import { performance } from "perf_hooks"; /** * @type {esbuild.WatchMode|false} @@ -115,7 +114,7 @@ await Promise.all([ sourcemap: false, watch, minify: true, - }) + }), ]).catch(err => { console.error("Build failed"); console.error(err.message); diff --git a/buildWeb.mjs b/buildWeb.mjs new file mode 100644 index 000000000..9031d1493 --- /dev/null +++ b/buildWeb.mjs @@ -0,0 +1,86 @@ +// TODO: Modularise these plugins since both build scripts use them + +import { execSync } from "child_process"; +import { createWriteStream, readdirSync } from "fs"; +import yazl from "yazl"; +import esbuild from "esbuild"; + +/** + * @type {esbuild.Plugin} + */ +const globPlugins = { + name: "glob-plugins", + setup: build => { + build.onResolve({ filter: /^plugins$/ }, args => { + return { + namespace: "import-plugins", + path: args.path + }; + }); + + build.onLoad({ filter: /^plugins$/, namespace: "import-plugins" }, () => { + const files = readdirSync("./src/plugins"); + let code = ""; + let obj = ""; + for (let i = 0; i < files.length; i++) { + if (files[i] === "index.ts") { + continue; + } + const mod = `__pluginMod${i}`; + code += `import ${mod} from "./${files[i].replace(/.tsx?$/, "")}";\n`; + obj += `[${mod}.name]: ${mod},`; + } + code += `export default {${obj}}`; + return { + contents: code, + resolveDir: "./src/plugins" + }; + }); + } +}; + +const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); +/** + * @type {esbuild.Plugin} + */ +const gitHashPlugin = { + name: "git-hash-plugin", + setup: build => { + const filter = /^git-hash$/; + build.onResolve({ filter }, args => ({ + namespace: "git-hash", path: args.path + })); + build.onLoad({ filter, namespace: "git-hash" }, () => ({ + contents: `export default "${gitHash}"` + })); + } +}; + +await esbuild.build({ + logLevel: "info", + entryPoints: ["browser/Vencord.ts"], + outfile: "dist/browser.js", + format: "iife", + bundle: true, + globalName: "Vencord", + target: ["esnext"], + footer: { js: "//# sourceURL=VencordWeb" }, + external: ["plugins", "git-hash"], + plugins: [ + globPlugins, + gitHashPlugin + ], + sourcemap: false, + minify: true, +}); + +const zip = new yazl.ZipFile(); +zip.outputStream.pipe(createWriteStream("dist/extension.zip")).on("close", () => { + console.info("Extension written to dist/extension.zip"); +}); + +zip.addFile("dist/browser.js", "dist/Vencord.js"); +["background.js", "content.js", "manifest.json"].forEach(f => { + zip.addFile(`browser/${f}`, `${f}`); +}); +zip.end(); diff --git a/package.json b/package.json index bde4752d6..39ac69961 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,17 @@ "devDependencies": { "@types/node": "^18.7.13", "@types/react": "^18.0.17", + "@types/yazl": "^2.4.2", "electron": "^20.1.0", - "esbuild": "^0.15.5" + "esbuild": "^0.15.5", + "yazl": "^2.5.1" }, "dependencies": { "discord-types": "^1.3.26", "electron-devtools-installer": "^3.2.0" }, "scripts": { + "buildWeb": "node buildWeb.mjs", "build": "node build.mjs", "watch": "node build.mjs --watch" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 304261da2..25adfb49e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,28 +1,26 @@ -lockfileVersion: 5.3 +lockfileVersion: 5.4 specifiers: - '@types/flux': ^3.1.11 '@types/node': ^18.7.13 '@types/react': ^18.0.17 + '@types/yazl': ^2.4.2 discord-types: ^1.3.26 electron: ^20.1.0 electron-devtools-installer: ^3.2.0 esbuild: ^0.15.5 - jsposed: ^1.0.2 - prettier: ^2.7.1 + yazl: ^2.5.1 dependencies: discord-types: 1.3.26 electron-devtools-installer: 3.2.0 - jsposed: 1.0.2 - prettier: 2.7.1 devDependencies: - '@types/flux': 3.1.11 '@types/node': 18.7.13 '@types/react': 18.0.17 + '@types/yazl': 2.4.2 electron: 20.1.0 esbuild: 0.15.5 + yazl: 2.5.1 packages: @@ -65,17 +63,6 @@ packages: defer-to-connect: 1.1.3 dev: true - /@types/fbemitter/2.0.32: - resolution: {integrity: sha512-Hwq28bBlbmfCgLnNJvjl5ssTrbZCTSblI4vqPpqZrbbEL8vn5l2UivxhlMYfUY7a4SR8UB6RKoLjOZfljqAa6g==} - dev: true - - /@types/flux/3.1.11: - resolution: {integrity: sha512-Aq4UB1ZqAKcPbhB0GpgMw2sntvOh71he9tjz53TLKrI7rw3Y3LxCW5pTYY9IV455hQapm4pmxFjpqlWOs308Yg==} - dependencies: - '@types/fbemitter': 2.0.32 - '@types/react': 18.0.17 - dev: true - /@types/keyv/3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: @@ -126,6 +113,12 @@ packages: dev: true optional: true + /@types/yazl/2.4.2: + resolution: {integrity: sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ==} + dependencies: + '@types/node': 18.7.13 + dev: true + /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: false @@ -684,10 +677,6 @@ packages: graceful-fs: 4.2.10 dev: true - /jsposed/1.0.2: - resolution: {integrity: sha512-t1vQsxnH65kOBRc4swue6EFm/WmPZwLLJ/84IV3aP93f2F724tHK5HIWwrRUKYYqJb9BJEewBsVh/jHQssJfbw==} - dev: false - /jszip/3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} dependencies: @@ -822,12 +811,6 @@ packages: engines: {node: '>=4'} dev: true - /prettier/2.7.1: - resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: false - /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: false @@ -1001,3 +984,9 @@ packages: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 dev: true + + /yazl/2.5.1: + resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==} + dependencies: + buffer-crc32: 0.2.13 + dev: true diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index d4a3dcaec..4dbb1b21a 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -9,6 +9,7 @@ import { startPlugin } from "../plugins"; import { stopPlugin } from '../plugins/index'; import { Flex } from './Flex'; import { ChangeList } from '../utils/ChangeList'; +import { IS_WEB } from '../utils/isWeb'; function showErrorToast(message: string) { Toasts.show({ @@ -72,7 +73,7 @@ export default ErrorBoundary.wrap(function Settings() { SettingsDir: {settingsDir} - + {!IS_WEB && - + } Settings Use QuickCss - settings.notifyAboutUpdates = v} note="Shows a Toast on StartUp" > Get notified about new Updates - - } + {!IS_WEB && settings.unsafeRequire = v} note="Enables VencordNative.require. Useful for testing, very bad for security. Leave this off unless you need it." > Enable Unsafe Require - + } diff --git a/src/plugins/settings.ts b/src/plugins/settings.ts index 2ed85e63e..6927f642f 100644 --- a/src/plugins/settings.ts +++ b/src/plugins/settings.ts @@ -1,6 +1,7 @@ import definePlugin from "../utils/types"; import gitHash from "git-hash"; import { Devs } from '../utils/constants'; +import { IS_WEB } from "../utils/isWeb"; export default definePlugin({ name: "Settings", @@ -15,9 +16,12 @@ export default definePlugin({ replace: m => { const idx = m.indexOf("Host") - 1; const template = m.slice(0, idx); - return `${m}, ${template}"Vencord ", "${gitHash}"), " "), ` + - `${template} "Electron ",VencordNative.getVersions().electron)," "), ` + - `${template} "Chrome ",VencordNative.getVersions().chrome)," ")`; + let r = `${m}, ${template}"Vencord ", "${gitHash}${IS_WEB ? " (Web)" : ""}"), " ")`; + if (!IS_WEB) { + r += `,${template} "Electron ",VencordNative.getVersions().electron)," "),`; + r += `${template} "Chrome ",VencordNative.getVersions().chrome)," ")`; + } + return r; } } ] @@ -28,7 +32,7 @@ export default definePlugin({ replace: (m, mod) => `{section:${mod}.ID.HEADER,label:"Vencord"},` + `{section:"VencordSetting",label:"Vencord",element:Vencord.Components.Settings},` + - `{section:"VencordUpdater",label:"Updater",element:Vencord.Components.Updater},` + + `{section:"VencordUpdater",label:"Updater",element:Vencord.Components.Updater,predicate:()=>!IS_WEB},` + `{section:${mod}.ID.DIVIDER},${m}` } diff --git a/src/utils/isWeb.ts b/src/utils/isWeb.ts new file mode 100644 index 000000000..408216483 --- /dev/null +++ b/src/utils/isWeb.ts @@ -0,0 +1 @@ +export const IS_WEB = window.IS_WEB = typeof window.DiscordNative === "undefined"; diff --git a/src/utils/types.ts b/src/utils/types.ts index a6b79c5e3..1c6361360 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -33,6 +33,10 @@ interface PluginDef { patches?: Omit[]; dependencies?: string[], required?: boolean; + /** + * Set this if your plugin only works on Browser or Desktop, not both + */ + target?: "WEB" | "DESKTOP" | "BOTH"; } export type IpcRes = { ok: true; value: V; } | { ok: false, error: any; }; diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 469b9302c..ffd81d5a8 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -6,6 +6,7 @@ let webpackChunk: any[]; const logger = new Logger("WebpackInterceptor", "#8caaee"); +console.log("prepatch is", window[WEBPACK_CHUNK]); Object.defineProperty(window, WEBPACK_CHUNK, { get: () => webpackChunk, set: (v) => {