Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
kmmiio99o 2025-12-23 15:54:30 +01:00
commit 31715ad513
No known key found for this signature in database
GPG key ID: 13A107AB06ACF9FA
7 changed files with 912 additions and 223 deletions

426
bun.lock

File diff suppressed because it is too large Load diff

View file

@ -15,44 +15,46 @@
"devDependencies": {
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^7.1.0",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"semantic-release": "^25.0.2",
"@swc/core": "^1.15.7",
"semantic-release": "^24.2.7",
"@swc/core": "^1.13.1",
"@swc/helpers": "^0.5.17",
"@types/node": "^25.0.3",
"@types/node": "^20.19.9",
"@types/yargs-parser": "^21.0.3",
"@typescript-eslint/eslint-plugin": "^8.50.0",
"@typescript-eslint/parser": "^8.50.0",
"@typescript-eslint/typescript-estree": "^8.50.0",
"chalk": "^5.6.2",
"esbuild": "^0.27.2",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@typescript-eslint/typescript-estree": "^7.18.0",
"chalk": "^5.4.1",
"esbuild": "^0.20.2",
"esbuild-plugin-globals": "^0.2.0",
"eslint": "^9.39.2",
"eslint": "^8.57.1",
"eslint-plugin-import-alias": "^1.2.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.3.0",
"typescript": "^5.9.3",
"yargs-parser": "^22.0.0"
"eslint-plugin-unused-imports": "^3.2.0",
"typescript": "^5.8.3",
"yargs-parser": "^21.1.1"
},
"dependencies": {
"@gullerya/object-observer": "^6.1.4",
"@tanstack/react-query": "^5.90.12",
"@types/chroma-js": "~3.1.2",
"@types/lodash": "~4.17.21",
"@types/react": "19.2.7",
"es-toolkit": "^1.43.0",
"@tanstack/react-query": "^5.83.0",
"@types/chroma-js": "~2.4.5",
"@types/lodash": "~4.17.20",
"@types/react": "19.1.8",
"es-toolkit": "^1.39.7",
"fuzzysort": "^3.1.0",
"intl-messageformat": "^11.0.4",
"moment": "2.30.1",
"react-native": "0.83.1",
"react-native-reanimated": "^4.2.1",
"react-native-webview": "^13.16.0",
"spitroast": "^2.1.6",
"type-fest": "^5.3.1"
"intl-messageformat": "^10.7.16",
"moment": "2.22.2",
"react-devtools": "^7.0.1",
"react-native": "0.78.0",
"react-native-reanimated": "^3.18.0",
"spitroast": "^1.4.4",
"type-fest": "^4.41.0"
},
"trustedDependencies": [
"@swc/core"
"@swc/core",
"electron",
"esbuild"
]
}

View file

@ -3,14 +3,8 @@ import { CheckState, useFileExists } from "@core/ui/hooks/useFS";
import AssetBrowser from "@core/ui/settings/pages/Developer/AssetBrowser";
import { useProxy } from "@core/vendetta/storage";
import { findAssetId } from "@lib/api/assets";
import { connectToDebugger } from "@lib/api/debug";
import {
getReactDevToolsProp,
getReactDevToolsVersion,
isLoaderConfigSupported,
isReactDevToolsPreloaded,
isVendettaLoader,
} from "@lib/api/native/loader";
import { connectToDebugger, disconnectFromDebugger, isConnectedToDebugger } from "@lib/api/debug";
import { getReactDevToolsProp, getReactDevToolsVersion, isLoaderConfigSupported, isReactDevToolsPreloaded, isVendettaLoader } from "@lib/api/native/loader";
import { loaderConfig, settings } from "@lib/api/settings";
import { lazyDestructure } from "@lib/utils/lazy";
import { NavigationNative } from "@metro/common";
@ -31,6 +25,7 @@ import { createStyles, TextStyleSheet } from "@ui/styles";
import { NativeModules } from "react-native";
import { ScrollView, StyleSheet } from "react-native";
import { showToast } from "@ui/toasts";
import { useState, useEffect } from "react";
const { hideActionSheet } = lazyDestructure(() =>
findByProps("openLazy", "hideActionSheet"),
@ -45,8 +40,7 @@ const { AlertModal, AlertActionButton } = lazyDestructure(() =>
findByProps("AlertModal", "AlertActions"),
);
// todo: This link isnt minified, but it probably should be
const RDT_EMBED_LINK = "https://codeberg.org/raincord/Devtools/raw/branch/main/reactDevtools.js";
const RDT_EMBED_LINK = "https://codeberg.org/raincord/raindevtools/raw/branch/dev/dist/index.bundle";
const useStyles = createStyles({
leadingText: {
@ -63,7 +57,8 @@ const useStyles = createStyles({
});
export default function Developer() {
const [rdtFileExists, fs] = useFileExists("preloads/reactDevtools.js");
const [rdtFileExists, fs] = useFileExists("preloads/reactDevtools.js");
const [isDebuggerConnected, setIsDebuggerConnected] = useState(isConnectedToDebugger());
const styles = useStyles();
const navigation = NavigationNative.useNavigation();

View file

@ -1,14 +1,13 @@
import { getThemeFromLoader, selectTheme, themes } from "@lib/addons/themes";
import { findAssetId } from "@lib/api/assets";
import { getLoaderName, getLoaderVersion, isThemeSupported, getReactDevToolsProp } from "@lib/api/native/loader";
import { getLoaderName, getLoaderVersion, isThemeSupported, getReactDevToolsProp, isReactDevToolsPreloaded } from "@lib/api/native/loader";
import { BundleUpdaterManager, NativeClientInfoModule, NativeDeviceModule } from "@lib/api/native/modules";
import { after } from "@lib/api/patcher";
import { settings } from "@lib/api/settings";
import { logger } from "@lib/utils/logger";
import { showToast } from "@ui/toasts";
import { version } from "bunny-build-info";
import { Platform, type PlatformConstants } from "react-native";
import { StyleSheet } from "react-native";
import { Platform, type PlatformConstants, StyleSheet } from "react-native";
export interface RNConstants extends PlatformConstants {
// Android
@ -44,38 +43,291 @@ export async function toggleSafeMode() {
setTimeout(BundleUpdaterManager.reload, 400);
}
let socket: WebSocket;
// --- Debugger State & Logic ---
let socket: WebSocket | undefined;
let originalConsoleLog: any;
let originalConsoleError: any;
let originalConsoleWarn: any;
let originalLoggerLog: any;
let originalLoggerError: any;
let originalLoggerWarn: any;
const VERSION = 1;
enum MessageType {
Hello = 'hello',
Hi = 'hi',
Log = 'log',
Run = 'run',
}
enum LogLevel {
Debug = 'debug',
Default = 'default',
Warn = 'warn',
Error = 'error',
}
interface LogMessage {
type: MessageType.Log;
data: {
level: LogLevel;
message: any[];
};
}
interface HelloMessage {
type: MessageType.Hello;
data: {
version: number;
};
}
interface RunMessage {
type: MessageType.Run;
data: {
code: string;
};
}
function serializeMessage(msg: any): string {
return JSON.stringify(msg);
}
function sendLog(level: LogLevel, ...args: any[]) {
if (socket?.readyState === WebSocket.OPEN) {
const message: LogMessage = {
type: MessageType.Log,
data: {
level,
message: args
}
};
socket.send(serializeMessage(message));
}
}
function patchConsoleAndLogger() {
originalConsoleLog = console.log;
console.log = function(...args: any[]) {
originalConsoleLog.apply(console, args);
sendLog(LogLevel.Default, ...args);
};
originalConsoleError = console.error;
console.error = function(...args: any[]) {
originalConsoleError.apply(console, args);
sendLog(LogLevel.Error, ...args);
};
originalConsoleWarn = console.warn;
console.warn = function(...args: any[]) {
originalConsoleWarn.apply(console, args);
sendLog(LogLevel.Warn, ...args);
};
if (logger) {
originalLoggerLog = logger.log;
logger.log = function(...args: any[]) {
originalLoggerLog.apply(logger, args);
sendLog(LogLevel.Default, ...args);
};
originalLoggerError = logger.error;
logger.error = function(...args: any[]) {
originalLoggerError.apply(logger, args);
sendLog(LogLevel.Error, ...args);
};
originalLoggerWarn = logger.warn;
logger.warn = function(...args: any[]) {
originalLoggerWarn.apply(logger, args);
sendLog(LogLevel.Warn, ...args);
};
}
}
function unpatchConsoleAndLogger() {
if (originalConsoleLog) {
console.log = originalConsoleLog;
originalConsoleLog = undefined;
}
if (originalConsoleError) {
console.error = originalConsoleError;
originalConsoleError = undefined;
}
if (originalConsoleWarn) {
console.warn = originalConsoleWarn;
originalConsoleWarn = undefined;
}
if (logger) {
if (originalLoggerLog) {
logger.log = originalLoggerLog;
originalLoggerLog = undefined;
}
if (originalLoggerError) {
logger.error = originalLoggerError;
originalLoggerError = undefined;
}
if (originalLoggerWarn) {
logger.warn = originalLoggerWarn;
originalLoggerWarn = undefined;
}
}
}
export function connectToDebugger(url: string) {
if (socket !== undefined && socket.readyState !== WebSocket.CLOSED) socket.close();
if (socket !== undefined && socket.readyState !== WebSocket.CLOSED) {
unpatchConsoleAndLogger();
socket.close();
}
if (!url) {
showToast("Invalid debugger URL!", findAssetId("Small"));
return;
}
socket = new WebSocket(`ws://${url}`);
try {
socket = new WebSocket(`ws://${url}`);
socket.addEventListener("open", () => showToast("Connected to debugger.", findAssetId("Check")));
socket.addEventListener("message", (message: any) => {
try {
(0, eval)(message.data);
} catch (e) {
console.error(e);
}
});
socket.addEventListener("open", () => {
showToast("Connected to debugger.", findAssetId("Check"));
const hello: HelloMessage = {
type: MessageType.Hello,
data: {
version: VERSION
}
};
socket?.send(serializeMessage(hello));
socket.addEventListener("error", (err: any) => {
console.log(`Debugger error: ${err.message}`);
showToast("An error occurred with the debugger connection!", findAssetId("Small"));
});
patchConsoleAndLogger();
});
socket.addEventListener("message", (message: any) => {
try {
const data = JSON.parse(message.data);
if (data.type === MessageType.Run && data.data?.code) {
try {
(0, eval)(data.data.code);
} catch (e) {
console.error("Error executing remote code:", e);
}
}
} catch (e) {
try {
(0, eval)(message.data);
} catch (err) {
console.error(err);
}
}
});
socket.addEventListener("close", () => {
showToast("Disconnected from debugger.", findAssetId("Small"));
unpatchConsoleAndLogger();
});
socket.addEventListener("error", (err: any) => {
console.log(`Debugger error: ${err.message}`);
showToast("An error occurred with the debugger connection!", findAssetId("Small"));
unpatchConsoleAndLogger();
});
} catch (e) {
logger.error("Failed to connect to debugger:", e);
showToast("Failed to connect to debugger!", findAssetId("Small"));
}
}
export function disconnectFromDebugger() {
if (socket) {
unpatchConsoleAndLogger();
socket.close();
socket = undefined;
showToast("Disconnected from debugger.", findAssetId("Check"));
}
}
export function isConnectedToDebugger(): boolean {
return socket?.readyState === WebSocket.OPEN;
}
// --- React DevTools (RDT) Logic ---
const rdtPort = 8097;
export let rdtClient: WebSocket | null = null;
export let rdtConnected = false;
const changeHooks = new Set<(value: boolean) => void>();
function bump() {
for (const x of changeHooks) x(rdtConnected);
}
function cleanupRdt() {
rdtClient = null;
rdtConnected = false;
bump();
}
/** @internal */
export function connectRdt(url: string, quiet?: boolean) {
if (!isReactDevToolsPreloaded() || rdtClient) return;
const base = url.split(":").slice(0, -1).join(":");
const ws = (rdtClient = new WebSocket(`ws://${base}:${rdtPort}`));
ws.addEventListener('open', () => {
if (!quiet) showToast("Connected to React DevTools", findAssetId("CheckmarkSmallIcon"));
rdtConnected = true;
bump();
});
ws.addEventListener('close', () => {
cleanupRdt();
});
ws.addEventListener('error', (e: any) => {
cleanupRdt();
const err = e?.message ?? e?.stack ?? String(e);
logger.error('React DevTools error:', err);
if (!quiet) showToast(err, findAssetId("CircleXIcon-primary"));
});
const devTools = window[getReactDevToolsProp() || "__vendetta_rdc"];
if (devTools?.connectToDevTools) {
devTools.connectToDevTools({
websocket: ws,
resolveRNStyle: StyleSheet.flatten,
});
}
}
export function disconnectRdt() {
rdtClient?.close();
}
export function useIsRdtConnected() {
const [connected, update] = React.useState(rdtConnected);
React.useEffect(() => {
changeHooks.add(update);
return () => void changeHooks.delete(update);
}, []);
return connected;
}
// --- Core Utils ---
/**
* @internal
*/
export function patchLogHook() {
const unpatch = after("nativeLoggingHook", globalThis, args => {
if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ message: args[0], level: args[1] }));
if (socket?.readyState === WebSocket.OPEN) {
sendLog(args[1] === 'error' ? LogLevel.Error : args[1] === 'warn' ? LogLevel.Warn : LogLevel.Default, args[0]);
}
logger.log(args[0]);
});
@ -89,19 +341,13 @@ export function patchLogHook() {
export const versionHash = version;
export function getDebugInfo() {
// Hermes
const hermesProps = window.HermesInternal.getRuntimeProperties();
const hermesVer = hermesProps["OSS Release Version"];
const padding = "for RN ";
// RN
const PlatformConstants = Platform.constants as RNConstants;
const rnVer = PlatformConstants.reactNativeVersion;
return {
/**
* @deprecated use `bunny` field
* */
vendetta: {
version: versionHash.split("-")[0],
loader: getLoaderName(),
@ -126,76 +372,35 @@ export function getDebugInfo() {
buildType: hermesProps.Build,
bytecodeVersion: hermesProps["Bytecode Version"],
},
...Platform.select(
{
android: {
os: {
name: "Android",
version: PlatformConstants.Release,
sdk: PlatformConstants.Version
},
},
ios: {
os: {
name: PlatformConstants.systemName,
version: PlatformConstants.osVersion
},
}
}
)!,
...Platform.select(
{
android: {
device: {
manufacturer: PlatformConstants.Manufacturer,
brand: PlatformConstants.Brand,
model: PlatformConstants.Model,
codename: NativeDeviceModule.device
}
},
ios: {
device: {
manufacturer: NativeDeviceModule.deviceManufacturer,
brand: NativeDeviceModule.deviceBrand,
model: NativeDeviceModule.deviceModel,
codename: NativeDeviceModule.device
}
}
}
)!
...Platform.select({
android: { os: { name: "Android", version: PlatformConstants.Release, sdk: PlatformConstants.Version } },
ios: { os: { name: PlatformConstants.systemName, version: PlatformConstants.osVersion } }
})!,
...Platform.select({
android: { device: { manufacturer: PlatformConstants.Manufacturer, brand: PlatformConstants.Brand, model: PlatformConstants.Model, codename: NativeDeviceModule.device } },
ios: { device: { manufacturer: NativeDeviceModule.deviceManufacturer, brand: NativeDeviceModule.deviceBrand, model: NativeDeviceModule.deviceModel, codename: NativeDeviceModule.device } }
})!
};
}
/**
* @internal
*/
export function initDebugger() {
if (settings.autoDebugger) {
try {
connectToDebugger(settings.debuggerUrl);
} catch (e) {
logger.error("Failed to connect to Debugger during startup:", e);
}
try {
connectToDebugger(settings.debuggerUrl);
} catch (e) {
logger.error("Failed to connect to Debugger during startup:", e);
}
}
if (settings.autoDevTools) {
try {
(async () => {
const devTools = window[getReactDevToolsProp() || "__vendetta_rdc"];
if (!devTools?.connectToDevTools) {
showToast("Invalid devTools URL!", findAssetId("Small"));
return;
}
await devTools.connectToDevTools({
host: settings.devToolsUrl.split(":")?.[0],
resolveRNStyle: StyleSheet.flatten,
});
})();
} catch (e) {
logger.error("Failed to connect to ReactDevTools during startup:", e);
}
try {
if (settings.devToolsUrl) {
connectRdt(settings.devToolsUrl, true);
}
} catch (e) {
logger.error("Failed to connect to ReactDevTools during startup:", e);
}
}
}
}

View file

@ -175,10 +175,7 @@ export function getThemeFilePath() {
export function isReactDevToolsPreloaded() {
if (isPyonLoader()) {
return Boolean(window.__reactDevTools);
}
if (isRa1nLoader()) {
return false;
return Boolean(window.__REACT_DEVTOOLS__);
}
if (isVendettaLoader()) {
return vendettaLoaderIdentity!!.features.devtools != null;
@ -191,7 +188,7 @@ export function getReactDevToolsProp(): string | null {
if (!isReactDevToolsPreloaded()) return null;
if (isPyonLoader()) {
window.__pyoncord_rdt = window.__reactDevTools.exports;
window.__pyoncord_rdt = window.__REACT_DEVTOOLS__.exports;
return "__pyoncord_rdt";
}
@ -206,7 +203,7 @@ export function getReactDevToolsVersion() {
if (!isReactDevToolsPreloaded()) return null;
if (isPyonLoader()) {
return window.__reactDevTools.version || null;
return window.__REACT_DEVTOOLS__.version || null;
}
if (isVendettaLoader()) {
return vendettaLoaderIdentity!!.features.devtools!!.version;

View file

@ -0,0 +1,67 @@
import { logger } from "@lib/utils/logger";
import { getReactDevToolsProp, isReactDevToolsPreloaded } from "./native/loader";
import { showToast } from "@lib/ui/toasts";
import { findAssetId } from "./assets";
const rdtPort = 8097;
export let rdtClient: WebSocket | null = null;
export let rdtConnected = false;
const changeHooks = new Set<(value: boolean) => void>();
function bump() {
for (const x of changeHooks) x(rdtConnected);
}
function cleanup() {
rdtClient = null;
rdtConnected = false;
bump();
}
/** @internal */
export function connectRdt(url: string, quiet?: boolean) {
if (!isReactDevToolsPreloaded() || rdtClient) return;
const base = url.split(":").slice(0, -1);
const ws = (rdtClient = new WebSocket(`ws://${base}:${rdtPort}`));
ws.addEventListener('open', () => {
if (!quiet) showToast("Connected to React DevTools", findAssetId("CheckmarkSmallIcon"));
rdtConnected = true;
bump();
});
ws.addEventListener('close', () => {
cleanup();
});
ws.addEventListener('error', (e: any) => {
cleanup();
const err = e?.message ?? e?.stack ?? String(e);
logger.error('React DevTools error:', err);
if (!quiet) showToast(err, findAssetId("CircleXIcon-primary"));
});
window[getReactDevToolsProp() || "__vendetta_rdc"].connectToDevTools({
websocket: ws,
});
}
export function disconnectRdt() {
rdtClient?.close();
}
export function useIsRdtConnected() {
const [connected, update] = React.useState(rdtConnected);
React.useEffect(() => {
changeHooks.add(update);
return () => void changeHooks.delete(update);
});
return connected;
}

View file

@ -1,6 +1,7 @@
import { findByNameLazy } from "@metro/wrappers";
type LoggerFunction = (...messages: any[]) => void;
export interface Logger {
log: LoggerFunction;
info: LoggerFunction;
@ -13,3 +14,185 @@ export interface Logger {
export const LoggerClass = findByNameLazy("Logger");
export const logger: Logger = new LoggerClass("ShiggyCord");
let socket: WebSocket | undefined;
let originalConsoleLog: any;
let originalConsoleError: any;
let originalConsoleWarn: any;
let originalLoggerLog: any;
let originalLoggerError: any;
let originalLoggerWarn: any;
interface LogMessage {
type: 'log';
data: {
level: 'debug' | 'default' | 'warn' | 'error';
message: any[];
};
}
interface HelloMessage {
type: 'hello';
data: {
version: number;
};
}
const VERSION = 1;
function serializeMessage(msg: any): string {
return JSON.stringify(msg);
}
function sendLog(level: 'debug' | 'default' | 'warn' | 'error', ...args: any[]) {
if (socket?.readyState === WebSocket.OPEN) {
const message: LogMessage = {
type: 'log',
data: {
level,
message: args
}
};
socket.send(serializeMessage(message));
}
}
function patchConsoleAndLogger() {
originalConsoleLog = console.log;
console.log = function(...args: any[]) {
originalConsoleLog.apply(console, args);
sendLog('default', ...args);
};
originalConsoleError = console.error;
console.error = function(...args: any[]) {
originalConsoleError.apply(console, args);
sendLog('error', ...args);
};
originalConsoleWarn = console.warn;
console.warn = function(...args: any[]) {
originalConsoleWarn.apply(console, args);
sendLog('warn', ...args);
};
if (logger) {
originalLoggerLog = logger.log;
logger.log = function(...args: any[]) {
originalLoggerLog.apply(logger, args);
sendLog('default', ...args);
};
originalLoggerError = logger.error;
logger.error = function(...args: any[]) {
originalLoggerError.apply(logger, args);
sendLog('error', ...args);
};
originalLoggerWarn = logger.warn;
logger.warn = function(...args: any[]) {
originalLoggerWarn.apply(logger, args);
sendLog('warn', ...args);
};
}
}
function unpatchConsoleAndLogger() {
if (originalConsoleLog) {
console.log = originalConsoleLog;
originalConsoleLog = undefined;
}
if (originalConsoleError) {
console.error = originalConsoleError;
originalConsoleError = undefined;
}
if (originalConsoleWarn) {
console.warn = originalConsoleWarn;
originalConsoleWarn = undefined;
}
if (logger) {
if (originalLoggerLog) {
logger.log = originalLoggerLog;
originalLoggerLog = undefined;
}
if (originalLoggerError) {
logger.error = originalLoggerError;
originalLoggerError = undefined;
}
if (originalLoggerWarn) {
logger.warn = originalLoggerWarn;
originalLoggerWarn = undefined;
}
}
}
export function connectToDebugger(url: string) {
if (socket !== undefined && socket.readyState !== WebSocket.CLOSED) {
unpatchConsoleAndLogger();
socket.close();
}
if (!url) {
console.error("Invalid debugger URL!");
return;
}
try {
socket = new WebSocket(`ws://${url}`);
socket.addEventListener("open", () => {
console.log("Connected to debugger.");
const hello: HelloMessage = {
type: 'hello',
data: {
version: VERSION
}
};
socket?.send(serializeMessage(hello));
patchConsoleAndLogger();
});
socket.addEventListener("message", (message: any) => {
try {
const data = JSON.parse(message.data);
if (data.type === 'run' && data.data?.code) {
try {
(0, eval)(data.data.code);
} catch (e) {
console.error("Error executing remote code:", e);
}
}
} catch (e) {
console.error("Error processing message:", e);
}
});
socket.addEventListener("close", () => {
console.log("Disconnected from debugger.");
unpatchConsoleAndLogger();
});
socket.addEventListener("error", (err: any) => {
console.error(`Debugger error: ${err.message}`);
unpatchConsoleAndLogger();
});
} catch (e) {
console.error("Failed to connect to debugger:", e);
}
}
export function disconnectFromDebugger() {
if (socket) {
unpatchConsoleAndLogger();
socket.close();
socket = undefined;
}
}
export function isConnectedToDebugger(): boolean {
return socket?.readyState === WebSocket.OPEN;
}