feat: expose scheduled notifications to plugins
This commit is contained in:
@@ -220,6 +220,24 @@ export function createPluginAPI(pluginId) {
|
||||
}
|
||||
},
|
||||
|
||||
notifications: {
|
||||
replace: function(items) {
|
||||
assertActive('notifications.replace');
|
||||
if (!Array.isArray(items)) {
|
||||
throw new Error('notifications.replace requires an array');
|
||||
}
|
||||
return callBackendErrorString(pluginId, 'notifications.replace', function() {
|
||||
return App.ReplacePluginNotifications(pluginId, items);
|
||||
});
|
||||
},
|
||||
clear: function() {
|
||||
assertActive('notifications.clear');
|
||||
return callBackendErrorString(pluginId, 'notifications.clear', function() {
|
||||
return App.ClearPluginNotifications(pluginId);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
settings: {
|
||||
read: async function(key) {
|
||||
assertActive('settings.read');
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
const pluginData = {};
|
||||
const scheduledNotifications = [];
|
||||
|
||||
globalThis.window = {
|
||||
__VERSTAK_PLUGIN_REGISTRY__: {},
|
||||
@@ -38,6 +39,14 @@ globalThis.window = {
|
||||
pluginData[pluginId][name] = Object.assign({}, data || {});
|
||||
return Promise.resolve('');
|
||||
},
|
||||
ReplacePluginNotifications: (pluginId, items) => {
|
||||
scheduledNotifications.push({ pluginId, items });
|
||||
return Promise.resolve(items[0]?.id === 'rejected' ? 'notification permission denied' : '');
|
||||
},
|
||||
ClearPluginNotifications: (pluginId) => {
|
||||
scheduledNotifications.push({ pluginId, clear: true });
|
||||
return Promise.resolve('');
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -116,6 +125,28 @@ if (stored.version !== 1 || stored.workspaceRootPath !== 'Project') {
|
||||
throw new Error(`unexpected storage data: ${JSON.stringify(stored)}`);
|
||||
}
|
||||
|
||||
if (!api.notifications || typeof api.notifications.replace !== 'function' || typeof api.notifications.clear !== 'function') {
|
||||
throw new Error('api.notifications replace/clear contract is missing');
|
||||
}
|
||||
await api.notifications.replace([{ id: 'reminder-1', dueAt: '2026-07-14T10:00:00Z', title: 'Reminder' }]);
|
||||
await api.notifications.clear();
|
||||
if (scheduledNotifications.length !== 2
|
||||
|| scheduledNotifications[0].pluginId !== 'verstak.files'
|
||||
|| scheduledNotifications[0].items[0].id !== 'reminder-1'
|
||||
|| scheduledNotifications[1].pluginId !== 'verstak.files'
|
||||
|| !scheduledNotifications[1].clear) {
|
||||
throw new Error(`unexpected notification calls: ${JSON.stringify(scheduledNotifications)}`);
|
||||
}
|
||||
let rejected = false;
|
||||
try {
|
||||
await api.notifications.replace([{ id: 'rejected', dueAt: '2026-07-14T10:00:00Z', title: 'Reminder' }]);
|
||||
} catch (err) {
|
||||
rejected = String(err.message || err).includes('[plugin:verstak.files] notifications.replace failed: notification permission denied');
|
||||
}
|
||||
if (!rejected) {
|
||||
throw new Error('notification backend errors must be plugin-scoped rejections');
|
||||
}
|
||||
|
||||
api.dispose();
|
||||
if (localeListeners.size !== 0) {
|
||||
throw new Error('api.i18n locale subscription was not disposed');
|
||||
|
||||
Reference in New Issue
Block a user