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

223 lines
6.1 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';
2021-06-26 18:18:10 +01:00
import uuid from '../../uuid';
2021-06-25 14:51:36 +01:00
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 {
2021-06-26 22:13:38 +01:00
return JSON.stringify(info, null, '\t');
2021-06-25 14:51:36 +01:00
}
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];
// }
2021-06-26 18:18:10 +01:00
function defaultSyncTargetInfo(): SyncTargetInfo {
return {
e2ee: false,
activeMasterKeyId: '',
masterKeys: {},
version: 0,
updatedTime: 0,
};
}
2021-06-25 14:51:36 +01:00
export function setLocalSyncTargetInfo(info: SyncTargetInfo) {
Setting.setValue('sync.info', serializeSyncTargetInfo(info));
}
2021-06-26 18:18:10 +01:00
export function localSyncTargetInfo(mustExist: boolean = false): SyncTargetInfo | null {
2021-06-25 14:51:36 +01:00
const info = Setting.value('sync.info');
if (mustExist && !info) throw new Error('Sync info is not set');
2021-06-26 18:18:10 +01:00
return info ? unserializeSyncTargetInfo(info) : defaultSyncTargetInfo();
2021-06-25 14:51:36 +01:00
}
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,
2021-06-26 18:18:10 +01:00
updatedTime: 0,
2021-06-25 14:51:36 +01:00
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];
}
2021-06-26 18:18:10 +01:00
export function activeMasterKeyId() {
return localSyncTargetInfo().activeMasterKeyId;
}
export function setActiveMasterKeyId(id: string) {
const info = localSyncTargetInfo();
if (info.activeMasterKeyId === id) return;
2021-06-25 14:51:36 +01:00
setLocalSyncTargetInfo({
...localSyncTargetInfo(),
2021-06-26 18:18:10 +01:00
activeMasterKeyId: id,
updatedTime: Date.now(),
});
}
export function setEncryptionEnabled(enable: boolean = true, activeMasterKeyId: string = null) {
const info = localSyncTargetInfo(false);
if (info.e2ee === enable) return;
const newInfo = {
...info,
2021-06-25 14:51:36 +01:00
e2ee: enable,
2021-06-26 18:18:10 +01:00
updatedTime: Date.now(),
};
if (activeMasterKeyId !== null) newInfo.activeMasterKeyId = activeMasterKeyId;
setLocalSyncTargetInfo(newInfo);
}
export function encryptionEnabled() {
const info = localSyncTargetInfo(false);
return info.e2ee;
}
export function encryptionDisabled() {
return !encryptionEnabled();
}
export function masterKeyById(id: string): MasterKeyEntity {
return localSyncTargetInfo().masterKeys[id];
}
export function saveMasterKey(mk: MasterKeyEntity): MasterKeyEntity {
const info = localSyncTargetInfo();
const id = mk.id ? mk.id : uuid.create();
const newMasterKey = {
id,
...info.masterKeys[id],
...mk,
};
setLocalSyncTargetInfo({
...info,
masterKeys: {
...info.masterKeys,
[newMasterKey.id]: newMasterKey,
},
updatedTime: Date.now(),
2021-06-25 14:51:36 +01:00
});
2021-06-26 18:18:10 +01:00
return newMasterKey;
2021-06-25 14:51:36 +01:00
}
2021-06-26 18:18:10 +01:00
export function masterKeyAll(): MasterKeyEntity[] {
const masterKeys = localSyncTargetInfo().masterKeys;
return Object.keys(masterKeys).map(id => masterKeys[id]);
2021-06-25 14:51:36 +01:00
}