feat: generic plugin runtime SPI, web admin manager, and desktop extension host - #498
BillyOutlast wants to merge 13 commits into
Conversation
plugin_subscribe emits a Tauri event named plugin:event, but subscribe()
listened with window.addEventListener and expected a DOM CustomEvent, so
plugin WebSocket messages never reached their listeners. Register a real
Tauri listener for plugin:event (payload {channel,data}) in the Tauri host
and keep the DOM CustomEvent path as the browser/dev fallback.
|
Pushed 7bd4707: the desktop extension host's One item for review, not changed here: |
plugin_game_check_anticheat hardcoded EAC/BattlEye/Vanguard/Denuvo names in the generic client host. Replace it with plugin_game_find_files, which returns installed paths matching caller-supplied patterns, and drop the AntiCheatReport/checkAntiCheat SPI so that domain knowledge lives in the plugin (e.g. drop-gse) rather than core.
|
Update: the anti-cheat genericization I flagged is now folded into this PR. Pushed 60183f0:
Companion changes outside this PR:
One heads-up unrelated to this change: the branch was cut before develop's |
Narrow useGame's status to Ref<GameStatus> under noUncheckedIndexedAccess so template discriminant checks narrow correctly, guard optional index access in LibrarySearch/queue/compat/ModalStack, and type the nuxt configs with satisfies NuxtConfig. GameStatusButton now declares the playActions prop and play-action emit (and renders the menu entries) so plugin play actions type-check and actually run.
|
Pushed 0624294, which resolves the pre-existing desktop typecheck errors noted earlier (
This supersedes my earlier note about rebasing for cb56fe5 — the equivalent fixes are applied directly on this branch. |
|
Gentle ping for review. The branch is up to date with |
DecDuck
left a comment
There was a problem hiding this comment.
- Looks like a lot of it is LLM's tendency to create "god files", just don't want that in this repo
- Some deduplication stuff
Would also like a high level overview of the features and examples of what the plugins can do. Also some screenshots.
Port the upstream-ready plugin work that landed on Heretek develop after the initial extraction: v2 bundle signatures covering the manifest, MetadataProvider / PaymentGateway / CloudSavePathResolver SPIs, public WebSocket channel hardening, desktop path_guard and native-command hardening, and the associated server and desktop test suites. Excludes fork-domain code (achievement toasts) and neutralises example provider names in tests so the bundle stays generic.
- manager.ts is now a thin facade; routes, websocket, events, lifecycle, context, bundle, compat, and verification each live in their own module - bundle checksum and signature verification moves into PluginRegistry.verifyBundle instead of the manager - ws.get.ts shrinks to a thin Nitro route; peer/session transport logic moves to the new ws-gateway module - FilePluginStorage drops the speculative legacy-directory migration and bundle hashing now ignores only drop-plugin.json
… storage - new desktop/src-tauri/plugins crate owns the Tauri plugin commands and the moved path_guard module - plugin key/value storage is persisted through the Rust database instead of webview localStorage, keeping one source of truth - remove the discouraged command blocklist and keep the fail-closed per-plugin allowlist with explicit trust guidance
…ent host - add a type-only workspace package holding the manifest, capability, and client plugin contracts so the server and desktop cannot drift - server plugin types keep only the h3/pino runtime contract and the API version constants, re-exporting the shared contracts - split the desktop ClientPluginManager into host adapters (storage, game fs, scanner, websocket, system) and a launch pipeline module
- document the @Drop-OSS scope effective with SDK 0.6.0 and the pending repository transfer - add a prominent unsandboxed/privileged warning and fix the capability consent claims for the server admin flow - document signature v2 manifest coverage and drop-plugin verify
|
All review feedback is addressed on the branch (@ 58766bb), every thread has a reply with the corresponding change, and the PR body now includes the high-level feature/SPI overview plus a Play Actions section. Rebase-free update note: the branch also picked up the latest |
|
@DecDuck — all review follow-ups are now pushed to the head branch (and the screenshots requested in the review are attached to the PR description). Summary of what changed since your God-file decomposition
Deduplication
Docs & overview
Status: One operational note: the upstream ruleset requires review-thread resolution, and we noticed the PR head SHAs have shown 0 check-runs — it would be good to confirm CI is actually running on this PR (both contributors see no checks). Happy to resolve any threads you confirm as addressed — requesting re-review. |
DecDuck
left a comment
There was a problem hiding this comment.
Looks better after being broken up, but will have to go over the server code more carefully later.
There was a problem hiding this comment.
Does this actually render the error message anywhere? I don't think the :title attribute on a div does anything.
| actions advertises that extra capabilities (multiplayer, mods, ...) | ||
| require installing an extension. | ||
| --> | ||
| <button |
There was a problem hiding this comment.
Not sure we want this here, I'd prefer keeping it clean without plugins installed
| :game-id="game.id" | ||
| /> | ||
|
|
||
| <GameSetupModal |
There was a problem hiding this comment.
What's this game setup modal? Was this moved from somewhere?
| v-model="dependencyRequiredModal" | ||
| /> | ||
|
|
||
| <ModalTemplate v-model="extensionsPromptOpen"> |
There was a problem hiding this comment.
Without the extension prompt we don't need this model either.
|
|
||
| let app = builder | ||
| .plugin(tauri_plugin_deep_link::init()) | ||
| .manage(PluginCommandAllowlist::default()) |
There was a problem hiding this comment.
Can you have multiple .manage calls on a single app? I thought it had to be only one
There was a problem hiding this comment.
I think page and the one on the client need to be way more explicit about the lack of sandboxing for plugins. Maybe a separate confirmation modal and/or warning banner.
Maybe also make the client users confirm that they want to use the plugins that the server has installed.
There was a problem hiding this comment.
Any reason not to implement the company lookup?
…racts - bump PLUGIN_API_VERSION to 3 and support versions 1, 2, and 3 - add PluginSettingsSchema and settings snapshot to server and client contexts - add 'pre-launch:network-post' stage to LaunchStage and preLaunchStages pipeline - expand ServerCapability (auth:provider, storage:depot) and ClientCapability (game:runner) - declare RunnerProvider, AuthProvider, and DepotStorageProvider SPI types in @drop/plugin-api - add upstream compatibility unit tests verifying SDK v0.7.0 contracts


Summary
This PR introduces an extensible, generic Plugin SPI and Extension Host for both the Drop server and the desktop client, enabling community addons, integrations, and customization without modifying Drop core.
The runtime SPI is completely generic and contains zero vendor-specific, emulator, network, or payment logic. Every concrete plugin (metadata providers, store scanners, payment gateways, cloud save resolvers, emulation/networking integrations) lives in an external repository.
Accompanied by the public plugin SDK at
Drop-OSS/drop-plugin-sdk(transferring fromHeretek-Games/drop-plugin-sdk), which provides TypeScript contracts, unit test mock harnesses (MockPluginContext), and automated packaging tools (drop-plugin pack/sign/verify).What's included
1. Server Plugin SPI (
PluginManager)/api/v1/plugins/:id/*with automatic authentication/ACL resolution.ctx.storage).ctx.registerWebSocket), plus opt-in public channels..droppluginarchives (and bundle JSON), enforcing SHA-256 digests and optional HMAC-SHA256 signatures.signatureVersion: 2signatures cover the canonical manifest, soid/version/capabilitiestampering invalidates them.DROP_PLUGIN_REGISTRY).2. Web Admin Plugin Manager (
/admin/settings/plugins).droppluginupload, or multi-file bundle JSON.3. Desktop Extension Host (
ClientPluginManager)<PluginSlot name="..." />injects plugin components intogame-detail:badges,game-detail:actions,game-detail:panels,settings:tabs,topbar:status,sidebar:nav, and overlay slots.executeLaunchPipeline()wraps game execution with pre-launch and post-exit hooks, rolling completed stages back in reverse order if a hook aborts.path_guardrejects directory traversal (..) and symlink escapes.manifest.client.commandsentries and is enforced in the Rust command layer. There is deliberately no blocklist: allowlisted commands run unsandboxed with the user's privileges, so only trusted plugins should be installed (documented in the UI and docs).Plugin capabilities and SPIs
routesctx.registerRoute/api/v1/plugins/:id/*storagectx.storagemigrateStoragewebsocketctx.registerWebSocket/ctx.registerPublicWebSocketChanneleventsctx.broadcast/ctx.subscribenetworkctx.fetchmetadata:providerctx.registerMetadataProvidercommerce:paymentctx.registerPaymentGatewaycloudsave:providerctx.registerCloudSaveResolverui:slot/ui:sidebar/ui:topbarctx.registerSlot/registerSidebarItem/registerTopBarItemui:play-actionctx.registerPlayActionui:context-menuctx.registerGameMenuItemgame:launch-hookctx.registerLaunchHookgame:fs,game:scanctx.gameFs,ctx.gameScannerclient:library-scanctx.registerStoreScannermetadata:provider/cloudsave:providersystem:commandctx.system.runclient:wsctx.serverWsPlay Actions
Play Actions are a client-plugin SPI for contextual launch commands. A plugin registers a provider:
The desktop game-detail menu renders the contributed actions next to the normal launch button. Running one goes through
executeLaunchPipeline(), which orders hooks by stage and priority (validate -> prepare -> stage -> network -> launch -> cleanup -> restore -> sync), and rolls completed pre-launch stages back in reverse order if any stage throws. Plugins that abort a launch can never leave half-applied state behind.Examples of what plugins can build
Security model
DROP_PLUGIN_REQUIRE_SIGNATURE=truerefuses unsigned bundles.manifest.client.commands.SDK & tooling
Drop-OSS/drop-plugin-sdk(transfer pending;Heretek-Games/drop-plugin-sdkremains canonical until then).@drop-oss/plugin-sdk/@drop-oss/plugin-cliscope alongside the published@droposs/*0.5.x line, addsdrop-plugin verify, aligns verification with the server core (manifest-covering v2 signatures, legacy entry-only bundles, ignored root files), fixes the starter template, and adds capability conformance helpers: chore(release): v0.6.0 — reconciled CLI, conformance helpers, and @drop-oss scope prep Heretek-Games/drop-plugin-sdk#20.drop-ossorg/trusted publishing, and a compatibility alias/notice for existing@dropossconsumers.Non-breakage & upstream compatibility
Automated tests at
server/server/internal/plugins/__tests__/upstream_compatibility.test.tsprove:Review follow-ups
PluginManagersplit into focused modules;registry.verifyBundle()owns signature verification;ws.get.tsis now a 10-line route over aws-gatewaymodule; storage no longer carries a speculative legacy migration.@drop/plugin-apiworkspace package.Verification
pnpm --filter drop test: 5/5 files, 46 tests (plugin runtime 30, split-module coverage 8, auth 3, v2 signatures 2, upstream invariants 3).pnpm --filter drop run typecheck,pnpm --filter drop run lint:eslint(0 errors), andpnpm --filter drop run build(production Nitro bundle) all pass.pnpm test(5 tests) andpnpm run typecheckpass.cargo +nightly test -p plugins --lib(8 path-guard tests),cargo +nightly check -p drop-app, andcargo +nightly test -p processpass.dev-tools/sample-plugininstalls, servesGET /hellothrough the manager, and uninstalls leaving zero residual routes/state.Screenshots
Screenshots (desktop Plugins settings page, capability consent dialog, game-detail slots, Play Actions menu, and the web admin plugin manager) will be attached in the morning.