feat!: allow plugin API extensions to get runtime options, and allow API extensions to pass options, refactor api.storage to API extensions
This commit is contained in:
parent
bf9e723e73
commit
fb8cc2d897
10 changed files with 118 additions and 69 deletions
|
|
@ -1,19 +1,12 @@
|
|||
import { TypedEventEmitter } from '@revenge-mod/discord/common/utils'
|
||||
import { getStorage } from '@revenge-mod/storage'
|
||||
import { defineLazyProperty } from '@revenge-mod/utils/objects'
|
||||
import { allSettled, sleepReject } from '@revenge-mod/utils/promises'
|
||||
import { _uapi as uapi } from './apis'
|
||||
import {
|
||||
PluginFlags as Flag,
|
||||
PluginsStorageDirectory,
|
||||
PluginStatus as Status,
|
||||
} from './constants'
|
||||
import type { StorageOptions } from '@revenge-mod/storage'
|
||||
import type { AnyObject } from '@revenge-mod/utils/types'
|
||||
import { PluginFlags as Flag, PluginStatus as Status } from './constants'
|
||||
import type {
|
||||
InitPluginApi,
|
||||
Plugin,
|
||||
PluginApi,
|
||||
PluginApiExtensionOptions,
|
||||
PluginCleanup,
|
||||
PluginManifest,
|
||||
PluginOptions,
|
||||
|
|
@ -23,7 +16,7 @@ import type {
|
|||
export const _uapi = uapi
|
||||
|
||||
export const _emitter = new TypedEventEmitter<{
|
||||
register: [InternalPlugin]
|
||||
register: [InternalPlugin, PluginOptions<any>]
|
||||
disabled: [InternalPlugin]
|
||||
enabled: [InternalPlugin]
|
||||
// Add preInit API extensions
|
||||
|
|
@ -50,9 +43,9 @@ export const _metas = new Map<
|
|||
]
|
||||
>()
|
||||
|
||||
export function registerPlugin<S extends AnyObject = AnyObject>(
|
||||
export function registerPlugin<E extends PluginApiExtensionOptions>(
|
||||
manifest: PluginManifest,
|
||||
options: PluginOptions<S>,
|
||||
options: PluginOptions<E>,
|
||||
flags: number,
|
||||
iflags: number,
|
||||
) {
|
||||
|
|
@ -62,7 +55,6 @@ export function registerPlugin<S extends AnyObject = AnyObject>(
|
|||
|
||||
const plugin: InternalPlugin = {
|
||||
_c: [],
|
||||
_s: options.storage,
|
||||
errors: [],
|
||||
manifest,
|
||||
lifecycles: {
|
||||
|
|
@ -81,7 +73,7 @@ export function registerPlugin<S extends AnyObject = AnyObject>(
|
|||
_metas.set(manifest.id, [undefined, [], iflags, PluginApiLevel.None])
|
||||
_plugins.set(manifest.id, plugin)
|
||||
|
||||
_emitter.emit('register', plugin)
|
||||
_emitter.emit('register', plugin, options)
|
||||
}
|
||||
|
||||
function handlePluginError(e: unknown, plugin: InternalPlugin) {
|
||||
|
|
@ -118,26 +110,15 @@ function preparePluginPreInit(plugin: InternalPlugin) {
|
|||
|
||||
function preparePluginInit(plugin: InternalPlugin) {
|
||||
const meta = _metas.get(plugin.manifest.id)!
|
||||
const api = meta[0] as InitPluginApi
|
||||
|
||||
defineLazyProperty(api, 'storage', () =>
|
||||
getStorage(`${PluginsStorageDirectory}/${plugin.manifest.id}.json`, {
|
||||
...plugin._s,
|
||||
directory: 'documents',
|
||||
}),
|
||||
)
|
||||
|
||||
_emitter.emit('init', plugin, api)
|
||||
_emitter.emit('init', plugin, meta[0] as InitPluginApi)
|
||||
meta[3] = PluginApiLevel.Init
|
||||
}
|
||||
|
||||
function preparePluginStart(plugin: InternalPlugin) {
|
||||
const meta = _metas.get(plugin.manifest.id)!
|
||||
|
||||
const api = meta[0] as PluginApi
|
||||
// api.settings = ...
|
||||
|
||||
_emitter.emit('start', plugin, api)
|
||||
_emitter.emit('start', plugin, meta[0] as PluginApi)
|
||||
meta[3] = PluginApiLevel.Start
|
||||
}
|
||||
|
||||
|
|
@ -381,8 +362,7 @@ export async function stopPlugin(plugin: InternalPlugin) {
|
|||
}
|
||||
}
|
||||
|
||||
export interface InternalPlugin extends Plugin {
|
||||
_s?: StorageOptions<AnyObject>
|
||||
export interface InternalPlugin extends Plugin<any> {
|
||||
_c: PluginCleanup[]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ for (const plugin of _plugins.values())
|
|||
const unsub = onRunApplication(() => {
|
||||
unsub()
|
||||
|
||||
// TODO(plugins/start): init start APIs
|
||||
// const UILibrary = require('@revenge-mod/ui')
|
||||
// uapi.ui = UiLibrary
|
||||
|
||||
for (const plugin of _plugins.values())
|
||||
if (plugin.flags & PluginFlags.Enabled) startPlugin(plugin)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import type { Storage, StorageOptions } from '@revenge-mod/storage'
|
||||
import type { AnyObject } from '@revenge-mod/utils/types'
|
||||
import type { FunctionComponent } from 'react'
|
||||
import type { PluginApiModules } from './apis/modules'
|
||||
import type { PluginApiPlugins } from './apis/plugins'
|
||||
import type { PluginApiReact } from './apis/react'
|
||||
import type { PluginFlags, PluginStatus } from './constants'
|
||||
|
||||
// biome-ignore lint/suspicious/noEmptyInterface: To be extended by actual extensions
|
||||
export interface PluginApiExtensionOptions {}
|
||||
|
||||
/**
|
||||
* The unscoped plugin API (very limited). This API is available as a global for plugins.
|
||||
* Available in the `preInit` phase.
|
||||
*/
|
||||
export interface UnscopedPreInitPluginApi {
|
||||
export interface UnscopedPreInitPluginApi<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> {
|
||||
modules: PluginApiModules
|
||||
patcher: typeof import('@revenge-mod/patcher')
|
||||
plugins: PluginApiPlugins
|
||||
|
|
@ -21,7 +24,9 @@ export interface UnscopedPreInitPluginApi {
|
|||
* The unscoped plugin API (limited). This API is available as a global for plugins.
|
||||
* Available in the `init` phase.
|
||||
*/
|
||||
export interface UnscopedInitPluginApi extends UnscopedPreInitPluginApi {
|
||||
export interface UnscopedInitPluginApi<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> extends UnscopedPreInitPluginApi<O> {
|
||||
assets: typeof import('@revenge-mod/assets')
|
||||
react: PluginApiReact
|
||||
}
|
||||
|
|
@ -30,7 +35,9 @@ export interface UnscopedInitPluginApi extends UnscopedPreInitPluginApi {
|
|||
* The unscoped plugin API. This API is available as a global for plugins.
|
||||
* Available in the `start` and `stop` phase.
|
||||
*/
|
||||
export interface UnscopedPluginApi extends UnscopedInitPluginApi {
|
||||
export interface UnscopedPluginApi<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> extends UnscopedInitPluginApi<O> {
|
||||
// ui: typeof import('@revenge-mod/ui')
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +48,9 @@ export type PluginCleanupApi = (...fns: PluginCleanup[]) => void
|
|||
* The plugin API (very limited).
|
||||
* Available in the `preInit` phase.
|
||||
*/
|
||||
export interface PreInitPluginApi {
|
||||
export interface PreInitPluginApi<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> {
|
||||
unscoped: UnscopedPreInitPluginApi
|
||||
cleanup: PluginCleanupApi
|
||||
plugin: Plugin
|
||||
|
|
@ -51,26 +60,19 @@ export interface PreInitPluginApi {
|
|||
* The plugin API (limited).
|
||||
* Available in the `init` phase.
|
||||
*/
|
||||
export interface InitPluginApi<S extends AnyObject = AnyObject>
|
||||
extends PreInitPluginApi {
|
||||
export interface InitPluginApi<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> extends PreInitPluginApi<O> {
|
||||
unscoped: UnscopedInitPluginApi
|
||||
/**
|
||||
* The plugin storage.
|
||||
*
|
||||
* Note that the instance is only created when the plugin accesses the API.
|
||||
* This is to prevent unnecessary storage instances from being created.
|
||||
*
|
||||
* To preload storage, simply call `api.storage.get()`.
|
||||
*/
|
||||
storage: Storage<S>
|
||||
}
|
||||
|
||||
/**
|
||||
* The plugin API.
|
||||
* Available in the `start` and `stop` phase.
|
||||
*/
|
||||
export interface PluginApi<S extends AnyObject = AnyObject>
|
||||
extends InitPluginApi<S> {
|
||||
export interface PluginApi<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> extends InitPluginApi<O> {
|
||||
unscoped: UnscopedPluginApi
|
||||
}
|
||||
|
||||
|
|
@ -113,42 +115,46 @@ export interface PluginManifest {
|
|||
icon?: string
|
||||
}
|
||||
|
||||
export interface PluginOptions<S extends AnyObject = AnyObject>
|
||||
extends PluginLifecycles<S> {
|
||||
SettingsComponent?: PluginSettingsComponent<S>
|
||||
storage?: Omit<StorageOptions<S>, 'directory'>
|
||||
export interface PluginOptions<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> extends PluginLifecycles<O> {
|
||||
SettingsComponent?: PluginSettingsComponent<O>
|
||||
}
|
||||
|
||||
export interface PluginLifecycles<S extends AnyObject = AnyObject> {
|
||||
export interface PluginLifecycles<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> {
|
||||
/**
|
||||
* Runs as soon as possible with very limited APIs.
|
||||
* Before the index module (module 0)'s factory is run.
|
||||
*
|
||||
* @param api Plugin API (very limited).
|
||||
*/
|
||||
preInit?: (api: PreInitPluginApi) => any
|
||||
preInit?: (api: PreInitPluginApi<O>) => any
|
||||
/**
|
||||
* Runs as soon as all important modules are initialized.
|
||||
* After the index module (module 0)'s factory is run.
|
||||
*
|
||||
* @param api Plugin API (limited).
|
||||
*/
|
||||
init?: (api: InitPluginApi<S>) => any
|
||||
init?: (api: InitPluginApi<O>) => any
|
||||
/**
|
||||
* Runs when the plugin can be started with all APIs available.
|
||||
*
|
||||
* @param api Plugin API.
|
||||
*/
|
||||
start?: (api: PluginApi<S>) => any
|
||||
start?: (api: PluginApi<O>) => any
|
||||
/**
|
||||
* Runs when the plugin is stopped.
|
||||
*
|
||||
* @param api Plugin API.
|
||||
*/
|
||||
stop?: (api: PluginApi<S>) => any
|
||||
stop?: (api: PluginApi<O>) => any
|
||||
}
|
||||
|
||||
export interface Plugin<S extends AnyObject = AnyObject> {
|
||||
export interface Plugin<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> {
|
||||
// TODO(plugins): support plugin bundles
|
||||
// /**
|
||||
// * The plugin bundle this plugin belongs to.
|
||||
|
|
@ -161,7 +167,7 @@ export interface Plugin<S extends AnyObject = AnyObject> {
|
|||
/**
|
||||
* The plugin lifecycles.
|
||||
*/
|
||||
lifecycles: PluginLifecycles<S>
|
||||
lifecycles: PluginLifecycles<O>
|
||||
|
||||
/**
|
||||
* The plugin flags.
|
||||
|
|
@ -181,7 +187,7 @@ export interface Plugin<S extends AnyObject = AnyObject> {
|
|||
/**
|
||||
* The plugin settings page.
|
||||
*/
|
||||
SettingsComponent?: PluginSettingsComponent<S>
|
||||
SettingsComponent?: PluginSettingsComponent<O>
|
||||
|
||||
/**
|
||||
* Disable the plugin. This will also stop the plugin if it is running.
|
||||
|
|
@ -193,5 +199,6 @@ export interface Plugin<S extends AnyObject = AnyObject> {
|
|||
stop(): Promise<void>
|
||||
}
|
||||
|
||||
export type PluginSettingsComponent<S extends AnyObject = AnyObject> =
|
||||
FunctionComponent<{ api: PluginApi<S> }>
|
||||
export interface PluginSettingsComponent<
|
||||
O extends PluginApiExtensionOptions = PluginApiExtensionOptions,
|
||||
> extends FunctionComponent<{ api: PluginApi<O> }> {}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@
|
|||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.ts"
|
||||
},
|
||||
"./preinit": {
|
||||
"default": "./src/preinit.ts"
|
||||
},
|
||||
"./init": {
|
||||
"default": "./src/init.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export function getStorage<T extends AnyObject = AnyObject>(
|
|||
return storage
|
||||
}
|
||||
|
||||
export interface StorageOptions<T> {
|
||||
export interface StorageOptions<T extends AnyObject = AnyObject> {
|
||||
/**
|
||||
* The directory of the storage file.
|
||||
*/
|
||||
|
|
|
|||
13
lib/storage/src/init.ts
Normal file
13
lib/storage/src/init.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { _emitter } from '@revenge-mod/plugins/_'
|
||||
import { PluginsStorageDirectory } from '@revenge-mod/plugins/constants'
|
||||
import { defineLazyProperty } from '@revenge-mod/utils/objects'
|
||||
import { getStorage } from '.'
|
||||
|
||||
_emitter.on('init', ({ _s: storageOptions, manifest: { id } }, api) =>
|
||||
defineLazyProperty(api, 'storage', () =>
|
||||
getStorage(`${PluginsStorageDirectory}/${id}.json`, {
|
||||
...storageOptions,
|
||||
directory: 'documents',
|
||||
}),
|
||||
),
|
||||
)
|
||||
3
lib/storage/src/preinit.ts
Normal file
3
lib/storage/src/preinit.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { _emitter } from '@revenge-mod/plugins/_'
|
||||
|
||||
_emitter.on('register', (plugin, options) => (plugin._s = options.storage))
|
||||
42
lib/storage/src/types.ts
Normal file
42
lib/storage/src/types.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import type {
|
||||
PluginApiExtensionOptions,
|
||||
PluginManifest,
|
||||
PluginOptions,
|
||||
} from '@revenge-mod/plugins/types'
|
||||
import type { AnyObject } from '@revenge-mod/utils/types'
|
||||
import type { Storage, StorageOptions } from '.'
|
||||
|
||||
declare module '@revenge-mod/plugins/types' {
|
||||
export interface PluginApiExtensionOptions {
|
||||
storage: AnyObject
|
||||
}
|
||||
|
||||
export interface PluginOptions<O extends PluginApiExtensionOptions> {
|
||||
storage?: Omit<StorageOptions<O['storage']>, 'directory'>
|
||||
}
|
||||
|
||||
export interface InitPluginApi<O extends PluginApiExtensionOptions> {
|
||||
/**
|
||||
* The plugin storage.
|
||||
*
|
||||
* Note that the instance is only created when the plugin accesses the API.
|
||||
* This is to prevent unnecessary storage instances from being created.
|
||||
*
|
||||
* To preload storage, simply call `api.storage.get()`.
|
||||
*/
|
||||
storage: Storage<O['storage']>
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@revenge-mod/plugins/_' {
|
||||
export function registerPlugin<E extends PluginApiExtensionOptions>(
|
||||
manifest: PluginManifest,
|
||||
options: PluginOptions<E>,
|
||||
flags: number,
|
||||
iflags: number,
|
||||
): void
|
||||
|
||||
export interface InternalPlugin {
|
||||
_s?: StorageOptions<AnyObject>
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ onModuleFirstRequired(IndexModuleId, function onIndexRequired() {
|
|||
require('@revenge-mod/modules/preinit')
|
||||
require('@revenge-mod/react/preinit')
|
||||
require('@revenge-mod/assets/preinit')
|
||||
require('@revenge-mod/storage/preinit')
|
||||
require('@revenge-mod/discord/preinit')
|
||||
|
||||
// Run all preinit plugins
|
||||
|
|
@ -35,6 +36,7 @@ onModuleFirstRequired(IndexModuleId, function onIndexRequired() {
|
|||
onModuleInitialized(IndexModuleId, function onIndexInitialized() {
|
||||
runCatching(() => {
|
||||
// Initialize init libraries
|
||||
require('@revenge-mod/storage/init')
|
||||
require('@revenge-mod/externals/init')
|
||||
require('@revenge-mod/discord/init')
|
||||
require('@revenge-mod/components/init')
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const defaultStorage: Storage = {
|
|||
},
|
||||
}
|
||||
|
||||
registerPlugin<Storage>(
|
||||
registerPlugin<{ storage: Storage }>(
|
||||
{
|
||||
id: 'revenge.settings.developer',
|
||||
name: 'Developer Settings',
|
||||
|
|
@ -57,4 +57,4 @@ registerPlugin<Storage>(
|
|||
)
|
||||
|
||||
// Expose to EvaluateJavaScriptSetting
|
||||
export let api: PluginApi<Storage>
|
||||
export let api: PluginApi<{ storage: Storage }>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue