Harden core snapshot sync and workspace lifecycle
This commit is contained in:
@@ -99,7 +99,7 @@ test.describe('Command Palette', () => {
|
||||
|
||||
test('runs sync workflow commands', async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const err = await window.go.api.App.PluginSyncConfigure('verstak.sync', 'https://sync.example.test', 'alice', 'secret');
|
||||
const err = await window.go.api.App.PluginSyncConfigure('verstak.sync', 'https://sync.example.test', 'alice', 'secret', '');
|
||||
if (err) throw new Error(err);
|
||||
});
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ test.describe('D: Plugin API bridge', () => {
|
||||
const api = window.createPluginAPI('verstak.sync');
|
||||
const initial = await api.sync.status();
|
||||
await api.sync.testConnection('https://sync.example.test', 'alice', 'secret');
|
||||
await api.sync.configure('https://sync.example.test', 'alice', 'secret');
|
||||
await api.sync.configure('https://sync.example.test', 'alice', 'secret', 'existing-remote-vault');
|
||||
await api.sync.setInterval(15);
|
||||
const configured = await api.sync.status();
|
||||
const syncNow = await api.sync.now();
|
||||
@@ -117,6 +117,8 @@ test.describe('D: Plugin API bridge', () => {
|
||||
expect(result.initial.statusLabel).toBe('disabled');
|
||||
expect(result.configured.configured).toBe(true);
|
||||
expect(result.configured.serverUrl).toBe('https://sync.example.test');
|
||||
expect(result.configured.vaultId).toBe('existing-remote-vault');
|
||||
expect(result.configured.lastWarning).toBe('');
|
||||
expect(result.configured.syncInterval).toBe(15);
|
||||
expect(result.syncNow).toEqual({ pushed: 0, pulled: 0, serverSequence: 0 });
|
||||
expect(result.reset.configured).toBe(false);
|
||||
|
||||
@@ -468,10 +468,10 @@ export function createPluginAPI(pluginId) {
|
||||
return App.PluginSyncStatus(pluginId);
|
||||
});
|
||||
},
|
||||
configure: function(serverURL, username, password) {
|
||||
configure: function(serverURL, username, password, vaultId) {
|
||||
assertActive('sync.configure');
|
||||
return callBackendErrorString(pluginId, 'sync.configure', function() {
|
||||
return App.PluginSyncConfigure(pluginId, serverURL || '', username || '', password || '');
|
||||
return App.PluginSyncConfigure(pluginId, serverURL || '', username || '', password || '', vaultId || '');
|
||||
});
|
||||
},
|
||||
disconnect: function() {
|
||||
|
||||
@@ -626,6 +626,7 @@ import journalSource from '../../../../../verstak-official-plugins/plugins/journ
|
||||
return {
|
||||
configured: false,
|
||||
serverUrl: '',
|
||||
vaultId: '',
|
||||
deviceId: 'mock-device',
|
||||
deviceName: '',
|
||||
connected: false,
|
||||
@@ -635,6 +636,7 @@ import journalSource from '../../../../../verstak-official-plugins/plugins/journ
|
||||
lastSyncAt: '',
|
||||
syncInterval: 0,
|
||||
lastError: '',
|
||||
lastWarning: '',
|
||||
statusLabel: 'disabled',
|
||||
serverSequence: 0
|
||||
};
|
||||
@@ -649,7 +651,7 @@ import journalSource from '../../../../../verstak-official-plugins/plugins/journ
|
||||
if (p.charAt(0) === '/' || /^[A-Za-z]:/.test(p)) return { error: 'invalid-path: absolute path rejected' };
|
||||
var parts = p.split('/').filter(Boolean);
|
||||
if (parts.indexOf('..') !== -1) return { error: 'invalid-path: path-traversal' };
|
||||
if (parts[0] && parts[0].toLowerCase() === '.verstak') return { error: 'reserved-path: .verstak is internal' };
|
||||
if (parts.some(function(part) { return part.toLowerCase() === '.verstak'; })) return { error: 'reserved-path: .verstak is internal' };
|
||||
return { path: parts.join('/') };
|
||||
}
|
||||
|
||||
@@ -751,6 +753,7 @@ import journalSource from '../../../../../verstak-official-plugins/plugins/journ
|
||||
return {
|
||||
configured: syncState.configured,
|
||||
serverUrl: syncState.serverUrl,
|
||||
vaultId: syncState.vaultId,
|
||||
deviceId: syncState.deviceId,
|
||||
deviceName: syncState.deviceName,
|
||||
connected: syncState.connected,
|
||||
@@ -760,6 +763,7 @@ import journalSource from '../../../../../verstak-official-plugins/plugins/journ
|
||||
lastSyncAt: syncState.lastSyncAt,
|
||||
syncInterval: syncState.syncInterval,
|
||||
lastError: syncState.lastError,
|
||||
lastWarning: syncState.lastWarning,
|
||||
statusLabel: syncState.statusLabel
|
||||
};
|
||||
}
|
||||
@@ -3477,11 +3481,12 @@ import journalSource from '../../../../../verstak-official-plugins/plugins/journ
|
||||
if (err) return Promise.resolve([{}, err]);
|
||||
return Promise.resolve([syncStatusDTO(), '']);
|
||||
},
|
||||
PluginSyncConfigure: function (pluginId, serverUrl) {
|
||||
PluginSyncConfigure: function (pluginId, serverUrl, username, password, vaultId) {
|
||||
var err = requirePluginSyncPermission(pluginId, true);
|
||||
if (err) return Promise.resolve(err);
|
||||
syncState.configured = true;
|
||||
syncState.serverUrl = serverUrl || '';
|
||||
syncState.vaultId = vaultId || 'test-vault-001';
|
||||
syncState.deviceId = 'mock-device';
|
||||
syncState.deviceName = 'mock-device';
|
||||
syncState.connected = true;
|
||||
@@ -3491,6 +3496,7 @@ import journalSource from '../../../../../verstak-official-plugins/plugins/journ
|
||||
syncState.statusLabel = 'connected';
|
||||
pluginSettings[pluginId] = Object.assign({}, pluginSettings[pluginId] || {}, {
|
||||
serverUrl: syncState.serverUrl,
|
||||
vaultId: syncState.vaultId,
|
||||
syncStatus: syncState.statusLabel
|
||||
});
|
||||
return Promise.resolve('');
|
||||
|
||||
Vendored
+1
-1
@@ -114,7 +114,7 @@ export function PluginSecretsUnlock(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function PluginSecretsWrite(arg1:string,arg2:Record<string, any>):Promise<Record<string, any>|string>;
|
||||
|
||||
export function PluginSyncConfigure(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
|
||||
export function PluginSyncConfigure(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string):Promise<string>;
|
||||
|
||||
export function PluginSyncDisconnect(arg1:string):Promise<string>;
|
||||
|
||||
|
||||
@@ -210,8 +210,8 @@ export function PluginSecretsWrite(arg1, arg2) {
|
||||
return window['go']['api']['App']['PluginSecretsWrite'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function PluginSyncConfigure(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['api']['App']['PluginSyncConfigure'](arg1, arg2, arg3, arg4);
|
||||
export function PluginSyncConfigure(arg1, arg2, arg3, arg4, arg5) {
|
||||
return window['go']['api']['App']['PluginSyncConfigure'](arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
export function PluginSyncDisconnect(arg1) {
|
||||
|
||||
@@ -310,6 +310,7 @@ export namespace api {
|
||||
export class SyncStatusDTO {
|
||||
configured: boolean;
|
||||
serverUrl: string;
|
||||
vaultId: string;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
connected: boolean;
|
||||
@@ -319,6 +320,7 @@ export namespace api {
|
||||
lastSyncAt: string;
|
||||
syncInterval: number;
|
||||
lastError: string;
|
||||
lastWarning: string;
|
||||
statusLabel: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
@@ -329,6 +331,7 @@ export namespace api {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.configured = source["configured"];
|
||||
this.serverUrl = source["serverUrl"];
|
||||
this.vaultId = source["vaultId"];
|
||||
this.deviceId = source["deviceId"];
|
||||
this.deviceName = source["deviceName"];
|
||||
this.connected = source["connected"];
|
||||
@@ -338,6 +341,7 @@ export namespace api {
|
||||
this.lastSyncAt = source["lastSyncAt"];
|
||||
this.syncInterval = source["syncInterval"];
|
||||
this.lastError = source["lastError"];
|
||||
this.lastWarning = source["lastWarning"];
|
||||
this.statusLabel = source["statusLabel"];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user