Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
+189
View File
@@ -0,0 +1,189 @@
/**
* @license
* Copyright 2025-2026 NomiFun (nomifun.com)
* SPDX-License-Identifier: Apache-2.0
*/
/**
* In-house bridge: transport-agnostic pub/sub + request/response RPC.
*
* Replaces the bridge surface of the former third-party platform package.
* The wire protocol is preserved byte-for-byte so the Rust backend
* (`WebSocketMessage{name,data}`), the mobile client, and the desktop
* adapters all keep working unchanged:
*
* - invoke(name, data):
* emits event `"subscribe-" + name` with body `{ id, data }`,
* then awaits a one-shot `"subscribe.callback-" + name + id`.
* - subscribe(name, handler):
* listens on `"subscribe-" + name`, runs the handler, then emits
* `"subscribe.callback-" + name + body.id` with the result.
* - buildEmitter uses the bare event name (fire-and-forget, no callback).
*
* The id is concatenated to the name WITHOUT a delimiter — this matches the
* original library and the consumers that hand-build the callback name
* (mobile bridge.ts, useDirectorySelection.tsx). Do not change it.
*/
import EventEmitter from 'eventemitter3';
type AnyFn = (...args: any[]) => any;
interface Adapter {
emit(name: string, data: any, ...args: any[]): void;
on(emitter: { emit(name: string, ...args: any[]): void }): void;
}
interface InterceptParams {
name: string;
data: any;
}
// Internal event hub. The adapter plugs the real transport into `outbound`
// (how events leave this process) and feeds inbound events back via the
// emitter handed to `adapter.on`.
const hub = new EventEmitter();
let logOutput: ((...args: any[]) => void) | null = null;
const log = (...args: any[]) => {
if (logOutput) logOutput(...args);
};
const interceptors: Array<(params: InterceptParams) => Promise<void>> = [];
// Default outbound: loopback (same-process). The adapter overrides this.
let outbound: (name: string, data: any, ...args: any[]) => void = (name, data, ...args) => {
hub.emit(name, data, ...args);
};
/**
* Register a listener. Callback-channel events (`subscribe(.callback)?-`)
* bypass interceptors so RPC plumbing is never delayed by app-level guards.
*/
const on = (name: string, callback: AnyFn): (() => void) => {
const wrapped = (...args: any[]) => {
if (/^subscribe(\.callback)?-/.test(name) || interceptors.length === 0) {
return callback(...args);
}
return Promise.all(interceptors.map((i) => i({ name, data: args[0] }))).then(() => callback(...args));
};
hub.on(name, wrapped);
return () => hub.off(name, wrapped);
};
const off = (name: string, callback: AnyFn): void => {
// Best-effort: matches the original loose contract. Listeners created via
// `on()` return their own disposer; this removes direct hub listeners.
hub.off(name, callback);
};
const emit = (name: string, data: any, ...args: any[]): void => {
log('bridge.emit', name, data);
outbound(name, data, ...args);
};
// Monotonic, collision-resistant id. Format is opaque; only uniqueness as a
// string matters (consumers concatenate it onto the callback event name).
let idCounter = 0;
const nextId = (prefix: string): string => `${prefix}_${idCounter++}_${randomSuffix()}`;
const randomSuffix = (): string => {
// Avoid Math.random/Date in hot equality paths is unnecessary here; this is
// a fresh id each call. Use performance-cheap entropy.
let s = '';
for (let i = 0; i < 6; i++) s += ((Math.random() * 36) | 0).toString(36);
return s;
};
const subscribe = <Data = any, Result = any>(name: string, callback: (data: Data) => Promise<Result>): (() => void) => {
return on('subscribe-' + name, (body: { id: string; data: Data }, ...rest: any[]) => {
Promise.resolve(callback(body.data)).then((result) => {
emit('subscribe.callback-' + name + body.id, result, ...rest);
});
});
};
const invoke = <Data = any>(name: string, data?: any): Promise<Data> => {
const id = nextId(name);
return new Promise<Data>((resolve) => {
// Register the one-shot callback listener BEFORE emitting the request, so a
// synchronous responder (loopback / same-process provider) cannot deliver
// the result before we are listening. With an async transport the order is
// immaterial; this is correct in both cases.
const dispose = on('subscribe.callback-' + name + id, (result: Data) => {
resolve(result);
dispose();
});
emit('subscribe-' + name, { id, data });
});
};
const create = <Data = any, Result = any>(key: string) => ({
invoke: (data: Data) => invoke<Result>(key, data),
subscribe: (handler: (data: Data) => Promise<Result>) => subscribe<Data, Result>(key, handler),
});
const buildProvider = <Data = any, Params = undefined>(key: string) => {
const channel = create<Params, Data>(key);
return {
provider: (provider: Params extends undefined ? () => Promise<Data> : (params: Params) => Promise<Data>) => {
channel.subscribe((params: Params) => (provider as (p: Params) => Promise<Data>)(params));
},
invoke: ((params?: Params) => channel.invoke(params as Params)) as Params extends undefined
? () => Promise<Data>
: (params: Params) => Promise<Data>,
};
};
const buildEmitter = <Params = undefined>(key: string) => ({
on: (callback: Params extends undefined ? () => void : (params: Params) => void) => on(key, callback as AnyFn),
emit: ((params?: Params) => emit(key, params)) as Params extends undefined ? () => void : (params: Params) => void,
});
const intercept = (callback: (params: InterceptParams) => Promise<void>): (() => void) => {
interceptors.push(callback);
return () => {
const i = interceptors.indexOf(callback);
if (i >= 0) interceptors.splice(i, 1);
};
};
/**
* Install the real transport. `emit` ships an outbound event; `on` is handed
* an emitter whose `.emit(name, data)` feeds inbound events into the hub.
*/
const adapter = (config: Adapter): void => {
outbound = (name, data, ...args) => config.emit(name, data, ...args);
config.on({
emit: (name: string, ...args: any[]) => {
hub.emit(name, ...args);
},
});
};
const logger = (output: (...args: any[]) => void): void => {
logOutput = output;
};
// Inert lifecycle hooks kept for API compatibility (no consumer relies on
// their behavior; they existed on the original surface).
const start = (): void => {};
const stop = (): void => {};
const status = (): boolean => true;
const debug = (_flag?: boolean): void => {};
export {
adapter,
buildProvider,
buildEmitter,
on,
off,
emit,
subscribe,
invoke,
create,
intercept,
logger,
start,
stop,
status,
debug,
};