fix: support versions with New Architecture (#193)
This commit is contained in:
commit
1b96867ac7
7 changed files with 144 additions and 36 deletions
|
|
@ -46,6 +46,7 @@ const config = {
|
|||
".png": "dataurl"
|
||||
},
|
||||
define: {
|
||||
window: "globalThis",
|
||||
__DEV__: dev ?? JSON.stringify(releaseBranch !== "main")
|
||||
},
|
||||
inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"],
|
||||
|
|
|
|||
85
src/entry.ts
85
src/entry.ts
|
|
@ -1,10 +1,10 @@
|
|||
import type { Metro } from "@metro/types";
|
||||
import { version } from "bunny-build-info";
|
||||
const { instead } = require("spitroast");
|
||||
|
||||
// @ts-ignore - shut up fr
|
||||
// @ts-ignore - window is defined later in the bundle, so we assign it early
|
||||
globalThis.window = globalThis;
|
||||
|
||||
async function initializeBunny() {
|
||||
async function initializeRevenge() {
|
||||
try {
|
||||
// Make 'freeze' and 'seal' do nothing
|
||||
Object.freeze = Object.seal = Object;
|
||||
|
|
@ -18,51 +18,80 @@ async function initializeBunny() {
|
|||
console.log(stack ?? e?.toString?.() ?? e);
|
||||
alert([
|
||||
"Failed to load Revenge!\n",
|
||||
`Build Number: ${ClientInfoManager.Build}`,
|
||||
`Build Number: ${ClientInfoManager.getConstants().Build}`,
|
||||
`Revenge: ${version}`,
|
||||
stack || e?.toString?.(),
|
||||
].join("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (typeof globalThis.__r !== "undefined") {
|
||||
initializeBunny();
|
||||
} else {
|
||||
// We hold calls from the native side
|
||||
function onceIndexRequired(originalRequire: any) {
|
||||
const batchedBridge = window.__fbBatchedBridge;
|
||||
if (typeof window.__r === "undefined") {
|
||||
// Used for storing the current require function for the global.__r getter defined below
|
||||
var _requireFunc: any;
|
||||
|
||||
const callQueue = new Array<any>;
|
||||
const unpatchHook = instead("callFunctionReturnFlushedQueue", batchedBridge, (args: any, orig: any) => {
|
||||
if (args[0] === "AppRegistry" || !batchedBridge.getCallableModule(args[0])) {
|
||||
callQueue.push(args);
|
||||
return batchedBridge.flushedQueue();
|
||||
// Calls from the native side are deferred until the index.ts(x) is loaded
|
||||
// Revenge delays the execution of index.ts(x) because Revenge's initialization is asynchronous
|
||||
interface DeferredQueue {
|
||||
object: any;
|
||||
original: any;
|
||||
method: string;
|
||||
args: any[];
|
||||
}
|
||||
|
||||
const deferredCalls: Array<DeferredQueue> = [];
|
||||
|
||||
const deferMethodExecution = (object: any, method: string, condition?: (...args: any[]) => boolean) => {
|
||||
const originalMethod = object[method];
|
||||
object[method] = (...args: any[]) => {
|
||||
if (condition && !condition(...args)) {
|
||||
originalMethod(...args);
|
||||
return;
|
||||
}
|
||||
|
||||
return orig.apply(batchedBridge, args);
|
||||
});
|
||||
deferredCalls.push({ object, original: originalMethod, method, args });
|
||||
};
|
||||
}
|
||||
|
||||
const resumeDeferredAndRestore = () => {
|
||||
for (const { object, method, args, original } of deferredCalls) {
|
||||
object[method] = original;
|
||||
object[method](...args);
|
||||
}
|
||||
|
||||
deferredCalls.length = 0;
|
||||
}
|
||||
|
||||
const onceIndexRequired = (originalRequire: Metro.RequireFn) => {
|
||||
// We hold calls from the native side
|
||||
if (window.__fbBatchedBridge) {
|
||||
deferMethodExecution(window.__fbBatchedBridge, "callFunctionReturnFlushedQueue", args => {
|
||||
// If the call is to AppRegistry, we want to defer it because it is not yet registered (Revenge delays it)
|
||||
// Same goes to the non-callable modules, which are not registered yet, so we ensure that only registered ones can get through
|
||||
return args[0] !== "AppRegistry" && window.__fbBatchedBridge.getCallableModule(args[0]);
|
||||
});
|
||||
}
|
||||
|
||||
// Introduced since RN New Architecture
|
||||
if (window.RN$AppRegistry) {
|
||||
deferMethodExecution(window.RN$AppRegistry, "runApplication");
|
||||
}
|
||||
|
||||
const startDiscord = async () => {
|
||||
await initializeBunny();
|
||||
unpatchHook();
|
||||
await initializeRevenge();
|
||||
originalRequire(0);
|
||||
|
||||
callQueue.forEach(arg =>
|
||||
batchedBridge.getCallableModule(arg[0])
|
||||
&& batchedBridge.__callFunction(...arg));
|
||||
resumeDeferredAndRestore();
|
||||
};
|
||||
|
||||
startDiscord();
|
||||
}
|
||||
|
||||
var _requireFunc: any; // We can't set properties to 'this' during __r set for some reason
|
||||
|
||||
Object.defineProperties(globalThis, {
|
||||
__r: {
|
||||
configurable: true,
|
||||
get: () => _requireFunc,
|
||||
set(v) {
|
||||
// _requireFunc is required here, because using 'this' here errors for some unknown reason
|
||||
_requireFunc = function patchedRequire(a: number) {
|
||||
// Initializing index.ts(x)
|
||||
if (a === 0) {
|
||||
|
|
@ -85,4 +114,8 @@ if (typeof globalThis.__r !== "undefined") {
|
|||
set(v) { this.value = v; }
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// It is too late to late to hook __r, so we just initialize Revenge here
|
||||
// Likely because of using the legacy loader (from Vendetta)
|
||||
initializeRevenge();
|
||||
}
|
||||
1
src/global.d.ts
vendored
1
src/global.d.ts
vendored
|
|
@ -6,6 +6,7 @@ declare global {
|
|||
var globalEvalWithSourceUrl: (script: string, sourceURL: string) => any;
|
||||
var nativePerformanceNow: typeof performance.now;
|
||||
var nativeModuleProxy: Record<string, any>;
|
||||
var __turboModuleProxy: (name: string) => any;
|
||||
|
||||
interface Window {
|
||||
[key: string]: any;
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ export function getDebugInfo() {
|
|||
}
|
||||
},
|
||||
discord: {
|
||||
version: NativeClientInfoModule.Version,
|
||||
build: NativeClientInfoModule.Build,
|
||||
version: NativeClientInfoModule.getConstants().Version,
|
||||
build: NativeClientInfoModule.getConstants().Build,
|
||||
},
|
||||
react: {
|
||||
version: React.version,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,34 @@ import { RNModules } from "./types";
|
|||
|
||||
const nmp = window.nativeModuleProxy;
|
||||
|
||||
export const NativeCacheModule = (nmp.NativeCacheModule ?? nmp.MMKVManager) as RNModules.MMKVManager;
|
||||
export const NativeFileModule = (nmp.NativeFileModule ?? nmp.RTNFileManager ?? nmp.DCDFileManager) as RNModules.FileManager;
|
||||
export const NativeClientInfoModule = nmp.NativeClientInfoModule ?? nmp.RTNClientInfoManager ?? nmp.InfoDictionaryManager;
|
||||
export const NativeDeviceModule = nmp.NativeDeviceModule ?? nmp.RTNDeviceManager ?? nmp.DCDDeviceManager;
|
||||
export const NativeThemeModule = nmp.NativeThemeModule ?? nmp.RTNThemeManager ?? nmp.DCDTheme;
|
||||
function getNativeModule<T = any>(...names: string[]): T | undefined {
|
||||
for (const name of names) {
|
||||
if (globalThis.__turboModuleProxy) {
|
||||
const module = globalThis.__turboModuleProxy(name);
|
||||
if (module) return module as T;
|
||||
}
|
||||
|
||||
export const { BundleUpdaterManager } = nmp;
|
||||
if (nmp[name]) return nmp[name] as T;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const NativeCacheModule = getNativeModule<RNModules.MMKVManager>(
|
||||
"NativeCacheModule", "MMKVManager"
|
||||
)!;
|
||||
export const NativeFileModule = getNativeModule<RNModules.FileManager>(
|
||||
"NativeFileModule", "RTNFileManager", "DCDFileManager"
|
||||
)!;
|
||||
export const NativeClientInfoModule = getNativeModule<RNModules.ClientInfoModule>(
|
||||
"NativeClientInfoModule", "RTNClientInfoManager", "InfoDictionaryManager"
|
||||
)!;
|
||||
export const NativeDeviceModule = getNativeModule(
|
||||
"NativeDeviceModule", "RTNDeviceManager", "DCDDeviceManager"
|
||||
)!;
|
||||
export const NativeThemeModule = getNativeModule(
|
||||
"NativeThemeModule", "RTNThemeManager", "DCDTheme"
|
||||
)!;
|
||||
export const BundleUpdaterManager = getNativeModule(
|
||||
"BundleUpdaterManager"
|
||||
)!;
|
||||
|
|
|
|||
|
|
@ -86,4 +86,53 @@ export namespace RNModules {
|
|||
*/
|
||||
DocumentsDirPath: string;
|
||||
}
|
||||
|
||||
export interface ClientInfoModule {
|
||||
getConstants(): {
|
||||
/**
|
||||
* Sentry ingestion DSN URL for alpha/beta builds
|
||||
*/
|
||||
SentryAlphaBetaDsn: string
|
||||
/**
|
||||
* Sentry ingestion DSN URL for staff builds (?)
|
||||
*/
|
||||
SentryStaffDsn: string
|
||||
/**
|
||||
* Sentry ingestion DSN URL for stable builds
|
||||
*/
|
||||
SentryDsn: string
|
||||
DeviceVendorID: string
|
||||
Manifest: string
|
||||
/**
|
||||
* Version code
|
||||
*
|
||||
* Follows the format of `{MINOR}{CHANNEL}{PATCH}` for `{MINOR}.{PATCH} ({CHANNEL})`
|
||||
* - `248200` for `248.0 (alpha)`
|
||||
* - `247105` for `247.5 (beta)`
|
||||
* - `246011` for `246.11 (stable)`
|
||||
*/
|
||||
Build: string
|
||||
/**
|
||||
* Version string
|
||||
*
|
||||
* Eg. `248.0`
|
||||
*/
|
||||
Version: string
|
||||
/**
|
||||
* Release channel
|
||||
*/
|
||||
ReleaseChannel: string
|
||||
/**
|
||||
* Matches `Version`
|
||||
*/
|
||||
OTABuild: string
|
||||
/**
|
||||
* Identifier for the installed client
|
||||
*
|
||||
* - **Android**: Package name
|
||||
* - **iOS**: Bundle ID
|
||||
*/
|
||||
Identifier: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const getMetroCache = () => _metroCache;
|
|||
function buildInitCache() {
|
||||
const cache = {
|
||||
_v: CACHE_VERSION,
|
||||
_buildNumber: NativeClientInfoModule.Build as number,
|
||||
_buildNumber: NativeClientInfoModule.getConstants().Build,
|
||||
_modulesCount: Object.keys(window.modules).length,
|
||||
flagsIndex: {} as Record<string, number>,
|
||||
findIndex: {} as Record<string, ModulesMap | undefined>,
|
||||
|
|
@ -48,7 +48,7 @@ export async function initMetroCache() {
|
|||
_metroCache = null!;
|
||||
throw "cache invalidated; cache version outdated";
|
||||
}
|
||||
if (_metroCache._buildNumber !== NativeClientInfoModule.Build) {
|
||||
if (_metroCache._buildNumber !== NativeClientInfoModule.getConstants().Build) {
|
||||
_metroCache = null!;
|
||||
throw "cache invalidated; version mismatch";
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue