feat: Experimental browser support

This commit is contained in:
Vendicated 2022-10-04 00:52:42 +02:00
parent a9eae106c7
commit cc25753314
No known key found for this signature in database
GPG key ID: EC781ADFB93EFFA3
14 changed files with 212 additions and 41 deletions

3
browser/Vencord.ts Normal file
View file

@ -0,0 +1,3 @@
import "./VencordNativeStub";
export * from "../src/Vencord";

View file

@ -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))
},
};

1
browser/background.js Normal file
View file

@ -0,0 +1 @@
// could use this in the future

10
browser/content.js Normal file
View file

@ -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);

30
browser/manifest.json Normal file
View file

@ -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"
]
}

View file

@ -2,7 +2,6 @@
import { execSync } from "child_process"; import { execSync } from "child_process";
import esbuild from "esbuild"; import esbuild from "esbuild";
import { readdirSync } from "fs"; import { readdirSync } from "fs";
import { performance } from "perf_hooks";
/** /**
* @type {esbuild.WatchMode|false} * @type {esbuild.WatchMode|false}
@ -115,7 +114,7 @@ await Promise.all([
sourcemap: false, sourcemap: false,
watch, watch,
minify: true, minify: true,
}) }),
]).catch(err => { ]).catch(err => {
console.error("Build failed"); console.error("Build failed");
console.error(err.message); console.error(err.message);

86
buildWeb.mjs Normal file
View file

@ -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();

View file

@ -2,14 +2,17 @@
"devDependencies": { "devDependencies": {
"@types/node": "^18.7.13", "@types/node": "^18.7.13",
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
"@types/yazl": "^2.4.2",
"electron": "^20.1.0", "electron": "^20.1.0",
"esbuild": "^0.15.5" "esbuild": "^0.15.5",
"yazl": "^2.5.1"
}, },
"dependencies": { "dependencies": {
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"electron-devtools-installer": "^3.2.0" "electron-devtools-installer": "^3.2.0"
}, },
"scripts": { "scripts": {
"buildWeb": "node buildWeb.mjs",
"build": "node build.mjs", "build": "node build.mjs",
"watch": "node build.mjs --watch" "watch": "node build.mjs --watch"
} }

View file

@ -1,28 +1,26 @@
lockfileVersion: 5.3 lockfileVersion: 5.4
specifiers: specifiers:
'@types/flux': ^3.1.11
'@types/node': ^18.7.13 '@types/node': ^18.7.13
'@types/react': ^18.0.17 '@types/react': ^18.0.17
'@types/yazl': ^2.4.2
discord-types: ^1.3.26 discord-types: ^1.3.26
electron: ^20.1.0 electron: ^20.1.0
electron-devtools-installer: ^3.2.0 electron-devtools-installer: ^3.2.0
esbuild: ^0.15.5 esbuild: ^0.15.5
jsposed: ^1.0.2 yazl: ^2.5.1
prettier: ^2.7.1
dependencies: dependencies:
discord-types: 1.3.26 discord-types: 1.3.26
electron-devtools-installer: 3.2.0 electron-devtools-installer: 3.2.0
jsposed: 1.0.2
prettier: 2.7.1
devDependencies: devDependencies:
'@types/flux': 3.1.11
'@types/node': 18.7.13 '@types/node': 18.7.13
'@types/react': 18.0.17 '@types/react': 18.0.17
'@types/yazl': 2.4.2
electron: 20.1.0 electron: 20.1.0
esbuild: 0.15.5 esbuild: 0.15.5
yazl: 2.5.1
packages: packages:
@ -65,17 +63,6 @@ packages:
defer-to-connect: 1.1.3 defer-to-connect: 1.1.3
dev: true 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: /@types/keyv/3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies: dependencies:
@ -126,6 +113,12 @@ packages:
dev: true dev: true
optional: 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: /balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: false dev: false
@ -684,10 +677,6 @@ packages:
graceful-fs: 4.2.10 graceful-fs: 4.2.10
dev: true dev: true
/jsposed/1.0.2:
resolution: {integrity: sha512-t1vQsxnH65kOBRc4swue6EFm/WmPZwLLJ/84IV3aP93f2F724tHK5HIWwrRUKYYqJb9BJEewBsVh/jHQssJfbw==}
dev: false
/jszip/3.10.1: /jszip/3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
dependencies: dependencies:
@ -822,12 +811,6 @@ packages:
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true 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: /process-nextick-args/2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: false dev: false
@ -1001,3 +984,9 @@ packages:
buffer-crc32: 0.2.13 buffer-crc32: 0.2.13
fd-slicer: 1.1.0 fd-slicer: 1.1.0
dev: true dev: true
/yazl/2.5.1:
resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==}
dependencies:
buffer-crc32: 0.2.13
dev: true

View file

@ -9,6 +9,7 @@ import { startPlugin } from "../plugins";
import { stopPlugin } from '../plugins/index'; import { stopPlugin } from '../plugins/index';
import { Flex } from './Flex'; import { Flex } from './Flex';
import { ChangeList } from '../utils/ChangeList'; import { ChangeList } from '../utils/ChangeList';
import { IS_WEB } from '../utils/isWeb';
function showErrorToast(message: string) { function showErrorToast(message: string) {
Toasts.show({ Toasts.show({
@ -72,7 +73,7 @@ export default ErrorBoundary.wrap(function Settings() {
SettingsDir: <code style={{ userSelect: 'text', cursor: 'text' }}>{settingsDir}</code> SettingsDir: <code style={{ userSelect: 'text', cursor: 'text' }}>{settingsDir}</code>
</Forms.FormText> </Forms.FormText>
<Flex className={classes(Margins.marginBottom20)}> {!IS_WEB && <Flex className={classes(Margins.marginBottom20)}>
<Button <Button
onClick={() => window.DiscordNative.app.relaunch()} onClick={() => window.DiscordNative.app.relaunch()}
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
@ -94,7 +95,7 @@ export default ErrorBoundary.wrap(function Settings() {
> >
Open QuickCSS File Open QuickCSS File
</Button> </Button>
</Flex> </Flex>}
<Forms.FormDivider /> <Forms.FormDivider />
<Forms.FormTitle tag="h5">Settings</Forms.FormTitle> <Forms.FormTitle tag="h5">Settings</Forms.FormTitle>
<Switch <Switch
@ -104,20 +105,20 @@ export default ErrorBoundary.wrap(function Settings() {
> >
Use QuickCss Use QuickCss
</Switch> </Switch>
<Switch {!IS_WEB && <Switch
value={settings.notifyAboutUpdates} value={settings.notifyAboutUpdates}
onChange={(v: boolean) => settings.notifyAboutUpdates = v} onChange={(v: boolean) => settings.notifyAboutUpdates = v}
note="Shows a Toast on StartUp" note="Shows a Toast on StartUp"
> >
Get notified about new Updates Get notified about new Updates
</Switch> </Switch>}
<Switch {!IS_WEB && <Switch
value={settings.unsafeRequire} value={settings.unsafeRequire}
onChange={(v: boolean) => settings.unsafeRequire = v} onChange={(v: boolean) => settings.unsafeRequire = v}
note="Enables VencordNative.require. Useful for testing, very bad for security. Leave this off unless you need it." note="Enables VencordNative.require. Useful for testing, very bad for security. Leave this off unless you need it."
> >
Enable Unsafe Require Enable Unsafe Require
</Switch> </Switch>}
<Forms.FormDivider /> <Forms.FormDivider />

View file

@ -1,6 +1,7 @@
import definePlugin from "../utils/types"; import definePlugin from "../utils/types";
import gitHash from "git-hash"; import gitHash from "git-hash";
import { Devs } from '../utils/constants'; import { Devs } from '../utils/constants';
import { IS_WEB } from "../utils/isWeb";
export default definePlugin({ export default definePlugin({
name: "Settings", name: "Settings",
@ -15,9 +16,12 @@ export default definePlugin({
replace: m => { replace: m => {
const idx = m.indexOf("Host") - 1; const idx = m.indexOf("Host") - 1;
const template = m.slice(0, idx); const template = m.slice(0, idx);
return `${m}, ${template}"Vencord ", "${gitHash}"), " "), ` + let r = `${m}, ${template}"Vencord ", "${gitHash}${IS_WEB ? " (Web)" : ""}"), " ")`;
`${template} "Electron ",VencordNative.getVersions().electron)," "), ` + if (!IS_WEB) {
`${template} "Chrome ",VencordNative.getVersions().chrome)," ")`; r += `,${template} "Electron ",VencordNative.getVersions().electron)," "),`;
r += `${template} "Chrome ",VencordNative.getVersions().chrome)," ")`;
}
return r;
} }
} }
] ]
@ -28,7 +32,7 @@ export default definePlugin({
replace: (m, mod) => replace: (m, mod) =>
`{section:${mod}.ID.HEADER,label:"Vencord"},` + `{section:${mod}.ID.HEADER,label:"Vencord"},` +
`{section:"VencordSetting",label:"Vencord",element:Vencord.Components.Settings},` + `{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}` `{section:${mod}.ID.DIVIDER},${m}`
} }

1
src/utils/isWeb.ts Normal file
View file

@ -0,0 +1 @@
export const IS_WEB = window.IS_WEB = typeof window.DiscordNative === "undefined";

View file

@ -33,6 +33,10 @@ interface PluginDef {
patches?: Omit<Patch, "plugin">[]; patches?: Omit<Patch, "plugin">[];
dependencies?: string[], dependencies?: string[],
required?: boolean; required?: boolean;
/**
* Set this if your plugin only works on Browser or Desktop, not both
*/
target?: "WEB" | "DESKTOP" | "BOTH";
} }
export type IpcRes<V = any> = { ok: true; value: V; } | { ok: false, error: any; }; export type IpcRes<V = any> = { ok: true; value: V; } | { ok: false, error: any; };

View file

@ -6,6 +6,7 @@ let webpackChunk: any[];
const logger = new Logger("WebpackInterceptor", "#8caaee"); const logger = new Logger("WebpackInterceptor", "#8caaee");
console.log("prepatch is", window[WEBPACK_CHUNK]);
Object.defineProperty(window, WEBPACK_CHUNK, { Object.defineProperty(window, WEBPACK_CHUNK, {
get: () => webpackChunk, get: () => webpackChunk,
set: (v) => { set: (v) => {