XuqmGroup-RNSDK/packages/update/src/PluginRuntime.ts

97 行
3.1 KiB
TypeScript

import { UpdateSDK, type PluginRegistration } from './UpdateSDK'
export interface XuqmPluginContext {
navigate(route: string, params?: unknown): void
emit(event: string, payload?: unknown): void
getHostState<T = unknown>(): T
}
export interface XuqmPluginDefinition {
moduleId: string
activate(context: XuqmPluginContext): void | Promise<void>
deactivate?(): void | Promise<void>
}
export interface XuqmRuntimeOptions {
plugins: PluginRegistration[]
context: XuqmPluginContext
loadBundle(moduleId: string, bundlePath: string): Promise<void>
checkUpdatesOnStart?: boolean
}
const definitions = new Map<string, XuqmPluginDefinition>()
/** Called by a plugin bundle when it is evaluated. */
export function definePlugin(definition: XuqmPluginDefinition): void {
if (!definition.moduleId.trim()) {
throw new Error('[XuqmRuntime] moduleId is required')
}
const existing = definitions.get(definition.moduleId)
if (existing && existing !== definition) {
throw new Error(`[XuqmRuntime] Plugin "${definition.moduleId}" is already defined`)
}
definitions.set(definition.moduleId, definition)
}
export const XuqmRuntime = (() => {
let options: XuqmRuntimeOptions | null = null
const active = new Set<string>()
return {
configure(next: XuqmRuntimeOptions): void {
if (!next.plugins.length) {
throw new Error('[XuqmRuntime] At least one plugin registration is required')
}
options = next
UpdateSDK.registerPlugins(next.plugins)
},
async start() {
if (!options) throw new Error('[XuqmRuntime] configure() must be called before start()')
await UpdateSDK.preparePendingRelease()
if (options.checkUpdatesOnStart !== false) {
return UpdateSDK.checkStartupUpdate()
}
return { kind: 'none' as const }
},
async activate(moduleId: string): Promise<void> {
if (!options) throw new Error('[XuqmRuntime] configure() must be called before activate()')
if (active.has(moduleId)) return
let definition = definitions.get(moduleId)
if (!definition) {
const bundlePath = await UpdateSDK.getLaunchBundlePath(moduleId)
await options.loadBundle(moduleId, bundlePath)
definition = definitions.get(moduleId)
}
if (!definition) {
await UpdateSDK.reportPluginLaunchFailure(moduleId, 'Plugin did not call definePlugin()')
throw new Error(`[XuqmRuntime] Plugin "${moduleId}" did not register after bundle load`)
}
try {
await definition.activate(options.context)
active.add(moduleId)
await UpdateSDK.confirmPluginLaunch(moduleId)
} catch (error) {
await UpdateSDK.reportPluginLaunchFailure(
moduleId,
error instanceof Error ? error.message : String(error),
)
throw error
}
},
async deactivate(moduleId: string): Promise<void> {
const definition = definitions.get(moduleId)
if (definition?.deactivate) await definition.deactivate()
active.delete(moduleId)
},
getDefinedPlugins(): string[] {
return Array.from(definitions.keys())
},
}
})()