Skip to main content

Plugin System Architecture

HyperHQ supports a hybrid plugin system with JavaScript and executable plugins.

Runtime overview​

Plugins extend HyperHQ without modifying core code. They can add setup flows, integrations, actions, data providers, hardware helpers, and background services.

Plugin typeEntry pointCommunication
javascriptmain (.js/.mjs, defaults to index.js)Renderer compatibility runner with simulated isolation.
executableexecutable / executableProvidersStdio JSON lines or Socket.IO.

HyperHQ discovers installed plugins by scanning the configured HyperSpin install plugins/ folder and loading folders that contain plugin.json. Plugin docs and packages should not use manifest.json for plugin manifests.

Main services​

ServicePurpose
PluginManagerServiceRegistry, discovery, lifecycle, start/stop, settings refresh.
PluginOperationServiceInstall, uninstall, update operations.
PluginValidationServiceManifest and package validation.
PluginSettingsServicePer-plugin settings persistence.
PluginOnboardingService / launcherManifest-driven setup wizards.
PluginOAuthSessionServiceOAuth setting session state and artifacts.
ExecutablePluginRunnerRenderer-side facade for executable plugin messaging.
JavaScriptPluginRunnerJavaScript compatibility runner; incomplete host APIs and simulated isolation.
Main-process PluginLauncherSpawns executable plugins and chooses stdio vs Socket.IO.
Main-process Socket.IO serverAuthenticated realtime plugin transport and data bridge.

Manifest schema summary​

Every plugin requires plugin.json.

{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"description": "What this plugin does",
"author": "Author Name",
"type": "executable",
"executable": "plugin.exe",
"executableProviders": {
"windows": "plugin.exe",
"linux": "plugin",
"macos": "plugin"
},
"communication": {
"preferred": "socketio",
"fallback": "stdio",
"stdio": { "enabled": true },
"socketio": { "enabled": true }
},
"capabilities": [
{ "name": "realtime", "description": "Uses realtime plugin events", "required": true }
],
"permissions": [
{ "type": "network", "scope": "localhost", "description": "Connect to HyperHQ Socket.IO" }
],
"settings": [
{ "key": "enabled", "type": "boolean", "label": "Enabled", "defaultValue": true },
{ "key": "romFolder", "type": "directory", "label": "ROM Folder", "defaultValue": "" },
{ "key": "installPath", "type": "string", "label": "Install Path", "browse": "directory" }
]
}

Important current behavior:

  • id, name, version, description, author, type, and non-empty capabilities are required.
  • type must be javascript or executable.
  • permissions is an array. Valid permission types are file, network, system, database, process, and credential.
  • Settings use label instead of name.
  • Supported setting types include string, number, boolean, email, password, url, textarea, select, oauth, array, object, file, directory, color, and range.
  • file and directory settings render a Browse button. string settings render the same picker when browse is set to file, directory, true, or an object with type and optional buttonLabel.
  • Platform executable resolution prefers executableProviders, then legacy executableLinux/executableMac, then executable.

Plugin locations and storage​

  • Installed plugin packages: configured HyperSpin installation plugins/ folder.
  • Plugin-owned cached data/assets: _hsm/Plugins/{PluginName}/.
  • Do not claim a %PLUGIN_DATA% manifest magic variable unless code support is added. Use real paths from settings or HyperHQ APIs.

Lifecycle​

Discover plugin.json → Validate → Register → Start → Initialize → Active
│ │
└─ Requires config ◄──┘

For stdio plugins that emit a complete PluginResponse, initialize may return top-level status: "requiresConfig" with a message. HyperHQ marks the plugin requires_config, emits a plugin requires-config event, and surfaces setup to the UI. Socket.IO and JavaScript paths currently wrap method return values, so use a clear initialization error/action result there until top-level status propagation is implemented.

JavaScript plugins​

The current JavaScript compatibility runner supplies a plugin object and validates these methods. Isolation is simulated rather than a secure sandbox. Several host APIs return placeholder results.

plugin.initialize = async (data) => 'initialized';
plugin.execute = async (data) => ({ success: true });
plugin.test = async () => true;
plugin.shutdown = async () => 'ok'; // optional

Old module.exports = { onLoad, onGameLaunch, onUnload } style examples do not match the current runner.

Executable plugins​

Executable plugins support two transports.

Stdio​

HyperHQ sends JSON-line requests on stdin and expects JSON-line responses on stdout.

Socket.IO​

Socket.IO configuration belongs under communication, not as a top-level socketio object.

Socket.IO plugins are launched with:

  • HYPERHQ_PLUGIN_ID
  • HYPERHQ_PLUGIN_NAME
  • HYPERHQ_PLUGIN_VERSION
  • HYPERHQ_SOCKET_PORT
  • HYPERHQ_AUTH_CHALLENGE
  • PLUGIN_SETTINGS (compatibility only; current settings come from initialize(data).settings)

Plugins connect to the root namespace at http://localhost:${HYPERHQ_SOCKET_PORT}. There is no /plugin namespace.

Authentication uses authenticate:

  • Initial: { pluginId, challenge } with the one-time 30-second challenge.
  • Reconnect: { pluginId, sessionToken } within the 30-second reconnect grace period.

Core method calls use request from HyperHQ and response from the plugin.

Socket.IO data bridge​

Data requests are sent by plugins on requestData or request_data. HyperHQ emits both dataResponse and data_response replies for compatibility.

MethodBehavior
getSystemsBlocking, waits for frontend response.
getMediaFoldersBlocking.
getGamesForSystemBlocking; frontend handler supports flexible system identifiers where available.
createSystemFire-and-forget.
createEmulatorFire-and-forget.
addGamesFire-and-forget with broad game payload support.
removeGamesFire-and-forget; frontend integration handles common removal aliases.
launchGameFire-and-forget.

Blocking data requests time out after 30 seconds. Fire-and-forget requests return an immediate acknowledgement after dispatch.

Event subscriptions​

Plugins subscribe with subscribeEvents and receive broadcasts on hyperHqEvent.

socket.emit('subscribeEvents', ['gameLaunched', 'gameClosed']);
socket.on('hyperHqEvent', ({ type, data, timestamp }) => {});

Packaged file requests​

Socket.IO plugins can request packaged files with requestFile. The request must include the current session token, resolve inside the plugin directory, and target a file no larger than 10 MB. HyperHQ responds on fileData with base64 data or an error.

Actions, onboarding, and OAuth​

Manifests can define actions and onboarding wizards. Action type wizard can launch a wizard via wizard_id. Wizard step types are info, oauth, action, async-action, selection-list, and form.

Wizard form steps use the same setting definition shape as top-level settings, including path picker fields.

OAuth settings (type: "oauth") and OAuth wizard steps are handled by HyperHQ's OAuth session service. Expired/invalid OAuth state should be surfaced as a configuration problem so users can reconnect; stdio/full-response plugins can use requiresConfig, while other transports should return a clear recoverable initialization/action error.

Leaderboard provider capability​

Leaderboard provider plugins advertise:

{
"capabilities": [
{ "name": "leaderboard-provider", "description": "Provides leaderboard and score operations", "required": false }
]
}

Provider method names routed by HyperHQ:

  • leaderboard.getProviderMetadata
  • leaderboard.getLeaderboard
  • leaderboard.getScore
  • leaderboard.submitScore

Marketplace/version metadata​

Marketplace plugin versions can include channel and platform, allowing platform-specific or channel-specific releases.