@evolu/common 6.0.1-preview.4 → 6.0.1-preview.6
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/src/Assert.d.ts +6 -3
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +6 -3
- package/dist/src/Crypto.d.ts +11 -0
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +1 -0
- package/dist/src/Evolu/Protocol.d.ts +65 -25
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +146 -37
- package/dist/src/Evolu/Relay.d.ts +3 -1
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +14 -2
- package/package.json +1 -1
- package/src/Assert.ts +6 -3
- package/src/Crypto.ts +13 -0
- package/src/Evolu/Db.ts +1 -0
- package/src/Evolu/Protocol.ts +181 -57
- package/src/Evolu/Relay.ts +20 -3
package/dist/src/Assert.d.ts
CHANGED
|
@@ -16,9 +16,12 @@
|
|
|
16
16
|
* assert(true, "true is not true"); // no-op
|
|
17
17
|
* assert(false, "true is not true"); // throws Error
|
|
18
18
|
*
|
|
19
|
-
* const
|
|
20
|
-
* //
|
|
21
|
-
* assert(
|
|
19
|
+
* const length = buffer.getLength();
|
|
20
|
+
* // We know length is logically non-negative, but TypeScript doesn't
|
|
21
|
+
* assert(
|
|
22
|
+
* NonNegativeInt.is(length),
|
|
23
|
+
* "buffer length should be non-negative",
|
|
24
|
+
* );
|
|
22
25
|
* ```
|
|
23
26
|
*/
|
|
24
27
|
export declare const assert: (condition: unknown, message: string) => asserts condition;
|
package/dist/src/Assert.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Assert.d.ts","sourceRoot":"","sources":["../../src/Assert.ts"],"names":[],"mappings":"AAgBA
|
|
1
|
+
{"version":3,"file":"Assert.d.ts","sourceRoot":"","sources":["../../src/Assert.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,MAAM,EAAE,CACnB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,SAIZ,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,EAClC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EACb,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAKlC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,EAAE,CAAC,CAAC,EAC1C,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAK3C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,CAO3E"}
|
package/dist/src/Assert.js
CHANGED
|
@@ -16,9 +16,12 @@
|
|
|
16
16
|
* assert(true, "true is not true"); // no-op
|
|
17
17
|
* assert(false, "true is not true"); // throws Error
|
|
18
18
|
*
|
|
19
|
-
* const
|
|
20
|
-
* //
|
|
21
|
-
* assert(
|
|
19
|
+
* const length = buffer.getLength();
|
|
20
|
+
* // We know length is logically non-negative, but TypeScript doesn't
|
|
21
|
+
* assert(
|
|
22
|
+
* NonNegativeInt.is(length),
|
|
23
|
+
* "buffer length should be non-negative",
|
|
24
|
+
* );
|
|
22
25
|
* ```
|
|
23
26
|
*/
|
|
24
27
|
export const assert = (condition, message) => {
|
package/dist/src/Crypto.d.ts
CHANGED
|
@@ -79,4 +79,15 @@ export declare const padmePaddedLength: (length: NonNegativeInt) => NonNegativeI
|
|
|
79
79
|
* {@link padmePaddedLength}.
|
|
80
80
|
*/
|
|
81
81
|
export declare const padmePaddingLength: (length: NonNegativeInt) => NonNegativeInt;
|
|
82
|
+
/**
|
|
83
|
+
* Performs a timing-safe comparison of two Uint8Arrays. Returns true if they
|
|
84
|
+
* are equal, false otherwise. Takes constant time regardless of where the
|
|
85
|
+
* arrays differ.
|
|
86
|
+
*
|
|
87
|
+
* @see https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
|
|
88
|
+
*/
|
|
89
|
+
export type TimingSafeEqual = (a: Uint8Array, b: Uint8Array) => boolean;
|
|
90
|
+
export interface TimingSafeEqualDep {
|
|
91
|
+
readonly timingSafeEqual: TimingSafeEqual;
|
|
92
|
+
}
|
|
82
93
|
//# sourceMappingURL=Crypto.d.ts.map
|
package/dist/src/Crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Crypto.d.ts","sourceRoot":"","sources":["../../src/Crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,EAAc,MAAM,EAAW,MAAM,aAAa,CAAC;AAC1D,OAAO,EAEL,EAAE,EAEF,QAAQ,EACR,cAAc,EAEf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,yDAAyD;AACzD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,WAAW,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;CAC/C;AAED,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,EAAE,iBACS,CAAC;AAE1C,MAAM,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,eAAO,MAAM,qBAAqB,EAAE,cACe,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,GAAI,UAAU,QAAQ,KAAG,YACR,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,YAAY,EAClB,MAAM,aAAa,CAAC,MAAM,CAAC,KAC1B,UAUF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,YAAY,EAClB,MAAM,aAAa,CAAC,MAAM,CAAC,KAC1B,EAUF,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,aAAa,8fAAiD,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,eAAO,MAAM,mBAAmB,GAAI,MAAM,YAAY,KAAG,aACS,CAAC;AAEnE,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IAErC,QAAQ,CAAC,OAAO,EAAE,CAChB,SAAS,EAAE,UAAU,EACrB,aAAa,EAAE,aAAa,KACzB;QACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;QAC3B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;KACjC,CAAC;IAEF,QAAQ,CAAC,OAAO,EAAE,CAChB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,UAAU,KACd,MAAM,CAAC,UAAU,EAAE,2BAA2B,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,6BAA6B,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,oBAAoB,KACzB,eAyBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,cAAc,KAAG,cAO1D,CAAC;AAEF;;;GAGG;AAEH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,cAAc,KAAG,cAE3D,CAAC"}
|
|
1
|
+
{"version":3,"file":"Crypto.d.ts","sourceRoot":"","sources":["../../src/Crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,EAAc,MAAM,EAAW,MAAM,aAAa,CAAC;AAC1D,OAAO,EAEL,EAAE,EAEF,QAAQ,EACR,cAAc,EAEf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,yDAAyD;AACzD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,WAAW,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;CAC/C;AAED,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,EAAE,iBACS,CAAC;AAE1C,MAAM,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,eAAO,MAAM,qBAAqB,EAAE,cACe,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,GAAI,UAAU,QAAQ,KAAG,YACR,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,YAAY,EAClB,MAAM,aAAa,CAAC,MAAM,CAAC,KAC1B,UAUF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,YAAY,EAClB,MAAM,aAAa,CAAC,MAAM,CAAC,KAC1B,EAUF,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,aAAa,8fAAiD,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,eAAO,MAAM,mBAAmB,GAAI,MAAM,YAAY,KAAG,aACS,CAAC;AAEnE,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IAErC,QAAQ,CAAC,OAAO,EAAE,CAChB,SAAS,EAAE,UAAU,EACrB,aAAa,EAAE,aAAa,KACzB;QACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;QAC3B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;KACjC,CAAC;IAEF,QAAQ,CAAC,OAAO,EAAE,CAChB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,UAAU,KACd,MAAM,CAAC,UAAU,EAAE,2BAA2B,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,6BAA6B,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,oBAAoB,KACzB,eAyBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,cAAc,KAAG,cAO1D,CAAC;AAEF;;;GAGG;AAEH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,cAAc,KAAG,cAE3D,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C"}
|
package/dist/src/Evolu/Db.js
CHANGED
|
@@ -720,6 +720,7 @@ const createClientStorage = (deps) => (options) => {
|
|
|
720
720
|
const storage = {
|
|
721
721
|
...sqliteStorageBase.value,
|
|
722
722
|
validateWriteKey: constFalse,
|
|
723
|
+
setWriteKey: constFalse,
|
|
723
724
|
writeMessages: (_ownerId, messages) => {
|
|
724
725
|
// TODO: Get owner by _ownerId when we support more.
|
|
725
726
|
const owner = deps.ownerRowRef.get();
|
|
@@ -15,21 +15,39 @@
|
|
|
15
15
|
*
|
|
16
16
|
* ### Message Structure
|
|
17
17
|
*
|
|
18
|
-
* | Field | Notes
|
|
19
|
-
* | :----------------------------- |
|
|
20
|
-
* | **Header** |
|
|
21
|
-
* | - {@link protocolVersion} |
|
|
22
|
-
* | - {@link OwnerId} |
|
|
23
|
-
* |
|
|
24
|
-
* |
|
|
25
|
-
* | - {@link
|
|
26
|
-
* | - {@link
|
|
27
|
-
* | -
|
|
28
|
-
* |
|
|
29
|
-
* |
|
|
30
|
-
* | - {@link
|
|
31
|
-
*
|
|
32
|
-
*
|
|
18
|
+
* | Field | Notes |
|
|
19
|
+
* | :----------------------------- | :------------------------ |
|
|
20
|
+
* | **Header** | |
|
|
21
|
+
* | - {@link protocolVersion} | |
|
|
22
|
+
* | - {@link OwnerId} | {@link Owner} |
|
|
23
|
+
* | **Initiator** | |
|
|
24
|
+
* | - {@link WriteKeyMode} | |
|
|
25
|
+
* | - {@link WriteKey} | If WriteKeyMode >= 1 |
|
|
26
|
+
* | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
|
|
27
|
+
* | **Non-initiator** | |
|
|
28
|
+
* | - {@link ProtocolErrorCode} | |
|
|
29
|
+
* | **Messages** | |
|
|
30
|
+
* | - {@link NonNegativeInt} | A number of messages. |
|
|
31
|
+
* | - {@link EncryptedCrdtMessage} | |
|
|
32
|
+
* | **Ranges** | |
|
|
33
|
+
* | - {@link NonNegativeInt} | Number of ranges. |
|
|
34
|
+
* | - {@link Range} | |
|
|
35
|
+
*
|
|
36
|
+
* ### WriteKey Validation
|
|
37
|
+
*
|
|
38
|
+
* The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
|
|
39
|
+
* for write operations and two for key rotation (current and new). Note that
|
|
40
|
+
* it's ok to not send any key if initiator is going to be synced with readonly
|
|
41
|
+
* owner. The non-initiator validates them immediately after parsing the
|
|
42
|
+
* initiator header, before processing any messages or ranges.
|
|
43
|
+
*
|
|
44
|
+
* ### WriteKey Rotation
|
|
45
|
+
*
|
|
46
|
+
* When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
|
|
47
|
+
* present:
|
|
48
|
+
*
|
|
49
|
+
* 1. Current WriteKey (for validation)
|
|
50
|
+
* 2. New WriteKey (to be stored)
|
|
33
51
|
*
|
|
34
52
|
* ### Synchronization
|
|
35
53
|
*
|
|
@@ -147,6 +165,11 @@ export declare const ProtocolErrorCode: {
|
|
|
147
165
|
readonly SyncError: 3;
|
|
148
166
|
};
|
|
149
167
|
type ProtocolErrorCode = (typeof ProtocolErrorCode)[keyof typeof ProtocolErrorCode];
|
|
168
|
+
export declare const WriteKeyMode: {
|
|
169
|
+
readonly None: 0;
|
|
170
|
+
readonly Single: 1;
|
|
171
|
+
readonly Rotation: 2;
|
|
172
|
+
};
|
|
150
173
|
/**
|
|
151
174
|
* Evolu Protocol Storage
|
|
152
175
|
*
|
|
@@ -168,13 +191,10 @@ export interface Storage {
|
|
|
168
191
|
readonly fingerprintRanges: (ownerId: BinaryOwnerId, buckets: ReadonlyArray<NonNegativeInt>, upperBound?: RangeUpperBound) => ReadonlyArray<FingerprintRange> | null;
|
|
169
192
|
readonly findLowerBound: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt, upperBound: RangeUpperBound) => NonNegativeInt | null;
|
|
170
193
|
readonly iterate: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt, callback: (timestamp: BinaryTimestamp, index: NonNegativeInt) => boolean) => void;
|
|
171
|
-
/**
|
|
172
|
-
* Authorizes the initiator's {@link WriteKey} for the given
|
|
173
|
-
* {@link BinaryOwnerId}.
|
|
174
|
-
*
|
|
175
|
-
* For a client that does not expect foreign writes, return `false`.
|
|
176
|
-
*/
|
|
194
|
+
/** Validates the {@link WriteKey} for the given {@link Owner}. */
|
|
177
195
|
readonly validateWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
|
|
196
|
+
/** Sets the {@link WriteKey} for the given {@link Owner}. */
|
|
197
|
+
readonly setWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
|
|
178
198
|
/** Write encrypted {@link CrdtMessage}s to storage. */
|
|
179
199
|
readonly writeMessages: (ownerId: BinaryOwnerId, messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>) => boolean;
|
|
180
200
|
/** Read encrypted {@link DbChange}s from storage. */
|
|
@@ -306,6 +326,8 @@ export interface ProtocolSyncError extends ProtocolErrorBase {
|
|
|
306
326
|
export declare const createProtocolMessageFromCrdtMessages: (deps: SymmetricCryptoDep & CreateRandomBytesDep) => (owner: OwnerWithWriteAccess, messages: NonEmptyReadonlyArray<CrdtMessage>, maxSize?: PositiveInt) => ProtocolMessage;
|
|
307
327
|
/** Creates a {@link ProtocolMessage} for sync. */
|
|
308
328
|
export declare const createProtocolMessageForSync: (deps: StorageDep) => (ownerId: OwnerId) => ProtocolMessage | null;
|
|
329
|
+
/** Creates a ProtocolMessage for {@link WriteKey} rotation. */
|
|
330
|
+
export declare const createProtocolMessageForWriteKeyRotation: (ownerId: OwnerId, currentWriteKey: WriteKey, newWriteKey: WriteKey) => ProtocolMessage;
|
|
309
331
|
/**
|
|
310
332
|
* Mutable builder for constructing {@link ProtocolMessage} respecting size
|
|
311
333
|
* limits.
|
|
@@ -319,13 +341,18 @@ export interface ProtocolMessageBuffer {
|
|
|
319
341
|
readonly unwrap: () => ProtocolMessage;
|
|
320
342
|
readonly getSize: () => PositiveInt;
|
|
321
343
|
}
|
|
322
|
-
export declare const createProtocolMessageBuffer: (ownerId: OwnerId, options
|
|
323
|
-
readonly errorCode?: ProtocolErrorCode;
|
|
324
|
-
readonly writeKey?: WriteKey;
|
|
344
|
+
export declare const createProtocolMessageBuffer: (ownerId: OwnerId, options: {
|
|
325
345
|
readonly totalMaxSize?: PositiveInt | undefined;
|
|
326
346
|
readonly rangesMaxSize?: PositiveInt | undefined;
|
|
327
347
|
readonly version?: NonNegativeInt;
|
|
328
|
-
}
|
|
348
|
+
} & ({
|
|
349
|
+
readonly type: "initiator";
|
|
350
|
+
/** Single key or [current, new] for rotation. */
|
|
351
|
+
readonly writeKey?: WriteKey | readonly [WriteKey, WriteKey];
|
|
352
|
+
} | {
|
|
353
|
+
readonly type: "non-initiator";
|
|
354
|
+
readonly errorCode: ProtocolErrorCode;
|
|
355
|
+
})) => ProtocolMessageBuffer;
|
|
329
356
|
export interface TimestampsBuffer {
|
|
330
357
|
readonly add: (timestamp: Timestamp) => void;
|
|
331
358
|
readonly addInfinite: () => void;
|
|
@@ -425,5 +452,18 @@ export declare const ProtocolValueType: {
|
|
|
425
452
|
};
|
|
426
453
|
export declare const encodeSqliteValue: (buffer: Buffer, value: SqliteValue) => void;
|
|
427
454
|
export declare const decodeSqliteValue: (buffer: Buffer) => SqliteValue;
|
|
455
|
+
/**
|
|
456
|
+
* Decodes a ProtocolMessage into a readable JSON object for debugging.
|
|
457
|
+
*
|
|
458
|
+
* Note: This is a stub for future implementation. It should use:
|
|
459
|
+
*
|
|
460
|
+
* - DecodeVersionAndOwner
|
|
461
|
+
* - DecodeError or decodeWriteKeys (depending on context)
|
|
462
|
+
* - DecodeMessages
|
|
463
|
+
* - DecodeRanges
|
|
464
|
+
*
|
|
465
|
+
* If you want to help, please contribute to this function.
|
|
466
|
+
*/
|
|
467
|
+
export declare const decodeProtocolMessageToJson: (_protocolMessage: ProtocolMessage, _isInitiator: boolean) => unknown;
|
|
428
468
|
export {};
|
|
429
469
|
//# sourceMappingURL=Protocol.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Protocol.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Protocol.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Protocol.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,21 +15,39 @@
|
|
|
15
15
|
*
|
|
16
16
|
* ### Message Structure
|
|
17
17
|
*
|
|
18
|
-
* | Field | Notes
|
|
19
|
-
* | :----------------------------- |
|
|
20
|
-
* | **Header** |
|
|
21
|
-
* | - {@link protocolVersion} |
|
|
22
|
-
* | - {@link OwnerId} |
|
|
23
|
-
* |
|
|
24
|
-
* |
|
|
25
|
-
* | - {@link
|
|
26
|
-
* | - {@link
|
|
27
|
-
* | -
|
|
28
|
-
* |
|
|
29
|
-
* |
|
|
30
|
-
* | - {@link
|
|
18
|
+
* | Field | Notes |
|
|
19
|
+
* | :----------------------------- | :------------------------ |
|
|
20
|
+
* | **Header** | |
|
|
21
|
+
* | - {@link protocolVersion} | |
|
|
22
|
+
* | - {@link OwnerId} | {@link Owner} |
|
|
23
|
+
* | **Initiator** | |
|
|
24
|
+
* | - {@link WriteKeyMode} | |
|
|
25
|
+
* | - {@link WriteKey} | If WriteKeyMode >= 1 |
|
|
26
|
+
* | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
|
|
27
|
+
* | **Non-initiator** | |
|
|
28
|
+
* | - {@link ProtocolErrorCode} | |
|
|
29
|
+
* | **Messages** | |
|
|
30
|
+
* | - {@link NonNegativeInt} | A number of messages. |
|
|
31
|
+
* | - {@link EncryptedCrdtMessage} | |
|
|
32
|
+
* | **Ranges** | |
|
|
33
|
+
* | - {@link NonNegativeInt} | Number of ranges. |
|
|
34
|
+
* | - {@link Range} | |
|
|
31
35
|
*
|
|
32
|
-
*
|
|
36
|
+
* ### WriteKey Validation
|
|
37
|
+
*
|
|
38
|
+
* The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
|
|
39
|
+
* for write operations and two for key rotation (current and new). Note that
|
|
40
|
+
* it's ok to not send any key if initiator is going to be synced with readonly
|
|
41
|
+
* owner. The non-initiator validates them immediately after parsing the
|
|
42
|
+
* initiator header, before processing any messages or ranges.
|
|
43
|
+
*
|
|
44
|
+
* ### WriteKey Rotation
|
|
45
|
+
*
|
|
46
|
+
* When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
|
|
47
|
+
* present:
|
|
48
|
+
*
|
|
49
|
+
* 1. Current WriteKey (for validation)
|
|
50
|
+
* 2. New WriteKey (to be stored)
|
|
33
51
|
*
|
|
34
52
|
* ### Synchronization
|
|
35
53
|
*
|
|
@@ -124,7 +142,7 @@ import { sha256 } from "@noble/hashes/sha2";
|
|
|
124
142
|
import { pack, unpack, unpackMultiple } from "msgpackr";
|
|
125
143
|
import { isNonEmptyReadonlyArray } from "../Array.js";
|
|
126
144
|
import { assert } from "../Assert.js";
|
|
127
|
-
import { BufferError, bytesToHex, bytesToUtf8,
|
|
145
|
+
import { BufferError, bytesToHex, bytesToUtf8, createBuffer, hexToBytes, utf8ToBytes, } from "../Buffer.js";
|
|
128
146
|
import { padmePaddingLength, } from "../Crypto.js";
|
|
129
147
|
import { eqArrayNumber } from "../Eq.js";
|
|
130
148
|
import { computeBalancedBuckets } from "../Number.js";
|
|
@@ -149,6 +167,11 @@ export const ProtocolErrorCode = {
|
|
|
149
167
|
/** A code for {@link ProtocolSyncError}. */
|
|
150
168
|
SyncError: 3,
|
|
151
169
|
};
|
|
170
|
+
export const WriteKeyMode = {
|
|
171
|
+
None: 0,
|
|
172
|
+
Single: 1,
|
|
173
|
+
Rotation: 2,
|
|
174
|
+
};
|
|
152
175
|
/**
|
|
153
176
|
* Base64Url string with maximum length of 256 characters. Encoding strings as
|
|
154
177
|
* Base64UrlString saves up to 25% in size compared to regular strings.
|
|
@@ -181,6 +204,7 @@ export const zeroFingerprint = new Uint8Array(fingerprintSize);
|
|
|
181
204
|
*/
|
|
182
205
|
export const createProtocolMessageFromCrdtMessages = (deps) => (owner, messages, maxSize) => {
|
|
183
206
|
const buffer = createProtocolMessageBuffer(owner.id, {
|
|
207
|
+
type: "initiator",
|
|
184
208
|
totalMaxSize: maxSize ?? maxProtocolMessageSize,
|
|
185
209
|
writeKey: owner.writeKey,
|
|
186
210
|
});
|
|
@@ -224,7 +248,7 @@ export const createProtocolMessageFromCrdtMessages = (deps) => (owner, messages,
|
|
|
224
248
|
};
|
|
225
249
|
/** Creates a {@link ProtocolMessage} for sync. */
|
|
226
250
|
export const createProtocolMessageForSync = (deps) => (ownerId) => {
|
|
227
|
-
const buffer = createProtocolMessageBuffer(ownerId);
|
|
251
|
+
const buffer = createProtocolMessageBuffer(ownerId, { type: "initiator" });
|
|
228
252
|
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
229
253
|
const size = deps.storage.getSize(binaryOwnerId);
|
|
230
254
|
// Errors are handled by the storage.
|
|
@@ -233,8 +257,16 @@ export const createProtocolMessageForSync = (deps) => (ownerId) => {
|
|
|
233
257
|
splitRange(deps)(binaryOwnerId, 0, size, InfiniteUpperBound, buffer);
|
|
234
258
|
return buffer.unwrap();
|
|
235
259
|
};
|
|
236
|
-
|
|
237
|
-
|
|
260
|
+
/** Creates a ProtocolMessage for {@link WriteKey} rotation. */
|
|
261
|
+
export const createProtocolMessageForWriteKeyRotation = (ownerId, currentWriteKey, newWriteKey) => {
|
|
262
|
+
const buffer = createProtocolMessageBuffer(ownerId, {
|
|
263
|
+
type: "initiator",
|
|
264
|
+
writeKey: [currentWriteKey, newWriteKey],
|
|
265
|
+
});
|
|
266
|
+
return buffer.unwrap();
|
|
267
|
+
};
|
|
268
|
+
export const createProtocolMessageBuffer = (ownerId, options) => {
|
|
269
|
+
const { totalMaxSize = maxProtocolMessageSize, rangesMaxSize = maxProtocolMessageRangesSize, version = protocolVersion, } = options;
|
|
238
270
|
const buffers = {
|
|
239
271
|
header: createBuffer(),
|
|
240
272
|
messages: {
|
|
@@ -249,17 +281,29 @@ export const createProtocolMessageBuffer = (ownerId, options = {}) => {
|
|
|
249
281
|
};
|
|
250
282
|
encodeNonNegativeInt(buffers.header, version);
|
|
251
283
|
buffers.header.extend(ownerIdToBinaryOwnerId(ownerId));
|
|
252
|
-
if (
|
|
253
|
-
|
|
284
|
+
if (options.type === "initiator") {
|
|
285
|
+
if (!options.writeKey) {
|
|
286
|
+
buffers.header.extend([WriteKeyMode.None]);
|
|
287
|
+
}
|
|
288
|
+
else if (!Array.isArray(options.writeKey)) {
|
|
289
|
+
buffers.header.extend([WriteKeyMode.Single]);
|
|
290
|
+
buffers.header.extend(options.writeKey);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
buffers.header.extend([WriteKeyMode.Rotation]);
|
|
294
|
+
buffers.header.extend(options.writeKey[0]); // current
|
|
295
|
+
buffers.header.extend(options.writeKey[1]); // new
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
buffers.header.extend([options.errorCode]);
|
|
300
|
+
}
|
|
254
301
|
let isLastRangeInfinite = false;
|
|
255
302
|
const isWithinSizeLimits = () => getSize() <= totalMaxSize;
|
|
256
303
|
const getSize = () => (getHeaderAndMessagesSize() + getRangesSize());
|
|
257
304
|
const getHeaderAndMessagesSize = () => buffers.header.getLength() +
|
|
258
305
|
buffers.messages.timestamps.getLength() +
|
|
259
|
-
buffers.messages.dbChanges.getLength()
|
|
260
|
-
(buffers.messages.timestamps.getCount() > 0 && writeKey
|
|
261
|
-
? writeKeyLength
|
|
262
|
-
: 0);
|
|
306
|
+
buffers.messages.dbChanges.getLength();
|
|
263
307
|
const getRangesSize = () => buffers.ranges.timestamps.getCount() > 0
|
|
264
308
|
? buffers.ranges.timestamps.getLength() +
|
|
265
309
|
buffers.ranges.types.getLength() +
|
|
@@ -345,8 +389,6 @@ export const createProtocolMessageBuffer = (ownerId, options = {}) => {
|
|
|
345
389
|
}
|
|
346
390
|
buffers.messages.timestamps.append(buffers.header);
|
|
347
391
|
buffers.header.extend(buffers.messages.dbChanges.unwrap());
|
|
348
|
-
if (buffers.messages.timestamps.getCount() > 0 && writeKey)
|
|
349
|
-
buffers.header.extend(writeKey);
|
|
350
392
|
if (buffers.ranges.timestamps.getCount() > 0) {
|
|
351
393
|
buffers.ranges.timestamps.append(buffers.header);
|
|
352
394
|
buffers.header.extend(buffers.ranges.types.unwrap());
|
|
@@ -434,7 +476,6 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
|
|
|
434
476
|
ownerId,
|
|
435
477
|
});
|
|
436
478
|
}
|
|
437
|
-
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
438
479
|
const errorCode = input.shift();
|
|
439
480
|
if (errorCode !== ProtocolErrorCode.NoError) {
|
|
440
481
|
switch (errorCode) {
|
|
@@ -458,6 +499,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
|
|
|
458
499
|
}
|
|
459
500
|
}
|
|
460
501
|
const messages = decodeMessages(input);
|
|
502
|
+
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
461
503
|
if (isNonEmptyReadonlyArray(messages) &&
|
|
462
504
|
!deps.storage.writeMessages(binaryOwnerId, messages)) {
|
|
463
505
|
return ok(null);
|
|
@@ -468,6 +510,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
|
|
|
468
510
|
if (writeKey == null)
|
|
469
511
|
return ok(null);
|
|
470
512
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
513
|
+
type: "initiator",
|
|
471
514
|
writeKey,
|
|
472
515
|
totalMaxSize,
|
|
473
516
|
rangesMaxSize,
|
|
@@ -477,8 +520,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
|
|
|
477
520
|
export const applyProtocolMessageAsRelay = (deps) => (inputMessage, { subscribe, broadcast, totalMaxSize, rangesMaxSize, } = {},
|
|
478
521
|
/** For testing purposes only; should not be used in production. */
|
|
479
522
|
version = protocolVersion) => tryDecodeProtocolData(inputMessage, (input) => {
|
|
480
|
-
const requestedVersion =
|
|
481
|
-
const ownerId = decodeOwnerId(input);
|
|
523
|
+
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
482
524
|
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
483
525
|
if (requestedVersion !== version) {
|
|
484
526
|
// Non-initiator responds with its version and ownerId.
|
|
@@ -488,26 +530,68 @@ version = protocolVersion) => tryDecodeProtocolData(inputMessage, (input) => {
|
|
|
488
530
|
return ok(output.unwrap());
|
|
489
531
|
}
|
|
490
532
|
subscribe?.(ownerId);
|
|
533
|
+
const writeKeyMode = input.shift();
|
|
534
|
+
let writeKey;
|
|
535
|
+
let newWriteKey;
|
|
536
|
+
if (writeKeyMode !== WriteKeyMode.None) {
|
|
537
|
+
writeKey = input.shiftN(writeKeyLength);
|
|
538
|
+
switch (writeKeyMode) {
|
|
539
|
+
case WriteKeyMode.Single:
|
|
540
|
+
break;
|
|
541
|
+
case WriteKeyMode.Rotation:
|
|
542
|
+
newWriteKey = input.shiftN(writeKeyLength);
|
|
543
|
+
break;
|
|
544
|
+
default:
|
|
545
|
+
throw new ProtocolDecodeError(`Invalid WriteKeyMode: ${writeKeyMode}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (writeKey) {
|
|
549
|
+
const isValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
|
|
550
|
+
if (!isValid) {
|
|
551
|
+
return ok(createProtocolMessageBuffer(ownerId, {
|
|
552
|
+
type: "non-initiator",
|
|
553
|
+
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
554
|
+
}).unwrap());
|
|
555
|
+
}
|
|
556
|
+
if (newWriteKey) {
|
|
557
|
+
const rotationSuccess = deps.storage.setWriteKey(binaryOwnerId, newWriteKey);
|
|
558
|
+
if (!rotationSuccess) {
|
|
559
|
+
return ok(createProtocolMessageBuffer(ownerId, {
|
|
560
|
+
type: "non-initiator",
|
|
561
|
+
errorCode: ProtocolErrorCode.WriteError,
|
|
562
|
+
}).unwrap());
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
491
566
|
const messages = decodeMessages(input);
|
|
492
567
|
if (isNonEmptyReadonlyArray(messages)) {
|
|
493
|
-
|
|
494
|
-
const writeKey = input.shiftN(writeKeyLength);
|
|
495
|
-
const writeKeyIsValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
|
|
496
|
-
if (!writeKeyIsValid)
|
|
568
|
+
if (!writeKey)
|
|
497
569
|
return ok(createProtocolMessageBuffer(ownerId, {
|
|
570
|
+
type: "non-initiator",
|
|
498
571
|
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
499
572
|
}).unwrap());
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
|
|
573
|
+
// Only broadcast if there's no ranges.
|
|
574
|
+
if (broadcast && input.getLength() === 0) {
|
|
575
|
+
const broadcastBuffer = createProtocolMessageBuffer(ownerId, {
|
|
576
|
+
type: "non-initiator",
|
|
577
|
+
errorCode: ProtocolErrorCode.NoError,
|
|
578
|
+
totalMaxSize,
|
|
579
|
+
rangesMaxSize,
|
|
580
|
+
version,
|
|
581
|
+
});
|
|
582
|
+
for (const message of messages) {
|
|
583
|
+
broadcastBuffer.addMessage(message);
|
|
584
|
+
}
|
|
585
|
+
broadcast(ownerId, broadcastBuffer.unwrap());
|
|
504
586
|
}
|
|
505
587
|
if (!deps.storage.writeMessages(binaryOwnerId, messages))
|
|
506
588
|
return ok(createProtocolMessageBuffer(ownerId, {
|
|
589
|
+
type: "non-initiator",
|
|
507
590
|
errorCode: ProtocolErrorCode.WriteError,
|
|
508
591
|
}).unwrap());
|
|
509
592
|
}
|
|
510
593
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
594
|
+
type: "non-initiator",
|
|
511
595
|
errorCode: ProtocolErrorCode.NoError,
|
|
512
596
|
totalMaxSize,
|
|
513
597
|
rangesMaxSize,
|
|
@@ -534,6 +618,10 @@ const tryDecodeProtocolData = (data, callback) => {
|
|
|
534
618
|
}
|
|
535
619
|
};
|
|
536
620
|
const decodeVersionAndOwner = (input) => {
|
|
621
|
+
// This structure must never change across protocol versions. The version
|
|
622
|
+
// and owner ID must always be the first two fields in every protocol message
|
|
623
|
+
// to enable version negotiation and owner identification before any other
|
|
624
|
+
// processing occurs.
|
|
537
625
|
const version = decodeNonNegativeInt(input);
|
|
538
626
|
const ownerId = decodeOwnerId(input);
|
|
539
627
|
return [version, ownerId];
|
|
@@ -573,6 +661,7 @@ const sync = (deps) => (role, input, output, ownerId) => {
|
|
|
573
661
|
return ok(null);
|
|
574
662
|
}
|
|
575
663
|
const message = createProtocolMessageBuffer(binaryOwnerId, {
|
|
664
|
+
type: "non-initiator",
|
|
576
665
|
errorCode: ProtocolErrorCode.SyncError,
|
|
577
666
|
});
|
|
578
667
|
return ok(message.unwrap());
|
|
@@ -1192,3 +1281,23 @@ export const decodeSqliteValue = (buffer) => {
|
|
|
1192
1281
|
throw new ProtocolDecodeError("invalid ProtocolValueType");
|
|
1193
1282
|
}
|
|
1194
1283
|
};
|
|
1284
|
+
/**
|
|
1285
|
+
* Decodes a ProtocolMessage into a readable JSON object for debugging.
|
|
1286
|
+
*
|
|
1287
|
+
* Note: This is a stub for future implementation. It should use:
|
|
1288
|
+
*
|
|
1289
|
+
* - DecodeVersionAndOwner
|
|
1290
|
+
* - DecodeError or decodeWriteKeys (depending on context)
|
|
1291
|
+
* - DecodeMessages
|
|
1292
|
+
* - DecodeRanges
|
|
1293
|
+
*
|
|
1294
|
+
* If you want to help, please contribute to this function.
|
|
1295
|
+
*/
|
|
1296
|
+
export const decodeProtocolMessageToJson = (_protocolMessage, _isInitiator) => {
|
|
1297
|
+
// TODO: Implement using
|
|
1298
|
+
// - decodeVersionAndOwner
|
|
1299
|
+
// -- decodeError or decodeWriteKeys (should be refactored out),
|
|
1300
|
+
// -- decodeMessages, and decodeRanges.
|
|
1301
|
+
// This is a stub for PRs and community contributions.
|
|
1302
|
+
throw new Error("decodeProtocolMessageToJson is not implemented yet.");
|
|
1303
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConsoleConfig } from "../Console.js";
|
|
2
|
+
import { TimingSafeEqualDep } from "../Crypto.js";
|
|
2
3
|
import { Result } from "../Result.js";
|
|
3
4
|
import { SqliteError } from "../Sqlite.js";
|
|
4
5
|
import { SimpleName } from "../Type.js";
|
|
@@ -9,5 +10,6 @@ export interface Relay extends Disposable {
|
|
|
9
10
|
export interface RelayConfig extends ConsoleConfig {
|
|
10
11
|
readonly name?: SimpleName;
|
|
11
12
|
}
|
|
12
|
-
export
|
|
13
|
+
export type RelaySqliteStorageDeps = SqliteStorageDeps & TimingSafeEqualDep;
|
|
14
|
+
export declare const createRelayStorage: (deps: RelaySqliteStorageDeps) => (options: CreateSqliteStorageBaseOptions) => Result<Storage, SqliteError>;
|
|
13
15
|
//# sourceMappingURL=Relay.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Relay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Relay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAM,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAO,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAqB,OAAO,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAEL,8BAA8B,EAC9B,iBAAiB,EAClB,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,KAAM,SAAQ,UAAU;CAAG;AAE5C,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAE5E,eAAO,MAAM,kBAAkB,GAC5B,MAAM,sBAAsB,MAC5B,SAAS,8BAA8B,KAAG,MAAM,CAAC,OAAO,EAAE,WAAW,CAqIrE,CAAC"}
|
package/dist/src/Evolu/Relay.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isNonEmptyReadonlyArray } from "../Array.js";
|
|
2
|
-
import { eqArrayNumber } from "../Eq.js";
|
|
3
2
|
import { ok } from "../Result.js";
|
|
4
3
|
import { sql } from "../Sqlite.js";
|
|
5
4
|
import { createSqliteStorageBase, } from "./Storage.js";
|
|
@@ -64,7 +63,20 @@ export const createRelayStorage = (deps) => (options) => {
|
|
|
64
63
|
}
|
|
65
64
|
return true;
|
|
66
65
|
}
|
|
67
|
-
return
|
|
66
|
+
return deps.timingSafeEqual(rows[0].writeKey, writeKey);
|
|
67
|
+
},
|
|
68
|
+
setWriteKey: (ownerId, writeKey) => {
|
|
69
|
+
const upsertWriteKey = deps.sqlite.exec(sql `
|
|
70
|
+
insert into evolu_writeKey (ownerId, writeKey)
|
|
71
|
+
values (${ownerId}, ${writeKey})
|
|
72
|
+
on conflict (ownerId) do update
|
|
73
|
+
set writeKey = excluded.writeKey;
|
|
74
|
+
`);
|
|
75
|
+
if (!upsertWriteKey.ok) {
|
|
76
|
+
options.onStorageError(upsertWriteKey.error);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
68
80
|
},
|
|
69
81
|
writeMessages: (ownerId, messages) => {
|
|
70
82
|
const result = deps.sqlite.transaction(() => {
|
package/package.json
CHANGED
package/src/Assert.ts
CHANGED
|
@@ -32,9 +32,12 @@ import type { Type } from "./Type.js";
|
|
|
32
32
|
* assert(true, "true is not true"); // no-op
|
|
33
33
|
* assert(false, "true is not true"); // throws Error
|
|
34
34
|
*
|
|
35
|
-
* const
|
|
36
|
-
* //
|
|
37
|
-
* assert(
|
|
35
|
+
* const length = buffer.getLength();
|
|
36
|
+
* // We know length is logically non-negative, but TypeScript doesn't
|
|
37
|
+
* assert(
|
|
38
|
+
* NonNegativeInt.is(length),
|
|
39
|
+
* "buffer length should be non-negative",
|
|
40
|
+
* );
|
|
38
41
|
* ```
|
|
39
42
|
*/
|
|
40
43
|
export const assert: (
|
package/src/Crypto.ts
CHANGED
|
@@ -189,3 +189,16 @@ export const padmePaddedLength = (length: NonNegativeInt): NonNegativeInt => {
|
|
|
189
189
|
export const padmePaddingLength = (length: NonNegativeInt): NonNegativeInt => {
|
|
190
190
|
return (padmePaddedLength(length) - length) as NonNegativeInt;
|
|
191
191
|
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Performs a timing-safe comparison of two Uint8Arrays. Returns true if they
|
|
195
|
+
* are equal, false otherwise. Takes constant time regardless of where the
|
|
196
|
+
* arrays differ.
|
|
197
|
+
*
|
|
198
|
+
* @see https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
|
|
199
|
+
*/
|
|
200
|
+
export type TimingSafeEqual = (a: Uint8Array, b: Uint8Array) => boolean;
|
|
201
|
+
|
|
202
|
+
export interface TimingSafeEqualDep {
|
|
203
|
+
readonly timingSafeEqual: TimingSafeEqual;
|
|
204
|
+
}
|
package/src/Evolu/Db.ts
CHANGED
package/src/Evolu/Protocol.ts
CHANGED
|
@@ -15,21 +15,39 @@
|
|
|
15
15
|
*
|
|
16
16
|
* ### Message Structure
|
|
17
17
|
*
|
|
18
|
-
* | Field | Notes
|
|
19
|
-
* | :----------------------------- |
|
|
20
|
-
* | **Header** |
|
|
21
|
-
* | - {@link protocolVersion} |
|
|
22
|
-
* | - {@link OwnerId} |
|
|
23
|
-
* |
|
|
24
|
-
* |
|
|
25
|
-
* | - {@link
|
|
26
|
-
* | - {@link
|
|
27
|
-
* | -
|
|
28
|
-
* |
|
|
29
|
-
* |
|
|
30
|
-
* | - {@link
|
|
18
|
+
* | Field | Notes |
|
|
19
|
+
* | :----------------------------- | :------------------------ |
|
|
20
|
+
* | **Header** | |
|
|
21
|
+
* | - {@link protocolVersion} | |
|
|
22
|
+
* | - {@link OwnerId} | {@link Owner} |
|
|
23
|
+
* | **Initiator** | |
|
|
24
|
+
* | - {@link WriteKeyMode} | |
|
|
25
|
+
* | - {@link WriteKey} | If WriteKeyMode >= 1 |
|
|
26
|
+
* | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
|
|
27
|
+
* | **Non-initiator** | |
|
|
28
|
+
* | - {@link ProtocolErrorCode} | |
|
|
29
|
+
* | **Messages** | |
|
|
30
|
+
* | - {@link NonNegativeInt} | A number of messages. |
|
|
31
|
+
* | - {@link EncryptedCrdtMessage} | |
|
|
32
|
+
* | **Ranges** | |
|
|
33
|
+
* | - {@link NonNegativeInt} | Number of ranges. |
|
|
34
|
+
* | - {@link Range} | |
|
|
31
35
|
*
|
|
32
|
-
*
|
|
36
|
+
* ### WriteKey Validation
|
|
37
|
+
*
|
|
38
|
+
* The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
|
|
39
|
+
* for write operations and two for key rotation (current and new). Note that
|
|
40
|
+
* it's ok to not send any key if initiator is going to be synced with readonly
|
|
41
|
+
* owner. The non-initiator validates them immediately after parsing the
|
|
42
|
+
* initiator header, before processing any messages or ranges.
|
|
43
|
+
*
|
|
44
|
+
* ### WriteKey Rotation
|
|
45
|
+
*
|
|
46
|
+
* When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
|
|
47
|
+
* present:
|
|
48
|
+
*
|
|
49
|
+
* 1. Current WriteKey (for validation)
|
|
50
|
+
* 2. New WriteKey (to be stored)
|
|
33
51
|
*
|
|
34
52
|
* ### Synchronization
|
|
35
53
|
*
|
|
@@ -130,7 +148,6 @@ import {
|
|
|
130
148
|
BufferError,
|
|
131
149
|
bytesToHex,
|
|
132
150
|
bytesToUtf8,
|
|
133
|
-
concatBytes,
|
|
134
151
|
createBuffer,
|
|
135
152
|
hexToBytes,
|
|
136
153
|
utf8ToBytes,
|
|
@@ -163,7 +180,6 @@ import {
|
|
|
163
180
|
} from "../Type.js";
|
|
164
181
|
import { Brand, Predicate } from "../Types.js";
|
|
165
182
|
import {
|
|
166
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
167
183
|
Owner,
|
|
168
184
|
OwnerId,
|
|
169
185
|
OwnerWithWriteAccess,
|
|
@@ -205,6 +221,14 @@ export const ProtocolErrorCode = {
|
|
|
205
221
|
type ProtocolErrorCode =
|
|
206
222
|
(typeof ProtocolErrorCode)[keyof typeof ProtocolErrorCode];
|
|
207
223
|
|
|
224
|
+
export const WriteKeyMode = {
|
|
225
|
+
None: 0,
|
|
226
|
+
Single: 1,
|
|
227
|
+
Rotation: 2,
|
|
228
|
+
} as const;
|
|
229
|
+
|
|
230
|
+
type WriteKeyMode = (typeof WriteKeyMode)[keyof typeof WriteKeyMode];
|
|
231
|
+
|
|
208
232
|
/**
|
|
209
233
|
* Evolu Protocol Storage
|
|
210
234
|
*
|
|
@@ -249,17 +273,15 @@ export interface Storage {
|
|
|
249
273
|
callback: (timestamp: BinaryTimestamp, index: NonNegativeInt) => boolean,
|
|
250
274
|
) => void;
|
|
251
275
|
|
|
252
|
-
/**
|
|
253
|
-
* Authorizes the initiator's {@link WriteKey} for the given
|
|
254
|
-
* {@link BinaryOwnerId}.
|
|
255
|
-
*
|
|
256
|
-
* For a client that does not expect foreign writes, return `false`.
|
|
257
|
-
*/
|
|
276
|
+
/** Validates the {@link WriteKey} for the given {@link Owner}. */
|
|
258
277
|
readonly validateWriteKey: (
|
|
259
278
|
ownerId: BinaryOwnerId,
|
|
260
279
|
writeKey: WriteKey,
|
|
261
280
|
) => boolean;
|
|
262
281
|
|
|
282
|
+
/** Sets the {@link WriteKey} for the given {@link Owner}. */
|
|
283
|
+
readonly setWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
|
|
284
|
+
|
|
263
285
|
/** Write encrypted {@link CrdtMessage}s to storage. */
|
|
264
286
|
readonly writeMessages: (
|
|
265
287
|
ownerId: BinaryOwnerId,
|
|
@@ -435,6 +457,7 @@ export const createProtocolMessageFromCrdtMessages =
|
|
|
435
457
|
maxSize?: PositiveInt,
|
|
436
458
|
): ProtocolMessage => {
|
|
437
459
|
const buffer = createProtocolMessageBuffer(owner.id, {
|
|
460
|
+
type: "initiator",
|
|
438
461
|
totalMaxSize: maxSize ?? maxProtocolMessageSize,
|
|
439
462
|
writeKey: owner.writeKey,
|
|
440
463
|
});
|
|
@@ -490,7 +513,7 @@ export const createProtocolMessageFromCrdtMessages =
|
|
|
490
513
|
export const createProtocolMessageForSync =
|
|
491
514
|
(deps: StorageDep) =>
|
|
492
515
|
(ownerId: OwnerId): ProtocolMessage | null => {
|
|
493
|
-
const buffer = createProtocolMessageBuffer(ownerId);
|
|
516
|
+
const buffer = createProtocolMessageBuffer(ownerId, { type: "initiator" });
|
|
494
517
|
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
495
518
|
|
|
496
519
|
const size = deps.storage.getSize(binaryOwnerId);
|
|
@@ -508,6 +531,19 @@ export const createProtocolMessageForSync =
|
|
|
508
531
|
return buffer.unwrap();
|
|
509
532
|
};
|
|
510
533
|
|
|
534
|
+
/** Creates a ProtocolMessage for {@link WriteKey} rotation. */
|
|
535
|
+
export const createProtocolMessageForWriteKeyRotation = (
|
|
536
|
+
ownerId: OwnerId,
|
|
537
|
+
currentWriteKey: WriteKey,
|
|
538
|
+
newWriteKey: WriteKey,
|
|
539
|
+
): ProtocolMessage => {
|
|
540
|
+
const buffer = createProtocolMessageBuffer(ownerId, {
|
|
541
|
+
type: "initiator",
|
|
542
|
+
writeKey: [currentWriteKey, newWriteKey],
|
|
543
|
+
});
|
|
544
|
+
return buffer.unwrap();
|
|
545
|
+
};
|
|
546
|
+
|
|
511
547
|
/**
|
|
512
548
|
* Mutable builder for constructing {@link ProtocolMessage} respecting size
|
|
513
549
|
* limits.
|
|
@@ -535,16 +571,22 @@ export interface ProtocolMessageBuffer {
|
|
|
535
571
|
export const createProtocolMessageBuffer = (
|
|
536
572
|
ownerId: OwnerId,
|
|
537
573
|
options: {
|
|
538
|
-
readonly errorCode?: ProtocolErrorCode;
|
|
539
|
-
readonly writeKey?: WriteKey;
|
|
540
574
|
readonly totalMaxSize?: PositiveInt | undefined;
|
|
541
575
|
readonly rangesMaxSize?: PositiveInt | undefined;
|
|
542
576
|
readonly version?: NonNegativeInt;
|
|
543
|
-
}
|
|
577
|
+
} & (
|
|
578
|
+
| {
|
|
579
|
+
readonly type: "initiator";
|
|
580
|
+
/** Single key or [current, new] for rotation. */
|
|
581
|
+
readonly writeKey?: WriteKey | readonly [WriteKey, WriteKey];
|
|
582
|
+
}
|
|
583
|
+
| {
|
|
584
|
+
readonly type: "non-initiator";
|
|
585
|
+
readonly errorCode: ProtocolErrorCode;
|
|
586
|
+
}
|
|
587
|
+
),
|
|
544
588
|
): ProtocolMessageBuffer => {
|
|
545
589
|
const {
|
|
546
|
-
errorCode,
|
|
547
|
-
writeKey,
|
|
548
590
|
totalMaxSize = maxProtocolMessageSize,
|
|
549
591
|
rangesMaxSize = maxProtocolMessageRangesSize,
|
|
550
592
|
version = protocolVersion,
|
|
@@ -565,7 +607,21 @@ export const createProtocolMessageBuffer = (
|
|
|
565
607
|
|
|
566
608
|
encodeNonNegativeInt(buffers.header, version);
|
|
567
609
|
buffers.header.extend(ownerIdToBinaryOwnerId(ownerId));
|
|
568
|
-
|
|
610
|
+
|
|
611
|
+
if (options.type === "initiator") {
|
|
612
|
+
if (!options.writeKey) {
|
|
613
|
+
buffers.header.extend([WriteKeyMode.None]);
|
|
614
|
+
} else if (!Array.isArray(options.writeKey)) {
|
|
615
|
+
buffers.header.extend([WriteKeyMode.Single]);
|
|
616
|
+
buffers.header.extend(options.writeKey as WriteKey);
|
|
617
|
+
} else {
|
|
618
|
+
buffers.header.extend([WriteKeyMode.Rotation]);
|
|
619
|
+
buffers.header.extend(options.writeKey[0] as WriteKey); // current
|
|
620
|
+
buffers.header.extend(options.writeKey[1] as WriteKey); // new
|
|
621
|
+
}
|
|
622
|
+
} else {
|
|
623
|
+
buffers.header.extend([options.errorCode]);
|
|
624
|
+
}
|
|
569
625
|
|
|
570
626
|
let isLastRangeInfinite = false;
|
|
571
627
|
|
|
@@ -577,10 +633,7 @@ export const createProtocolMessageBuffer = (
|
|
|
577
633
|
const getHeaderAndMessagesSize = () =>
|
|
578
634
|
buffers.header.getLength() +
|
|
579
635
|
buffers.messages.timestamps.getLength() +
|
|
580
|
-
buffers.messages.dbChanges.getLength()
|
|
581
|
-
(buffers.messages.timestamps.getCount() > 0 && writeKey
|
|
582
|
-
? writeKeyLength
|
|
583
|
-
: 0);
|
|
636
|
+
buffers.messages.dbChanges.getLength();
|
|
584
637
|
|
|
585
638
|
const getRangesSize = () =>
|
|
586
639
|
buffers.ranges.timestamps.getCount() > 0
|
|
@@ -695,8 +748,6 @@ export const createProtocolMessageBuffer = (
|
|
|
695
748
|
|
|
696
749
|
buffers.messages.timestamps.append(buffers.header);
|
|
697
750
|
buffers.header.extend(buffers.messages.dbChanges.unwrap());
|
|
698
|
-
if (buffers.messages.timestamps.getCount() > 0 && writeKey)
|
|
699
|
-
buffers.header.extend(writeKey);
|
|
700
751
|
|
|
701
752
|
if (buffers.ranges.timestamps.getCount() > 0) {
|
|
702
753
|
buffers.ranges.timestamps.append(buffers.header);
|
|
@@ -846,8 +897,6 @@ export const applyProtocolMessageAsClient =
|
|
|
846
897
|
});
|
|
847
898
|
}
|
|
848
899
|
|
|
849
|
-
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
850
|
-
|
|
851
900
|
const errorCode = input.shift() as ProtocolErrorCode;
|
|
852
901
|
if (errorCode !== ProtocolErrorCode.NoError) {
|
|
853
902
|
switch (errorCode) {
|
|
@@ -874,6 +923,7 @@ export const applyProtocolMessageAsClient =
|
|
|
874
923
|
}
|
|
875
924
|
|
|
876
925
|
const messages = decodeMessages(input);
|
|
926
|
+
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
877
927
|
|
|
878
928
|
if (
|
|
879
929
|
isNonEmptyReadonlyArray(messages) &&
|
|
@@ -887,6 +937,7 @@ export const applyProtocolMessageAsClient =
|
|
|
887
937
|
if (writeKey == null) return ok(null);
|
|
888
938
|
|
|
889
939
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
940
|
+
type: "initiator",
|
|
890
941
|
writeKey,
|
|
891
942
|
totalMaxSize,
|
|
892
943
|
rangesMaxSize,
|
|
@@ -921,8 +972,7 @@ export const applyProtocolMessageAsRelay =
|
|
|
921
972
|
version = protocolVersion,
|
|
922
973
|
): Result<ProtocolMessage | null, ProtocolInvalidDataError> =>
|
|
923
974
|
tryDecodeProtocolData(inputMessage, (input) => {
|
|
924
|
-
const requestedVersion =
|
|
925
|
-
const ownerId = decodeOwnerId(input);
|
|
975
|
+
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
926
976
|
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
927
977
|
|
|
928
978
|
if (requestedVersion !== version) {
|
|
@@ -935,44 +985,89 @@ export const applyProtocolMessageAsRelay =
|
|
|
935
985
|
|
|
936
986
|
subscribe?.(ownerId);
|
|
937
987
|
|
|
938
|
-
const
|
|
988
|
+
const writeKeyMode = input.shift() as WriteKeyMode;
|
|
989
|
+
let writeKey: WriteKey | undefined;
|
|
990
|
+
let newWriteKey: WriteKey | undefined;
|
|
991
|
+
|
|
992
|
+
if (writeKeyMode !== WriteKeyMode.None) {
|
|
993
|
+
writeKey = input.shiftN(writeKeyLength) as WriteKey;
|
|
994
|
+
switch (writeKeyMode) {
|
|
995
|
+
case WriteKeyMode.Single:
|
|
996
|
+
break;
|
|
997
|
+
case WriteKeyMode.Rotation:
|
|
998
|
+
newWriteKey = input.shiftN(writeKeyLength) as WriteKey;
|
|
999
|
+
break;
|
|
1000
|
+
default:
|
|
1001
|
+
throw new ProtocolDecodeError(
|
|
1002
|
+
`Invalid WriteKeyMode: ${writeKeyMode}`,
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
939
1006
|
|
|
940
|
-
if (
|
|
941
|
-
const
|
|
942
|
-
|
|
1007
|
+
if (writeKey) {
|
|
1008
|
+
const isValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
|
|
1009
|
+
if (!isValid) {
|
|
1010
|
+
return ok(
|
|
1011
|
+
createProtocolMessageBuffer(ownerId, {
|
|
1012
|
+
type: "non-initiator",
|
|
1013
|
+
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
1014
|
+
}).unwrap(),
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
943
1017
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1018
|
+
if (newWriteKey) {
|
|
1019
|
+
const rotationSuccess = deps.storage.setWriteKey(
|
|
1020
|
+
binaryOwnerId,
|
|
1021
|
+
newWriteKey,
|
|
1022
|
+
);
|
|
1023
|
+
if (!rotationSuccess) {
|
|
1024
|
+
return ok(
|
|
1025
|
+
createProtocolMessageBuffer(ownerId, {
|
|
1026
|
+
type: "non-initiator",
|
|
1027
|
+
errorCode: ProtocolErrorCode.WriteError,
|
|
1028
|
+
}).unwrap(),
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
948
1033
|
|
|
949
|
-
|
|
1034
|
+
const messages = decodeMessages(input);
|
|
1035
|
+
|
|
1036
|
+
if (isNonEmptyReadonlyArray(messages)) {
|
|
1037
|
+
if (!writeKey)
|
|
950
1038
|
return ok(
|
|
951
1039
|
createProtocolMessageBuffer(ownerId, {
|
|
1040
|
+
type: "non-initiator",
|
|
952
1041
|
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
953
1042
|
}).unwrap(),
|
|
954
1043
|
);
|
|
955
1044
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1045
|
+
// Only broadcast if there's no ranges.
|
|
1046
|
+
if (broadcast && input.getLength() === 0) {
|
|
1047
|
+
const broadcastBuffer = createProtocolMessageBuffer(ownerId, {
|
|
1048
|
+
type: "non-initiator",
|
|
1049
|
+
errorCode: ProtocolErrorCode.NoError,
|
|
1050
|
+
totalMaxSize,
|
|
1051
|
+
rangesMaxSize,
|
|
1052
|
+
version,
|
|
1053
|
+
});
|
|
1054
|
+
for (const message of messages) {
|
|
1055
|
+
broadcastBuffer.addMessage(message);
|
|
1056
|
+
}
|
|
1057
|
+
broadcast(ownerId, broadcastBuffer.unwrap());
|
|
965
1058
|
}
|
|
966
1059
|
|
|
967
1060
|
if (!deps.storage.writeMessages(binaryOwnerId, messages))
|
|
968
1061
|
return ok(
|
|
969
1062
|
createProtocolMessageBuffer(ownerId, {
|
|
1063
|
+
type: "non-initiator",
|
|
970
1064
|
errorCode: ProtocolErrorCode.WriteError,
|
|
971
1065
|
}).unwrap(),
|
|
972
1066
|
);
|
|
973
1067
|
}
|
|
974
1068
|
|
|
975
1069
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
1070
|
+
type: "non-initiator",
|
|
976
1071
|
errorCode: ProtocolErrorCode.NoError,
|
|
977
1072
|
totalMaxSize,
|
|
978
1073
|
rangesMaxSize,
|
|
@@ -1005,6 +1100,10 @@ const tryDecodeProtocolData = <T, E>(
|
|
|
1005
1100
|
};
|
|
1006
1101
|
|
|
1007
1102
|
const decodeVersionAndOwner = (input: Buffer): [NonNegativeInt, OwnerId] => {
|
|
1103
|
+
// This structure must never change across protocol versions. The version
|
|
1104
|
+
// and owner ID must always be the first two fields in every protocol message
|
|
1105
|
+
// to enable version negotiation and owner identification before any other
|
|
1106
|
+
// processing occurs.
|
|
1008
1107
|
const version = decodeNonNegativeInt(input);
|
|
1009
1108
|
const ownerId = decodeOwnerId(input);
|
|
1010
1109
|
return [version, ownerId];
|
|
@@ -1062,6 +1161,7 @@ const sync =
|
|
|
1062
1161
|
return ok(null);
|
|
1063
1162
|
}
|
|
1064
1163
|
const message = createProtocolMessageBuffer(binaryOwnerId, {
|
|
1164
|
+
type: "non-initiator",
|
|
1065
1165
|
errorCode: ProtocolErrorCode.SyncError,
|
|
1066
1166
|
});
|
|
1067
1167
|
return ok(message.unwrap());
|
|
@@ -1898,3 +1998,27 @@ export const decodeSqliteValue = (buffer: Buffer): SqliteValue => {
|
|
|
1898
1998
|
throw new ProtocolDecodeError("invalid ProtocolValueType");
|
|
1899
1999
|
}
|
|
1900
2000
|
};
|
|
2001
|
+
|
|
2002
|
+
/**
|
|
2003
|
+
* Decodes a ProtocolMessage into a readable JSON object for debugging.
|
|
2004
|
+
*
|
|
2005
|
+
* Note: This is a stub for future implementation. It should use:
|
|
2006
|
+
*
|
|
2007
|
+
* - DecodeVersionAndOwner
|
|
2008
|
+
* - DecodeError or decodeWriteKeys (depending on context)
|
|
2009
|
+
* - DecodeMessages
|
|
2010
|
+
* - DecodeRanges
|
|
2011
|
+
*
|
|
2012
|
+
* If you want to help, please contribute to this function.
|
|
2013
|
+
*/
|
|
2014
|
+
export const decodeProtocolMessageToJson = (
|
|
2015
|
+
_protocolMessage: ProtocolMessage,
|
|
2016
|
+
_isInitiator: boolean,
|
|
2017
|
+
): unknown => {
|
|
2018
|
+
// TODO: Implement using
|
|
2019
|
+
// - decodeVersionAndOwner
|
|
2020
|
+
// -- decodeError or decodeWriteKeys (should be refactored out),
|
|
2021
|
+
// -- decodeMessages, and decodeRanges.
|
|
2022
|
+
// This is a stub for PRs and community contributions.
|
|
2023
|
+
throw new Error("decodeProtocolMessageToJson is not implemented yet.");
|
|
2024
|
+
};
|
package/src/Evolu/Relay.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isNonEmptyReadonlyArray } from "../Array.js";
|
|
2
2
|
import { ConsoleConfig } from "../Console.js";
|
|
3
|
-
import {
|
|
3
|
+
import { TimingSafeEqualDep } from "../Crypto.js";
|
|
4
4
|
import { ok, Result } from "../Result.js";
|
|
5
5
|
import { sql, SqliteError } from "../Sqlite.js";
|
|
6
6
|
import { SimpleName } from "../Type.js";
|
|
@@ -19,8 +19,10 @@ export interface RelayConfig extends ConsoleConfig {
|
|
|
19
19
|
readonly name?: SimpleName;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export type RelaySqliteStorageDeps = SqliteStorageDeps & TimingSafeEqualDep;
|
|
23
|
+
|
|
22
24
|
export const createRelayStorage =
|
|
23
|
-
(deps:
|
|
25
|
+
(deps: RelaySqliteStorageDeps) =>
|
|
24
26
|
(options: CreateSqliteStorageBaseOptions): Result<Storage, SqliteError> => {
|
|
25
27
|
const sqliteStorageBase = createSqliteStorageBase(deps)(options);
|
|
26
28
|
if (!sqliteStorageBase.ok) return sqliteStorageBase;
|
|
@@ -87,7 +89,22 @@ export const createRelayStorage =
|
|
|
87
89
|
return true;
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
return
|
|
92
|
+
return deps.timingSafeEqual(rows[0].writeKey, writeKey);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
setWriteKey: (ownerId, writeKey) => {
|
|
96
|
+
const upsertWriteKey = deps.sqlite.exec(sql`
|
|
97
|
+
insert into evolu_writeKey (ownerId, writeKey)
|
|
98
|
+
values (${ownerId}, ${writeKey})
|
|
99
|
+
on conflict (ownerId) do update
|
|
100
|
+
set writeKey = excluded.writeKey;
|
|
101
|
+
`);
|
|
102
|
+
if (!upsertWriteKey.ok) {
|
|
103
|
+
options.onStorageError(upsertWriteKey.error);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
91
108
|
},
|
|
92
109
|
|
|
93
110
|
writeMessages: (ownerId, messages) => {
|