utils/sync.ts
Rama main · réplica de lectura del repositorio original.
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<number>,
idPulled: Ref<string[]>,
actionObject: Ref<any>,
isPasswordExist: Ref<boolean>,
activeNoteId: Ref<string>
) {
// 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<any>,
lastPushData: Ref<number>,
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 };