522 lines
18 KiB
JavaScript
522 lines
18 KiB
JavaScript
import * as App from '../../../wailsjs/go/api/App';
|
|
|
|
window.__VERSTAK_PLUGIN_REGISTRY__ = window.__VERSTAK_PLUGIN_REGISTRY__ || {};
|
|
window.__VERSTAK_EVENT_HANDLERS__ = window.__VERSTAK_EVENT_HANDLERS__ || {};
|
|
window.__VERSTAK_COMMAND_HANDLERS__ = window.__VERSTAK_COMMAND_HANDLERS__ || {};
|
|
|
|
if (!window.VerstakPluginRegister) {
|
|
window.VerstakPluginRegister = function(pluginId, bundle) {
|
|
if (!pluginId || !bundle || !bundle.components) {
|
|
console.error('[VerstakPluginRegister] invalid registration:', pluginId);
|
|
return;
|
|
}
|
|
console.log('[VerstakPluginRegister] registered:', pluginId, Object.keys(bundle.components));
|
|
window.__VERSTAK_PLUGIN_REGISTRY__[pluginId] = bundle.components;
|
|
};
|
|
}
|
|
|
|
function unpack(result) {
|
|
if (Array.isArray(result) && result.length === 2 && (typeof result[1] === 'string' || result[1] == null)) {
|
|
return [result[0], result[1] || ''];
|
|
}
|
|
return [result, ''];
|
|
}
|
|
|
|
async function callBackend(pluginId, label, fn) {
|
|
try {
|
|
const [value, err] = unpack(await fn());
|
|
if (err) {
|
|
throw new Error(err);
|
|
}
|
|
return value;
|
|
} catch (e) {
|
|
const message = e && e.message ? e.message : String(e);
|
|
throw new Error('[plugin:' + pluginId + '] ' + label + ' failed: ' + message);
|
|
}
|
|
}
|
|
|
|
async function callBackendErrorString(pluginId, label, fn) {
|
|
try {
|
|
const err = await fn();
|
|
if (err) {
|
|
throw new Error(err);
|
|
}
|
|
} catch (e) {
|
|
const message = e && e.message ? e.message : String(e);
|
|
throw new Error('[plugin:' + pluginId + '] ' + label + ' failed: ' + message);
|
|
}
|
|
}
|
|
|
|
function getEventHandlers(eventName) {
|
|
if (!window.__VERSTAK_EVENT_HANDLERS__[eventName]) {
|
|
window.__VERSTAK_EVENT_HANDLERS__[eventName] = [];
|
|
}
|
|
return window.__VERSTAK_EVENT_HANDLERS__[eventName];
|
|
}
|
|
|
|
function dispatchLocalEvent(pluginId, eventName, payload) {
|
|
const event = {
|
|
name: eventName,
|
|
pluginId: pluginId,
|
|
payload: payload || {},
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
const handlers = getEventHandlers(eventName).slice();
|
|
handlers.forEach(function(handler) {
|
|
try {
|
|
handler(event);
|
|
} catch (e) {
|
|
console.error('[VerstakPluginAPI] event handler error:', e);
|
|
}
|
|
});
|
|
}
|
|
|
|
function dispatchBackendEvent(event) {
|
|
if (!event || !event.name) return;
|
|
const handlers = getEventHandlers(event.name).slice();
|
|
handlers.forEach(function(handler) {
|
|
try {
|
|
handler(event);
|
|
} catch (e) {
|
|
console.error('[VerstakPluginAPI] backend event handler error:', e);
|
|
}
|
|
});
|
|
}
|
|
|
|
window.__VERSTAK_DISPATCH_BACKEND_EVENT__ = dispatchBackendEvent;
|
|
|
|
if (!window.__VERSTAK_BACKEND_EVENT_BRIDGE__ && window.runtime && typeof window.runtime.EventsOnMultiple === 'function') {
|
|
window.__VERSTAK_BACKEND_EVENT_BRIDGE__ = window.runtime.EventsOnMultiple('verstak:plugin-event', dispatchBackendEvent, -1);
|
|
}
|
|
|
|
function commandKey(pluginId, commandId) {
|
|
return pluginId + ':' + commandId;
|
|
}
|
|
|
|
export async function executePluginCommand(pluginId, cmdId, args) {
|
|
if (!pluginId) {
|
|
throw new Error('executePluginCommand requires pluginId');
|
|
}
|
|
if (!cmdId) {
|
|
throw new Error('executePluginCommand requires command id');
|
|
}
|
|
const declared = await callBackend(pluginId, 'commands.execute(' + cmdId + ')', function() {
|
|
return App.ExecutePluginCommand(pluginId, cmdId, args || {});
|
|
});
|
|
const handler = window.__VERSTAK_COMMAND_HANDLERS__[commandKey(pluginId, cmdId)];
|
|
if (!handler) {
|
|
throw new Error('[plugin:' + pluginId + '] commands.execute(' + cmdId + ') failed: declared-but-unhandled');
|
|
}
|
|
const result = await handler(args || {}, declared);
|
|
return {
|
|
status: 'handled',
|
|
pluginId: pluginId,
|
|
commandId: cmdId,
|
|
result: result
|
|
};
|
|
}
|
|
|
|
export function createPluginAPI(pluginId) {
|
|
if (!pluginId) {
|
|
throw new Error('createPluginAPI requires pluginId');
|
|
}
|
|
|
|
const cleanups = [];
|
|
let disposed = false;
|
|
|
|
function assertActive(label) {
|
|
if (disposed) {
|
|
throw new Error('[plugin:' + pluginId + '] ' + label + ' failed: API disposed');
|
|
}
|
|
}
|
|
|
|
function trackCleanup(fn) {
|
|
cleanups.push(fn);
|
|
return function untrackAndRun() {
|
|
const idx = cleanups.indexOf(fn);
|
|
if (idx !== -1) {
|
|
cleanups.splice(idx, 1);
|
|
}
|
|
fn();
|
|
};
|
|
}
|
|
|
|
return {
|
|
pluginId: pluginId,
|
|
|
|
ui: {
|
|
openSettings: function(panelId) {
|
|
assertActive('ui.openSettings');
|
|
window.dispatchEvent(new CustomEvent('verstak:open-settings', {
|
|
detail: { pluginId: pluginId, panelId: panelId || '' }
|
|
}));
|
|
}
|
|
},
|
|
|
|
capabilities: {
|
|
has: async function(capId) {
|
|
const info = await callBackend(pluginId, 'capabilities.has(' + capId + ')', function() {
|
|
return App.GetPluginCapability(pluginId, capId);
|
|
});
|
|
return !!(info && info.available);
|
|
},
|
|
get: function(capId) {
|
|
return callBackend(pluginId, 'capabilities.get(' + capId + ')', function() {
|
|
return App.GetPluginCapability(pluginId, capId);
|
|
});
|
|
},
|
|
list: function() {
|
|
return callBackend(pluginId, 'capabilities.list', function() {
|
|
return App.ListPluginCapabilities(pluginId);
|
|
});
|
|
}
|
|
},
|
|
|
|
events: {
|
|
publish: async function(type, payload) {
|
|
await callBackendErrorString(pluginId, 'events.publish(' + type + ')', function() {
|
|
return App.PublishPluginEvent(pluginId, type, payload || {});
|
|
});
|
|
if (!window.__VERSTAK_BACKEND_EVENT_BRIDGE__) {
|
|
dispatchLocalEvent(pluginId, type, payload || {});
|
|
}
|
|
},
|
|
subscribe: function(type, handler) {
|
|
assertActive('events.subscribe(' + type + ')');
|
|
if (typeof handler !== 'function') {
|
|
throw new Error('events.subscribe requires a handler function');
|
|
}
|
|
return callBackendErrorString(pluginId, 'events.subscribe(' + type + ')', function() {
|
|
return App.SubscribePluginEvent(pluginId, type);
|
|
}).then(function() {
|
|
const handlers = getEventHandlers(type);
|
|
handlers.push(handler);
|
|
return trackCleanup(function unsubscribe() {
|
|
const current = getEventHandlers(type);
|
|
window.__VERSTAK_EVENT_HANDLERS__[type] = current.filter(function(item) {
|
|
return item !== handler;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
},
|
|
|
|
settings: {
|
|
read: async function(key) {
|
|
assertActive('settings.read');
|
|
const settings = await callBackend(pluginId, 'settings.read', function() {
|
|
return App.ReadPluginSettings(pluginId);
|
|
});
|
|
if (!key) {
|
|
return settings || {};
|
|
}
|
|
return settings ? settings[key] : undefined;
|
|
},
|
|
write: async function(key, value) {
|
|
assertActive('settings.write(' + key + ')');
|
|
if (!key) {
|
|
throw new Error('settings.write requires a key');
|
|
}
|
|
const settings = await this.read();
|
|
settings[key] = value;
|
|
await callBackendErrorString(pluginId, 'settings.write(' + key + ')', function() {
|
|
return App.WritePluginSettings(pluginId, settings);
|
|
});
|
|
return settings;
|
|
},
|
|
writeAll: function(settings) {
|
|
assertActive('settings.writeAll');
|
|
return callBackendErrorString(pluginId, 'settings.writeAll', function() {
|
|
return App.WritePluginSettings(pluginId, settings || {});
|
|
});
|
|
}
|
|
},
|
|
|
|
storage: {
|
|
data: {
|
|
read: function(name) {
|
|
assertActive('storage.data.read(' + name + ')');
|
|
if (!name) {
|
|
throw new Error('storage.data.read requires a name');
|
|
}
|
|
return callBackend(pluginId, 'storage.data.read(' + name + ')', function() {
|
|
return App.ReadPluginDataJSON(pluginId, name);
|
|
}).then(function(data) {
|
|
return data || {};
|
|
});
|
|
},
|
|
write: function(name, data) {
|
|
assertActive('storage.data.write(' + name + ')');
|
|
if (!name) {
|
|
throw new Error('storage.data.write requires a name');
|
|
}
|
|
return callBackendErrorString(pluginId, 'storage.data.write(' + name + ')', function() {
|
|
return App.WritePluginDataJSON(pluginId, name, data || {});
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
secrets: {
|
|
status: function() {
|
|
assertActive('secrets.status');
|
|
return callBackend(pluginId, 'secrets.status', function() {
|
|
return App.PluginSecretsStatus(pluginId);
|
|
});
|
|
},
|
|
unlock: function(masterPassword) {
|
|
assertActive('secrets.unlock');
|
|
return callBackendErrorString(pluginId, 'secrets.unlock', function() {
|
|
return App.PluginSecretsUnlock(pluginId, String(masterPassword == null ? '' : masterPassword));
|
|
});
|
|
},
|
|
list: function() {
|
|
assertActive('secrets.list');
|
|
return callBackend(pluginId, 'secrets.list', function() {
|
|
return App.PluginSecretsList(pluginId);
|
|
}).then(function(records) {
|
|
return Array.isArray(records) ? records : [];
|
|
});
|
|
},
|
|
read: function(secretId) {
|
|
assertActive('secrets.read(' + secretId + ')');
|
|
if (!secretId) {
|
|
throw new Error('secrets.read requires a secret id');
|
|
}
|
|
return callBackend(pluginId, 'secrets.read(' + secretId + ')', function() {
|
|
return App.PluginSecretsRead(pluginId, secretId);
|
|
});
|
|
},
|
|
write: function(record) {
|
|
assertActive('secrets.write');
|
|
return callBackend(pluginId, 'secrets.write', function() {
|
|
return App.PluginSecretsWrite(pluginId, record || {});
|
|
});
|
|
},
|
|
delete: function(secretId) {
|
|
assertActive('secrets.delete(' + secretId + ')');
|
|
if (!secretId) {
|
|
throw new Error('secrets.delete requires a secret id');
|
|
}
|
|
return callBackendErrorString(pluginId, 'secrets.delete(' + secretId + ')', function() {
|
|
return App.PluginSecretsDelete(pluginId, secretId);
|
|
});
|
|
},
|
|
copyLink: function(secretId) {
|
|
assertActive('secrets.copyLink(' + secretId + ')');
|
|
if (!secretId) {
|
|
throw new Error('secrets.copyLink requires a secret id');
|
|
}
|
|
return callBackend(pluginId, 'secrets.copyLink(' + secretId + ')', function() {
|
|
return App.PluginSecretsCopyLink(pluginId, secretId);
|
|
});
|
|
}
|
|
},
|
|
|
|
files: {
|
|
list: function(relativeDir) {
|
|
assertActive('files.list');
|
|
return callBackend(pluginId, 'files.list(' + (relativeDir || '') + ')', function() {
|
|
return App.ListVaultFiles(pluginId, relativeDir || '');
|
|
});
|
|
},
|
|
metadata: function(relativePath) {
|
|
assertActive('files.metadata(' + relativePath + ')');
|
|
return callBackend(pluginId, 'files.metadata(' + relativePath + ')', function() {
|
|
return App.GetVaultFileMetadata(pluginId, relativePath);
|
|
});
|
|
},
|
|
readText: function(relativePath) {
|
|
assertActive('files.readText(' + relativePath + ')');
|
|
return callBackend(pluginId, 'files.readText(' + relativePath + ')', function() {
|
|
return App.ReadVaultTextFile(pluginId, relativePath);
|
|
});
|
|
},
|
|
readBytes: function(relativePath) {
|
|
assertActive('files.readBytes(' + relativePath + ')');
|
|
return callBackend(pluginId, 'files.readBytes(' + relativePath + ')', function() {
|
|
return App.ReadVaultFileBytes(pluginId, relativePath);
|
|
});
|
|
},
|
|
writeText: function(relativePath, content, options) {
|
|
assertActive('files.writeText(' + relativePath + ')');
|
|
return callBackendErrorString(pluginId, 'files.writeText(' + relativePath + ')', function() {
|
|
return App.WriteVaultTextFile(pluginId, relativePath, String(content == null ? '' : content), options || {});
|
|
});
|
|
},
|
|
writeBytes: function(relativePath, dataBase64, options) {
|
|
assertActive('files.writeBytes(' + relativePath + ')');
|
|
return callBackendErrorString(pluginId, 'files.writeBytes(' + relativePath + ')', function() {
|
|
return App.WriteVaultFileBytes(pluginId, relativePath, String(dataBase64 == null ? '' : dataBase64), options || {});
|
|
});
|
|
},
|
|
createFolder: function(relativePath) {
|
|
assertActive('files.createFolder(' + relativePath + ')');
|
|
return callBackendErrorString(pluginId, 'files.createFolder(' + relativePath + ')', function() {
|
|
return App.CreateVaultFolder(pluginId, relativePath);
|
|
});
|
|
},
|
|
move: function(fromRelativePath, toRelativePath, options) {
|
|
assertActive('files.move(' + fromRelativePath + ')');
|
|
return callBackendErrorString(pluginId, 'files.move(' + fromRelativePath + ')', function() {
|
|
return App.MoveVaultPath(pluginId, fromRelativePath, toRelativePath, options || {});
|
|
});
|
|
},
|
|
trash: function(relativePath) {
|
|
assertActive('files.trash(' + relativePath + ')');
|
|
return callBackend(pluginId, 'files.trash(' + relativePath + ')', function() {
|
|
return App.TrashVaultPath(pluginId, relativePath);
|
|
});
|
|
},
|
|
listTrash: function() {
|
|
assertActive('files.listTrash');
|
|
return callBackend(pluginId, 'files.listTrash', function() {
|
|
return App.ListVaultTrash(pluginId);
|
|
});
|
|
},
|
|
restoreTrash: function(trashId, options) {
|
|
assertActive('files.restoreTrash(' + trashId + ')');
|
|
return callBackend(pluginId, 'files.restoreTrash(' + trashId + ')', function() {
|
|
return App.RestoreVaultTrash(pluginId, trashId, options || {});
|
|
});
|
|
},
|
|
openExternal: function(relativePath) {
|
|
assertActive('files.openExternal(' + relativePath + ')');
|
|
return callBackendErrorString(pluginId, 'files.openExternal(' + relativePath + ')', function() {
|
|
return App.OpenVaultPathExternal(pluginId, relativePath);
|
|
});
|
|
},
|
|
showInFolder: function(relativePath) {
|
|
assertActive('files.showInFolder(' + relativePath + ')');
|
|
return callBackendErrorString(pluginId, 'files.showInFolder(' + relativePath + ')', function() {
|
|
return App.ShowVaultPathInFolder(pluginId, relativePath);
|
|
});
|
|
}
|
|
},
|
|
|
|
sync: {
|
|
status: function() {
|
|
assertActive('sync.status');
|
|
return callBackend(pluginId, 'sync.status', function() {
|
|
return App.PluginSyncStatus(pluginId);
|
|
});
|
|
},
|
|
configure: function(serverURL, username, password) {
|
|
assertActive('sync.configure');
|
|
return callBackendErrorString(pluginId, 'sync.configure', function() {
|
|
return App.PluginSyncConfigure(pluginId, serverURL || '', username || '', password || '');
|
|
});
|
|
},
|
|
disconnect: function() {
|
|
assertActive('sync.disconnect');
|
|
return callBackendErrorString(pluginId, 'sync.disconnect', function() {
|
|
return App.PluginSyncDisconnect(pluginId);
|
|
});
|
|
},
|
|
testConnection: function(serverURL, username, password) {
|
|
assertActive('sync.testConnection');
|
|
return callBackendErrorString(pluginId, 'sync.testConnection', function() {
|
|
return App.PluginSyncTestConnection(pluginId, serverURL || '', username || '', password || '');
|
|
});
|
|
},
|
|
setInterval: function(minutes) {
|
|
assertActive('sync.setInterval');
|
|
return callBackendErrorString(pluginId, 'sync.setInterval', function() {
|
|
return App.PluginSyncSetInterval(pluginId, Number(minutes) || 0);
|
|
});
|
|
},
|
|
resetKey: function() {
|
|
assertActive('sync.resetKey');
|
|
return callBackendErrorString(pluginId, 'sync.resetKey', function() {
|
|
return App.PluginSyncResetKey(pluginId);
|
|
});
|
|
},
|
|
now: function() {
|
|
assertActive('sync.now');
|
|
return callBackend(pluginId, 'sync.now', function() {
|
|
return App.PluginSyncNow(pluginId);
|
|
});
|
|
}
|
|
},
|
|
|
|
workbench: {
|
|
openResource: async function(request) {
|
|
assertActive('workbench.openResource');
|
|
const result = await callBackend(pluginId, 'workbench.openResource', function() {
|
|
return App.OpenWorkbenchResource(pluginId, request || {});
|
|
});
|
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
|
return result;
|
|
},
|
|
editResource: async function(request) {
|
|
assertActive('workbench.editResource');
|
|
const result = await callBackend(pluginId, 'workbench.editResource', function() {
|
|
return App.EditWorkbenchResource(pluginId, request || {});
|
|
});
|
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
|
return result;
|
|
}
|
|
},
|
|
|
|
contributions: {
|
|
list: async function(point) {
|
|
assertActive('contributions.list');
|
|
const summary = await callBackend(pluginId, 'contributions.list', function() {
|
|
return App.GetContributions();
|
|
});
|
|
if (!point) {
|
|
return summary || {};
|
|
}
|
|
return Array.isArray((summary || {})[point]) ? summary[point] : [];
|
|
}
|
|
},
|
|
|
|
commands: {
|
|
register: function(cmdId, handler) {
|
|
assertActive('commands.register(' + cmdId + ')');
|
|
if (!cmdId) {
|
|
throw new Error('commands.register requires a command id');
|
|
}
|
|
if (typeof handler !== 'function') {
|
|
throw new Error('commands.register requires a handler function');
|
|
}
|
|
return callBackend(pluginId, 'commands.register(' + cmdId + ')', function() {
|
|
return App.ExecutePluginCommand(pluginId, cmdId, { validateOnly: true });
|
|
}).then(function() {
|
|
const key = commandKey(pluginId, cmdId);
|
|
window.__VERSTAK_COMMAND_HANDLERS__[key] = handler;
|
|
return trackCleanup(function unregisterCommand() {
|
|
if (window.__VERSTAK_COMMAND_HANDLERS__[key] === handler) {
|
|
delete window.__VERSTAK_COMMAND_HANDLERS__[key];
|
|
}
|
|
});
|
|
});
|
|
},
|
|
execute: async function(cmdId, args) {
|
|
assertActive('commands.execute(' + cmdId + ')');
|
|
return executePluginCommand(pluginId, cmdId, args || {});
|
|
},
|
|
executeFor: async function(targetPluginId, cmdId, args) {
|
|
assertActive('commands.executeFor(' + targetPluginId + ':' + cmdId + ')');
|
|
return executePluginCommand(targetPluginId, cmdId, args || {});
|
|
}
|
|
},
|
|
|
|
dispose: function() {
|
|
if (disposed) return;
|
|
disposed = true;
|
|
while (cleanups.length > 0) {
|
|
const cleanup = cleanups.pop();
|
|
try {
|
|
cleanup();
|
|
} catch (e) {
|
|
console.error('[VerstakPluginAPI] cleanup error:', e);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
window.createPluginAPI = createPluginAPI;
|
|
window.VerstakPluginAPI = createPluginAPI;
|