@vrtmrz/livesync-commonlib 0.1.34 → 0.1.35

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.
@@ -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[key] = value;
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[key] = { [MARK_SWAPPED]: value };
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[key] = { [MARK_SWAPPED]: value };
53
+ setOwnProperty(ret, key, { [MARK_SWAPPED]: value });
43
54
  } else if (v !== null && value === null) {
44
- ret[key] = { [MARK_SWAPPED]: value };
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[key] = wk;
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[key] = wk;
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[key] = value;
67
+ setOwnProperty(ret, key, value);
57
68
  }
58
69
  } else {
59
70
  if (JSON.stringify(tempMap.get(key)) !== JSON.stringify(value)) {
60
- ret[key] = { [MARK_SWAPPED]: value };
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[key] = MARK_DELETED;
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 == MARK_DELETED) {
87
+ if (value === MARK_DELETED) {
77
88
  delete ret[key];
78
89
  continue;
79
90
  }
80
91
  if (value === null) {
81
- ret[key] = null;
92
+ setOwnProperty(ret, key, null);
82
93
  continue;
83
94
  }
84
95
  if (typeof value == "object") {
85
- if (MARK_SWAPPED in value) {
86
- ret[key] = value[MARK_SWAPPED];
96
+ if (hasOwnProperty(value, MARK_SWAPPED)) {
97
+ setOwnProperty(ret, key, value[MARK_SWAPPED]);
87
98
  continue;
88
99
  }
89
- if (MARK_ISARRAY in value) {
90
- if (!(key in ret)) ret[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[key] = [...appliedArray];
111
+ setOwnProperty(ret, key, [...appliedArray]);
101
112
  } else {
102
- if (!(key in ret)) {
103
- ret[key] = value;
113
+ if (!hasOwnProperty(ret, key)) {
114
+ setOwnProperty(ret, key, value);
104
115
  continue;
105
116
  }
106
- ret[key] = applyPatch(
107
- ret[key],
108
- value
117
+ setOwnProperty(
118
+ ret,
119
+ key,
120
+ applyPatch(
121
+ ret[key],
122
+ value
123
+ )
109
124
  );
110
125
  }
111
126
  } else {
112
- ret[key] = value;
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 in ret) {
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[key] = v;
142
+ setOwnProperty(ret, key, v);
128
143
  } else {
129
144
  if (typeof v == "object" && typeof value == "object" && !Array.isArray(v) && !Array.isArray(value)) {
130
- ret[key] = mergeObject(
131
- v,
132
- value
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[key] = [.../* @__PURE__ */ new Set([...v, ...value])];
154
+ setOwnProperty(ret, key, [.../* @__PURE__ */ new Set([...v, ...value])]);
136
155
  } else {
137
- ret[key] = v;
156
+ setOwnProperty(ret, key, v);
138
157
  }
139
158
  }
140
159
  } else {
141
- ret[key] = v;
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));
@@ -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: ObsidianLiveSyncSettings): Promise<void>;
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
- delete settings.P2P_managedType;
189
- delete settings.P2P_managedId;
190
- delete settings.P2P_managedToken;
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.couchDB_PASSWORD != "" || settings.couchDB_URI != "" || settings.couchDB_USER != "" || settings.couchDB_DBNAME) {
200
- const connectionSetting = {
201
- couchDB_DBNAME: settings.couchDB_DBNAME,
202
- couchDB_PASSWORD: settings.couchDB_PASSWORD,
203
- couchDB_URI: settings.couchDB_URI,
204
- couchDB_USER: settings.couchDB_USER,
205
- accessKey: settings.accessKey,
206
- bucket: settings.bucket,
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.couchDB_PASSWORD = "";
228
- settings.couchDB_DBNAME = "";
229
- settings.couchDB_URI = "";
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 = await this.encryptPlainConfigurationItem(settings.passphrase, settings);
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
- await this.saveData(settings);
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
- for (const key of keys) {
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: ObsidianLiveSyncSettings) => Promise<void>, unknown>;
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
  }
@@ -12,3 +12,4 @@ export { assessTweakCompatibility } from "./common/models/tweak.compatibility.js
12
12
  export type { TweakAssessment, TweakAssessmentEntry, TweakAssessmentRelation, TweakAssessmentValue, TweakTransition, TweakTransitionReason, } from "./common/models/tweak.compatibility.js";
13
13
  export type { TweakReconstruction, TweakValues } from "./common/models/tweak.definition.js";
14
14
  export type { ObsidianLiveSyncSettings, RemoteDBSettings, RemoteTypeSettings } from "./common/models/setting.type.js";
15
+ export { SETTINGS_PROPERTY_POLICY, SettingPolicies, createMarkdownSettings, mergeMarkdownSettings, type SettingPropertyPolicy, type MarkdownSettings, type CredentialFreeMarkdownSettings, type PersistedSettings, } from "./common/models/setting.policy.js";