mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 08:56:41 +00:00
* feat(plugins): add onReady() API with IPC ping + fix consent write delay #7326 - Remove setTimeout(5000) from _getNodeExecutionConsent; write consent immediately - Add plugin.onReady(fn) to PluginAPI — fires after plugin.js evaluation and IPC bridge confirmation - Add _pingNodeBridge() in plugin.service.ts with 3-attempt retry (1s, 2s delays) - Add triggerReady() and pingNodeBridge() to PluginRunner - Show snack + set error state if IPC bridge unavailable after retries - Add NODE_EXECUTION_BRIDGE_UNAVAILABLE translation key - Add focused tests for onReady, triggerReady, pingNodeBridge, consent persistence - Update plugin-development.md with onReady usage and nodeExecution guidance * test(plugins): fix unused variable lint errors in spec files * fix(plugins): guard triggerReady on instance.loaded; fix doc numbering * fix(plugins): remove _triggerReady from public API, route ping via bridge, add retry tests * fix(electron): add paths for @sp/sync-providers subpath exports (node moduleResolution compat) * fix(plugins): centralize onReady, tear down runtime on activation error, add iframe onReady Address review on #7578: - All plugin load paths (startup, upload, reload, lazy) now go through _fireOnReady, ensuring the IPC ping + onReady fire on every successful load — not just lazy. - activatePlugin error path now unloads the plugin runtime (hooks, buttons, side effects) before setting status='error', preventing partially-running plugins. - Iframe PluginAPI now exposes onReady (fires on next microtask after plugin.js evaluates), matching the host-side contract for typed iframe plugins. * fix(plugins): clean up half-loaded plugins on onReady error, test real retry util Self-review followups: - _fireOnReadyWithCleanup wraps the 3 non-activatePlugin load paths and tears down the plugin (unloadPlugin + remove from list + status='error' + snack) if the IPC ping or onReady callback throws. Previously, those paths only logged and rethrew, leaving partially-running plugins. - Extracted retry loop into pure pingWithRetry utility; spec now exercises production code instead of an inline-replicated stub. Removed the old plugin-ping-node-bridge.spec.ts which was just testing its own copy of the logic. - Documented iframe onReady semantic (fires on microtask, no ping) in both the source comment and docs/plugin-development.md, since cold-boot is not a concern for iframe plugins (rendered on demand). * ci(plugins): use npm i for root install to tolerate override drift The root lockfile pins app-builder-lib's transitive minimatch via the `overrides` field. npm 10.9.7 (bundled with Node 22 in setup-node@v6) flags this as drift and fails `npm ci`, while npm 11 accepts it. ci.yml's main test job uses `npm i`, which tolerates the drift without mutating the lockfile on disk. Plugin-Tests has been red on every PR since 2026-05-08 for this reason. The inner `npm ci` for plugin-specific deps stays strict. * fix(plugins): make onReady optional, assert callback isolation in spec - packages/plugin-api/src/types.ts: mark onReady? optional on the public PluginAPI interface so existing plugin TypeScript typings (and any third-party PluginAPI implementations) remain assignable after upgrade. The host runtime already treats onReady as optional (no-op if no registration callback is provided), so this aligns the type with the actual contract. - src/app/plugins/plugin-runner.spec.ts: the previous isolation test only asserted that triggerReady() resolved for both plugins; it would still pass if triggerReady fired every registered callback. The updated test wires per-plugin Jasmine spies through globalThis (the same context the plugin code's `new Function` runs in) and asserts call counts before and after each triggerReady, actually proving isolation. * refactor(plugins): test real consent logic; scope startup snacks; tighten ping timeout Address review feedback on PR #7578: - Extract consent decision into pure `decideNodeExecutionConsent` util so the spec exercises real code instead of a reimplemented stub. Delete the stub-based plugin-consent.spec.ts and plugin-fire-on-ready.spec.ts (the latter was orchestration glue already covered by plugin-runner.spec.ts and ping-with-retry.util.spec.ts). - Reduce per-ping timeout 5000ms -> 1500ms. Worst-case cold-boot bridge-down detection drops from ~17s to ~7.5s; in-process vm script returning true doesn't need 5s. - Add PLUGIN_LOAD_FAILED translation wrapping plugin name + error. Strip the now-redundant pluginName from NODE_EXECUTION_BRIDGE_UNAVAILABLE. - Scope activation-failure snack to manual activations only — startup auto-activation failures stay silent (plugin tile shows error state). _handleReadyFailure still snacks unconditionally since onReady failure leaves a partially-loaded runtime that the user needs to see. --------- Co-authored-by: Benjamin <1159333+benjaminburzan@users.noreply.github.com> Co-authored-by: Johannes Millan <johannes.millan@gmail.com> |
||
|---|---|---|
| .. | ||
| src | ||
| .gitignore | ||
| .npmignore | ||
| DEVELOPMENT.md | ||
| package-lock.json | ||
| package.json | ||
| publish.sh | ||
| PUBLISHING.md | ||
| README.md | ||
| tsconfig.json | ||
@super-productivity/plugin-api
Official TypeScript definitions for developing Super Productivity plugins.
Installation
npm install @super-productivity/plugin-api
Usage
TypeScript Plugin Development
import type {
PluginAPI,
PluginManifest,
PluginHooks,
} from '@super-productivity/plugin-api';
// Your plugin code with full type support
PluginAPI.registerHook(PluginHooks.TASK_COMPLETE, (taskData) => {
console.log('Task completed!', taskData);
PluginAPI.showSnack({
msg: 'Task completed successfully!',
type: 'SUCCESS',
ico: 'celebration',
});
});
// Register a header button
PluginAPI.registerHeaderButton({
label: 'My Plugin',
icon: 'extension',
onClick: () => {
PluginAPI.showIndexHtmlAsView();
},
});
// Register a keyboard shortcut
PluginAPI.registerShortcut({
id: 'my_shortcut',
label: 'My Custom Shortcut',
onExec: () => {
PluginAPI.showSnack({
msg: 'Shortcut executed!',
type: 'SUCCESS',
});
},
});
Plugin Manifest
{
"name": "My Awesome Plugin",
"id": "my-awesome-plugin",
"manifestVersion": 1,
"version": "1.0.0",
"minSupVersion": "13.0.0",
"description": "An awesome plugin for Super Productivity",
"hooks": ["taskComplete", "taskUpdate"],
"permissions": ["showSnack", "getTasks", "addTask", "showIndexHtmlAsView"],
"iFrame": true,
"uiKit": true,
"icon": "icon.svg"
}
Available Types
Core Types
PluginAPI- Main plugin API interfacePluginManifest- Plugin configurationPluginHooks- Available hook typesPluginBaseCfg- Runtime configuration
Data Types
TaskData- Task informationProjectData- Project informationTagData- Tag information
UI Types
DialogCfg- Dialog configurationSnackCfg- Notification configurationPluginMenuEntryCfg- Menu entry configurationPluginShortcutCfg- Keyboard shortcut configuration
Plugin Development Guide
1. Available Hooks
enum PluginHooks {
TASK_COMPLETE = 'taskComplete',
TASK_UPDATE = 'taskUpdate',
TASK_DELETE = 'taskDelete',
FINISH_DAY = 'finishDay',
LANGUAGE_CHANGE = 'languageChange',
PERSISTED_DATA_UPDATE = 'persistedDataUpdate',
ACTION = 'action',
}
2. Required Permissions
Add these to your manifest.json based on what your plugin needs:
showSnack- Show notificationsnotify- System notificationsshowIndexHtmlAsView- Display plugin UIopenDialog- Show dialogsgetTasks- Read tasksgetArchivedTasks- Read archived tasksgetCurrentContextTasks- Read current context tasksaddTask- Create tasksgetAllProjects- Read projectsaddProject- Create projectsgetAllTags- Read tagsaddTag- Create tagspersistDataSynced- Persist plugin data
3. Plugin Structure
my-plugin/
├── manifest.json
├── plugin.js
├── index.html (optional, if iFrame: true)
└── icon.svg (optional)
4. Example Plugin
// plugin.js
console.log('My Plugin initializing...', PluginAPI);
// Register hook for task completion
PluginAPI.registerHook(PluginAPI.Hooks.TASK_COMPLETE, function (taskData) {
console.log('Task completed!', taskData);
PluginAPI.showSnack({
msg: '🎉 Task completed!',
type: 'SUCCESS',
ico: 'celebration',
});
});
// Register header button
PluginAPI.registerHeaderButton({
label: 'My Plugin',
icon: 'dashboard',
onClick: function () {
PluginAPI.showIndexHtmlAsView();
},
});
License
MIT - See the main Super Productivity repository for details.
Contributing
Please contribute to the main Super Productivity repository.