97 行
3.1 KiB
TypeScript
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())
|
||
|
|
},
|
||
|
|
}
|
||
|
|
})()
|