feat: Phase 3+4 — security hardening + polish + offline + Electron push
Phase 3 (security): - P3.1: Feature gate server-side enforcement (403 on disabled features) - P3.2: Unified auth error interceptor (401→logout) - P3.3: Store-level state isolation via StoreSnapshot contract (added message-list-tabs + task stores to snapshot/restore cycle) - P3.4: Push event bus extraction — email-store no longer imports calendar/contact/filter/file stores directly - P1.3: Auth localStorage AES-GCM encryption via custom Zustand adapter Phase 4 (polish): - P4.1: Offline write queue — pending operations in localStorage, auto-retry on reconnect, offline-queue-indicator banner - P4.2: Identity spoofing — fromOverrideEmail domain validation - P4.3: WebSocket push for Electron via main-process IPC bridge (ws package with Authorization headers)
This commit is contained in:
+30
-4
@@ -2,6 +2,8 @@ import { create } from 'zustand';
|
||||
import type { CalendarTask } from '@/lib/jmap/types';
|
||||
import type { IJMAPClient } from '@/lib/jmap/client-interface';
|
||||
import { debug } from '@/lib/debug';
|
||||
import { enqueueOperation, isNetworkError } from '@/lib/offline-write-queue';
|
||||
import { useAccountStore } from '@/stores/account-store';
|
||||
|
||||
export type TaskViewFilter = 'all' | 'pending' | 'completed' | 'overdue';
|
||||
|
||||
@@ -58,10 +60,22 @@ export const useTaskStore = create<TaskStore>((set, get) => ({
|
||||
|
||||
createTask: async (client, task) => {
|
||||
debug.log('tasks', 'TaskStore/createTask', task);
|
||||
const created = await client.createCalendarTask(task);
|
||||
debug.log('tasks', 'TaskStore/createTask result', { id: created.id, uid: created.uid, title: created.title });
|
||||
set({ tasks: [...get().tasks, created] });
|
||||
return created;
|
||||
try {
|
||||
const created = await client.createCalendarTask(task);
|
||||
debug.log('tasks', 'TaskStore/createTask result', { id: created.id, uid: created.uid, title: created.title });
|
||||
set({ tasks: [...get().tasks, created] });
|
||||
return created;
|
||||
} catch (error) {
|
||||
debug.error('TaskStore/createTask failed', error);
|
||||
if (isNetworkError(error)) {
|
||||
const accountId = useAccountStore.getState().activeAccountId;
|
||||
if (accountId) {
|
||||
enqueueOperation({ type: 'createTask', accountId, payload: task });
|
||||
}
|
||||
}
|
||||
set({ error: 'Failed to create task' });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
updateTask: async (client, id, updates) => {
|
||||
@@ -72,6 +86,12 @@ export const useTaskStore = create<TaskStore>((set, get) => ({
|
||||
});
|
||||
} catch (error) {
|
||||
debug.error('TaskStore/updateTask failed', error);
|
||||
if (isNetworkError(error)) {
|
||||
const accountId = useAccountStore.getState().activeAccountId;
|
||||
if (accountId) {
|
||||
enqueueOperation({ type: 'updateTask', accountId, payload: { id, updates } });
|
||||
}
|
||||
}
|
||||
set({ error: 'Failed to update task' });
|
||||
}
|
||||
},
|
||||
@@ -85,6 +105,12 @@ export const useTaskStore = create<TaskStore>((set, get) => ({
|
||||
});
|
||||
} catch (error) {
|
||||
debug.error('TaskStore/deleteTask failed', error);
|
||||
if (isNetworkError(error)) {
|
||||
const accountId = useAccountStore.getState().activeAccountId;
|
||||
if (accountId) {
|
||||
enqueueOperation({ type: 'deleteTask', accountId, payload: { id } });
|
||||
}
|
||||
}
|
||||
set({ error: 'Failed to delete task' });
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user