@vrtmrz/livesync-commonlib 0.1.17 → 0.1.19-rc.0
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/API/DirectFileManipulatorV2.js +3 -1
- package/dist/common/ConnectionString.js +9 -1
- package/dist/common/models/fileaccess.type.d.ts +7 -0
- package/dist/common/models/setting.const.d.ts +18 -0
- package/dist/common/models/setting.const.defaults.js +10 -1
- package/dist/common/models/setting.const.js +12 -0
- package/dist/common/models/setting.const.qr.js +3 -1
- package/dist/common/models/setting.p2p.d.ts +21 -0
- package/dist/common/models/setting.p2p.js +35 -0
- package/dist/common/models/setting.type.d.ts +11 -1
- package/dist/common/models/shared.definition.configNames.js +8 -0
- package/dist/common/models/tweak.definition.d.ts +2 -0
- package/dist/common/settingConstants.d.ts +2 -0
- package/dist/common/types.d.ts +5 -3
- package/dist/common/types.js +17 -1
- package/dist/common/utils.js +4 -1
- package/dist/interfaces/DatabaseRebuilder.d.ts +4 -2
- package/dist/managers/StorageEventManager.d.ts +16 -5
- package/dist/managers/StorageEventManager.js +46 -8
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
- package/dist/pouchdb/LiveSyncLocalDB.js +120 -38
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +8 -0
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +36 -8
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +2 -2
- package/dist/rpc/transports/TrysteroTransport.d.ts +2 -0
- package/dist/rpc/transports/TrysteroTransport.js +18 -2
- package/dist/rpc/transports/trysteroUtils.js +12 -1
- package/dist/serviceFeatures/prepareDatabaseForUse.js +18 -15
- package/dist/serviceModules/Rebuilder.d.ts +27 -4
- package/dist/serviceModules/Rebuilder.js +113 -35
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +17 -0
- package/dist/serviceModules/ServiceFileHandlerBase.js +145 -9
- package/dist/services/base/DatabaseService.d.ts +11 -3
- package/dist/services/base/DatabaseService.js +63 -7
- package/dist/services/base/IService.d.ts +1 -0
- package/dist/services/base/ReplicationService.d.ts +16 -0
- package/dist/services/base/ReplicationService.js +36 -4
- package/dist/services/base/ReplicatorService.d.ts +1 -0
- package/dist/services/base/ReplicatorService.js +32 -13
- package/dist/worker/bgWorker.js +1 -1
- package/docs/development.md +2 -0
- package/docs/p2p-transport-lifecycle.md +4 -2
- package/docs/proven-in-use.md +5 -1
- package/docs/releasing.md +24 -0
- package/docs/remote-configurations.md +2 -0
- package/package.json +1 -1
|
@@ -48,7 +48,9 @@ class DirectFileManipulator {
|
|
|
48
48
|
async init() {
|
|
49
49
|
try {
|
|
50
50
|
await this.services.appLifecycle.onReady();
|
|
51
|
-
await this.liveSyncLocalDB.initializeDatabase()
|
|
51
|
+
if (!await this.liveSyncLocalDB.initializeDatabase()) {
|
|
52
|
+
throw new Error("Direct database initialisation was rejected.");
|
|
53
|
+
}
|
|
52
54
|
this.liveSyncLocalDB.refreshSettings();
|
|
53
55
|
this.ready.resolve();
|
|
54
56
|
} catch (error) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normaliseP2PConnectionPath, normaliseP2PMaxWirePayloadBytes } from "./models/setting.p2p.js";
|
|
1
2
|
const PROXY_SCHEME = "https";
|
|
2
3
|
function parseSlsUri(uriString) {
|
|
3
4
|
const match = uriString.match(/^sls\+([^:]+):(.*)$/);
|
|
@@ -155,7 +156,9 @@ class ConnectionStringParser {
|
|
|
155
156
|
P2P_AutoBroadcast: searchParams.get("autoBroadcast") === "true",
|
|
156
157
|
P2P_turnServers: searchParams.get("turnServers") || "",
|
|
157
158
|
P2P_turnUsername: searchParams.get("turnUser") || "",
|
|
158
|
-
P2P_turnCredential: searchParams.get("turnPass") || ""
|
|
159
|
+
P2P_turnCredential: searchParams.get("turnPass") || "",
|
|
160
|
+
P2P_maxWirePayloadBytes: normaliseP2PMaxWirePayloadBytes(Number(searchParams.get("maxWirePayloadBytes"))),
|
|
161
|
+
P2P_connectionPath: normaliseP2PConnectionPath(searchParams.get("connectionPath"))
|
|
159
162
|
};
|
|
160
163
|
}
|
|
161
164
|
static serializeP2P(settings) {
|
|
@@ -168,6 +171,11 @@ class ConnectionStringParser {
|
|
|
168
171
|
if (settings.P2P_turnServers) searchParams.set("turnServers", settings.P2P_turnServers);
|
|
169
172
|
if (settings.P2P_turnUsername) searchParams.set("turnUser", settings.P2P_turnUsername);
|
|
170
173
|
if (settings.P2P_turnCredential) searchParams.set("turnPass", settings.P2P_turnCredential);
|
|
174
|
+
searchParams.set(
|
|
175
|
+
"maxWirePayloadBytes",
|
|
176
|
+
String(normaliseP2PMaxWirePayloadBytes(settings.P2P_maxWirePayloadBytes))
|
|
177
|
+
);
|
|
178
|
+
searchParams.set("connectionPath", normaliseP2PConnectionPath(settings.P2P_connectionPath));
|
|
171
179
|
const credentials = settings.P2P_passphrase ? `:${encodeURIComponent(settings.P2P_passphrase)}@` : "";
|
|
172
180
|
const host = encodeURIComponent(settings.P2P_roomID);
|
|
173
181
|
const query = searchParams.toString() ? `?${searchParams.toString()}` : "";
|
|
@@ -59,6 +59,13 @@ export type FileEventItem = {
|
|
|
59
59
|
type: FileEventType;
|
|
60
60
|
args: FileEventArgs;
|
|
61
61
|
key: string;
|
|
62
|
+
/**
|
|
63
|
+
* The event was loaded from the persisted storage-operation snapshot.
|
|
64
|
+
*
|
|
65
|
+
* A restored event records pending operation intent, not current storage
|
|
66
|
+
* state. File handlers must revalidate it before applying side effects.
|
|
67
|
+
*/
|
|
68
|
+
restoredFromPreviousRuntime?: true;
|
|
62
69
|
skipBatchWait?: boolean;
|
|
63
70
|
cancelled?: boolean;
|
|
64
71
|
batched?: boolean;
|
|
@@ -9,6 +9,24 @@ export declare const RemoteTypes: {
|
|
|
9
9
|
export declare const REMOTE_COUCHDB: "";
|
|
10
10
|
export declare const REMOTE_MINIO: "MINIO";
|
|
11
11
|
export declare const REMOTE_P2P: "ONLY_P2P";
|
|
12
|
+
/** ICE route policies supported by a P2P connection profile. */
|
|
13
|
+
export declare const P2PConnectionPaths: {
|
|
14
|
+
readonly Automatic: "automatic";
|
|
15
|
+
readonly Relay: "relay";
|
|
16
|
+
};
|
|
17
|
+
export type P2PConnectionPath = (typeof P2PConnectionPaths)[keyof typeof P2PConnectionPaths];
|
|
18
|
+
/**
|
|
19
|
+
* User-facing message-size presets for the P2P RPC transport.
|
|
20
|
+
*
|
|
21
|
+
* These values limit Commonlib wire payloads before Trystero applies its own
|
|
22
|
+
* framing. They are not file Chunk sizes or network MTUs.
|
|
23
|
+
*/
|
|
24
|
+
export declare const P2PMessageSizePresets: {
|
|
25
|
+
readonly Standard: 15360;
|
|
26
|
+
readonly Reduced: 2048;
|
|
27
|
+
readonly Conservative: 1024;
|
|
28
|
+
readonly MaximumCompatibility: 800;
|
|
29
|
+
};
|
|
12
30
|
export declare const E2EEAlgorithmNames: {
|
|
13
31
|
readonly "": "V1: Legacy";
|
|
14
32
|
readonly v2: "V2: AES-256-GCM With HKDF";
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ChunkAlgorithms,
|
|
3
|
+
CURRENT_SETTING_VERSION,
|
|
4
|
+
E2EEAlgorithms,
|
|
5
|
+
P2PConnectionPaths,
|
|
6
|
+
P2PMessageSizePresets,
|
|
7
|
+
REMOTE_COUCHDB
|
|
8
|
+
} from "./setting.const.js";
|
|
2
9
|
import { PREFERRED_BASE } from "./setting.const.preferred.js";
|
|
3
10
|
import { AutoAccepting } from "./setting.type.js";
|
|
4
11
|
const P2P_DEFAULT_SETTINGS = {
|
|
@@ -21,6 +28,8 @@ const P2P_DEFAULT_SETTINGS = {
|
|
|
21
28
|
P2P_turnServers: "",
|
|
22
29
|
P2P_turnUsername: "",
|
|
23
30
|
P2P_turnCredential: "",
|
|
31
|
+
P2P_maxWirePayloadBytes: P2PMessageSizePresets.Standard,
|
|
32
|
+
P2P_connectionPath: P2PConnectionPaths.Automatic,
|
|
24
33
|
P2P_useDiagRTC: false
|
|
25
34
|
};
|
|
26
35
|
const SETTINGS_SCHEMA_DEFAULTS = {
|
|
@@ -9,6 +9,16 @@ const RemoteTypes = {
|
|
|
9
9
|
const REMOTE_COUCHDB = RemoteTypes.REMOTE_COUCHDB;
|
|
10
10
|
const REMOTE_MINIO = RemoteTypes.REMOTE_MINIO;
|
|
11
11
|
const REMOTE_P2P = RemoteTypes.REMOTE_P2P;
|
|
12
|
+
const P2PConnectionPaths = {
|
|
13
|
+
Automatic: "automatic",
|
|
14
|
+
Relay: "relay"
|
|
15
|
+
};
|
|
16
|
+
const P2PMessageSizePresets = {
|
|
17
|
+
Standard: 15360,
|
|
18
|
+
Reduced: 2048,
|
|
19
|
+
Conservative: 1024,
|
|
20
|
+
MaximumCompatibility: 800
|
|
21
|
+
};
|
|
12
22
|
const E2EEAlgorithmNames = {
|
|
13
23
|
"": "V1: Legacy",
|
|
14
24
|
v2: "V2: AES-256-GCM With HKDF",
|
|
@@ -59,6 +69,8 @@ export {
|
|
|
59
69
|
MODE_SELECTIVE,
|
|
60
70
|
MODE_SHINY,
|
|
61
71
|
NetworkWarningStyles,
|
|
72
|
+
P2PConnectionPaths,
|
|
73
|
+
P2PMessageSizePresets,
|
|
62
74
|
REMOTE_COUCHDB,
|
|
63
75
|
REMOTE_MINIO,
|
|
64
76
|
REMOTE_P2P,
|
|
@@ -168,8 +168,10 @@ const KeyIndexOfSettings = {
|
|
|
168
168
|
P2P_ActiveRemoteConfigurationId: 159,
|
|
169
169
|
autoAcceptCompatibleTweak: 160,
|
|
170
170
|
tweakModified: -1,
|
|
171
|
-
P2P_useDiagRTC: -1
|
|
171
|
+
P2P_useDiagRTC: -1,
|
|
172
172
|
// Do not encode into the QR Code.
|
|
173
|
+
P2P_maxWirePayloadBytes: 163,
|
|
174
|
+
P2P_connectionPath: 164
|
|
173
175
|
};
|
|
174
176
|
export {
|
|
175
177
|
KeyIndexOfSettings
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type P2PConnectionPath } from "./setting.const.js";
|
|
2
|
+
/**
|
|
3
|
+
* Return a safe outgoing RPC wire-payload bound for Trystero.
|
|
4
|
+
*
|
|
5
|
+
* Existing profiles can omit this value. Invalid, excessively small, and
|
|
6
|
+
* excessively large values retain the established 15,360-byte behaviour.
|
|
7
|
+
*/
|
|
8
|
+
export declare function normaliseP2PMaxWirePayloadBytes(value: unknown): number;
|
|
9
|
+
/** Return a recognised P2P connection path, defaulting legacy values to automatic selection. */
|
|
10
|
+
export declare function normaliseP2PConnectionPath(value: unknown): P2PConnectionPath;
|
|
11
|
+
/** Split the stored comma-separated TURN server list without changing its entries. */
|
|
12
|
+
export declare function splitP2PTurnServerUrls(value: string): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Report whether a value is a minimally valid TURN or TURN-over-TLS URL.
|
|
15
|
+
*
|
|
16
|
+
* Full allocation validation remains the responsibility of WebRTC. This
|
|
17
|
+
* check only prevents relay-only mode when no TURN endpoint can be attempted.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isValidP2PTurnServerUrl(value: string): boolean;
|
|
20
|
+
/** Report whether a stored TURN server list contains at least one usable TURN URL. */
|
|
21
|
+
export declare function hasValidP2PTurnServerUrl(value: string): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { P2PConnectionPaths, P2PMessageSizePresets } from "./setting.const.js";
|
|
2
|
+
function normaliseP2PMaxWirePayloadBytes(value) {
|
|
3
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < P2PMessageSizePresets.MaximumCompatibility || value > P2PMessageSizePresets.Standard) {
|
|
4
|
+
return P2PMessageSizePresets.Standard;
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
function normaliseP2PConnectionPath(value) {
|
|
9
|
+
return value === P2PConnectionPaths.Relay ? P2PConnectionPaths.Relay : P2PConnectionPaths.Automatic;
|
|
10
|
+
}
|
|
11
|
+
function splitP2PTurnServerUrls(value) {
|
|
12
|
+
return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
13
|
+
}
|
|
14
|
+
function isValidP2PTurnServerUrl(value) {
|
|
15
|
+
const candidate = value.trim();
|
|
16
|
+
if (!candidate || /\s/.test(candidate)) return false;
|
|
17
|
+
try {
|
|
18
|
+
const parsed = new URL(candidate);
|
|
19
|
+
const protocol = parsed.protocol.toLowerCase();
|
|
20
|
+
if (protocol !== "turn:" && protocol !== "turns:") return false;
|
|
21
|
+
return (parsed.hostname || parsed.pathname).length > 0;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function hasValidP2PTurnServerUrl(value) {
|
|
27
|
+
return splitP2PTurnServerUrls(value).some(isValidP2PTurnServerUrl);
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
hasValidP2PTurnServerUrl,
|
|
31
|
+
isValidP2PTurnServerUrl,
|
|
32
|
+
normaliseP2PConnectionPath,
|
|
33
|
+
normaliseP2PMaxWirePayloadBytes,
|
|
34
|
+
splitP2PTurnServerUrls
|
|
35
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChunkAlgorithms, E2EEAlgorithms, HashAlgorithms, MODE_AUTOMATIC, MODE_PAUSED, MODE_SELECTIVE, MODE_SHINY, RemoteTypes } from "./setting.const.js";
|
|
1
|
+
import type { ChunkAlgorithms, E2EEAlgorithms, HashAlgorithms, MODE_AUTOMATIC, MODE_PAUSED, MODE_SELECTIVE, MODE_SHINY, P2PConnectionPath, RemoteTypes } from "./setting.const.js";
|
|
2
2
|
import type { CustomRegExpSourceList } from "./shared.type.util.js";
|
|
3
3
|
import type { JWTAlgorithm } from "./auth.type.js";
|
|
4
4
|
/** Display languages recognised by the current LiveSync settings schema. */
|
|
@@ -528,6 +528,16 @@ export interface P2PConnectionInfo {
|
|
|
528
528
|
* The TURN credential (password, secret, etc...) for the P2P connection.
|
|
529
529
|
*/
|
|
530
530
|
P2P_turnCredential: string;
|
|
531
|
+
/**
|
|
532
|
+
* Maximum serialised RPC wire payload sent through Trystero before
|
|
533
|
+
* Commonlib splits it. Applies to outgoing messages.
|
|
534
|
+
*/
|
|
535
|
+
P2P_maxWirePayloadBytes?: number;
|
|
536
|
+
/**
|
|
537
|
+
* ICE route policy for this P2P profile. Relay-only mode is effective only
|
|
538
|
+
* when the profile contains at least one valid TURN URL.
|
|
539
|
+
*/
|
|
540
|
+
P2P_connectionPath?: P2PConnectionPath;
|
|
531
541
|
/**
|
|
532
542
|
* Use Diagnostic Wrapper for RTCPeerConnection to collect statistics.
|
|
533
543
|
*/
|
|
@@ -152,6 +152,14 @@ const configurationNames = {
|
|
|
152
152
|
desc: "The credential/password for the TURN servers.",
|
|
153
153
|
isHidden: true
|
|
154
154
|
},
|
|
155
|
+
P2P_maxWirePayloadBytes: {
|
|
156
|
+
name: "P2P Message Size",
|
|
157
|
+
desc: "The maximum outgoing RPC wire payload before Commonlib splits it for the P2P transport."
|
|
158
|
+
},
|
|
159
|
+
P2P_connectionPath: {
|
|
160
|
+
name: "Connection Path",
|
|
161
|
+
desc: "Select the WebRTC route automatically or require a configured TURN relay."
|
|
162
|
+
},
|
|
155
163
|
useOnlyLocalChunk: {
|
|
156
164
|
name: "Use Only Local Chunks",
|
|
157
165
|
desc: "If enabled, the plugin will not attempt to connect to the remote database even if the chunk was not found locally.",
|
|
@@ -186,6 +186,8 @@ export declare const TweakValuesTemplate: {
|
|
|
186
186
|
P2P_turnServers?: string;
|
|
187
187
|
P2P_turnUsername?: string;
|
|
188
188
|
P2P_turnCredential?: string;
|
|
189
|
+
P2P_maxWirePayloadBytes?: number;
|
|
190
|
+
P2P_connectionPath?: import("./setting.const.js").P2PConnectionPath;
|
|
189
191
|
P2P_useDiagRTC?: boolean;
|
|
190
192
|
};
|
|
191
193
|
export type TweakValues = Partial<typeof TweakValuesTemplate>;
|
|
@@ -196,6 +196,8 @@ export declare const AllSettingDefault: {
|
|
|
196
196
|
P2P_turnServers: string;
|
|
197
197
|
P2P_turnUsername: string;
|
|
198
198
|
P2P_turnCredential: string;
|
|
199
|
+
P2P_maxWirePayloadBytes?: number;
|
|
200
|
+
P2P_connectionPath?: import("./types.js").P2PConnectionPath;
|
|
199
201
|
P2P_useDiagRTC?: boolean;
|
|
200
202
|
};
|
|
201
203
|
export type AllSettings = ObsidianLiveSyncSettings & OnDialogSettings;
|
package/dist/common/types.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ import { ChunkTypes, EntryTypes, MILESTONE_DOCID, NODEINFO_DOCID, NoteTypes, SYN
|
|
|
8
8
|
import { AUTO_MERGED, CANCELLED, LEAVE_TO_SUBSEQUENT, MISSING_OR_ERROR, NOT_CONFLICTED, TIME_ARGUMENT_INFINITY } from "./models/shared.const.symbols.js";
|
|
9
9
|
import { IDPrefixes, LEAF_WAIT_ONLY_REMOTE, LEAF_WAIT_TIMEOUT, LEAF_WAIT_TIMEOUT_SEQUENTIAL_REPLICATOR, MAX_DOC_SIZE, MAX_DOC_SIZE_BIN, PREFIX_CHUNK, PREFIX_ENCRYPTED_CHUNK, PREFIX_OBFUSCATED, RECENT_MODIFIED_DOCS_QTY, REPLICATION_BUSY_TIMEOUT, SALT_OF_ID, SALT_OF_PASSPHRASE, SEED_MURMURHASH, VER } from "./models/shared.const.behabiour.js";
|
|
10
10
|
import { AutoAccepting, type BucketSyncSetting, type ChunkSplitterVersion, type ConfigPassphraseStore, type CouchDBConnection, type E2EEAlgorithm, type EncryptionSettings, type HashAlgorithm, type HasSettings, type LocalDBSettings, type ObsidianLiveSyncSettings, type P2PConnectionInfo, type P2PSyncSetting, type PluginSyncSettingEntry, type RemoteDBSettings, type RemoteType, type RemoteTypeSettings, type SYNC_MODE } from "./models/setting.type.js";
|
|
11
|
-
import { ChunkAlgorithmNames, ChunkAlgorithms, CURRENT_SETTING_VERSION, E2EEAlgorithmNames, E2EEAlgorithms, HashAlgorithms, REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, RemoteTypes, SETTING_VERSION_INITIAL, SETTING_VERSION_SUPPORT_CASE_INSENSITIVE, MODE_AUTOMATIC, MODE_PAUSED, MODE_SELECTIVE, MODE_SHINY } from "./models/setting.const.js";
|
|
11
|
+
import { ChunkAlgorithmNames, ChunkAlgorithms, CURRENT_SETTING_VERSION, E2EEAlgorithmNames, E2EEAlgorithms, HashAlgorithms, REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, RemoteTypes, SETTING_VERSION_INITIAL, SETTING_VERSION_SUPPORT_CASE_INSENSITIVE, MODE_AUTOMATIC, MODE_PAUSED, MODE_SELECTIVE, MODE_SHINY, P2PConnectionPaths, P2PMessageSizePresets, type P2PConnectionPath } from "./models/setting.const.js";
|
|
12
|
+
import { hasValidP2PTurnServerUrl, isValidP2PTurnServerUrl, normaliseP2PConnectionPath, normaliseP2PMaxWirePayloadBytes, splitP2PTurnServerUrls } from "./models/setting.p2p.js";
|
|
12
13
|
import { PREFERRED_BASE, PREFERRED_JOURNAL_SYNC, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED } from "./models/setting.const.preferred.js";
|
|
13
14
|
import { P2P_DEFAULT_SETTINGS, DEFAULT_SETTINGS, NEW_VAULT_SETTINGS, SETTINGS_SCHEMA_DEFAULTS, createNewVaultSettings } from "./models/setting.const.defaults.js";
|
|
14
15
|
import { prepareSettingsForLoad, SettingsMigrationReviewCodes, type PreparedSettings, type SettingsMigrationReviewCode, type SettingsMigrationReviewReason, type SettingsMigrationState } from "./models/setting.lifecycle.js";
|
|
@@ -34,8 +35,9 @@ export { MODE_SELECTIVE, MODE_AUTOMATIC, MODE_PAUSED, MODE_SHINY, type SYNC_MODE
|
|
|
34
35
|
export { type PluginSyncSettingEntry };
|
|
35
36
|
export { SETTING_VERSION_INITIAL, SETTING_VERSION_SUPPORT_CASE_INSENSITIVE, CURRENT_SETTING_VERSION };
|
|
36
37
|
export type { BucketSyncSetting, LocalDBSettings };
|
|
37
|
-
export { RemoteTypes, REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, type RemoteType, AutoAccepting };
|
|
38
|
-
export type { P2PConnectionInfo, P2PSyncSetting };
|
|
38
|
+
export { RemoteTypes, REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, P2PConnectionPaths, P2PMessageSizePresets, type RemoteType, AutoAccepting, };
|
|
39
|
+
export type { P2PConnectionInfo, P2PConnectionPath, P2PSyncSetting };
|
|
40
|
+
export { hasValidP2PTurnServerUrl, isValidP2PTurnServerUrl, normaliseP2PConnectionPath, normaliseP2PMaxWirePayloadBytes, splitP2PTurnServerUrls, };
|
|
39
41
|
export { P2P_DEFAULT_SETTINGS };
|
|
40
42
|
export type { RemoteTypeSettings };
|
|
41
43
|
export { E2EEAlgorithmNames, E2EEAlgorithms, type E2EEAlgorithm };
|
package/dist/common/types.js
CHANGED
|
@@ -59,8 +59,17 @@ import {
|
|
|
59
59
|
MODE_AUTOMATIC,
|
|
60
60
|
MODE_PAUSED,
|
|
61
61
|
MODE_SELECTIVE,
|
|
62
|
-
MODE_SHINY
|
|
62
|
+
MODE_SHINY,
|
|
63
|
+
P2PConnectionPaths,
|
|
64
|
+
P2PMessageSizePresets
|
|
63
65
|
} from "./models/setting.const.js";
|
|
66
|
+
import {
|
|
67
|
+
hasValidP2PTurnServerUrl,
|
|
68
|
+
isValidP2PTurnServerUrl,
|
|
69
|
+
normaliseP2PConnectionPath,
|
|
70
|
+
normaliseP2PMaxWirePayloadBytes,
|
|
71
|
+
splitP2PTurnServerUrls
|
|
72
|
+
} from "./models/setting.p2p.js";
|
|
64
73
|
import {
|
|
65
74
|
PREFERRED_BASE,
|
|
66
75
|
PREFERRED_JOURNAL_SYNC,
|
|
@@ -181,6 +190,8 @@ export {
|
|
|
181
190
|
NODEINFO_DOCID,
|
|
182
191
|
NOT_CONFLICTED,
|
|
183
192
|
NoteTypes,
|
|
193
|
+
P2PConnectionPaths,
|
|
194
|
+
P2PMessageSizePresets,
|
|
184
195
|
P2P_DEFAULT_SETTINGS,
|
|
185
196
|
PREFERRED_BASE,
|
|
186
197
|
PREFERRED_JOURNAL_SYNC,
|
|
@@ -225,7 +236,12 @@ export {
|
|
|
225
236
|
configURIBaseQR,
|
|
226
237
|
configurationNames,
|
|
227
238
|
createNewVaultSettings,
|
|
239
|
+
hasValidP2PTurnServerUrl,
|
|
228
240
|
isMetaEntry,
|
|
241
|
+
isValidP2PTurnServerUrl,
|
|
242
|
+
normaliseP2PConnectionPath,
|
|
243
|
+
normaliseP2PMaxWirePayloadBytes,
|
|
229
244
|
prepareSettingsForLoad,
|
|
245
|
+
splitP2PTurnServerUrls,
|
|
230
246
|
statusDisplay
|
|
231
247
|
};
|
package/dist/common/utils.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
PREFIX_OBFUSCATED,
|
|
8
8
|
SYNCINFO_ID
|
|
9
9
|
} from "./types.js";
|
|
10
|
+
import { normaliseP2PConnectionPath, normaliseP2PMaxWirePayloadBytes } from "./models/setting.p2p.js";
|
|
10
11
|
import { isErrorOfMissingDoc } from "../pouchdb/utils_couchdb.js";
|
|
11
12
|
import { replaceAll, replaceAllPairs } from "octagonal-wheels/string";
|
|
12
13
|
import { concatUInt8Array } from "octagonal-wheels/binary";
|
|
@@ -459,7 +460,9 @@ function pickP2PSyncSettings(setting) {
|
|
|
459
460
|
P2P_DevicePeerName: setting.P2P_DevicePeerName || "",
|
|
460
461
|
P2P_turnServers: setting.P2P_turnServers,
|
|
461
462
|
P2P_turnUsername: setting.P2P_turnUsername,
|
|
462
|
-
P2P_turnCredential: setting.P2P_turnCredential
|
|
463
|
+
P2P_turnCredential: setting.P2P_turnCredential,
|
|
464
|
+
P2P_maxWirePayloadBytes: normaliseP2PMaxWirePayloadBytes(setting.P2P_maxWirePayloadBytes),
|
|
465
|
+
P2P_connectionPath: normaliseP2PConnectionPath(setting.P2P_connectionPath)
|
|
463
466
|
};
|
|
464
467
|
}
|
|
465
468
|
function wrapByDefault(func, onError) {
|
|
@@ -17,7 +17,9 @@ export interface Rebuilder {
|
|
|
17
17
|
/** See {@link scheduleRebuild}; this variant writes the Fetch flag. */
|
|
18
18
|
scheduleFetch(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Complete the final reflection scan and checks owned by a fetch, then establish application readiness.
|
|
21
|
+
*
|
|
22
|
+
* @returns `true` when every required completion phase succeeded; otherwise, `false`.
|
|
21
23
|
*/
|
|
22
|
-
finishRebuild(): Promise<
|
|
24
|
+
finishRebuild(): Promise<boolean>;
|
|
23
25
|
}
|
|
@@ -21,6 +21,9 @@ type FileEventItemSentinelFlush = {
|
|
|
21
21
|
type: typeof TYPE_SENTINEL_FLUSH;
|
|
22
22
|
};
|
|
23
23
|
export type FileEventItemSentinel = FileEventItemSentinelFlush;
|
|
24
|
+
type RunQueuedEventsOptions = {
|
|
25
|
+
waitForProcessing?: boolean;
|
|
26
|
+
};
|
|
24
27
|
export interface StorageEventManagerBaseDependencies {
|
|
25
28
|
setting: SettingService;
|
|
26
29
|
vaultService: IVaultService;
|
|
@@ -61,13 +64,11 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
|
|
|
61
64
|
protected updateStatus(): void;
|
|
62
65
|
/**
|
|
63
66
|
* Snapshot restoration promise.
|
|
64
|
-
*
|
|
65
|
-
* In designed time, this has been called from Initialisation process, which has been implemented on `ModuleInitializerFile.ts`.
|
|
67
|
+
* It resolves after the restored file operations have finished, before Vault watching begins.
|
|
66
68
|
*/
|
|
67
69
|
snapShotRestored: Promise<void> | null;
|
|
68
70
|
/**
|
|
69
|
-
* Restore the previous snapshot
|
|
70
|
-
* @returns
|
|
71
|
+
* Restore and complete the previous storage-event snapshot when it exists.
|
|
71
72
|
*/
|
|
72
73
|
restoreState(): Promise<void>;
|
|
73
74
|
appendQueue(params: FileEvent[], ctx?: unknown): Promise<void>;
|
|
@@ -111,7 +112,17 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
|
|
|
111
112
|
processFileEvent(fei: FileEventItem): Promise<void>;
|
|
112
113
|
_takeSnapshot(): Promise<void>;
|
|
113
114
|
_restoreFromSnapshot(): Promise<void>;
|
|
114
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Dispatch buffered storage events.
|
|
117
|
+
*
|
|
118
|
+
* Ordinary watcher events retain the existing fire-and-forget behaviour.
|
|
119
|
+
* Snapshot restoration requests completion so its caller can use the replay as
|
|
120
|
+
* a lifecycle boundary. In that mode, a sentinel also waits for every operation
|
|
121
|
+
* dispatched before it, preserving the ordering represented by the snapshot.
|
|
122
|
+
* An individual restored operation failure is logged but does not prevent the
|
|
123
|
+
* following Offline Scanner from reconciling the resulting current state.
|
|
124
|
+
*/
|
|
125
|
+
protected runQueuedEvents(options?: RunQueuedEventsOptions): Promise<void>;
|
|
115
126
|
protected processingCount: number;
|
|
116
127
|
protected requestProcessQueue(fei: FileEventItem): Promise<void>;
|
|
117
128
|
isWaiting(filename: FilePath): boolean;
|
|
@@ -72,13 +72,11 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Snapshot restoration promise.
|
|
75
|
-
*
|
|
76
|
-
* In designed time, this has been called from Initialisation process, which has been implemented on `ModuleInitializerFile.ts`.
|
|
75
|
+
* It resolves after the restored file operations have finished, before Vault watching begins.
|
|
77
76
|
*/
|
|
78
77
|
snapShotRestored = null;
|
|
79
78
|
/**
|
|
80
|
-
* Restore the previous snapshot
|
|
81
|
-
* @returns
|
|
79
|
+
* Restore and complete the previous storage-event snapshot when it exists.
|
|
82
80
|
*/
|
|
83
81
|
restoreState() {
|
|
84
82
|
this.snapShotRestored = this._restoreFromSnapshot();
|
|
@@ -371,15 +369,44 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
371
369
|
const snapShot = await this._loadSnapshot();
|
|
372
370
|
if (snapShot && Array.isArray(snapShot) && snapShot.length > 0) {
|
|
373
371
|
this._log(`Restoring storage operation snapshot: ${snapShot.length} items`, LOG_LEVEL_VERBOSE);
|
|
374
|
-
this.bufferedQueuedItems = snapShot.map(
|
|
372
|
+
this.bufferedQueuedItems = snapShot.map(
|
|
373
|
+
(event) => event.type === TYPE_SENTINEL_FLUSH ? event : { ...event, restoredFromPreviousRuntime: true, skipBatchWait: true }
|
|
374
|
+
);
|
|
375
375
|
this.updateStatus();
|
|
376
|
-
await this.runQueuedEvents();
|
|
376
|
+
await this.runQueuedEvents({ waitForProcessing: true });
|
|
377
377
|
} else {
|
|
378
378
|
this._log(`No snapshot to restore`, LOG_LEVEL_VERBOSE);
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Dispatch buffered storage events.
|
|
383
|
+
*
|
|
384
|
+
* Ordinary watcher events retain the existing fire-and-forget behaviour.
|
|
385
|
+
* Snapshot restoration requests completion so its caller can use the replay as
|
|
386
|
+
* a lifecycle boundary. In that mode, a sentinel also waits for every operation
|
|
387
|
+
* dispatched before it, preserving the ordering represented by the snapshot.
|
|
388
|
+
* An individual restored operation failure is logged but does not prevent the
|
|
389
|
+
* following Offline Scanner from reconciling the resulting current state.
|
|
390
|
+
*/
|
|
391
|
+
runQueuedEvents(options = {}) {
|
|
392
|
+
const waitForProcessing = options.waitForProcessing ?? false;
|
|
382
393
|
return skipIfDuplicated("storage-event-manager-run-queued-events", async () => {
|
|
394
|
+
let dispatched = [];
|
|
395
|
+
const waitForDispatched = async () => {
|
|
396
|
+
if (dispatched.length === 0) return;
|
|
397
|
+
const pending = dispatched;
|
|
398
|
+
dispatched = [];
|
|
399
|
+
const results = await Promise.allSettled(pending.map(({ processing }) => processing));
|
|
400
|
+
for (const [index, result] of results.entries()) {
|
|
401
|
+
if (result.status === "rejected") {
|
|
402
|
+
this._log(
|
|
403
|
+
`Restored storage operation failed for ${pending[index].path}; the Offline Scanner will reconcile the current state`,
|
|
404
|
+
LOG_LEVEL_NOTICE
|
|
405
|
+
);
|
|
406
|
+
this._log(result.reason, LOG_LEVEL_VERBOSE);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
383
410
|
do {
|
|
384
411
|
if (this.bufferedQueuedItems.length === 0) {
|
|
385
412
|
break;
|
|
@@ -392,12 +419,23 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
392
419
|
this.updateStatus();
|
|
393
420
|
if (fei.type === TYPE_SENTINEL_FLUSH) {
|
|
394
421
|
this._log(`Waiting for idle`, LOG_LEVEL_VERBOSE);
|
|
422
|
+
if (waitForProcessing) {
|
|
423
|
+
await waitForDispatched();
|
|
424
|
+
}
|
|
395
425
|
await this.waitForIdle();
|
|
396
426
|
this.updateStatus();
|
|
397
427
|
continue;
|
|
398
428
|
}
|
|
399
|
-
|
|
429
|
+
const processing = this.processFileEvent(fei);
|
|
430
|
+
if (waitForProcessing) {
|
|
431
|
+
dispatched.push({ path: fei.args.file.path, processing });
|
|
432
|
+
} else {
|
|
433
|
+
fireAndForget(processing);
|
|
434
|
+
}
|
|
400
435
|
} while (this.bufferedQueuedItems.length > 0);
|
|
436
|
+
if (waitForProcessing) {
|
|
437
|
+
await waitForDispatched();
|
|
438
|
+
}
|
|
401
439
|
});
|
|
402
440
|
}
|
|
403
441
|
processingCount = 0;
|
|
@@ -46,11 +46,17 @@ export declare class LiveSyncLocalDB {
|
|
|
46
46
|
env: LiveSyncLocalDBEnv;
|
|
47
47
|
clearCaches(): void;
|
|
48
48
|
_prepareHashFunctions(): Promise<void>;
|
|
49
|
-
onunload(): void;
|
|
50
49
|
refreshSettings(): void;
|
|
51
50
|
offRemoteChunkFetchedHandler?: ReturnType<LiveSyncEventHub["onEvent"]>;
|
|
51
|
+
private databaseCloseCleanup?;
|
|
52
|
+
private databaseUnloadNotification?;
|
|
52
53
|
constructor(dbname: string, env: LiveSyncLocalDBEnv);
|
|
54
|
+
/** Notify dependent services at most once for each physical database lifecycle. */
|
|
55
|
+
private notifyDatabaseUnload;
|
|
56
|
+
onunload(): void;
|
|
53
57
|
close(): Promise<void>;
|
|
58
|
+
private teardownDatabaseDependencies;
|
|
59
|
+
private rollbackFailedInitialisation;
|
|
54
60
|
onNewLeaf(chunk: EntryLeaf): void;
|
|
55
61
|
initializeDatabase(): Promise<boolean>;
|
|
56
62
|
/**
|