import LocalForageAdapter from "~/adapter/localforage"; import TursoAdapter from "~/adapter/turso"; import { createFolder, createNote, getFolderDetail, getNoteDetail, getPassword, setPassword, updateFolder, updateNote, getTagDetail, createTag, updateTag, getNoteTagDetail, createNoteTag, updateNoteTag, } from "~/services/main"; import { syncImages } from "~/utils/image-sync"; function selectAdapter( adapter: string = "LocalForage", configuration: any, onLine: boolean ) { switch (adapter) { case "LocalForage": return new LocalForageAdapter(configuration); case "Turso": if (!onLine) { throw new NetworkError(""); } try { return new TursoAdapter(configuration); } catch (error) { throw new SyncError(""); } default: throw new SyncError(""); } } async function pullData( settings: any, privateKey: CryptoKey | undefined, lastPushData: number, lastPull: Ref, idPulled: Ref, actionObject: Ref, isPasswordExist: Ref, activeNoteId: Ref ) { // if local then ignore if (settings?.sync?.adapter === "LocalForage") { return { ok: false, }; } const syncAdapter: any = selectAdapter( settings?.sync?.adapter, settings?.sync?.configuration, window.navigator.onLine ); const now = nowUnix(); let lastDataAt = -Infinity; try { let needReloadActiveNote = false; let pullFolders = await syncAdapter.pullFolders(lastPull.value); let pullNotes = await syncAdapter.pullNotes(lastPull.value); let pullSettings = await syncAdapter.pullSettings(lastPull.value); let pullTags = await syncAdapter.pullTags(lastPull.value); let pullNoteTags = await syncAdapter.pullNoteTags(lastPull.value); pullFolders = pullFolders.filter((folder: any) => { return folder.lastSync > lastPushData; }); pullNotes = pullNotes.filter((note: any) => { return note.lastSync > lastPushData; }); pullSettings = pullSettings?.lastSync > lastPushData ? pullSettings : {}; pullTags = pullTags.filter((tag: any) => { return tag.lastSync > lastPushData; }); pullNoteTags = pullNoteTags.filter((noteTag: any) => { return noteTag.lastSync > lastPushData; }); idPulled.value = [ ...pullFolders.map((folder: any) => folder.id), ...pullNotes.map((note: any) => note.id), ]; if (pullFolders.length > 0) { for (const folder of pullFolders) { const isFolderExist = await getFolderDetail(folder.id); if (!isFolderExist.id) { await createFolder(folder); } else { await updateFolder( folder.id, folder.updatedAt > isFolderExist.updatedAt ? folder : isFolderExist ); } if (lastDataAt < +folder.updatedAt) { lastDataAt = +folder.updatedAt; } } } if (pullNotes.length > 0) { for (const note of pullNotes) { const isNoteExist = await getNoteDetail(note.id); if (!isNoteExist.id) { note.content = await decryptDataAES(note.content, privateKey!); await createNote("", note); } else { note.content = await decryptDataAES(note.content, privateKey!); const newNoteData = note.updatedAt > isNoteExist.updatedAt ? note : isNoteExist; if (Object.keys(actionObject.value).includes(note.id)) { newNoteData.content = mergeDiff(isNoteExist.content, note.content); newNoteData.updatedAt = nowUnix(); } await updateNote(note.id, newNoteData); } if (lastDataAt < +note.updatedAt) { lastDataAt = +note.updatedAt; } if (note.id === activeNoteId.value) { needReloadActiveNote = true; } } } if (pullTags.length > 0) { for (const tag of pullTags) { const isTagExist = await getTagDetail(tag.id); if (!isTagExist.id) { await createTag(tag); } else { await updateTag( tag.id, tag.updatedAt > isTagExist.updatedAt ? tag : isTagExist ); } if (lastDataAt < +tag.updatedAt) { lastDataAt = +tag.updatedAt; } } } if (pullNoteTags.length > 0) { for (const noteTag of pullNoteTags) { const isNoteTagExist: any = await getNoteTagDetail(noteTag.id); if (!isNoteTagExist.id) { await createNoteTag(noteTag); } else { await updateNoteTag( noteTag.id, noteTag.updatedAt > isNoteTagExist.updatedAt ? noteTag : isNoteTagExist ); } if (lastDataAt < +noteTag.updatedAt) { lastDataAt = +noteTag.updatedAt; } } } if (pullSettings) { if (pullSettings.password) { await setPassword(pullSettings.password); isPasswordExist.value = true; } if (lastDataAt < +pullSettings.updatedAt) { lastDataAt = +pullSettings.updatedAt; } } if (lastDataAt !== -Infinity) { lastPull.value = lastDataAt; } setTimeout(() => { idPulled.value = []; }, 1000); if (lastDataAt !== -Infinity) { return { ok: true, needReloadActiveNote, now, }; } else { return { ok: false, now, }; } } catch (error) { throw error; } } async function pushData( settings: any, privateKey: CryptoKey | undefined, actionObject: Ref, lastPushData: Ref, now: number ) { if (settings?.sync?.adapter === "LocalForage") { // Still sync images even with LocalForage adapter if image sync is enabled await syncImages(settings); return; } const syncAdapter: any = selectAdapter( settings?.sync?.adapter, settings?.sync?.configuration, window.navigator.onLine ); for (const key of Object.keys(actionObject.value)) { const obj = actionObject.value[key]; const id = key; switch (obj) { case "folder": { const folder = await getFolderDetail(id); await syncAdapter.pushFolder({ ...folder, lastSync: now, updatedAt: folder.updatedAt, }); break; } case "note": { const note = await getNoteDetail(id); await syncAdapter.pushNote({ ...note, content: await encryptDataAES(note.content, privateKey!), lastSync: now, updatedAt: note.updatedAt, }); break; } case "settings": { const password = await getPassword(); await syncAdapter.pushSettings({ password, id: "settings", lastSync: now, updatedAt: now, }); break; } case "tag": { const tag = await getTagDetail(id); await syncAdapter.pushTag({ ...tag, lastSync: now, updatedAt: tag.updatedAt, }); break; } case "noteTag": { const noteTag: any = await getNoteTagDetail(id); await syncAdapter.pushNoteTag({ ...noteTag, lastSync: now, updatedAt: noteTag.updatedAt, }); break; } } lastPushData.value = now; delete actionObject.value[key]; } // Sync images after syncing notes await syncImages(settings); } export { pullData, pushData };