From 1840577fb5aa6b3cc2dc38cbe566898a3c566864 Mon Sep 17 00:00:00 2001 From: redstonekasi Date: Mon, 6 Feb 2023 08:48:55 +0100 Subject: [PATCH] [Global] Loader config and identity (#17) * [TS] Add definition for DCDFileManager * [Storage] Introduce backends * [Settings] Add loader config * [TS] Update storage definitions * [TS] Update loader config and identity types * [Loader] Expose loader config and identity * [UI] Actually update UI for the new loader config fields --- src/def.d.ts | 69 +++++++++++++++++++++++- src/lib/plugins.ts | 6 +-- src/lib/settings.ts | 7 +-- src/lib/storage/backends.ts | 24 +++++++++ src/lib/{storage.ts => storage/index.ts} | 12 +++-- src/lib/windowObject.ts | 8 ++- src/ui/settings/pages/Developer.tsx | 47 ++++++++++++++-- 7 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 src/lib/storage/backends.ts rename src/lib/{storage.ts => storage/index.ts} (91%) diff --git a/src/def.d.ts b/src/def.d.ts index 8dd7eec..e01199d 100644 --- a/src/def.d.ts +++ b/src/def.d.ts @@ -176,6 +176,42 @@ interface MMKVManager { clear: () => void; } +interface DCDFileManager { + /** + * @param path **Full** path to file + */ + fileExists: (path: string) => Promise; + /** + * Allowed URI schemes on Android: `file://`, `content://` ([See here](https://developer.android.com/reference/android/content/ContentResolver#accepts-the-following-uri-schemes:_3)) + */ + getSize: (uri: string) => Promise; + /** + * @param path **Full** path to file + * @param encoding Set to `base64` in order to encode response + */ + readFile(path: string, encoding: "base64" | "utf8"): Promise; + /** + * Beware! This function has differing functionality on IOS and Android. + * @param storageDir Either `cache` or `documents`. + * @param path Path in `storageDir`, parents are recursively created. + * @param data The data to write to the file + * @param encoding Set to `base64` if `data` is base64 encoded. + * @returns Promise that resolves to path of the file once it got written + */ + writeFile(storageDir: "cache" | "documents", path: string, data: string, encoding: "base64" | "utf8"): Promise; + getConstants: () => { + /** + * The path the `documents` storage dir (see {@link writeFile}) represents. + */ + DocumentsDirPath: string; + }; + /** + * Will apparently cease to exist some time in the future so please use {@link getConstants} instead. + * @deprecated + */ + DocumentsDirPath: string; +} + type Indexable = { [index: string]: Type } type EmitterEvent = "SET" | "GET" | "DEL"; @@ -200,6 +236,30 @@ interface Emitter { emit: (event: EmitterEvent, data: EmitterListenerData) => void; } +interface StorageBackend { + get: () => unknown | Promise; + set: (data: unknown) => void | Promise; +} + +interface LoaderConfig { + customLoadUrl: { + enabled: boolean; + url: string; + }; + loadReactDevTools: boolean; +} + +interface LoaderIdentity { + name: string; + features: { + loaderConfig?: boolean; + devtools?: { + prop: string; + version: string; + } + } +} + interface VendettaObject { patcher: { after: typeof _spitroast.after; @@ -277,11 +337,17 @@ interface VendettaObject { storage: { createProxy: (target: T) => { proxy: T, emitter: Emitter }; useProxy: (storage: T) => T; - createStorage: (storeName: string) => Promise>; + createStorage: (backend: StorageBackend) => Promise>; wrapSync: >(store: T) => Awaited; awaitSyncWrapper: (store: any) => Promise; + createMMKVBackend: (store: string) => StorageBackend; + createFileBackend: (file: string) => StorageBackend; }; settings: Settings; + loader: { + identity?: LoaderIdentity; + config: LoaderConfig; + }; logger: Logger; version: string; unload: () => void; @@ -299,5 +365,6 @@ declare global { modules: MetroModules; vendetta: VendettaObject; React: typeof _React; + __vendetta_loader?: LoaderIdentity; } } diff --git a/src/lib/plugins.ts b/src/lib/plugins.ts index 3222d3b..c25e594 100644 --- a/src/lib/plugins.ts +++ b/src/lib/plugins.ts @@ -1,5 +1,5 @@ import { Indexable, PluginManifest, Plugin } from "@types"; -import { awaitSyncWrapper, createStorage, wrapSync } from "@lib/storage"; +import { awaitSyncWrapper, createMMKVBackend, createStorage, wrapSync } from "@lib/storage"; import safeFetch from "@utils/safeFetch"; import logger from "@lib/logger"; @@ -11,7 +11,7 @@ type EvaledPlugin = { settings: JSX.Element; }; -export const plugins = wrapSync(createStorage>("VENDETTA_PLUGINS")); +export const plugins = wrapSync(createStorage>(createMMKVBackend("VENDETTA_PLUGINS"))); const loadedPlugins: Indexable = {}; export async function fetchPlugin(id: string) { @@ -61,7 +61,7 @@ export async function evalPlugin(plugin: Plugin) { plugin: { manifest: plugin.manifest, // Wrapping this with wrapSync is NOT an option. - storage: await createStorage>(plugin.id), + storage: await createStorage>(createMMKVBackend(plugin.id)), } }; const pluginString = `vendetta=>{return ${plugin.js}}\n//# sourceURL=${plugin.id}`; diff --git a/src/lib/settings.ts b/src/lib/settings.ts index 0c8ada1..04ef89b 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -1,4 +1,5 @@ -import { createStorage, wrapSync } from "@lib/storage"; -import { Settings } from "@types"; +import { createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@lib/storage"; +import { LoaderConfig, Settings } from "@types"; -export default wrapSync(createStorage("VENDETTA_SETTINGS")); +export default wrapSync(createStorage(createMMKVBackend("VENDETTA_SETTINGS"))); +export const loaderConfig = wrapSync(createStorage(createFileBackend("vendetta_loader.json"))); diff --git a/src/lib/storage/backends.ts b/src/lib/storage/backends.ts new file mode 100644 index 0000000..d8ebb1e --- /dev/null +++ b/src/lib/storage/backends.ts @@ -0,0 +1,24 @@ +import { DCDFileManager, MMKVManager, StorageBackend } from "@types"; +import { ReactNative as RN } from "@metro/hoist"; + +const MMKVManager = RN.NativeModules.MMKVManager as MMKVManager; +const DCDFileManager = RN.NativeModules.DCDFileManager as DCDFileManager; + +const filePathFixer: (file: string) => string = RN.Platform.select({ + default: (f) => f, + ios: (f) => `Documents/${f}`, +}); + +export const createMMKVBackend = (store: string): StorageBackend => ({ + get: async function() { + return JSON.parse((await MMKVManager.getItem(store)) ?? "{}"); + }, + set: (data) => MMKVManager.setItem(store, JSON.stringify(data)), +}); + +export const createFileBackend = (file: string): StorageBackend => ({ + get: async function() { + return JSON.parse((await DCDFileManager.readFile(`${DCDFileManager.getConstants().DocumentsDirPath}/${file}`, "utf8")) ?? "{}"); + }, + set: (data) => void DCDFileManager.writeFile("documents", filePathFixer(file), JSON.stringify(data), "utf8"), +}); diff --git a/src/lib/storage.ts b/src/lib/storage/index.ts similarity index 91% rename from src/lib/storage.ts rename to src/lib/storage/index.ts index 1c48e8a..cd45159 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage/index.ts @@ -1,6 +1,6 @@ -import { Emitter, MMKVManager } from "@types"; +import { Emitter, MMKVManager, StorageBackend } from "@types"; import { ReactNative as RN } from "@metro/hoist"; -import createEmitter from "./emitter"; +import createEmitter from "../emitter"; const MMKVManager = RN.NativeModules.MMKVManager as MMKVManager; @@ -79,11 +79,11 @@ export function useProxy(storage: T): T { return storage; } -export async function createStorage(storeName: string): Promise> { - const data = JSON.parse((await MMKVManager.getItem(storeName)) ?? "{}"); +export async function createStorage(backend: StorageBackend): Promise> { + const data = await backend.get(); const { proxy, emitter } = createProxy(data); - const handler = () => MMKVManager.setItem(storeName, JSON.stringify(proxy)); + const handler = () => backend.set(proxy); emitter.on("SET", handler); emitter.on("DEL", handler); @@ -115,3 +115,5 @@ export function wrapSync>(store: T): Awaited { } export const awaitSyncWrapper = (store: any) => new Promise((res) => store[syncAwaitSymbol](res)); + +export * from "./backends"; diff --git a/src/lib/windowObject.ts b/src/lib/windowObject.ts index 42c6b76..7d2e8c5 100644 --- a/src/lib/windowObject.ts +++ b/src/lib/windowObject.ts @@ -1,7 +1,7 @@ import { VendettaObject } from "@types"; import patcher from "@lib/patcher"; import logger from "@lib/logger"; -import settings from "@lib/settings"; +import settings, { loaderConfig } from "@lib/settings"; import * as constants from "@lib/constants"; import * as debug from "@lib/debug"; import * as plugins from "@lib/plugins"; @@ -37,6 +37,10 @@ export default async function windowObject(unloads: any[]): Promise { @@ -45,4 +49,4 @@ export default async function windowObject(unloads: any[]): Promise @@ -46,6 +57,32 @@ export default function Developer() { }} />} + {window.__vendetta_loader?.features.loaderConfig && + } + value={loaderConfig.customLoadUrl.enabled} + onValueChange={(v: boolean) => { + loaderConfig.customLoadUrl.enabled = v; + }} + /> + {loaderConfig.customLoadUrl.enabled && loaderConfig.customLoadUrl.url = v} + placeholder="http://localhost:4040/vendetta.js" + title="VENDETTA URL" + />} + {window.__vendetta_loader.features.devtools && } + value={loaderConfig.loadReactDevTools} + onValueChange={(v: boolean) => { + loaderConfig.loadReactDevTools = v; + }} + />} + } ) -} \ No newline at end of file +}