* feat(plugins): enable OAuth-based issue-provider plugins Generic, provider-agnostic plugin-framework hooks so an issue-provider plugin that needs an exact OAuth redirect can work without any built-in code: - OAuthFlowConfig.redirectUri: a plugin may declare an exact pre-registered callback; the host uses it for both the authorize request and token exchange. - The Electron loopback honors a plugin-requested fixed port and rejects with a clear message when that port is already in use. - Apply user-supplied clientId/clientSecret/redirectUri overrides onto a plugin's oauthConfig (bring-your-own OAuth app). - Fix: merging a partial pluginConfig update no longer drops omitted keys and deep-merges nested objects (e.g. twoWaySync). Split out of the Basecamp community-plugin work per PR #8507 feedback; contains no provider-specific code. * fix(plugins): address #8546 review - validate OAuthFlowConfig.redirectUri per platform (loopback / same-origin / app scheme) and fail fast instead of hanging; restrict desktop loopback to 127.0.0.1 - warn when a client secret is dropped on web/native (bring-your-own credentials) - validate the IPC loopback port to [1024,65535], register the error handler before listen(), and close the failed server - merge pluginConfig via generic recursion instead of a hardcoded twoWaySync case - nits: named token-store imports; fix stale prepareRedirectUri comment - tests: redirectUri validation, the web client-secret warning, and generic merge * fix(plugins): address #8546 round-2 review - loopback error handler calls cleanupServer() so a post-listen runtime error doesn't leave the server ref / 5-min timer dangling - share OAUTH_LOOPBACK_PORT_{MIN,MAX} between the renderer and Electron main so the bounds never drift; reject out-of-range (incl. 0/80/443) redirect ports early - drop _getElectronLoopbackPort and parse the already-validated redirectUri once - document that a bring-your-own clientSecret syncs via pluginConfig (override boundary) - skip __proto__/constructor/prototype keys in the pluginConfig merge (defense-in-depth) - test: prototype-pollution guard * fix(plugins): address #8546 round-3 review - reject native redirectUri overrides outright (closes CodeQL js/incomplete-url-scheme-check) via a pure, per-platform validateOAuthRedirectUri util (electron loopback / native reject / web same-origin) - gate bring-your-own OAuth credentials to the desktop loopback flow and warn (instead of silently dropping clientId) when set on web/native - namespace BYO under pluginConfig.oauthOverrides (was flat keys); document the convention on OAuthFlowConfig (public plugin API) - shallow top-level pluginConfig merge: drop the deep recursion + proto guard; nested objects (e.g. twoWaySync) are replaced wholesale, matching callers - pin the web redirect to /assets/oauth-callback.html via a shared constant so a same-origin wrong-path URI fails fast; note the desktop 127.0.0.1-only rule - companion tests for each * fix(plugins): strip desktop redirectUri on web/native OAuth flows A plugin-declared redirectUri is the desktop loopback override; keeping it on the web/native branches made a web/native-capable plugin throw at connect time (the loopback URI fails web/native redirectUri validation). Strip it on those branches so prepareRedirectUri falls through to the platform default. Document redirectUri as desktop-only in the plugin API and fix a misleading test name. * refactor(plugins): extract resolveEffectiveOAuthConfig and harden native fallthrough Move the platform client/secret/redirectUri selection out of the bridge into a pure, parameterized util so every branch is unit-testable (the IS_* platform consts are module-level and cannot be mocked in karma). Also strip clientSecret and redirectUri on the native fall-through — a native platform where the plugin ships no matching client id — keeping both strictly desktop-only. Optional hardening on top of the redirectUri fix; safe to drop independently. |
||
|---|---|---|
| .. | ||
| 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 configurationDialogResult- Dialog return valueSnackCfg- 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_CHANGED = 'persistedDataChanged',
ACTION = 'action',
}
PERSISTED_DATA_CHANGED fires on any persistent-data change to this
plugin after the host has finished its initial boot load — including
remote sync deliveries and bulk imports. Handler receives no payload;
re-call loadSyncedData(key?) for any key your plugin tracks to get
fresh data (scoped to the calling plugin). Contract: call
loadSyncedData() on plugin init for the initial state; then use this
hook for subsequent changes. There is no replay-on-register, no
per-key discrimination in the event, and no guaranteed ordering across
rapid changes. Handlers must be idempotent.
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 tasksgetSelectedTask- Read the task selected in the task detail panelgetFocusedTask- Read the currently focused task row, if anyaddTask- Create tasksgetAllProjects- Read projectsaddProject- Create projectsgetAllTags- Read tagsaddTag- Create tagspersistDataSynced- Persist plugin datagetAppState- Read-only snapshot of application state
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();
},
});
// Read full app state
const state = await PluginAPI.getAppState();
License
MIT - See the main Super Productivity repository for details.
Contributing
Please contribute to the main Super Productivity repository.