feat: implement Gmail metadata synchronization to backend

- Added a new utility function `syncGmailMetadataToBackend` to emit Gmail profile and email summaries to the backend via the `integration:metadata-sync` socket event.
- Updated `SkillProvider` to call the new synchronization function, ensuring Gmail skill state is sent to the backend when updated.
This commit is contained in:
M3gA-Mind
2026-02-20 20:59:38 +05:30
parent 159e111a29
commit f628f870ad
2 changed files with 68 additions and 1 deletions
+62
View File
@@ -0,0 +1,62 @@
/**
* Send Gmail profile (and optionally emails) to the backend via the
* `integration:metadata-sync` socket event so the server can merge them
* into the user's Google OAuth integration metadata.
*/
import { emitViaRustSocket } from '../../utils/tauriSocket';
const INTEGRATION_METADATA_SYNC_EVENT = 'integration:metadata-sync';
const PROVIDER_GOOGLE = 'google';
/** Gmail profile shape from skill state (snake_case). */
interface GmailProfileLike {
email_address: string;
messages_total: number;
threads_total: number;
history_id: string;
}
/** Single email summary from skill state. */
interface GmailEmailSummaryLike {
id: string;
threadId: string;
snippet?: string;
subject?: string;
from?: string;
date?: string;
}
/** Gmail skill state slice we care about for metadata sync. */
export interface GmailStateForSync {
profile?: GmailProfileLike | null;
emails?: GmailEmailSummaryLike[] | null;
}
/**
* Emit `integration:metadata-sync` with Gmail profile and emails so the
* backend can merge them into the user's Google OAuth integration.
* No-op when profile is missing or not in Tauri.
*/
export function syncGmailMetadataToBackend(gmailState: GmailStateForSync | undefined): void {
if (!gmailState?.profile || typeof gmailState.profile !== 'object') return;
const profile = gmailState.profile as GmailProfileLike;
const metadata: Record<string, unknown> = {
email_address: profile.email_address,
messages_total: profile.messages_total,
threads_total: profile.threads_total,
history_id: profile.history_id,
};
if (Array.isArray(gmailState.emails) && gmailState.emails.length > 0) {
metadata.emails = gmailState.emails as GmailEmailSummaryLike[];
}
const payload = {
requestId: crypto.randomUUID(),
provider: PROVIDER_GOOGLE,
metadata,
};
void emitViaRustSocket(INTEGRATION_METADATA_SYNC_EVENT, payload);
}
+6 -1
View File
@@ -19,6 +19,10 @@ import {
type GmailProfile,
} from '../store/gmailSlice';
import { setSkillError, setSkillState, setSkillStatus } from '../store/skillsSlice';
import {
syncGmailMetadataToBackend,
type GmailStateForSync,
} from '../lib/skills/gmailMetadataSync';
import { DEV_AUTO_LOAD_SKILL, IS_DEV } from '../utils/config';
// ---------------------------------------------------------------------------
@@ -71,7 +75,7 @@ function parseSkillStatePayload(
return { skillId, state };
}
/** Sync profile and emails from gmail skill state into gmailSlice. */
/** Sync profile and emails from gmail skill state into gmailSlice and send to backend via socket. */
function syncGmailStateToSlice(
gmailState: Record<string, unknown> | undefined,
dispatch: ReturnType<typeof useAppDispatch>
@@ -89,6 +93,7 @@ function syncGmailStateToSlice(
Array.isArray(gmailState.emails) ? (gmailState.emails as GmailEmailSummary[]) : []
)
);
syncGmailMetadataToBackend(gmailState as GmailStateForSync);
}
export default function SkillProvider({ children }: { children: ReactNode }) {