fix: support New Architecture
- Retrieve turbo modules from `global.__turboModuleProxy` - Use `getConstants()` to retrieve constants of native modules - Defer some calls from the native bridge/JSI that could break the app when bundle index is not initialized properly Tested on Android 291.0 (291200), not tested on older versions (without New Architecture)
This commit is contained in:
parent
c263de39d0
commit
b92d2734b3
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