@vrtmrz/livesync-commonlib 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/models/setting.policy.d.ts +808 -0
- package/dist/common/models/setting.policy.js +298 -0
- package/dist/common/utils.patch.js +49 -30
- package/dist/services/base/RemoteService.js +6 -2
- package/dist/services/base/SettingService.d.ts +2 -1
- package/dist/services/base/SettingService.js +42 -63
- package/dist/services/implements/injectable/InjectableSettingService.d.ts +2 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +10 -0
- package/docs/proven-in-use.md +6 -0
- package/docs/settings-lifecycle.md +13 -1
- package/package.json +1 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { DEFAULT_SETTINGS } from "./setting.const.defaults.js";
|
|
2
|
+
const SettingPolicies = {
|
|
3
|
+
Default: { markdown: "include", persistence: "include" },
|
|
4
|
+
LocalOnly: { markdown: "omit", persistence: "include" },
|
|
5
|
+
DeviceLocal: { markdown: "include", persistence: "device" },
|
|
6
|
+
MarkdownCredentials: { markdown: "credentials", persistence: "include" },
|
|
7
|
+
RemoteProfiles: { markdown: "credentials", persistence: "profiles" },
|
|
8
|
+
LocalConnection: { markdown: "include", persistence: "connection" },
|
|
9
|
+
ConnectionCredentials: { markdown: "credentials", persistence: "connection" },
|
|
10
|
+
ConnectionContext: { markdown: "include", persistence: "connection-context" },
|
|
11
|
+
E2EEPassphrase: { markdown: "credentials", persistence: "passphrase" },
|
|
12
|
+
IdDerivationKey: { markdown: "credentials", persistence: "id-key" },
|
|
13
|
+
RuntimeOnly: { markdown: "omit", persistence: "omit" }
|
|
14
|
+
};
|
|
15
|
+
const SETTINGS_PROPERTY_POLICY = {
|
|
16
|
+
liveSync: SettingPolicies.Default,
|
|
17
|
+
syncOnSave: SettingPolicies.Default,
|
|
18
|
+
syncOnStart: SettingPolicies.Default,
|
|
19
|
+
syncOnFileOpen: SettingPolicies.Default,
|
|
20
|
+
syncOnEditorSave: SettingPolicies.Default,
|
|
21
|
+
keepReplicationActiveInBackground: SettingPolicies.Default,
|
|
22
|
+
allowSleepDuringSynchronisation: SettingPolicies.Default,
|
|
23
|
+
allowSleepDuringSynchronisationOnDesktop: SettingPolicies.Default,
|
|
24
|
+
syncMinimumInterval: SettingPolicies.Default,
|
|
25
|
+
showVerboseLog: SettingPolicies.Default,
|
|
26
|
+
lessInformationInLog: SettingPolicies.Default,
|
|
27
|
+
showLongerLogInsideEditor: SettingPolicies.Default,
|
|
28
|
+
showStatusOnEditor: SettingPolicies.Default,
|
|
29
|
+
showStatusOnStatusbar: SettingPolicies.Default,
|
|
30
|
+
showOnlyIconsOnEditor: SettingPolicies.Default,
|
|
31
|
+
hideFileWarningNotice: SettingPolicies.Default,
|
|
32
|
+
networkWarningStyle: SettingPolicies.Default,
|
|
33
|
+
displayLanguage: SettingPolicies.Default,
|
|
34
|
+
trashInsteadDelete: SettingPolicies.Default,
|
|
35
|
+
doNotDeleteFolder: SettingPolicies.Default,
|
|
36
|
+
batchSave: SettingPolicies.Default,
|
|
37
|
+
batchSaveMinimumDelay: SettingPolicies.Default,
|
|
38
|
+
batchSaveMaximumDelay: SettingPolicies.Default,
|
|
39
|
+
syncMaxSizeInMB: SettingPolicies.Default,
|
|
40
|
+
useIgnoreFiles: SettingPolicies.Default,
|
|
41
|
+
ignoreFiles: SettingPolicies.Default,
|
|
42
|
+
processSizeMismatchedFiles: SettingPolicies.Default,
|
|
43
|
+
syncOnlyRegEx: SettingPolicies.Default,
|
|
44
|
+
syncIgnoreRegEx: SettingPolicies.Default,
|
|
45
|
+
syncAfterMerge: SettingPolicies.Default,
|
|
46
|
+
resolveConflictsByNewerFile: SettingPolicies.Default,
|
|
47
|
+
writeDocumentsIfConflicted: SettingPolicies.Default,
|
|
48
|
+
disableMarkdownAutoMerge: SettingPolicies.Default,
|
|
49
|
+
configPassphraseStore: SettingPolicies.Default,
|
|
50
|
+
encryptedPassphrase: SettingPolicies.LocalOnly,
|
|
51
|
+
encryptedIdDerivationKey: SettingPolicies.LocalOnly,
|
|
52
|
+
encryptedCouchDBConnection: SettingPolicies.LocalOnly,
|
|
53
|
+
periodicReplication: SettingPolicies.Default,
|
|
54
|
+
periodicReplicationInterval: SettingPolicies.Default,
|
|
55
|
+
syncInternalFiles: SettingPolicies.Default,
|
|
56
|
+
syncInternalFilesBeforeReplication: SettingPolicies.Default,
|
|
57
|
+
syncInternalFilesInterval: SettingPolicies.Default,
|
|
58
|
+
syncInternalFilesIgnorePatterns: SettingPolicies.Default,
|
|
59
|
+
syncInternalFilesTargetPatterns: SettingPolicies.Default,
|
|
60
|
+
watchInternalFileChanges: SettingPolicies.Default,
|
|
61
|
+
suppressNotifyHiddenFilesChange: SettingPolicies.Default,
|
|
62
|
+
syncInternalFileOverwritePatterns: SettingPolicies.Default,
|
|
63
|
+
usePluginSync: SettingPolicies.Default,
|
|
64
|
+
usePluginSettings: SettingPolicies.Default,
|
|
65
|
+
showOwnPlugins: SettingPolicies.Default,
|
|
66
|
+
autoSweepPlugins: SettingPolicies.Default,
|
|
67
|
+
autoSweepPluginsPeriodic: SettingPolicies.Default,
|
|
68
|
+
notifyPluginOrSettingUpdated: SettingPolicies.Default,
|
|
69
|
+
deviceAndVaultName: SettingPolicies.DeviceLocal,
|
|
70
|
+
usePluginSyncV2: SettingPolicies.Default,
|
|
71
|
+
usePluginEtc: SettingPolicies.Default,
|
|
72
|
+
pluginSyncExtendedSetting: SettingPolicies.Default,
|
|
73
|
+
useAdvancedMode: SettingPolicies.Default,
|
|
74
|
+
usePowerUserMode: SettingPolicies.Default,
|
|
75
|
+
useEdgeCaseMode: SettingPolicies.Default,
|
|
76
|
+
notifyThresholdOfRemoteStorageSize: SettingPolicies.Default,
|
|
77
|
+
disableWorkerForGeneratingChunks: SettingPolicies.Default,
|
|
78
|
+
processSmallFilesInUIThread: SettingPolicies.Default,
|
|
79
|
+
savingDelay: SettingPolicies.Default,
|
|
80
|
+
gcDelay: SettingPolicies.Default,
|
|
81
|
+
skipOlderFilesOnSync: SettingPolicies.Default,
|
|
82
|
+
useIndexedDBAdapter: SettingPolicies.Default,
|
|
83
|
+
enableDebugTools: SettingPolicies.Default,
|
|
84
|
+
writeLogToTheFile: SettingPolicies.Default,
|
|
85
|
+
settingSyncFile: SettingPolicies.Default,
|
|
86
|
+
writeCredentialsForSettingSync: SettingPolicies.Default,
|
|
87
|
+
notifyAllSettingSyncFile: SettingPolicies.Default,
|
|
88
|
+
suspendFileWatching: SettingPolicies.Default,
|
|
89
|
+
suspendParseReplicationResult: SettingPolicies.Default,
|
|
90
|
+
doNotSuspendOnFetching: SettingPolicies.Default,
|
|
91
|
+
maxMTimeForReflectEvents: SettingPolicies.Default,
|
|
92
|
+
versionUpFlash: SettingPolicies.Default,
|
|
93
|
+
settingVersion: SettingPolicies.Default,
|
|
94
|
+
isConfigured: SettingPolicies.Default,
|
|
95
|
+
lastReadUpdates: SettingPolicies.Default,
|
|
96
|
+
doctorProcessedVersion: SettingPolicies.Default,
|
|
97
|
+
remoteConfigurations: SettingPolicies.RemoteProfiles,
|
|
98
|
+
activeConfigurationId: SettingPolicies.MarkdownCredentials,
|
|
99
|
+
P2P_ActiveRemoteConfigurationId: SettingPolicies.MarkdownCredentials,
|
|
100
|
+
couchDB_URI: SettingPolicies.LocalConnection,
|
|
101
|
+
couchDB_USER: SettingPolicies.ConnectionCredentials,
|
|
102
|
+
couchDB_PASSWORD: SettingPolicies.ConnectionCredentials,
|
|
103
|
+
couchDB_DBNAME: SettingPolicies.LocalConnection,
|
|
104
|
+
couchDB_CustomHeaders: SettingPolicies.ConnectionCredentials,
|
|
105
|
+
useJWT: SettingPolicies.Default,
|
|
106
|
+
jwtAlgorithm: SettingPolicies.Default,
|
|
107
|
+
jwtKey: SettingPolicies.ConnectionCredentials,
|
|
108
|
+
jwtKid: SettingPolicies.ConnectionCredentials,
|
|
109
|
+
jwtSub: SettingPolicies.ConnectionCredentials,
|
|
110
|
+
jwtExpDuration: SettingPolicies.Default,
|
|
111
|
+
useRequestAPI: SettingPolicies.Default,
|
|
112
|
+
accessKey: SettingPolicies.ConnectionCredentials,
|
|
113
|
+
secretKey: SettingPolicies.ConnectionCredentials,
|
|
114
|
+
bucket: SettingPolicies.LocalConnection,
|
|
115
|
+
region: SettingPolicies.ConnectionContext,
|
|
116
|
+
endpoint: SettingPolicies.LocalConnection,
|
|
117
|
+
useCustomRequestHandler: SettingPolicies.Default,
|
|
118
|
+
bucketCustomHeaders: SettingPolicies.ConnectionCredentials,
|
|
119
|
+
bucketPrefix: SettingPolicies.Default,
|
|
120
|
+
forcePathStyle: SettingPolicies.Default,
|
|
121
|
+
remoteType: SettingPolicies.Default,
|
|
122
|
+
encrypt: SettingPolicies.Default,
|
|
123
|
+
passphrase: SettingPolicies.E2EEPassphrase,
|
|
124
|
+
idDerivationVersion: SettingPolicies.MarkdownCredentials,
|
|
125
|
+
idDerivationKey: SettingPolicies.IdDerivationKey,
|
|
126
|
+
usePathObfuscation: SettingPolicies.Default,
|
|
127
|
+
encryptInternalMetadata: SettingPolicies.Default,
|
|
128
|
+
E2EEAlgorithm: SettingPolicies.Default,
|
|
129
|
+
hashAlg: SettingPolicies.Default,
|
|
130
|
+
minimumChunkSize: SettingPolicies.Default,
|
|
131
|
+
customChunkSize: SettingPolicies.Default,
|
|
132
|
+
longLineThreshold: SettingPolicies.Default,
|
|
133
|
+
useSegmenter: SettingPolicies.Default,
|
|
134
|
+
enableChunkSplitterV2: SettingPolicies.Default,
|
|
135
|
+
doNotUseFixedRevisionForChunks: SettingPolicies.Default,
|
|
136
|
+
chunkSplitterVersion: SettingPolicies.Default,
|
|
137
|
+
useEden: SettingPolicies.Default,
|
|
138
|
+
maxChunksInEden: SettingPolicies.Default,
|
|
139
|
+
maxTotalLengthInEden: SettingPolicies.Default,
|
|
140
|
+
maxAgeInEden: SettingPolicies.Default,
|
|
141
|
+
tweakModified: SettingPolicies.Default,
|
|
142
|
+
checkIntegrityOnSave: SettingPolicies.Default,
|
|
143
|
+
useHistory: SettingPolicies.Default,
|
|
144
|
+
disableRequestURI: SettingPolicies.Default,
|
|
145
|
+
sendChunksBulk: SettingPolicies.Default,
|
|
146
|
+
sendChunksBulkMaxSize: SettingPolicies.Default,
|
|
147
|
+
useDynamicIterationCount: SettingPolicies.Default,
|
|
148
|
+
doNotPaceReplication: SettingPolicies.Default,
|
|
149
|
+
readChunksOnline: SettingPolicies.Default,
|
|
150
|
+
useOnlyLocalChunk: SettingPolicies.Default,
|
|
151
|
+
concurrencyOfReadChunksOnline: SettingPolicies.Default,
|
|
152
|
+
minimumIntervalOfReadChunksOnline: SettingPolicies.Default,
|
|
153
|
+
enableCompression: SettingPolicies.Default,
|
|
154
|
+
batch_size: SettingPolicies.Default,
|
|
155
|
+
batches_limit: SettingPolicies.Default,
|
|
156
|
+
ignoreVersionCheck: SettingPolicies.Default,
|
|
157
|
+
disableCheckingConfigMismatch: SettingPolicies.Default,
|
|
158
|
+
autoAcceptCompatibleTweak: SettingPolicies.Default,
|
|
159
|
+
hashCacheMaxCount: SettingPolicies.Default,
|
|
160
|
+
hashCacheMaxAmount: SettingPolicies.Default,
|
|
161
|
+
permitEmptyPassphrase: SettingPolicies.Default,
|
|
162
|
+
handleFilenameCaseSensitive: SettingPolicies.Default,
|
|
163
|
+
checkConflictOnlyOnOpen: SettingPolicies.Default,
|
|
164
|
+
showMergeDialogOnlyOnActive: SettingPolicies.Default,
|
|
165
|
+
additionalSuffixOfDatabaseName: SettingPolicies.LocalOnly,
|
|
166
|
+
useTimeouts: SettingPolicies.Default,
|
|
167
|
+
deleteMetadataOfDeletedFiles: SettingPolicies.Default,
|
|
168
|
+
automaticallyDeleteMetadataOfDeletedFiles: SettingPolicies.Default,
|
|
169
|
+
P2P_AutoAccepting: SettingPolicies.Default,
|
|
170
|
+
P2P_AutoSyncPeers: SettingPolicies.Default,
|
|
171
|
+
P2P_AutoWatchPeers: SettingPolicies.Default,
|
|
172
|
+
P2P_SyncOnReplication: SettingPolicies.Default,
|
|
173
|
+
P2P_RebuildFrom: SettingPolicies.Default,
|
|
174
|
+
P2P_AutoAcceptingPeers: SettingPolicies.Default,
|
|
175
|
+
P2P_AutoDenyingPeers: SettingPolicies.Default,
|
|
176
|
+
P2P_IsHeadless: SettingPolicies.Default,
|
|
177
|
+
P2P_iceServers: SettingPolicies.RuntimeOnly,
|
|
178
|
+
P2P_iceServersExpiresAt: SettingPolicies.RuntimeOnly,
|
|
179
|
+
P2P_Enabled: SettingPolicies.Default,
|
|
180
|
+
P2P_relays: SettingPolicies.Default,
|
|
181
|
+
P2P_roomID: SettingPolicies.Default,
|
|
182
|
+
P2P_passphrase: SettingPolicies.MarkdownCredentials,
|
|
183
|
+
P2P_AppID: SettingPolicies.Default,
|
|
184
|
+
P2P_AutoStart: SettingPolicies.Default,
|
|
185
|
+
P2P_AutoBroadcast: SettingPolicies.Default,
|
|
186
|
+
P2P_DevicePeerName: SettingPolicies.DeviceLocal,
|
|
187
|
+
P2P_turnServers: SettingPolicies.Default,
|
|
188
|
+
P2P_turnUsername: SettingPolicies.MarkdownCredentials,
|
|
189
|
+
P2P_turnCredential: SettingPolicies.MarkdownCredentials,
|
|
190
|
+
P2P_managedType: SettingPolicies.RuntimeOnly,
|
|
191
|
+
P2P_managedId: SettingPolicies.RuntimeOnly,
|
|
192
|
+
P2P_managedToken: SettingPolicies.RuntimeOnly,
|
|
193
|
+
P2P_maxWirePayloadBytes: SettingPolicies.Default,
|
|
194
|
+
P2P_connectionPath: SettingPolicies.Default,
|
|
195
|
+
P2P_useDiagRTC: SettingPolicies.Default
|
|
196
|
+
};
|
|
197
|
+
const settingKeys = Object.keys(SETTINGS_PROPERTY_POLICY);
|
|
198
|
+
const protectedConnectionKeys = settingKeys.filter(
|
|
199
|
+
(key) => SETTINGS_PROPERTY_POLICY[key].persistence === "connection"
|
|
200
|
+
);
|
|
201
|
+
const connectionKeys = settingKeys.filter(
|
|
202
|
+
(key) => SETTINGS_PROPERTY_POLICY[key].persistence === "connection" || SETTINGS_PROPERTY_POLICY[key].persistence === "connection-context"
|
|
203
|
+
);
|
|
204
|
+
const omittedPersistenceKeys = settingKeys.filter((key) => SETTINGS_PROPERTY_POLICY[key].persistence === "omit");
|
|
205
|
+
function copySetting(target, source, key) {
|
|
206
|
+
if (key in source) target[key] = source[key];
|
|
207
|
+
else delete target[key];
|
|
208
|
+
}
|
|
209
|
+
function cloneProfiles(settings) {
|
|
210
|
+
const profiles = {};
|
|
211
|
+
for (const [id, profile] of Object.entries(settings.remoteConfigurations ?? {})) {
|
|
212
|
+
Object.defineProperty(profiles, id, {
|
|
213
|
+
value: { ...profile },
|
|
214
|
+
enumerable: true,
|
|
215
|
+
configurable: true,
|
|
216
|
+
writable: true
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return profiles;
|
|
220
|
+
}
|
|
221
|
+
function createMarkdownSettings(settings, includeCredentials = settings.writeCredentialsForSettingSync === true) {
|
|
222
|
+
const output = {};
|
|
223
|
+
for (const key of settingKeys) {
|
|
224
|
+
const action = SETTINGS_PROPERTY_POLICY[key].markdown;
|
|
225
|
+
if (action === "omit" || action === "credentials" && !includeCredentials) continue;
|
|
226
|
+
copySetting(output, settings, key);
|
|
227
|
+
}
|
|
228
|
+
if (output.remoteConfigurations) output.remoteConfigurations = cloneProfiles(output);
|
|
229
|
+
return output;
|
|
230
|
+
}
|
|
231
|
+
function mergeMarkdownSettings(incoming, current) {
|
|
232
|
+
const merged = { ...DEFAULT_SETTINGS, ...incoming };
|
|
233
|
+
for (const key of settingKeys) {
|
|
234
|
+
const action = SETTINGS_PROPERTY_POLICY[key].markdown;
|
|
235
|
+
if (action === "omit" || action === "credentials" && merged.writeCredentialsForSettingSync !== true) {
|
|
236
|
+
copySetting(merged, current, key);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
merged.remoteConfigurations = cloneProfiles(merged);
|
|
240
|
+
return merged;
|
|
241
|
+
}
|
|
242
|
+
function selectConnectionSettings(settings) {
|
|
243
|
+
const connection = {};
|
|
244
|
+
for (const key of connectionKeys) connection[key] = settings[key];
|
|
245
|
+
return connection;
|
|
246
|
+
}
|
|
247
|
+
function hasConnectionSettings(settings) {
|
|
248
|
+
return protectedConnectionKeys.some((key) => settings[key] !== "");
|
|
249
|
+
}
|
|
250
|
+
function clearConnectionSettings(settings) {
|
|
251
|
+
for (const key of connectionKeys) settings[key] = "";
|
|
252
|
+
}
|
|
253
|
+
function restoreConnectionSettings(settings, payload) {
|
|
254
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return false;
|
|
255
|
+
const connection = payload;
|
|
256
|
+
if (connectionKeys.some((key) => key in connection && typeof connection[key] !== "string")) return false;
|
|
257
|
+
for (const key of connectionKeys) {
|
|
258
|
+
if (key in connection) settings[key] = connection[key];
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
function omitUnpersistedSettings(settings) {
|
|
263
|
+
for (const key of omittedPersistenceKeys) delete settings[key];
|
|
264
|
+
}
|
|
265
|
+
function requireConfigurationCiphertext(value) {
|
|
266
|
+
if (value === "") throw new Error("Failed to encrypt configuration. Settings were not saved.");
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
function prepareSettingsForPersistence(settings) {
|
|
270
|
+
if (hasConnectionSettings(settings)) throw new Error("Connection settings were not prepared for persistence.");
|
|
271
|
+
if (settings.encrypt && settings.passphrase !== "")
|
|
272
|
+
throw new Error("The passphrase was not prepared for persistence.");
|
|
273
|
+
if (settings.idDerivationVersion === 1 && settings.idDerivationKey !== "") {
|
|
274
|
+
throw new Error("The ID derivation key was not prepared for persistence.");
|
|
275
|
+
}
|
|
276
|
+
if (omittedPersistenceKeys.some((key) => key in settings)) {
|
|
277
|
+
throw new Error("Runtime settings were not removed before persistence.");
|
|
278
|
+
}
|
|
279
|
+
if (settings.configPassphraseStore !== "" && Object.values(settings.remoteConfigurations).some(
|
|
280
|
+
(profile) => profile.uri.trim() !== "" && !profile.isEncrypted
|
|
281
|
+
)) {
|
|
282
|
+
throw new Error("Remote configurations were not prepared for persistence.");
|
|
283
|
+
}
|
|
284
|
+
return settings;
|
|
285
|
+
}
|
|
286
|
+
export {
|
|
287
|
+
SETTINGS_PROPERTY_POLICY,
|
|
288
|
+
SettingPolicies,
|
|
289
|
+
clearConnectionSettings,
|
|
290
|
+
createMarkdownSettings,
|
|
291
|
+
hasConnectionSettings,
|
|
292
|
+
mergeMarkdownSettings,
|
|
293
|
+
omitUnpersistedSettings,
|
|
294
|
+
prepareSettingsForPersistence,
|
|
295
|
+
requireConfigurationCiphertext,
|
|
296
|
+
restoreConnectionSettings,
|
|
297
|
+
selectConnectionSettings
|
|
298
|
+
};
|
|
@@ -2,6 +2,17 @@ const MARK_OPERATOR = ``;
|
|
|
2
2
|
const MARK_DELETED = `${MARK_OPERATOR}__DELETED`;
|
|
3
3
|
const MARK_ISARRAY = `${MARK_OPERATOR}__ARRAY`;
|
|
4
4
|
const MARK_SWAPPED = `${MARK_OPERATOR}__SWAP`;
|
|
5
|
+
function hasOwnProperty(target, key) {
|
|
6
|
+
return Object.prototype.hasOwnProperty.call(target, key);
|
|
7
|
+
}
|
|
8
|
+
function setOwnProperty(target, key, value) {
|
|
9
|
+
Object.defineProperty(target, key, {
|
|
10
|
+
value,
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true
|
|
14
|
+
});
|
|
15
|
+
}
|
|
5
16
|
function unorderedArrayToObject(obj) {
|
|
6
17
|
return obj.map((e) => ({ [e.id]: e })).reduce((p, c) => ({ ...p, ...c }), {});
|
|
7
18
|
}
|
|
@@ -30,34 +41,34 @@ function generatePatchObj(from, to) {
|
|
|
30
41
|
const newEntries = Object.entries(to);
|
|
31
42
|
for (const [key, value] of newEntries) {
|
|
32
43
|
if (!tempMap.has(key)) {
|
|
33
|
-
ret
|
|
44
|
+
setOwnProperty(ret, key, value);
|
|
34
45
|
tempMap.delete(key);
|
|
35
46
|
} else {
|
|
36
47
|
const v = tempMap.get(key);
|
|
37
48
|
if (typeof v !== typeof value || Array.isArray(v) !== Array.isArray(value)) {
|
|
38
|
-
ret
|
|
49
|
+
setOwnProperty(ret, key, { [MARK_SWAPPED]: value });
|
|
39
50
|
} else {
|
|
40
51
|
if (v === null && value === null) {
|
|
41
52
|
} else if (v === null && value !== null) {
|
|
42
|
-
ret
|
|
53
|
+
setOwnProperty(ret, key, { [MARK_SWAPPED]: value });
|
|
43
54
|
} else if (v !== null && value === null) {
|
|
44
|
-
ret
|
|
55
|
+
setOwnProperty(ret, key, { [MARK_SWAPPED]: value });
|
|
45
56
|
} else if (typeof v == "object" && typeof value == "object" && !Array.isArray(v) && !Array.isArray(value)) {
|
|
46
57
|
const wk = generatePatchObj(
|
|
47
58
|
v,
|
|
48
59
|
value
|
|
49
60
|
);
|
|
50
|
-
if (Object.keys(wk).length > 0) ret
|
|
61
|
+
if (Object.keys(wk).length > 0) setOwnProperty(ret, key, wk);
|
|
51
62
|
} else if (typeof v == "object" && typeof value == "object" && Array.isArray(v) && Array.isArray(value)) {
|
|
52
63
|
const wk = generatePatchUnorderedArray(v, value);
|
|
53
|
-
if (Object.keys(wk).length > 0) ret
|
|
64
|
+
if (Object.keys(wk).length > 0) setOwnProperty(ret, key, wk);
|
|
54
65
|
} else if (typeof v != "object" && typeof value != "object") {
|
|
55
66
|
if (JSON.stringify(tempMap.get(key)) !== JSON.stringify(value)) {
|
|
56
|
-
ret
|
|
67
|
+
setOwnProperty(ret, key, value);
|
|
57
68
|
}
|
|
58
69
|
} else {
|
|
59
70
|
if (JSON.stringify(tempMap.get(key)) !== JSON.stringify(value)) {
|
|
60
|
-
ret
|
|
71
|
+
setOwnProperty(ret, key, { [MARK_SWAPPED]: value });
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
74
|
}
|
|
@@ -65,7 +76,7 @@ function generatePatchObj(from, to) {
|
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
for (const [key] of tempMap) {
|
|
68
|
-
ret
|
|
79
|
+
setOwnProperty(ret, key, MARK_DELETED);
|
|
69
80
|
}
|
|
70
81
|
return ret;
|
|
71
82
|
}
|
|
@@ -73,21 +84,21 @@ function applyPatch(from, patch) {
|
|
|
73
84
|
const ret = from;
|
|
74
85
|
const patches = Object.entries(patch);
|
|
75
86
|
for (const [key, value] of patches) {
|
|
76
|
-
if (value
|
|
87
|
+
if (value === MARK_DELETED) {
|
|
77
88
|
delete ret[key];
|
|
78
89
|
continue;
|
|
79
90
|
}
|
|
80
91
|
if (value === null) {
|
|
81
|
-
ret
|
|
92
|
+
setOwnProperty(ret, key, null);
|
|
82
93
|
continue;
|
|
83
94
|
}
|
|
84
95
|
if (typeof value == "object") {
|
|
85
|
-
if (MARK_SWAPPED
|
|
86
|
-
ret
|
|
96
|
+
if (hasOwnProperty(value, MARK_SWAPPED)) {
|
|
97
|
+
setOwnProperty(ret, key, value[MARK_SWAPPED]);
|
|
87
98
|
continue;
|
|
88
99
|
}
|
|
89
|
-
if (MARK_ISARRAY
|
|
90
|
-
if (!(key
|
|
100
|
+
if (hasOwnProperty(value, MARK_ISARRAY)) {
|
|
101
|
+
if (!hasOwnProperty(ret, key)) setOwnProperty(ret, key, []);
|
|
91
102
|
if (!Array.isArray(ret[key])) {
|
|
92
103
|
throw new Error("Patch target type is mismatched (array to something)");
|
|
93
104
|
}
|
|
@@ -97,19 +108,23 @@ function applyPatch(from, patch) {
|
|
|
97
108
|
value[MARK_ISARRAY]
|
|
98
109
|
);
|
|
99
110
|
const appliedArray = objectToUnorderedArray(appliedObject);
|
|
100
|
-
ret
|
|
111
|
+
setOwnProperty(ret, key, [...appliedArray]);
|
|
101
112
|
} else {
|
|
102
|
-
if (!(key
|
|
103
|
-
ret
|
|
113
|
+
if (!hasOwnProperty(ret, key)) {
|
|
114
|
+
setOwnProperty(ret, key, value);
|
|
104
115
|
continue;
|
|
105
116
|
}
|
|
106
|
-
|
|
107
|
-
ret
|
|
108
|
-
|
|
117
|
+
setOwnProperty(
|
|
118
|
+
ret,
|
|
119
|
+
key,
|
|
120
|
+
applyPatch(
|
|
121
|
+
ret[key],
|
|
122
|
+
value
|
|
123
|
+
)
|
|
109
124
|
);
|
|
110
125
|
}
|
|
111
126
|
} else {
|
|
112
|
-
ret
|
|
127
|
+
setOwnProperty(ret, key, value);
|
|
113
128
|
}
|
|
114
129
|
}
|
|
115
130
|
return ret;
|
|
@@ -121,24 +136,28 @@ function mergeObject(objA, objB) {
|
|
|
121
136
|
return objB;
|
|
122
137
|
}
|
|
123
138
|
for (const [key, v] of newEntries) {
|
|
124
|
-
if (key
|
|
139
|
+
if (hasOwnProperty(ret, key)) {
|
|
125
140
|
const value = ret[key];
|
|
126
141
|
if (typeof v !== typeof value || Array.isArray(v) !== Array.isArray(value)) {
|
|
127
|
-
ret
|
|
142
|
+
setOwnProperty(ret, key, v);
|
|
128
143
|
} else {
|
|
129
144
|
if (typeof v == "object" && typeof value == "object" && !Array.isArray(v) && !Array.isArray(value)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
setOwnProperty(
|
|
146
|
+
ret,
|
|
147
|
+
key,
|
|
148
|
+
mergeObject(
|
|
149
|
+
v,
|
|
150
|
+
value
|
|
151
|
+
)
|
|
133
152
|
);
|
|
134
153
|
} else if (typeof v == "object" && typeof value == "object" && Array.isArray(v) && Array.isArray(value)) {
|
|
135
|
-
ret
|
|
154
|
+
setOwnProperty(ret, key, [.../* @__PURE__ */ new Set([...v, ...value])]);
|
|
136
155
|
} else {
|
|
137
|
-
ret
|
|
156
|
+
setOwnProperty(ret, key, v);
|
|
138
157
|
}
|
|
139
158
|
}
|
|
140
159
|
} else {
|
|
141
|
-
ret
|
|
160
|
+
setOwnProperty(ret, key, v);
|
|
142
161
|
}
|
|
143
162
|
}
|
|
144
163
|
const retSorted = Object.fromEntries(Object.entries(ret).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
@@ -226,7 +226,7 @@ class RemoteService extends ServiceBase {
|
|
|
226
226
|
const signal = connectionScope.combine(opts?.signal);
|
|
227
227
|
const response = await this.performFetch(
|
|
228
228
|
requestSrc,
|
|
229
|
-
{ ...opts, headers, signal },
|
|
229
|
+
{ ...opts, headers, signal, cache: "no-store" },
|
|
230
230
|
useRequestAPI ? FetchMethod.native : FetchMethod.webCompat
|
|
231
231
|
);
|
|
232
232
|
return response;
|
|
@@ -242,7 +242,11 @@ class RemoteService extends ServiceBase {
|
|
|
242
242
|
this._log(
|
|
243
243
|
"Failed to fetch by native fetch API. Trying to fetch by API to get more information."
|
|
244
244
|
);
|
|
245
|
-
const resp2 = await this.performFetch(
|
|
245
|
+
const resp2 = await this.performFetch(
|
|
246
|
+
requestSrc,
|
|
247
|
+
{ ...opts, headers, cache: "no-store" },
|
|
248
|
+
FetchMethod.native
|
|
249
|
+
);
|
|
246
250
|
if (resp2.status / 100 == 2) {
|
|
247
251
|
this.showError(
|
|
248
252
|
"The request was successful by API. But the native fetch API failed! Please check CORS settings on the remote database!. While this condition, you cannot enable LiveSync",
|
|
@@ -2,6 +2,7 @@ import { type ObsidianLiveSyncSettings, type SettingsMigrationState } from "../.
|
|
|
2
2
|
import type { IAPIService, ISettingService } from "./IService.js";
|
|
3
3
|
import { ServiceBase, type ServiceContext } from "./ServiceBase.js";
|
|
4
4
|
import { createInstanceLogFunction } from "../lib/logUtils.js";
|
|
5
|
+
import { type PersistedSettings } from "../../common/models/setting.policy.js";
|
|
5
6
|
export interface SettingServiceDependencies {
|
|
6
7
|
APIService: IAPIService;
|
|
7
8
|
/** Optional host hook for applying the loaded display language to its catalogue. */
|
|
@@ -17,7 +18,7 @@ export declare abstract class SettingService<T extends ServiceContext = ServiceC
|
|
|
17
18
|
_settings: ObsidianLiveSyncSettings;
|
|
18
19
|
get settings(): ObsidianLiveSyncSettings;
|
|
19
20
|
set settings(value: ObsidianLiveSyncSettings);
|
|
20
|
-
protected abstract saveData(setting:
|
|
21
|
+
protected abstract saveData(setting: PersistedSettings): Promise<void>;
|
|
21
22
|
protected abstract loadData(): Promise<ObsidianLiveSyncSettings | undefined>;
|
|
22
23
|
private _lastPersistedSettings?;
|
|
23
24
|
private _settingsMigrationState?;
|
|
@@ -22,6 +22,15 @@ import {
|
|
|
22
22
|
} from "../../serviceFeatures/remoteConfig.js";
|
|
23
23
|
import { ConnectionStringParser } from "../../common/ConnectionString.js";
|
|
24
24
|
import { configuredIdKey } from "../../common/idDerivation.js";
|
|
25
|
+
import {
|
|
26
|
+
clearConnectionSettings,
|
|
27
|
+
hasConnectionSettings,
|
|
28
|
+
omitUnpersistedSettings,
|
|
29
|
+
prepareSettingsForPersistence,
|
|
30
|
+
requireConfigurationCiphertext,
|
|
31
|
+
restoreConnectionSettings,
|
|
32
|
+
selectConnectionSettings
|
|
33
|
+
} from "../../common/models/setting.policy.js";
|
|
25
34
|
class SettingService extends ServiceBase {
|
|
26
35
|
deviceAndVaultName = "";
|
|
27
36
|
APIService;
|
|
@@ -185,62 +194,46 @@ class SettingService extends ServiceBase {
|
|
|
185
194
|
this.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, settings.P2P_DevicePeerName.trim());
|
|
186
195
|
settings.P2P_DevicePeerName = "";
|
|
187
196
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) {
|
|
197
|
+
omitUnpersistedSettings(settings);
|
|
198
|
+
const configurationPassphrase = this.usedPassphrase || await this.getPassphrase(settings);
|
|
199
|
+
if (!configurationPassphrase) {
|
|
192
200
|
if (Object.values(settings.remoteConfigurations).some((config) => this.hasManagedP2PProfileURI(config.uri))) {
|
|
193
201
|
const message = "Failed to retrieve a passphrase for managed P2P source data. Settings were not saved.";
|
|
194
202
|
this._log(message, LOG_LEVEL_URGENT);
|
|
195
203
|
throw new Error(message);
|
|
196
204
|
}
|
|
205
|
+
if (hasConnectionSettings(settings) || settings.encrypt && settings.passphrase !== "" || settings.configPassphraseStore !== "" && Object.values(settings.remoteConfigurations).some(
|
|
206
|
+
(config) => !config.isEncrypted && config.uri.trim() !== ""
|
|
207
|
+
)) {
|
|
208
|
+
const message = "Failed to retrieve a configuration passphrase. Settings were not saved.";
|
|
209
|
+
this._log(message, LOG_LEVEL_URGENT);
|
|
210
|
+
throw new Error(message);
|
|
211
|
+
}
|
|
197
212
|
this._log("Failed to retrieve passphrase. data.json contains unencrypted items!", LOG_LEVEL_NOTICE);
|
|
198
213
|
} else {
|
|
199
|
-
if (settings
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
endpoint: settings.endpoint,
|
|
208
|
-
region: settings.region,
|
|
209
|
-
secretKey: settings.secretKey,
|
|
210
|
-
useCustomRequestHandler: settings.useCustomRequestHandler,
|
|
211
|
-
bucketCustomHeaders: settings.bucketCustomHeaders,
|
|
212
|
-
couchDB_CustomHeaders: settings.couchDB_CustomHeaders,
|
|
213
|
-
useJWT: settings.useJWT,
|
|
214
|
-
jwtKey: settings.jwtKey,
|
|
215
|
-
jwtAlgorithm: settings.jwtAlgorithm,
|
|
216
|
-
jwtKid: settings.jwtKid,
|
|
217
|
-
jwtExpDuration: settings.jwtExpDuration,
|
|
218
|
-
jwtSub: settings.jwtSub,
|
|
219
|
-
useRequestAPI: settings.useRequestAPI,
|
|
220
|
-
bucketPrefix: settings.bucketPrefix,
|
|
221
|
-
forcePathStyle: settings.forcePathStyle
|
|
222
|
-
};
|
|
223
|
-
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(
|
|
224
|
-
JSON.stringify(connectionSetting),
|
|
225
|
-
settings
|
|
214
|
+
if (hasConnectionSettings(settings)) {
|
|
215
|
+
settings.encryptedCouchDBConnection = requireConfigurationCiphertext(
|
|
216
|
+
await this.encryptConfigurationItem(JSON.stringify(selectConnectionSettings(settings)), settings)
|
|
217
|
+
);
|
|
218
|
+
clearConnectionSettings(settings);
|
|
219
|
+
} else if (settings.encryptedCouchDBConnection) {
|
|
220
|
+
const existingConnection = this.tryDecodeJson(
|
|
221
|
+
await this.decryptConfigurationItem(settings.encryptedCouchDBConnection, configurationPassphrase)
|
|
226
222
|
);
|
|
227
|
-
settings
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
settings.couchDB_USER = "";
|
|
231
|
-
settings.accessKey = "";
|
|
232
|
-
settings.bucket = "";
|
|
233
|
-
settings.region = "";
|
|
234
|
-
settings.secretKey = "";
|
|
235
|
-
settings.endpoint = "";
|
|
223
|
+
if (restoreConnectionSettings(this.cloneSettings(settings), existingConnection)) {
|
|
224
|
+
settings.encryptedCouchDBConnection = "";
|
|
225
|
+
}
|
|
236
226
|
}
|
|
237
227
|
if (settings.encrypt && settings.passphrase != "") {
|
|
238
|
-
settings.encryptedPassphrase =
|
|
228
|
+
settings.encryptedPassphrase = requireConfigurationCiphertext(
|
|
229
|
+
await this.encryptPlainConfigurationItem(settings.passphrase, settings)
|
|
230
|
+
);
|
|
239
231
|
settings.passphrase = "";
|
|
240
232
|
}
|
|
241
233
|
await this.encryptRemoteConfigurationUris(settings);
|
|
242
234
|
}
|
|
243
|
-
|
|
235
|
+
const persisted = prepareSettingsForPersistence(settings);
|
|
236
|
+
await this.saveData(persisted);
|
|
244
237
|
this._lastPersistedSettings = this.cloneSettings(this.settings);
|
|
245
238
|
void this.onSettingSaved(settings);
|
|
246
239
|
}
|
|
@@ -268,6 +261,11 @@ class SettingService extends ServiceBase {
|
|
|
268
261
|
this._log(message, LOG_LEVEL_URGENT);
|
|
269
262
|
throw new Error(message);
|
|
270
263
|
}
|
|
264
|
+
if (settings.configPassphraseStore !== "") {
|
|
265
|
+
const message = `Failed to encrypt remote configuration '${id}'. Settings were not saved.`;
|
|
266
|
+
this._log(message, LOG_LEVEL_URGENT);
|
|
267
|
+
throw new Error(message);
|
|
268
|
+
}
|
|
271
269
|
this._log(
|
|
272
270
|
`Failed to encrypt remote configuration '${id}'. This entry will be saved in plain text.`,
|
|
273
271
|
LOG_LEVEL_URGENT
|
|
@@ -496,34 +494,15 @@ class SettingService extends ServiceBase {
|
|
|
496
494
|
}
|
|
497
495
|
} else {
|
|
498
496
|
if (settings.encryptedCouchDBConnection) {
|
|
499
|
-
const keys = [
|
|
500
|
-
"couchDB_URI",
|
|
501
|
-
"couchDB_USER",
|
|
502
|
-
"couchDB_PASSWORD",
|
|
503
|
-
"couchDB_DBNAME",
|
|
504
|
-
"accessKey",
|
|
505
|
-
"bucket",
|
|
506
|
-
"endpoint",
|
|
507
|
-
"region",
|
|
508
|
-
"secretKey"
|
|
509
|
-
];
|
|
510
497
|
const decrypted = this.tryDecodeJson(
|
|
511
498
|
await this.decryptConfigurationItem(settings.encryptedCouchDBConnection, passphrase)
|
|
512
499
|
);
|
|
513
|
-
if (decrypted) {
|
|
514
|
-
for (const key of keys) {
|
|
515
|
-
if (key in decrypted) {
|
|
516
|
-
settings[key] = decrypted[key];
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
} else {
|
|
500
|
+
if (!restoreConnectionSettings(settings, decrypted)) {
|
|
520
501
|
this._log(
|
|
521
502
|
"Failed to decrypt passphrase from data.json! Ensure configuration is correct before syncing with remote.",
|
|
522
503
|
LOG_LEVEL_URGENT
|
|
523
504
|
);
|
|
524
|
-
|
|
525
|
-
settings[key] = "";
|
|
526
|
-
}
|
|
505
|
+
clearConnectionSettings(settings);
|
|
527
506
|
}
|
|
528
507
|
}
|
|
529
508
|
if (settings.encrypt && settings.encryptedPassphrase) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { ServiceContext } from "../../base/ServiceBase.js";
|
|
2
2
|
import { SettingService, type SettingServiceDependencies } from "../../base/SettingService.js";
|
|
3
3
|
import type { ObsidianLiveSyncSettings } from "../../../common/types.js";
|
|
4
|
+
import type { PersistedSettings } from "../../../common/models/setting.policy.js";
|
|
4
5
|
export declare class InjectableSettingService<T extends ServiceContext> extends SettingService<T> {
|
|
5
6
|
constructor(context: T, dependencies: SettingServiceDependencies);
|
|
6
7
|
protected setItem(key: string, value: string): void;
|
|
7
8
|
protected getItem(key: string): string;
|
|
8
9
|
protected deleteItem(key: string): void;
|
|
9
|
-
saveData: import("../../lib/HandlerUtils.js").HandlerFunction<(data:
|
|
10
|
+
saveData: import("../../lib/HandlerUtils.js").HandlerFunction<(data: PersistedSettings) => Promise<void>, unknown>;
|
|
10
11
|
loadData: import("../../lib/HandlerUtils.js").HandlerFunction<() => Promise<ObsidianLiveSyncSettings | undefined>, unknown>;
|
|
11
12
|
}
|