feat: native bridge (#4)
* feat(lib/modules/native): types for calls * feat(lib): proper caching for `assets` and `modules` lib * fix(lib/modules/metro/runtime): add flag once `importedAll` or `importedDefault` exports cached * feat(lib/modules/native): add `fs` * feat(plugins/settings): add Loader info in Revenge page * feat: use native bridge for alerting errors * chore(lib/modules/native): remove useless `String()` call on error * feat(lib/modules/native/fs): rename `deleteFile` to `rm` * feat(lib/plugins): save plugin states * chore(preinit): correct arg name for native method
This commit is contained in:
commit
f89d523c0d
23 changed files with 317 additions and 171 deletions
|
|
@ -1,51 +1,50 @@
|
||||||
// TODO(lib/assets): do not depend on Discord's ClientInfoModule for versioning, use native interop
|
import {
|
||||||
import { ClientInfoModule } from '@revenge-mod/discord/native'
|
callBridgeMethod,
|
||||||
import { getStorage } from '@revenge-mod/storage'
|
callBridgeMethodSync,
|
||||||
|
} from '@revenge-mod/modules/native'
|
||||||
import { debounce } from '@revenge-mod/utils/callback'
|
import { debounce } from '@revenge-mod/utils/callback'
|
||||||
import { mergeDeep } from '@revenge-mod/utils/object'
|
|
||||||
import type { Metro } from '@revenge-mod/modules/types'
|
import type { Metro } from '@revenge-mod/modules/types'
|
||||||
import type { Asset } from './types'
|
import type { Asset } from './types'
|
||||||
|
|
||||||
const Version = 1
|
const ExpectedCacheVersion = 2
|
||||||
const Key = `${Version}.${ClientInfoModule.getConstants().Build}`
|
|
||||||
|
export const Uncached: Cache = {
|
||||||
|
data: {},
|
||||||
|
version: ExpectedCacheVersion,
|
||||||
|
}
|
||||||
|
|
||||||
// In-memory cache
|
// In-memory cache
|
||||||
export const cache: Cache = {}
|
export let cache: Cache =
|
||||||
|
callBridgeMethodSync('revenge.caches.assets.read', []) ?? Uncached
|
||||||
|
|
||||||
const CacheStorage = getStorage<Cache>(`revenge/assets.${Key}`, {
|
if (cache.version !== ExpectedCacheVersion) {
|
||||||
default: cache,
|
// TODO: Alert to user to update build
|
||||||
directory: 'cache',
|
cache = Uncached
|
||||||
})
|
}
|
||||||
|
|
||||||
// TODO(lib/assets/caches): This loads way too late and requires native interop to load earlier
|
|
||||||
export const cached = CacheStorage.get().then(cache_ => {
|
|
||||||
const cached = cache_ !== cache
|
|
||||||
// If we are not using the default value, merge it into the in-memory cache, then point the storage cache to the in-memory cache.
|
|
||||||
if (cached) {
|
|
||||||
Object.assign(cache, cache_)
|
|
||||||
CacheStorage.cache = cache
|
|
||||||
}
|
|
||||||
|
|
||||||
return cached
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface Cache {
|
export interface Cache {
|
||||||
[key: Asset['name']]: {
|
data: {
|
||||||
[key: Asset['type']]: Metro.ModuleID
|
[key: Asset['name']]: {
|
||||||
|
[key: Asset['type']]: Metro.ModuleID
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
version: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const save = debounce(() => {
|
const save = debounce(() => {
|
||||||
CacheStorage.set({})
|
callBridgeMethod('revenge.caches.assets.write', [cache.data])
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
export function cacheAsset(asset: Asset, moduleId: Metro.ModuleID) {
|
export function cacheAsset(asset: Asset, moduleId: Metro.ModuleID) {
|
||||||
// Merge directly into the cache, only debouncing actual writes
|
const reg = (cache.data[asset.name] ??= {})
|
||||||
mergeDeep(cache, {
|
reg[asset.type] = moduleId
|
||||||
[asset.name]: {
|
|
||||||
[asset.type]: moduleId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@revenge-mod/modules/native' {
|
||||||
|
interface Methods {
|
||||||
|
'revenge.caches.assets.read': [[], Cache | null]
|
||||||
|
'revenge.caches.assets.write': [[data: Cache['data']], void]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export function* getCustomAssets(): Generator<CustomAsset> {
|
||||||
* Yields all registered packager assets, including ones with same name but different types.
|
* Yields all registered packager assets, including ones with same name but different types.
|
||||||
*/
|
*/
|
||||||
export function* getPackagerAssets(): Generator<PackagerAsset> {
|
export function* getPackagerAssets(): Generator<PackagerAsset> {
|
||||||
for (const reg of Object.values(cache))
|
for (const reg of Object.values(cache.data))
|
||||||
for (const moduleId of Object.values(reg))
|
for (const moduleId of Object.values(reg))
|
||||||
yield AssetsRegistry.getAssetByID(metroRequire(moduleId))
|
yield AssetsRegistry.getAssetByID(metroRequire(moduleId))
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ export function getAssetByName(
|
||||||
export function getAssetsByName(
|
export function getAssetsByName(
|
||||||
name: string,
|
name: string,
|
||||||
): Record<Asset['type'], Asset> | undefined {
|
): Record<Asset['type'], Asset> | undefined {
|
||||||
const reg = cache[name]
|
const reg = cache.data[name]
|
||||||
if (!reg) return
|
if (!reg) return
|
||||||
|
|
||||||
return Object.entries(reg).reduce(
|
return Object.entries(reg).reduce(
|
||||||
|
|
@ -106,7 +106,7 @@ export function getAssetIdByName(
|
||||||
name: string,
|
name: string,
|
||||||
type?: Asset['type'],
|
type?: Asset['type'],
|
||||||
): AssetId | undefined {
|
): AssetId | undefined {
|
||||||
const reg = cache[name]
|
const reg = cache.data[name]
|
||||||
if (!reg) return
|
if (!reg) return
|
||||||
|
|
||||||
if (type !== undefined) {
|
if (type !== undefined) {
|
||||||
|
|
@ -127,7 +127,7 @@ export function getAssetIdByName(
|
||||||
* @returns The asset ID.
|
* @returns The asset ID.
|
||||||
*/
|
*/
|
||||||
export function registerAsset(asset: RegisterableAsset): AssetId {
|
export function registerAsset(asset: RegisterableAsset): AssetId {
|
||||||
if (cache[asset.name]?.[asset.type] !== undefined)
|
if (cache.data[asset.name]?.[asset.type] !== undefined)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Asset with name ${asset.name} and type ${asset.type} already exists!`,
|
`Asset with name ${asset.name} and type ${asset.type} already exists!`,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import { getModuleDependencies } from '@revenge-mod/modules/metro/utils'
|
import { getModuleDependencies } from '@revenge-mod/modules/metro/utils'
|
||||||
import { proxify } from '@revenge-mod/utils/proxy'
|
import { proxify } from '@revenge-mod/utils/proxy'
|
||||||
import { aOverrides } from './_internal'
|
import { aOverrides } from './_internal'
|
||||||
import { cacheAsset, cached } from './caches'
|
import { cache, cacheAsset, Uncached } from './caches'
|
||||||
import type { Metro } from '@revenge-mod/modules/types'
|
import type { Metro } from '@revenge-mod/modules/types'
|
||||||
import type { ReactNative } from '@revenge-mod/react/types'
|
import type { ReactNative } from '@revenge-mod/react/types'
|
||||||
import type { Asset, PackagerAsset } from './types'
|
import type { Asset, PackagerAsset } from './types'
|
||||||
|
|
@ -52,24 +52,20 @@ const unsubAR = waitForModules(
|
||||||
if (getModuleDependencies(id)!.length) {
|
if (getModuleDependencies(id)!.length) {
|
||||||
unsubAR()
|
unsubAR()
|
||||||
|
|
||||||
// TODO(lib/assets/caches): Ideally we should not wait for the cache promise here, see the promise impl for more info.
|
if (cache === Uncached) {
|
||||||
cached.then(cached => {
|
// Resolve reference once and keep in closure
|
||||||
if (!cached) {
|
const metroRequire = __r
|
||||||
// Resolve reference once and keep in closure
|
|
||||||
const metroRequire = __r
|
|
||||||
|
|
||||||
// More fragile way, but also more performant:
|
// More fragile way, but also more performant:
|
||||||
// There is exactly one asset before the reexported asset registry :/
|
// There is exactly one asset before the reexported asset registry :/
|
||||||
const firstAssetModuleId = id - 1
|
const firstAssetModuleId = id - 1
|
||||||
for (const mId of mUninitialized) {
|
for (const mId of mUninitialized) {
|
||||||
if (mId < firstAssetModuleId) continue
|
if (mId < firstAssetModuleId) continue
|
||||||
|
|
||||||
const deps = getModuleDependencies(mId)!
|
const deps = getModuleDependencies(mId)!
|
||||||
if (deps.length === 1 && deps[0] === id)
|
if (deps.length === 1 && deps[0] === id) metroRequire(mId)
|
||||||
metroRequire(mId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
// We already patched the original asset registry, so we don't need to patch again.
|
// We already patched the original asset registry, so we don't need to patch again.
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from '@revenge-mod/modules/finders/filters'
|
} from '@revenge-mod/modules/finders/filters'
|
||||||
import { getModuleDependencies } from '@revenge-mod/modules/metro/utils'
|
import { getModuleDependencies } from '@revenge-mod/modules/metro/utils'
|
||||||
import { asap, noop } from '@revenge-mod/utils/callback'
|
import { asap, noop } from '@revenge-mod/utils/callback'
|
||||||
import { cached, cacheFilterResultForId } from '#modules/src/caches'
|
import { cache, cacheFilterResultForId, Uncached } from '#modules/src/caches'
|
||||||
import { FilterResultFlags } from '#modules/src/finders/_internal'
|
import { FilterResultFlags } from '#modules/src/finders/_internal'
|
||||||
import { FilterFlag } from '#modules/src/finders/filters/constants'
|
import { FilterFlag } from '#modules/src/finders/filters/constants'
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -140,13 +140,11 @@ waitForModules(withStore(), (store, id) => {
|
||||||
Stores[name] = store
|
Stores[name] = store
|
||||||
})
|
})
|
||||||
|
|
||||||
cached.then(cached => {
|
if (cache === Uncached)
|
||||||
if (!cached)
|
asap(() => {
|
||||||
asap(() => {
|
const lookup = lookupModules(withStore(), {
|
||||||
const lookup = lookupModules(withStore(), {
|
uninitialized: true,
|
||||||
uninitialized: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
while (lookup.next().done);
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
while (lookup.next().done);
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@
|
||||||
"default": "./src/metro/utils.ts"
|
"default": "./src/metro/utils.ts"
|
||||||
},
|
},
|
||||||
"./native": {
|
"./native": {
|
||||||
"default": "./src/native.ts"
|
"default": "./src/native/index.ts"
|
||||||
|
},
|
||||||
|
"./native/fs": {
|
||||||
|
"default": "./src/native/fs.ts"
|
||||||
},
|
},
|
||||||
"./types": {
|
"./types": {
|
||||||
"types": "./src/types.ts"
|
"types": "./src/types.ts"
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,61 @@
|
||||||
// TODO(lib/modules/caches): do not depend on Discord's ClientInfoModule for versioning, use native interop
|
import {
|
||||||
import { ClientInfoModule } from '@revenge-mod/discord/native'
|
callBridgeMethod,
|
||||||
import { getStorage } from '@revenge-mod/storage'
|
callBridgeMethodSync,
|
||||||
|
} from '@revenge-mod/modules/native'
|
||||||
import { debounce } from '@revenge-mod/utils/callback'
|
import { debounce } from '@revenge-mod/utils/callback'
|
||||||
import type { Metro } from '@revenge-mod/modules/types'
|
import type { Metro } from '@revenge-mod/modules/types'
|
||||||
import type { FilterResultFlag } from './finders/_internal'
|
import type { FilterResultFlag } from './finders/_internal'
|
||||||
import type { Filter } from './finders/filters'
|
import type { Filter } from './finders/filters'
|
||||||
|
|
||||||
const Version = 2
|
const ExpectedCacheVersion = 3
|
||||||
const Key = `${Version}.${ClientInfoModule.getConstants().Build}`
|
|
||||||
|
export const Uncached: Cache = {
|
||||||
|
blacklist: [],
|
||||||
|
finds: {},
|
||||||
|
version: ExpectedCacheVersion,
|
||||||
|
}
|
||||||
|
|
||||||
// In-memory cache
|
// In-memory cache
|
||||||
const cache: Cache = { b: [], f: {} }
|
export let cache: Cache =
|
||||||
|
callBridgeMethodSync('revenge.caches.modules.read', []) ?? Uncached
|
||||||
|
|
||||||
const CacheStorage = getStorage<Cache>(`revenge/modules.${Key}`, {
|
if (cache.version !== ExpectedCacheVersion) {
|
||||||
default: cache,
|
// TODO: Alert to user to update build
|
||||||
directory: 'cache',
|
cache = Uncached
|
||||||
})
|
}
|
||||||
|
|
||||||
// TODO(lib/modules/caches): This loads way too late and requires native interop to load earlier
|
export type Blacklist = Metro.ModuleID[]
|
||||||
export const cached = CacheStorage.get().then(cache_ => {
|
export type Finds = Record<
|
||||||
const cached = cache_ !== cache
|
Filter['key'],
|
||||||
// If we are not using the default value, merge it into the in-memory cache, then point the storage cache to the in-memory cache.
|
Record<Metro.ModuleID, FilterResultFlag> | null
|
||||||
if (cached) {
|
>
|
||||||
Object.assign(cache, cache_)
|
|
||||||
CacheStorage.cache = cache
|
|
||||||
}
|
|
||||||
|
|
||||||
return cached
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface Cache {
|
export interface Cache {
|
||||||
b: Metro.ModuleID[]
|
blacklist: Blacklist
|
||||||
f: Record<
|
finds: Finds
|
||||||
Filter['key'],
|
version: number
|
||||||
Record<Metro.ModuleID, FilterResultFlag> | null | undefined
|
|
||||||
>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const save = debounce(() => {
|
const save = debounce(() => {
|
||||||
CacheStorage.set({})
|
callBridgeMethod('revenge.caches.modules.write', [
|
||||||
|
cache.blacklist,
|
||||||
|
cache.finds,
|
||||||
|
])
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
export function cacheBlacklistedModule(id: Metro.ModuleID): boolean {
|
export function cacheBlacklistedModule(id: Metro.ModuleID): boolean {
|
||||||
cache.b.push(id)
|
cache.blacklist.push(id)
|
||||||
save()
|
save()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cacheFilterResultForId(
|
export function cacheFilterResultForId(
|
||||||
key: Filter['key'],
|
key: keyof Finds,
|
||||||
id: Metro.ModuleID,
|
id: Metro.ModuleID,
|
||||||
flag: FilterResultFlag,
|
flag: FilterResultFlag,
|
||||||
): FilterResultFlag {
|
): FilterResultFlag {
|
||||||
const reg = (cache.f[key] ??= {})
|
const reg = (cache.finds[key] ??= {})
|
||||||
reg[id] = flag
|
reg[id] = flag
|
||||||
|
|
||||||
save()
|
save()
|
||||||
|
|
@ -61,12 +63,22 @@ export function cacheFilterResultForId(
|
||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cacheFilterNotFound(key: Filter['key']) {
|
export function cacheFilterNotFound(key: keyof Finds) {
|
||||||
cache.f[key] = null
|
cache.finds[key] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCachedFilterRegistry = (
|
export const getFilterMatches = (
|
||||||
key: Filter['key'],
|
key: keyof Finds,
|
||||||
): Cache['f'][Filter['key']] | undefined => cache.f[key]
|
): Finds[keyof Finds] | undefined => cache.finds[key]
|
||||||
|
|
||||||
export const getCachedBlacklistedModules = () => cache.b
|
export const getBlacklistedModules = () => cache.blacklist
|
||||||
|
|
||||||
|
declare module '@revenge-mod/modules/native' {
|
||||||
|
interface Methods {
|
||||||
|
'revenge.caches.modules.read': [[], Cache | null]
|
||||||
|
'revenge.caches.modules.write': [
|
||||||
|
[blacklist: Blacklist, finds: Finds],
|
||||||
|
void,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { getCurrentStack } from '@revenge-mod/utils/error'
|
import { getCurrentStack } from '@revenge-mod/utils/error'
|
||||||
import { cacheFilterResultForId } from '../caches'
|
import { cacheFilterResultForId } from '../caches'
|
||||||
import { metroRequire } from '../metro/custom'
|
|
||||||
import { mInitialized } from '../metro/patches'
|
import { mInitialized } from '../metro/patches'
|
||||||
|
import { metroRequire } from '../metro/runtime'
|
||||||
import { isModuleExportBad } from '../metro/utils'
|
import { isModuleExportBad } from '../metro/utils'
|
||||||
import { FilterFlag } from './filters'
|
import { FilterFlag } from './filters'
|
||||||
import type { If } from '@revenge-mod/utils/types'
|
import type { If } from '@revenge-mod/utils/types'
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { getCurrentStack } from '@revenge-mod/utils/error'
|
import { getCurrentStack } from '@revenge-mod/utils/error'
|
||||||
import { proxify } from '@revenge-mod/utils/proxy'
|
import { proxify } from '@revenge-mod/utils/proxy'
|
||||||
import { cacheFilterNotFound, getCachedFilterRegistry } from '../caches'
|
import { cacheFilterNotFound, getFilterMatches } from '../caches'
|
||||||
import { metroRequire } from '../metro/custom'
|
|
||||||
import {
|
import {
|
||||||
mImportedPaths,
|
mImportedPaths,
|
||||||
mInitialized,
|
mInitialized,
|
||||||
mList,
|
mList,
|
||||||
mUninitialized,
|
mUninitialized,
|
||||||
} from '../metro/patches'
|
} from '../metro/patches'
|
||||||
|
import { metroRequire } from '../metro/runtime'
|
||||||
import {
|
import {
|
||||||
getInitializedModuleExports,
|
getInitializedModuleExports,
|
||||||
isModuleInitialized,
|
isModuleInitialized,
|
||||||
|
|
@ -163,7 +163,7 @@ export function* lookupModules(filter: Filter, options?: LookupModulesOptions) {
|
||||||
if (options?.cached ?? true) {
|
if (options?.cached ?? true) {
|
||||||
const notInit = !(options?.initialize ?? true)
|
const notInit = !(options?.initialize ?? true)
|
||||||
|
|
||||||
const reg = getCachedFilterRegistry(filter.key)
|
const reg = getFilterMatches(filter.key)
|
||||||
// Return early if previous lookup was a full lookup and no modules were found
|
// Return early if previous lookup was a full lookup and no modules were found
|
||||||
if (reg === null) return
|
if (reg === null) return
|
||||||
|
|
||||||
|
|
@ -307,7 +307,7 @@ export function lookupModule(filter: Filter, options?: LookupModulesOptions) {
|
||||||
if (options?.cached ?? true) {
|
if (options?.cached ?? true) {
|
||||||
const notInit = !(options?.initialize ?? true)
|
const notInit = !(options?.initialize ?? true)
|
||||||
|
|
||||||
const reg = getCachedFilterRegistry(filter.key)
|
const reg = getFilterMatches(filter.key)
|
||||||
// Return early if previous lookup was a full lookup and no modules were found
|
// Return early if previous lookup was a full lookup and no modules were found
|
||||||
if (reg === null) return NotFoundResult
|
if (reg === null) return NotFoundResult
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { noop } from '@revenge-mod/utils/callback'
|
import { noop } from '@revenge-mod/utils/callback'
|
||||||
import { getCachedFilterRegistry } from '../caches'
|
import { getFilterMatches } from '../caches'
|
||||||
import { mInitialized } from '../metro/patches'
|
import { mInitialized } from '../metro/patches'
|
||||||
import {
|
import {
|
||||||
onAnyModuleInitialized,
|
onAnyModuleInitialized,
|
||||||
|
|
@ -97,13 +97,13 @@ export function waitForModules(
|
||||||
options?: WaitForModulesOptions,
|
options?: WaitForModulesOptions,
|
||||||
): WaitForModulesUnsubscribeFunction {
|
): WaitForModulesUnsubscribeFunction {
|
||||||
if (options?.cached) {
|
if (options?.cached) {
|
||||||
const reg = getCachedFilterRegistry(filter.key)
|
const reg = getFilterMatches(filter.key)
|
||||||
if (reg === null) return noop
|
if (reg === null) return noop
|
||||||
|
|
||||||
if (reg) {
|
if (reg) {
|
||||||
if (__BUILD_FLAG_DEBUG_MODULE_WAITS__)
|
if (__BUILD_FLAG_DEBUG_MODULE_WAITS__)
|
||||||
nativeLoggingHook(
|
nativeLoggingHook(
|
||||||
`\u001b[32mUsing cached results for wait \u001b[33m${filter.key}\u001b[0m`,
|
`\u001b[32mUsing cached results for wait: \u001b[33m${filter.key}\u001b[0m`,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
import {
|
import { cache, cacheBlacklistedModule, Uncached } from '../caches'
|
||||||
cacheBlacklistedModule,
|
|
||||||
cached,
|
|
||||||
getCachedBlacklistedModules,
|
|
||||||
} from '../caches'
|
|
||||||
import {
|
import {
|
||||||
global,
|
global,
|
||||||
metroImportAll,
|
metroImportAll,
|
||||||
metroImportDefault,
|
metroImportDefault,
|
||||||
metroRequire,
|
metroRequire,
|
||||||
} from './custom'
|
} from './runtime'
|
||||||
import { onModuleInitialized } from './subscriptions'
|
import { onModuleInitialized } from './subscriptions'
|
||||||
import {
|
import {
|
||||||
executeImportedPathSubscriptions,
|
executeImportedPathSubscriptions,
|
||||||
|
|
@ -144,11 +140,8 @@ function handleFactoryCall(
|
||||||
/// MODULE PATCHES AND BLACKLISTS
|
/// MODULE PATCHES AND BLACKLISTS
|
||||||
|
|
||||||
// Restore blacklists
|
// Restore blacklists
|
||||||
cached.then(cached => {
|
if (cache !== Uncached)
|
||||||
if (cached)
|
for (const id of cache.blacklist) mUninitialized.delete(id)
|
||||||
for (const id of getCachedBlacklistedModules())
|
|
||||||
mUninitialized.delete(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
const ImportTrackerModuleId = 2
|
const ImportTrackerModuleId = 2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* A more performant implementation of Metro's core functions.
|
* A minimal implementation of Metro's runtime with little overhead.
|
||||||
* Making initialization faster and use less resources.
|
* Making initialization faster and use less resources.
|
||||||
|
*
|
||||||
|
* Also avoids cloning exports, allowing for patches to be applied directly without checking for clones.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mList } from './patches'
|
import { mList } from './patches'
|
||||||
|
|
@ -58,6 +60,9 @@ export const metroImportDefault: Metro.RequireFn = moduleId => {
|
||||||
if (mod.flags & HasImportedDefault) return mod.importedDefault
|
if (mod.flags & HasImportedDefault) return mod.importedDefault
|
||||||
|
|
||||||
const exports = metroRequire(moduleId)
|
const exports = metroRequire(moduleId)
|
||||||
|
|
||||||
|
mod.flags |= HasImportedDefault
|
||||||
|
|
||||||
return (mod.importedDefault = exports?.__esModule
|
return (mod.importedDefault = exports?.__esModule
|
||||||
? exports.default
|
? exports.default
|
||||||
: exports)
|
: exports)
|
||||||
|
|
@ -68,7 +73,11 @@ export const metroImportAll: Metro.RequireFn = moduleId => {
|
||||||
if (mod.flags & HasImportedAll) return mod.importedAll
|
if (mod.flags & HasImportedAll) return mod.importedAll
|
||||||
|
|
||||||
const exports = metroRequire(moduleId)
|
const exports = metroRequire(moduleId)
|
||||||
|
// Our implementation doesn't match Metro's because we modify the exports directly instead of cloning
|
||||||
|
// But this is why ours is superior, it allows patching the exports without needing to do it more than a single time
|
||||||
if (!exports?.__esModule) exports.default = exports
|
if (!exports?.__esModule) exports.default = exports
|
||||||
|
|
||||||
|
mod.flags |= HasImportedAll
|
||||||
|
|
||||||
return (mod.importedAll = exports)
|
return (mod.importedAll = exports)
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { isProxy } from '@revenge-mod/utils/proxy'
|
import { isProxy } from '@revenge-mod/utils/proxy'
|
||||||
import { Initialized } from './custom'
|
|
||||||
import { mDeps, mList } from './patches'
|
import { mDeps, mList } from './patches'
|
||||||
|
import { Initialized } from './runtime'
|
||||||
import type { Metro } from '../types'
|
import type { Metro } from '../types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
45
lib/modules/src/native/fs.ts
Normal file
45
lib/modules/src/native/fs.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import {
|
||||||
|
callBridgeMethod,
|
||||||
|
callBridgeMethodSync,
|
||||||
|
} from '@revenge-mod/modules/native'
|
||||||
|
|
||||||
|
export function readFile(path: string) {
|
||||||
|
return callBridgeMethod('revenge.fs.read', [path])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeFile(path: string, data: string) {
|
||||||
|
return callBridgeMethod('revenge.fs.write', [path, data])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exists(path: string) {
|
||||||
|
return callBridgeMethod('revenge.fs.exists', [path])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rm(path: string) {
|
||||||
|
return callBridgeMethod('revenge.fs.delete', [path])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readFileSync(path: string) {
|
||||||
|
return callBridgeMethodSync('revenge.fs.read', [path])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeFileSync(path: string, data: string) {
|
||||||
|
return callBridgeMethodSync('revenge.fs.write', [path, data])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rmSync(path: string) {
|
||||||
|
return callBridgeMethodSync('revenge.fs.exists', [path])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteFileSync(path: string) {
|
||||||
|
return callBridgeMethodSync('revenge.fs.delete', [path])
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@revenge-mod/modules/native' {
|
||||||
|
export interface Methods {
|
||||||
|
'revenge.fs.read': [[path: string], string]
|
||||||
|
'revenge.fs.write': [[path: string, data: string], void]
|
||||||
|
'revenge.fs.exists': [[path: string], boolean]
|
||||||
|
'revenge.fs.delete': [[path: string], boolean]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,16 +45,22 @@ function makePayload(name: string, args: any[]): object {
|
||||||
* @param args The arguments to pass to the native method.
|
* @param args The arguments to pass to the native method.
|
||||||
* @returns A promise that resolves with the result of the native method call.
|
* @returns A promise that resolves with the result of the native method call.
|
||||||
*/
|
*/
|
||||||
export async function callMethod<T>(name: string, args: any[]): Promise<T> {
|
export async function callBridgeMethod<N extends MethodName>(
|
||||||
const result = await BridgePromise.readAsDataURL(makePayload(name, args))
|
name: N,
|
||||||
|
args: MethodArgs<N>,
|
||||||
|
): Promise<MethodResult<N>> {
|
||||||
|
try {
|
||||||
|
const result = await BridgePromise.readAsDataURL(
|
||||||
|
makePayload(name, args),
|
||||||
|
)
|
||||||
|
|
||||||
if ('error' in result)
|
if ('error' in result) throw result.error
|
||||||
throw new Error(`Call failed: ${result.error as string}`)
|
if ('result' in result) return result.result as MethodResult<N>
|
||||||
if ('result' in result) return result.result as T
|
|
||||||
|
|
||||||
throw new Error(
|
throw 'The module did not return a valid result. The native hook must have failed.'
|
||||||
'Call failed: The module did not return a valid result. The native hook must have failed.',
|
} catch (error) {
|
||||||
)
|
throw new Error(`Call failed: ${error}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -66,17 +72,46 @@ export async function callMethod<T>(name: string, args: any[]): Promise<T> {
|
||||||
* @param args The arguments to pass to the native method.
|
* @param args The arguments to pass to the native method.
|
||||||
* @returns The result of the native method call.
|
* @returns The result of the native method call.
|
||||||
*/
|
*/
|
||||||
export function callMethodSync<T>(name: string, args: any[]): T {
|
export function callBridgeMethodSync<N extends MethodName>(
|
||||||
|
name: N,
|
||||||
|
args: MethodArgs<N>,
|
||||||
|
): MethodResult<N> {
|
||||||
try {
|
try {
|
||||||
const result = Bridge.getBBox(0, makePayload(name, args))
|
const result = Bridge.getBBox(0, makePayload(name, args))
|
||||||
|
|
||||||
if ('error' in result) throw result.error
|
if ('error' in result) throw result.error
|
||||||
if ('result' in result) return result.result as T
|
if ('result' in result) return result.result as MethodResult<N>
|
||||||
|
|
||||||
throw 'The module did not return a valid result. The native hook must have failed.'
|
throw 'The module did not return a valid result. The native hook must have failed.'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(
|
throw new Error(`Call failed: ${error}`)
|
||||||
`Call failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bridge information.
|
||||||
|
*/
|
||||||
|
export function getBridgeInfo(): BridgeInfo | null {
|
||||||
|
try {
|
||||||
|
return callBridgeMethodSync('revenge.info', [])
|
||||||
|
} catch (e) {
|
||||||
|
nativeLoggingHook(
|
||||||
|
`\u001b[31mFailed to get native bridge info: ${e}\u001b[0m`,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BridgeInfo {
|
||||||
|
name: string
|
||||||
|
version: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MethodName = Extract<keyof Methods, string>
|
||||||
|
export type MethodArgs<T extends MethodName> = Methods[T][0]
|
||||||
|
export type MethodResult<T extends MethodName> = Methods[T][1]
|
||||||
|
|
||||||
|
export interface Methods {
|
||||||
|
'revenge.info': [[], BridgeInfo]
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import { TypedEventEmitter } from '@revenge-mod/discord/common/utils'
|
import { TypedEventEmitter } from '@revenge-mod/discord/common/utils'
|
||||||
|
import {
|
||||||
|
callBridgeMethod,
|
||||||
|
callBridgeMethodSync,
|
||||||
|
} from '@revenge-mod/modules/native'
|
||||||
import { getErrorStack } from '@revenge-mod/utils/error'
|
import { getErrorStack } from '@revenge-mod/utils/error'
|
||||||
import { sleepReject } from '@revenge-mod/utils/promise'
|
import { sleepReject } from '@revenge-mod/utils/promise'
|
||||||
import { pUnscopedApi as uapi } from '../apis'
|
import { pUnscopedApi as uapi } from '../apis'
|
||||||
import { PluginFlags as Flag, PluginStatus as Status } from '../constants'
|
import {
|
||||||
|
PluginFlags as Flag,
|
||||||
|
PersistentPluginFlags,
|
||||||
|
PluginStatus as Status,
|
||||||
|
} from '../constants'
|
||||||
import {
|
import {
|
||||||
addPluginApiDecorator,
|
addPluginApiDecorator,
|
||||||
decoratePluginApi,
|
decoratePluginApi,
|
||||||
|
|
@ -83,13 +91,30 @@ export const pEmitter = new TypedEventEmitter<{
|
||||||
export const pList = new Map<PluginManifest['id'], AnyPlugin>()
|
export const pList = new Map<PluginManifest['id'], AnyPlugin>()
|
||||||
export const pMetadata = new WeakMap<AnyPlugin, InternalPluginMeta>()
|
export const pMetadata = new WeakMap<AnyPlugin, InternalPluginMeta>()
|
||||||
|
|
||||||
|
const { flags: PersistedFlags }: PersistedPluginStates = callBridgeMethodSync(
|
||||||
|
'revenge.plugins.states.read',
|
||||||
|
[],
|
||||||
|
) ?? {
|
||||||
|
flags: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
pEmitter.on('flagUpdate', plugin => {
|
||||||
|
PersistedFlags[plugin.manifest.id] = plugin.flags & PersistentPluginFlags
|
||||||
|
callBridgeMethod('revenge.plugins.states.write', [PersistedFlags])
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new plugin with the system.
|
* Registers a new plugin with the system.
|
||||||
|
*
|
||||||
|
* @param manifest The manifest of the plugin.
|
||||||
|
* @param options The options for the plugin.
|
||||||
|
* @param defflags The default flags for the plugin.
|
||||||
|
* @param iflags The internal flags for the plugin.
|
||||||
*/
|
*/
|
||||||
export function registerPlugin<O extends PluginApiExtensionsOptions>(
|
export function registerPlugin<O extends PluginApiExtensionsOptions>(
|
||||||
manifest: PluginManifest,
|
manifest: PluginManifest,
|
||||||
options: PluginOptions<O>,
|
options: PluginOptions<O>,
|
||||||
flags: number,
|
defflags: number,
|
||||||
iflags: number,
|
iflags: number,
|
||||||
) {
|
) {
|
||||||
// TODO(plugins): verify plugin manifest
|
// TODO(plugins): verify plugin manifest
|
||||||
|
|
@ -127,7 +152,7 @@ export function registerPlugin<O extends PluginApiExtensionsOptions>(
|
||||||
dependents: [],
|
dependents: [],
|
||||||
handleError: e => handlePluginError(e, plugin),
|
handleError: e => handlePluginError(e, plugin),
|
||||||
options,
|
options,
|
||||||
flags,
|
flags: PersistedFlags[manifest.id] ?? defflags,
|
||||||
}
|
}
|
||||||
|
|
||||||
pMetadata.set(plugin, meta)
|
pMetadata.set(plugin, meta)
|
||||||
|
|
@ -299,7 +324,6 @@ export async function disablePlugin(plugin: AnyPlugin) {
|
||||||
if (plugin.status && !(plugin.status & Status.Stopping))
|
if (plugin.status && !(plugin.status & Status.Stopping))
|
||||||
await stopPlugin(plugin)
|
await stopPlugin(plugin)
|
||||||
|
|
||||||
// TODO(plugins): write to storage
|
|
||||||
plugin.flags &= ~Flag.Enabled
|
plugin.flags &= ~Flag.Enabled
|
||||||
pEmitter.emit('disabled', plugin)
|
pEmitter.emit('disabled', plugin)
|
||||||
}
|
}
|
||||||
|
|
@ -317,9 +341,7 @@ export async function enablePlugin(plugin: AnyPlugin) {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(plugins): write to storage
|
|
||||||
plugin.flags |= Flag.Enabled
|
plugin.flags |= Flag.Enabled
|
||||||
|
|
||||||
pEmitter.emit('enabled', plugin)
|
pEmitter.emit('enabled', plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -557,3 +579,18 @@ async function cleanupPlugin(plugin: AnyPlugin, meta: InternalPluginMeta) {
|
||||||
|
|
||||||
await Promise.all(proms)
|
await Promise.all(proms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@revenge-mod/modules/native' {
|
||||||
|
interface Methods {
|
||||||
|
'revenge.plugins.states.read': [[], PersistedPluginStates | null]
|
||||||
|
'revenge.plugins.states.write': [[statuses: PersistedPluginFlags], void]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PersistedPluginStates {
|
||||||
|
flags: PersistedPluginFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PersistedPluginFlags {
|
||||||
|
[id: PluginManifest['id']]: number
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import * as PluginApiModulesFindersFilters from '@revenge-mod/modules/finders/fi
|
||||||
import * as PluginApiModulesMetroSubscriptions from '@revenge-mod/modules/metro/subscriptions'
|
import * as PluginApiModulesMetroSubscriptions from '@revenge-mod/modules/metro/subscriptions'
|
||||||
import * as PluginApiModulesMetroUtils from '@revenge-mod/modules/metro/utils'
|
import * as PluginApiModulesMetroUtils from '@revenge-mod/modules/metro/utils'
|
||||||
import * as PluginApiModulesNative_ from '@revenge-mod/modules/native'
|
import * as PluginApiModulesNative_ from '@revenge-mod/modules/native'
|
||||||
|
import * as PluginApiModulesNativeFileSystem from '@revenge-mod/modules/native/fs'
|
||||||
import { spreadDescriptors } from '.'
|
import { spreadDescriptors } from '.'
|
||||||
|
|
||||||
export interface PluginApiModules {
|
export interface PluginApiModules {
|
||||||
|
|
@ -11,7 +12,9 @@ export interface PluginApiModules {
|
||||||
native: PluginApiModulesNative
|
native: PluginApiModulesNative
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginApiModulesNative = typeof PluginApiModulesNative_
|
export type PluginApiModulesNative = typeof PluginApiModulesNative_ & {
|
||||||
|
fs: typeof PluginApiModulesNativeFileSystem
|
||||||
|
}
|
||||||
|
|
||||||
export type PluginApiModulesMetro =
|
export type PluginApiModulesMetro =
|
||||||
// biome-ignore format: Don't
|
// biome-ignore format: Don't
|
||||||
|
|
@ -30,5 +33,7 @@ export const modules: PluginApiModules = {
|
||||||
PluginApiModulesMetroUtils,
|
PluginApiModulesMetroUtils,
|
||||||
spreadDescriptors(PluginApiModulesMetroSubscriptions, {}),
|
spreadDescriptors(PluginApiModulesMetroSubscriptions, {}),
|
||||||
),
|
),
|
||||||
native: PluginApiModulesNative_,
|
native: spreadDescriptors(PluginApiModulesNative_, {
|
||||||
|
fs: PluginApiModulesNativeFileSystem,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ export const PluginFlags = {
|
||||||
EnabledLate: 1 << 3,
|
EnabledLate: 1 << 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bitmask of {@link PluginFlags} that are persisted to storage.
|
||||||
|
*/
|
||||||
|
export const PersistentPluginFlags = PluginFlags.Enabled
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The plugin status.
|
* The plugin status.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import '@revenge-mod/react/init'
|
||||||
import '@revenge-mod/storage/init'
|
import '@revenge-mod/storage/init'
|
||||||
|
|
||||||
import { onRunApplication } from '@revenge-mod/react/native'
|
import { onRunApplication } from '@revenge-mod/react/native'
|
||||||
import { onError } from './preinit'
|
import { onError } from '~preinit'
|
||||||
|
|
||||||
const unsub = onRunApplication(() => {
|
const unsub = onRunApplication(() => {
|
||||||
unsub()
|
unsub()
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export const Setting = {
|
||||||
ReactVersion: 'ReactVersion',
|
ReactVersion: 'ReactVersion',
|
||||||
ReactNativeVersion: 'ReactNativeVersion',
|
ReactNativeVersion: 'ReactNativeVersion',
|
||||||
HermesVersion: 'HermesVersion',
|
HermesVersion: 'HermesVersion',
|
||||||
|
LoaderVersion: 'LoaderVersion',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const RouteNames = {
|
export const RouteNames = {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
|
||||||
|
import { getBridgeInfo } from '@revenge-mod/modules/native'
|
||||||
|
import { Setting } from '../constants'
|
||||||
|
import { CopyableSetting } from './shared'
|
||||||
|
import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
|
||||||
|
|
||||||
|
const bridgeInfo = getBridgeInfo()
|
||||||
|
|
||||||
|
const LoaderVersionSetting: SettingsItem = CopyableSetting(
|
||||||
|
{
|
||||||
|
parent: Setting.Revenge,
|
||||||
|
IconComponent: () => <TableRowAssetIcon name="SendMessageIcon" />,
|
||||||
|
title: () => 'Loader',
|
||||||
|
usePredicate: () => Boolean(bridgeInfo),
|
||||||
|
},
|
||||||
|
() => `${bridgeInfo!.name} (${bridgeInfo!.version})`,
|
||||||
|
)
|
||||||
|
|
||||||
|
export default LoaderVersionSetting
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
} from '@revenge-mod/discord/modules/settings'
|
} from '@revenge-mod/discord/modules/settings'
|
||||||
import { Setting } from './constants'
|
import { Setting } from './constants'
|
||||||
import HermesVersionSetting from './definitions/HermesVersionSetting'
|
import HermesVersionSetting from './definitions/HermesVersionSetting'
|
||||||
|
import LoaderVersionSetting from './definitions/LoaderVersionSetting'
|
||||||
import ReactNativeVersionSetting from './definitions/ReactNativeVersionSetting'
|
import ReactNativeVersionSetting from './definitions/ReactNativeVersionSetting'
|
||||||
import ReactVersionSetting from './definitions/ReactVersionSetting'
|
import ReactVersionSetting from './definitions/ReactVersionSetting'
|
||||||
import ReloadSetting from './definitions/ReloadSetting'
|
import ReloadSetting from './definitions/ReloadSetting'
|
||||||
|
|
@ -25,6 +26,7 @@ registerSettingsItems({
|
||||||
[Setting.ReactVersion]: ReactVersionSetting,
|
[Setting.ReactVersion]: ReactVersionSetting,
|
||||||
[Setting.ReactNativeVersion]: ReactNativeVersionSetting,
|
[Setting.ReactNativeVersion]: ReactNativeVersionSetting,
|
||||||
[Setting.HermesVersion]: HermesVersionSetting,
|
[Setting.HermesVersion]: HermesVersionSetting,
|
||||||
|
[Setting.LoaderVersion]: LoaderVersionSetting,
|
||||||
})
|
})
|
||||||
|
|
||||||
registerSettingsSection('REVENGE', {
|
registerSettingsSection('REVENGE', {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Design } from '@revenge-mod/discord/design'
|
|
||||||
import { SettingListRenderer } from '@revenge-mod/discord/modules/settings/renderer'
|
import { SettingListRenderer } from '@revenge-mod/discord/modules/settings/renderer'
|
||||||
import { Setting } from '../constants'
|
import { Setting } from '../constants'
|
||||||
|
|
||||||
|
|
@ -10,24 +9,11 @@ export default function RevengeSettingScreen() {
|
||||||
label: 'Revenge',
|
label: 'Revenge',
|
||||||
settings: [
|
settings: [
|
||||||
Setting.RevengeVersion,
|
Setting.RevengeVersion,
|
||||||
|
Setting.LoaderVersion,
|
||||||
Setting.RevengeDiscord,
|
Setting.RevengeDiscord,
|
||||||
Setting.RevengeSourceRepository,
|
Setting.RevengeSourceRepository,
|
||||||
Setting.RevengeLicense,
|
Setting.RevengeLicense,
|
||||||
],
|
],
|
||||||
subLabel: (
|
|
||||||
<>
|
|
||||||
<Design.Text variant="text-xs/medium">
|
|
||||||
You are using the next version of Revenge!
|
|
||||||
</Design.Text>
|
|
||||||
<Design.Text
|
|
||||||
color="text-danger"
|
|
||||||
variant="text-xs/semibold"
|
|
||||||
>
|
|
||||||
This version is experimental and may be
|
|
||||||
unstable.
|
|
||||||
</Design.Text>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Versions',
|
label: 'Versions',
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ import {
|
||||||
onModuleFirstRequired,
|
onModuleFirstRequired,
|
||||||
onModuleInitialized,
|
onModuleInitialized,
|
||||||
} from '@revenge-mod/modules/metro/subscriptions'
|
} from '@revenge-mod/modules/metro/subscriptions'
|
||||||
|
import { callBridgeMethod } from '@revenge-mod/modules/native'
|
||||||
import { getErrorStack } from '@revenge-mod/utils/error'
|
import { getErrorStack } from '@revenge-mod/utils/error'
|
||||||
import { BuildEnvironment, FullVersion } from './constants'
|
import { BuildEnvironment, FullVersion } from '~constants'
|
||||||
|
|
||||||
const IndexModuleId = 0
|
const IndexModuleId = 0
|
||||||
|
|
||||||
|
|
@ -49,19 +50,19 @@ onModuleFirstRequired(IndexModuleId, function onIndexRequired() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function onError(e: unknown) {
|
export function onError(error: unknown) {
|
||||||
// TODO(init): Move to use native provided alert function, which will accept a string stack trace
|
const stack = getErrorStack(error) ?? String(error)
|
||||||
// Above will reduce the need for runCatching() to exist, as the code won't be able to be deduped any further
|
|
||||||
const { ClientInfoModule, DeviceModule } =
|
|
||||||
require('@revenge-mod/discord/native') as typeof import('@revenge-mod/discord/native')
|
|
||||||
|
|
||||||
const Client = ClientInfoModule.getConstants()
|
callBridgeMethod('revenge.alertError', [
|
||||||
const Device = DeviceModule.getConstants()
|
stack,
|
||||||
|
`${FullVersion} (${BuildEnvironment})`,
|
||||||
|
])
|
||||||
|
|
||||||
alert(
|
nativeLoggingHook(`\u001b[31m${stack}\u001b[0m`, 2)
|
||||||
`Failed to load Revenge (${FullVersion} (${BuildEnvironment}))\n` +
|
}
|
||||||
`Discord: ${Client.Version} (${Client.Build})\n` +
|
|
||||||
`Device: ${Device.deviceManufacturer} ${Device.deviceModel}\n\n` +
|
declare module '@revenge-mod/modules/native' {
|
||||||
getErrorStack(e),
|
export interface Methods {
|
||||||
)
|
'revenge.alertError': [[error: string, version: string], void]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue