1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-20 23:30:05 +02:00
Files
joplin/packages/lib/services/synchronizer/syncTargetInfoUtils.ts

152 lines
4.6 KiB
TypeScript
Raw Normal View History

2021-06-25 14:51:36 +01:00
import { FileApi } from '../../file-api';
// import Logger from '../../Logger';
import Setting from '../../models/Setting';
import { MasterKeyEntity } from '../database/types';
const ArrayUtils = require('../../ArrayUtils');
// const logger = Logger.create('SyncTargetInfoHandler');
export interface SyncTargetInfo {
version: number;
e2ee: boolean;
updatedTime: number;
masterKeys: Record<string, MasterKeyEntity>;
activeMasterKeyId: string;
}
function serializeSyncTargetInfo(info: SyncTargetInfo): string {
return JSON.stringify(info);
}
function unserializeSyncTargetInfo(info: string): SyncTargetInfo {
return JSON.parse(info);
}
// export function setLocalSyncTargetInfoProp(key:string, value:any):void {
// const info = localSyncTargetInfo();
// if (!info) throw new Error('Local sync target info has not been set!');
// if (!(key in info)) throw new Error('Invalid sync target info key: ' + key);
// setLocalSyncTargetInfo({
// ...info,
// [key]: value,
// });
// }
// export function localSyncTargetInfoProp(key:string):any {
// const info = localSyncTargetInfo();
// if (!info) throw new Error('Local sync target info has not been set!');
// if (!(key in info)) throw new Error('Invalid sync target info key: ' + key);
// return (info as any)[key];
// }
export function setLocalSyncTargetInfo(info: SyncTargetInfo) {
Setting.setValue('sync.info', serializeSyncTargetInfo(info));
}
export function localSyncTargetInfo(mustExist: boolean = true): SyncTargetInfo | null {
const info = Setting.value('sync.info');
if (mustExist && !info) throw new Error('Sync info is not set');
return unserializeSyncTargetInfo(info);
}
function validateInfo(info: SyncTargetInfo) {
if (!info.version) throw new Error('Missing "version" field in info.json');
}
export function syncTargetInfoEquals(info1: SyncTargetInfo, info2: SyncTargetInfo): boolean {
if (info1.e2ee !== info2.e2ee) return false;
if (info1.version !== info2.version) return false;
const mks1 = info1.masterKeys || {};
const mks2 = info2.masterKeys || {};
if (Object.keys(mks1).length !== Object.keys(mks2).length) return false;
for (const [id, mk1] of Object.entries(mks1)) {
const mk2 = mks2[id];
if (!mk2) return false;
if (mk1.updated_time !== mk2.updated_time) return false;
}
return true;
}
export function mergeSyncTargetInfos(info1: SyncTargetInfo, info2: SyncTargetInfo): SyncTargetInfo {
const baseInfo = info1.updatedTime > info2.updatedTime ? info1 : info2;
const newInfo: SyncTargetInfo = { ...baseInfo };
const masterKeyIds = ArrayUtils.unique(
Object.keys(info1.masterKeys ? info1.masterKeys : {}).concat(
Object.keys(info2.masterKeys ? info2.masterKeys : {})
)
);
const mergedMasterKeys: Record<string, MasterKeyEntity> = {};
for (const id of masterKeyIds) {
const mk1 = info1.masterKeys[id] || { updated_time: 0 };
const mk2 = info2.masterKeys[id] || { updated_time: 0 };
mergedMasterKeys[id] = mk1.updated_time > mk2.updated_time ? mk1 : mk2;
}
newInfo.masterKeys = mergedMasterKeys;
return newInfo;
}
export async function remoteSyncTargetInfo(api: FileApi): Promise<SyncTargetInfo> {
const syncTargetInfoText = await api.get('info.json');
const defaultFields: SyncTargetInfo = {
version: 0,
e2ee: false,
updatedTime: Date.now(),
masterKeys: {},
activeMasterKeyId: '',
};
// Returns version 0 if the sync target is empty
let output: SyncTargetInfo = defaultFields;
if (syncTargetInfoText) {
output = unserializeSyncTargetInfo(syncTargetInfoText);
validateInfo(output);
} else {
const oldVersion = await api.get('.sync/version.txt');
if (oldVersion) output = { ...defaultFields, version: 1 };
}
return output;
}
export async function setRemoteSyncTargetInfo(api: FileApi, info: SyncTargetInfo) {
await api.put('info.json', serializeSyncTargetInfo(info));
}
// -----------------------------------------------------------------------
// Utility functions to manipulate the SyncTargetInfo data
// -----------------------------------------------------------------------
export function activeMasterKey(info: SyncTargetInfo): MasterKeyEntity {
if (!info.activeMasterKeyId) return null;
// Sanity check - but shouldn't happen because the key is saved at the same
// time as the active master key is set.
if (!info.masterKeys[info.activeMasterKeyId]) throw new Error('Active master key is not present in info.json');
return info.masterKeys[info.activeMasterKeyId];
}
export function enableEncryption(enable: boolean = true) {
setLocalSyncTargetInfo({
...localSyncTargetInfo(),
e2ee: enable,
});
}
export function disableEncryption() {
enableEncryption(false);
}