Files
notesnook/packages/core/api/sync.js

208 lines
5.4 KiB
JavaScript
Raw Normal View History

2020-02-11 16:28:28 +05:00
/**
* GENERAL PROCESS:
* make a get request to server with current lastSyncedTimestamp
* parse the response. the response should contain everything that user has on the server
* decrypt the response
* merge everything into the database and look for conflicts
* send the conflicts (if any) to the end-user for resolution
* once the conflicts have been resolved, send the updated data back to the server
*/
/**
* MERGING:
* Locally, get everything that was editted/added after the lastSyncedTimestamp
* Run forEach loop on the server response.
* Add items that do not exist in the local collections
* Remove items (without asking) that need to be removed
* Update items that were editted before the lastSyncedTimestamp
* Try to merge items that were edited after the lastSyncedTimestamp
* Items in which the content has changed, send them for conflict resolution
* Otherwise, keep the most recently updated copy.
*/
/**
* CONFLICTS:
* Syncing should pause until all the conflicts have been resolved
* And then it should continue.
*/
import Database from "./index";
2020-02-23 19:57:46 +05:00
import { HOST, HEADERS } from "../utils/constants";
2020-03-19 14:03:29 +05:00
var tfun = require("transfun/transfun.js").tfun;
if (!tfun) {
tfun = global.tfun;
}
2020-02-11 16:28:28 +05:00
export default class Sync {
/**
*
* @param {Database} db
*/
constructor(db) {
this.db = db;
}
async _fetch(lastSyncedTimestamp) {
let token = await this.db.user.token();
2020-03-19 14:30:05 +05:00
if (!token) throw new Error("You are not logged in");
2020-02-11 16:28:28 +05:00
let response = await fetch(`${HOST}sync?lst=${lastSyncedTimestamp}`, {
headers: { ...HEADERS, Authorization: `Bearer ${token}` }
});
//TODO decrypt the response.
return await response.json();
}
async start() {
let user = await this.db.user.get();
2020-03-19 14:30:05 +05:00
if (!user) throw new Error("You need to login to sync.");
2020-03-18 14:06:20 +05:00
let lastSyncedTimestamp = user.lastSynced || 0;
2020-02-11 16:28:28 +05:00
let serverResponse = await this._fetch(lastSyncedTimestamp);
2020-03-22 12:48:01 +05:00
// we prepare local data before merging so we always have correct data
const prepare = new Prepare(this.db, user);
const data = await prepare.get(lastSyncedTimestamp);
// merge the server response
const merger = new Merger(this.db);
await merger.merge(serverResponse);
// send the data back to server
2020-02-11 16:28:28 +05:00
await this._send(data);
2020-03-22 12:48:01 +05:00
// update our lastSynced time
2020-03-18 14:06:20 +05:00
await this.db.user.set({ lastSynced: data.lastSynced });
2020-02-11 16:28:28 +05:00
}
2020-03-22 12:48:01 +05:00
async _send(data) {
//TODO encrypt the payload
let token = await this.db.user.token();
if (!token) return;
let response = await fetch(`${HOST}sync`, {
method: "POST",
headers: { ...HEADERS, Authorization: `Bearer ${token}` },
body: JSON.stringify(data)
});
return response.ok;
}
}
class Merger {
/**
*
* @param {Database} db
*/
constructor(db) {
this._db = db;
}
async _mergeItem(remoteItem, get, add) {
let localItem = await get(remoteItem.id);
2020-03-23 13:22:28 +05:00
if (
!localItem ||
remoteItem.deleted ||
remoteItem.dateEdited > localItem.dateEdited
) {
2020-03-22 12:48:01 +05:00
await add({ ...JSON.parse(remoteItem.data), remote: true });
}
}
async _mergeArray(array, get, set) {
return Promise.all(
array.map(async item => await this._mergeItem(item, get, set))
);
}
async merge(serverResponse) {
const {
notes,
synced,
notebooks,
delta,
text,
tags,
2020-03-22 15:04:11 +05:00
colors,
trash
2020-03-22 12:48:01 +05:00
} = serverResponse;
2020-02-23 19:57:46 +05:00
2020-03-19 14:43:42 +05:00
if (!synced) {
2020-03-22 12:48:01 +05:00
await this._mergeArray(
2020-03-19 14:03:29 +05:00
notes,
2020-03-22 12:48:01 +05:00
id => this._db.notes.note(id),
item => this._db.notes.add(item)
2020-03-19 14:03:29 +05:00
);
2020-03-22 12:48:01 +05:00
await this._mergeArray(
2020-03-19 14:03:29 +05:00
notebooks,
2020-03-22 12:48:01 +05:00
id => this._db.notebooks.notebook(id),
item => this._db.notebooks.add(item)
2020-03-19 14:03:29 +05:00
);
2020-03-22 12:48:01 +05:00
await this._mergeArray(
2020-03-22 11:31:25 +05:00
delta,
2020-03-22 12:48:01 +05:00
id => this._db.delta.raw(id),
item => this._db.delta.add(item)
2020-03-22 11:31:25 +05:00
);
2020-03-22 12:48:01 +05:00
await this._mergeArray(
2020-03-22 11:31:25 +05:00
text,
2020-03-22 12:48:01 +05:00
id => this._db.text.raw(id),
item => this._db.text.add(item)
);
await this._mergeArray(
tags,
2020-03-23 15:06:12 +05:00
id => this._db.tags.tag(id),
2020-03-22 12:48:01 +05:00
item => this._db.tags.merge(item)
);
await this._mergeArray(
colors,
2020-03-23 15:06:12 +05:00
id => this._db.colors.tag(id),
2020-03-22 12:48:01 +05:00
item => this._db.colors.merge(item)
2020-03-22 11:31:25 +05:00
);
2020-03-22 15:04:11 +05:00
await this._mergeArray(
trash,
() => undefined,
item => this._db.trash.add(item)
);
2020-03-19 14:03:29 +05:00
}
2020-03-22 12:48:01 +05:00
}
}
class Prepare {
/**
*
* @param {Database} db
* @param {Object} user
* @param {Number} lastSyncedTimestamp
*/
constructor(db, user) {
this._db = db;
this._user = user;
}
async get(lastSyncedTimestamp) {
this._lastSyncedTimestamp = lastSyncedTimestamp;
2020-02-11 16:28:28 +05:00
return {
2020-03-23 15:06:12 +05:00
notes: this._prepareForServer(this._db.notes.raw),
notebooks: this._prepareForServer(this._db.notebooks.raw),
2020-03-22 13:03:26 +05:00
delta: this._prepareForServer(await this._db.delta.all()),
text: this._prepareForServer(await this._db.text.all()),
2020-03-23 15:06:12 +05:00
tags: this._prepareForServer(this._db.tags.raw),
colors: this._prepareForServer(this._db.colors.raw),
trash: this._prepareForServer(this._db.trash.raw),
lastSynced: Date.now()
2020-02-11 16:28:28 +05:00
};
}
2020-03-22 12:48:01 +05:00
_prepareForServer(array) {
return tfun
2020-03-23 15:06:12 +05:00
.filter(`.deleted === true || .dateEdited > ${this._lastSyncedTimestamp}`)
2020-03-22 12:48:01 +05:00
.map(item => ({
id: item.id,
dateEdited: item.dateEdited,
dateCreated: item.dateCreated,
data: JSON.stringify(item),
2020-03-22 12:58:38 +05:00
userId: this._user.Id
2020-03-22 12:48:01 +05:00
}))(array);
2020-02-11 16:28:28 +05:00
}
2020-03-19 14:03:29 +05:00
}