pull chatbubbles plugin support from kettu
This commit is contained in:
parent
ebfe35846f
commit
c9223d44f3
6 changed files with 238 additions and 1 deletions
|
|
@ -77,7 +77,7 @@ const config = {
|
||||||
constModules: {
|
constModules: {
|
||||||
globals: {
|
globals: {
|
||||||
"bunny-build-info": {
|
"bunny-build-info": {
|
||||||
version: `"1.1.10"`,
|
version: `"1.1.10-1"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
82
src/lib/api/native/modules/Loader.ts
Normal file
82
src/lib/api/native/modules/Loader.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { ImageLoader } from "./";
|
||||||
|
import type { LoaderPayload } from "./types";
|
||||||
|
|
||||||
|
type InteropReturn = Promise<{ ret: string } | { err: string } | { cancelled: true; reason: string }>;
|
||||||
|
|
||||||
|
export const loaderPayload = window.__PYON_LOADER__ as LoaderPayload;
|
||||||
|
|
||||||
|
export function isModuleRegistered(module: string) {
|
||||||
|
return loaderPayload.loader.modules[module] !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFunctionRegistered(module: string, function_: string) {
|
||||||
|
return loaderPayload.loader.modules[module]?.functions[function_] !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function callFunction(module: string, functionName: string, args: unknown[]) {
|
||||||
|
if (!isFunctionRegistered(module, functionName)) {
|
||||||
|
throw new Error(`Function ${module}.${functionName} is not registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise: InteropReturn = ImageLoader.queryCache([
|
||||||
|
"__pyon_bridge",
|
||||||
|
JSON.stringify({ m: module, f: functionName, a: args }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result = await promise;
|
||||||
|
|
||||||
|
if ("ret" in result) {
|
||||||
|
return JSON.parse(result.ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("cancelled" in result) {
|
||||||
|
throw new Error(`Function ${module}.${functionName} was cancelled: ${result.reason}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("err" in result) {
|
||||||
|
throw new Error(result.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoaderModule {
|
||||||
|
hasFunction(functionName: string): boolean;
|
||||||
|
callFunction(functionName: string, args: unknown[]): Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleProps<T extends LoaderModule> = {
|
||||||
|
name: string;
|
||||||
|
argumentProcessors?: Partial<{ [key in keyof T]: (args: any[]) => any[] }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getModule<T extends LoaderModule>({ name, argumentProcessors: processArgs }: ModuleProps<T>) {
|
||||||
|
const module = loaderPayload.loader.modules[name];
|
||||||
|
if (!module) {
|
||||||
|
throw new Error(`Module ${name} is not registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseObject: Record<string, any> = {
|
||||||
|
hasFunction: (functionName: string) => isFunctionRegistered(name, functionName),
|
||||||
|
callFunction: (functionName: string, args: unknown[]) => callFunction(name, functionName, args),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [functionName] of Object.entries(module.functions)) {
|
||||||
|
baseObject[functionName] = (...args: unknown[]) =>
|
||||||
|
callFunction(
|
||||||
|
name,
|
||||||
|
functionName,
|
||||||
|
// @ts-expect-error
|
||||||
|
processArgs && functionName in processArgs ? processArgs[functionName](args) : args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseObject as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if safe mode is currently enabled. The result remains consistent over time.
|
||||||
|
* This will not change until the app is restarted. For draft changes, use the InitConfigStore.
|
||||||
|
* @returns True if safe mode is enabled for the current instance, otherwise false.
|
||||||
|
*/
|
||||||
|
export function isSafeModeEnabled() {
|
||||||
|
return Boolean(loaderPayload.loader.initConfig.safeMode);
|
||||||
|
}
|
||||||
16
src/lib/api/native/modules/bubblemodule.ts
Normal file
16
src/lib/api/native/modules/bubblemodule.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { processColor } from "react-native";
|
||||||
|
import { getModule } from "./loader";
|
||||||
|
import type { LoaderModule } from "./loader";
|
||||||
|
|
||||||
|
interface BubbleModule extends LoaderModule {
|
||||||
|
hookBubbles(): Promise<void>;
|
||||||
|
unhookBubbles(): Promise<void>;
|
||||||
|
configure(avatarRadius?: number, bubbleRadius?: number, bubbleColor?: string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getModule<BubbleModule>({
|
||||||
|
name: "BubbleModule",
|
||||||
|
argumentProcessors: {
|
||||||
|
configure: args => [args[0], args[1], Number(processColor(args[2]))],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -33,3 +33,6 @@ export const NativeThemeModule = getNativeModule(
|
||||||
export const BundleUpdaterManager = getNativeModule(
|
export const BundleUpdaterManager = getNativeModule(
|
||||||
"BundleUpdaterManager"
|
"BundleUpdaterManager"
|
||||||
)!;
|
)!;
|
||||||
|
export const ImageLoader = getNativeModule(
|
||||||
|
"ImageLoader"
|
||||||
|
)!;
|
||||||
|
|
|
||||||
|
|
@ -134,3 +134,42 @@ export namespace RNModules {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InitConfig {
|
||||||
|
baseUrl: string;
|
||||||
|
forceUpdate: boolean;
|
||||||
|
skipUpdate: boolean;
|
||||||
|
bundlePath: string | null;
|
||||||
|
safeMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoaderConstants {
|
||||||
|
WINTRY_DIR: string;
|
||||||
|
DEFAULT_BASE_URL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoaderModule {
|
||||||
|
functions: {
|
||||||
|
[functionName: string]: number;
|
||||||
|
};
|
||||||
|
constants: {
|
||||||
|
[constantName: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoaderModules {
|
||||||
|
[moduleName: string]: LoaderModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoaderPayload {
|
||||||
|
bundle: {
|
||||||
|
revision: string;
|
||||||
|
};
|
||||||
|
loader: {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
initConfig: InitConfig;
|
||||||
|
constants: LoaderConstants;
|
||||||
|
modules: LoaderModules;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -264,3 +264,100 @@ export function* getCachedPolyfillModules(name: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WaitForOptions {
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModuleFilter<T = any> = {
|
||||||
|
(exports: any): T | undefined;
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function waitFor<T = any>(
|
||||||
|
filter: ModuleFilter<T>,
|
||||||
|
callback: (exports: T, id: Metro.ModuleID) => void,
|
||||||
|
options: WaitForOptions = {}
|
||||||
|
): () => void {
|
||||||
|
const { count = 1 } = options;
|
||||||
|
let currentCount = 0;
|
||||||
|
const unsubscribers: Array<() => void> = [];
|
||||||
|
let isActive = true;
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
if (!isActive) return;
|
||||||
|
isActive = false;
|
||||||
|
unsubscribers.forEach(unsub => unsub());
|
||||||
|
unsubscribers.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkModule(id: Metro.ModuleID): boolean {
|
||||||
|
if (!isActive) return true;
|
||||||
|
|
||||||
|
const exports = requireModule(id);
|
||||||
|
if (isBadExports(exports)) return false;
|
||||||
|
|
||||||
|
const result = filter(exports);
|
||||||
|
if (!result) return false;
|
||||||
|
|
||||||
|
callback(result, id);
|
||||||
|
|
||||||
|
if (++currentCount >= count) {
|
||||||
|
cleanup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.key) {
|
||||||
|
const cache = getMetroCache().findIndex[filter.key];
|
||||||
|
if (cache) {
|
||||||
|
for (const id in cache) {
|
||||||
|
if (id[0] === "_") continue;
|
||||||
|
const numId = Number(id);
|
||||||
|
|
||||||
|
if (metroModules[numId]?.isInitialized) {
|
||||||
|
if (checkModule(numId)) return cleanup;
|
||||||
|
} else {
|
||||||
|
const unsub = subscribeModule(numId, () => {
|
||||||
|
checkModule(numId);
|
||||||
|
});
|
||||||
|
unsubscribers.push(unsub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id in metroModules) {
|
||||||
|
if (!isActive) break;
|
||||||
|
const numId = Number(id);
|
||||||
|
|
||||||
|
if (metroModules[numId]?.isInitialized && !metroModules[numId]?.hasError) {
|
||||||
|
if (checkModule(numId)) return cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
for (const id in metroModules) {
|
||||||
|
const numId = Number(id);
|
||||||
|
if (!metroModules[numId]?.isInitialized) {
|
||||||
|
const unsub = subscribeModule(numId, () => {
|
||||||
|
checkModule(numId);
|
||||||
|
});
|
||||||
|
unsubscribers.push(unsub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function waitForModule<T = any>(
|
||||||
|
filter: ModuleFilter<T>,
|
||||||
|
options: WaitForOptions = {}
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
waitFor(filter, (exports) => resolve(exports), options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue