@interncom/diplomatic 0.0.33 → 0.0.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.
@@ -3,15 +3,17 @@ import { Applier } from './types';
3
3
  import { StateManager } from './state';
4
4
  export declare const entityTableName = "entities";
5
5
  export declare const typeIndexName = "entity_type";
6
+ export interface IEntity<T> {
7
+ eid: Uint8Array;
8
+ type: string;
9
+ updatedAt: Date;
10
+ createdAt: Date;
11
+ body: T;
12
+ }
6
13
  interface IEntityDB extends DBSchema {
7
14
  entities: {
8
15
  key: Uint8Array;
9
- value: {
10
- eid: Uint8Array;
11
- type: string;
12
- updatedAt: Date;
13
- body: unknown;
14
- };
16
+ value: IEntity<unknown>;
15
17
  indexes: {
16
18
  "entity_type": "string";
17
19
  };
@@ -13,17 +13,21 @@ declare class IDBStore implements IClientStateStore {
13
13
  setHostID(id: string): Promise<void>;
14
14
  getLastFetchedAt(): Promise<Date | undefined>;
15
15
  setLastFetchedAt(ts: Date): Promise<void>;
16
- uploadQueue: Map<string, Uint8Array>;
17
- enqueueUpload: (sha256: string, cipherOp: Uint8Array) => Promise<void>;
18
- dequeueUpload: (sha256: string) => Promise<void>;
19
- peekUpload: (sha256: string) => Promise<Uint8Array | undefined>;
20
- listUploads: () => Promise<string[]>;
16
+ enqueueUpload: (sha256: Uint8Array, cipherOp: Uint8Array) => Promise<void>;
17
+ dequeueUpload: (sha256: Uint8Array) => Promise<void>;
18
+ peekUpload: (sha256: Uint8Array) => Promise<Uint8Array | undefined>;
19
+ listUploads: () => Promise<Uint8Array[]>;
21
20
  numUploads: () => Promise<number>;
22
21
  downloadQueue: Set<string>;
23
- enqueueDownload: (path: string) => Promise<void>;
24
- dequeueDownload: (path: string) => Promise<void>;
25
- listDownloads: () => Promise<string[]>;
22
+ enqueueDownload: (sha256: Uint8Array, recordedAt: Date) => Promise<void>;
23
+ dequeueDownload: (sha256: Uint8Array) => Promise<void>;
24
+ listDownloads: () => Promise<{
25
+ sha256: Uint8Array;
26
+ recordedAt: Date;
27
+ }[]>;
26
28
  numDownloads: () => Promise<number>;
29
+ storeOp: (sha256: Uint8Array, cipherOp: Uint8Array) => Promise<void>;
30
+ clearOp: (sha256: Uint8Array) => Promise<void>;
27
31
  }
28
32
  export declare const idbStore: IDBStore;
29
33
  export {};
package/dist/index.mjs CHANGED
@@ -154,33 +154,51 @@ var LocalStorageStore = class {
154
154
  }
155
155
  uploadQueue = /* @__PURE__ */ new Map();
156
156
  enqueueUpload = async (sha256, cipherOp) => {
157
- this.uploadQueue.set(sha256, cipherOp);
157
+ const hex = btoh(sha256);
158
+ this.uploadQueue.set(hex, cipherOp);
158
159
  };
159
160
  dequeueUpload = async (sha256) => {
160
- this.uploadQueue.delete(sha256);
161
+ const hex = btoh(sha256);
162
+ this.uploadQueue.delete(hex);
161
163
  };
162
164
  peekUpload = async (sha256) => {
163
- return this.uploadQueue.get(sha256);
165
+ const hex = btoh(sha256);
166
+ return this.uploadQueue.get(hex);
164
167
  };
165
168
  listUploads = async () => {
166
- return Array.from(this.uploadQueue.keys());
169
+ const hexes = Array.from(this.uploadQueue.keys());
170
+ return hexes.map(htob);
167
171
  };
168
172
  numUploads = async () => {
169
173
  return this.uploadQueue.size;
170
174
  };
171
- downloadQueue = /* @__PURE__ */ new Set();
172
- enqueueDownload = async (path) => {
173
- this.downloadQueue.add(path);
175
+ downloadQueue = /* @__PURE__ */ new Map();
176
+ enqueueDownload = async (sha256, recordedAt) => {
177
+ const hex = btoh(sha256);
178
+ this.downloadQueue.set(hex, recordedAt);
174
179
  };
175
- dequeueDownload = async (path) => {
176
- this.downloadQueue.delete(path);
180
+ dequeueDownload = async (sha256) => {
181
+ const hex = btoh(sha256);
182
+ this.downloadQueue.delete(hex);
177
183
  };
178
184
  listDownloads = async () => {
179
- return Array.from(this.downloadQueue.keys());
185
+ const list = Array.from(this.downloadQueue.entries());
186
+ return list.map(([hex, recordedAt]) => {
187
+ return { sha256: htob(hex), recordedAt };
188
+ });
180
189
  };
181
190
  numDownloads = async () => {
182
191
  return this.downloadQueue.size;
183
192
  };
193
+ ops = /* @__PURE__ */ new Map();
194
+ storeOp = async (sha256, cipherOp) => {
195
+ const hex = btoh(sha256);
196
+ this.ops.set(hex, cipherOp);
197
+ };
198
+ clearOp = async (sha256) => {
199
+ const hex = btoh(sha256);
200
+ this.ops.delete(hex);
201
+ };
184
202
  };
185
203
  var localStorageStore = new LocalStorageStore();
186
204
 
@@ -420,14 +438,17 @@ replaceTraps((oldTraps) => ({
420
438
  }));
421
439
 
422
440
  // src/idbStore.ts
423
- var db = await openDB("client-store-db", 2, {
441
+ var db = await openDB("client-store-db", 5, {
424
442
  upgrade(db3) {
425
443
  db3.createObjectStore("metaKV");
444
+ db3.createObjectStore("ops", {
445
+ keyPath: "sha256"
446
+ });
426
447
  db3.createObjectStore("uploadQueue", {
427
448
  keyPath: "sha256"
428
449
  });
429
450
  db3.createObjectStore("downloadQueue", {
430
- keyPath: "path"
451
+ keyPath: "sha256"
431
452
  });
432
453
  }
433
454
  });
@@ -479,7 +500,6 @@ var IDBStore = class {
479
500
  const str = ts.toISOString();
480
501
  await db.put("metaKV", str, "lastFetchedAt");
481
502
  }
482
- uploadQueue = /* @__PURE__ */ new Map();
483
503
  enqueueUpload = async (sha256, cipherOp) => {
484
504
  await db.put("uploadQueue", { sha256, cipherOp });
485
505
  };
@@ -499,20 +519,26 @@ var IDBStore = class {
499
519
  return num;
500
520
  };
501
521
  downloadQueue = /* @__PURE__ */ new Set();
502
- enqueueDownload = async (path) => {
503
- await db.put("downloadQueue", { path });
522
+ enqueueDownload = async (sha256, recordedAt) => {
523
+ await db.put("downloadQueue", { sha256, recordedAt });
504
524
  };
505
- dequeueDownload = async (path) => {
506
- await db.delete("downloadQueue", path);
525
+ dequeueDownload = async (sha256) => {
526
+ await db.delete("downloadQueue", sha256);
507
527
  };
508
528
  listDownloads = async () => {
509
- const paths = await db.getAllKeys("downloadQueue");
510
- return paths;
529
+ const list = await db.getAll("downloadQueue");
530
+ return list;
511
531
  };
512
532
  numDownloads = async () => {
513
533
  const num = await db.count("downloadQueue");
514
534
  return num;
515
535
  };
536
+ storeOp = async (sha256, cipherOp) => {
537
+ await db.put("ops", { sha256, cipherOp });
538
+ };
539
+ clearOp = async (sha256) => {
540
+ await db.delete("ops", sha256);
541
+ };
516
542
  };
517
543
  var idbStore = new IDBStore();
518
544
 
@@ -8721,8 +8747,9 @@ var DiplomaticClientAPI = class {
8721
8747
  const opPath = await response.text();
8722
8748
  return opPath;
8723
8749
  }
8724
- async getDelta(hostURL, opPath, keyPair) {
8750
+ async getDelta(hostURL, sha256, keyPair) {
8725
8751
  const url = new URL(hostURL);
8752
+ const opPath = btoh(sha256);
8726
8753
  url.pathname = `/ops/${opPath}`;
8727
8754
  const sig = await this.crypto.signEd25519(opPath, keyPair.privateKey);
8728
8755
  const sigHex = btoh(sig);
@@ -8744,7 +8771,7 @@ var DiplomaticClientAPI = class {
8744
8771
  }
8745
8772
  return resp.cipher;
8746
8773
  }
8747
- async getDeltaPaths(hostURL, begin, keyPair) {
8774
+ async listDeltas(hostURL, begin, keyPair) {
8748
8775
  const t = begin.toISOString();
8749
8776
  const path = `/ops?begin=${t}`;
8750
8777
  const url = new URL(hostURL);
@@ -8958,33 +8985,34 @@ var DiplomaticClient = class {
8958
8985
  const lastFetchedAt = await this.store.getLastFetchedAt();
8959
8986
  const begin = lastFetchedAt ?? /* @__PURE__ */ new Date(0);
8960
8987
  const { hostURL, hostKeyPair } = this;
8961
- const pathResp = await api_default.getDeltaPaths(hostURL, begin, hostKeyPair);
8962
- const paths = pathResp.paths;
8963
- for (const path of paths) {
8964
- await this.store.enqueueDownload(path);
8988
+ const resp = await api_default.listDeltas(hostURL, begin, hostKeyPair);
8989
+ for (const item of resp.deltas) {
8990
+ await this.store.enqueueDownload(item.sha256, item.recordedAt);
8965
8991
  this.emitUpdate();
8966
8992
  }
8967
- await this.store.setLastFetchedAt(new Date(pathResp.fetchedAt));
8993
+ await this.store.setLastFetchedAt(new Date(resp.fetchedAt));
8968
8994
  }
8969
8995
  async fetchAndExecQueuedOps() {
8970
8996
  if (!this.hostURL || !this.hostKeyPair || !this.encKey) {
8971
8997
  return [];
8972
8998
  }
8973
8999
  const { hostURL, hostKeyPair, encKey } = this;
8974
- const paths = await this.store.listDownloads();
8975
- paths.sort((p1, p2) => p1.localeCompare(p2));
8976
- for (const path of paths) {
8977
- const cipher = await api_default.getDelta(hostURL, path, hostKeyPair);
9000
+ const items = await this.store.listDownloads();
9001
+ items.sort((i1, i2) => i1.recordedAt.getTime() - i2.recordedAt.getTime());
9002
+ for (const item of items) {
9003
+ const cipher = await api_default.getDelta(hostURL, item.sha256, hostKeyPair);
8978
9004
  const packed = await crypto_default.decryptXSalsa20Poly1305Combined(cipher, encKey);
8979
9005
  const op = decode(packed);
8980
9006
  try {
9007
+ const sha256 = await crypto_default.sha256Hash(cipher);
9008
+ await this.store.storeOp(sha256, cipher);
8981
9009
  await this.stateManager.apply(op);
8982
- await this.store.dequeueDownload(path);
9010
+ await this.store.dequeueDownload(sha256);
8983
9011
  this.emitUpdate();
8984
9012
  } catch {
8985
9013
  const transient = true;
8986
9014
  if (!transient) {
8987
- await this.store.dequeueDownload(path);
9015
+ await this.store.dequeueDownload(item.sha256);
8988
9016
  this.emitUpdate();
8989
9017
  }
8990
9018
  }
@@ -9017,17 +9045,17 @@ var DiplomaticClient = class {
9017
9045
  const packed = encode(op);
9018
9046
  const cipherOp = await crypto_default.encryptXSalsa20Poly1305Combined(packed, this.encKey);
9019
9047
  const sha256 = await crypto_default.sha256Hash(cipherOp);
9020
- const shaHex = btoh(sha256);
9021
- await this.store.enqueueUpload(shaHex, cipherOp);
9048
+ await this.store.storeOp(sha256, cipherOp);
9049
+ await this.store.enqueueUpload(sha256, cipherOp);
9022
9050
  this.emitUpdate();
9023
9051
  try {
9024
9052
  await this.stateManager.apply(op);
9025
9053
  } catch (err) {
9026
- await this.store.dequeueUpload(shaHex);
9054
+ await this.store.dequeueUpload(sha256);
9027
9055
  this.emitUpdate();
9028
9056
  }
9029
9057
  try {
9030
- await this.pushQueuedOp(shaHex);
9058
+ await this.pushQueuedOp(sha256);
9031
9059
  } catch (err) {
9032
9060
  console.info("failed to push");
9033
9061
  }
@@ -9143,7 +9171,7 @@ __export(entityDB_exports, {
9143
9171
  });
9144
9172
  var entityTableName = "entities";
9145
9173
  var typeIndexName = "entity_type";
9146
- var db2 = await openDB("db", 4, {
9174
+ var db2 = await openDB("db", 5, {
9147
9175
  upgrade(db3) {
9148
9176
  const store = db3.createObjectStore("entities", { keyPath: "eid", autoIncrement: false });
9149
9177
  store.createIndex("entity_type", "type");
@@ -9154,7 +9182,7 @@ var applier = async (op) => {
9154
9182
  case 1 /* UPSERT */: {
9155
9183
  const curr = await db2.get("entities", op.eid);
9156
9184
  if (new Date(op.ts) > (curr?.updatedAt ?? "")) {
9157
- await db2.put("entities", { eid: op.eid, type: op.type, updatedAt: new Date(op.ts), body: op.body });
9185
+ await db2.put("entities", { eid: op.eid, type: op.type, createdAt: curr?.createdAt ?? /* @__PURE__ */ new Date(), updatedAt: new Date(op.ts), body: op.body });
9158
9186
  }
9159
9187
  break;
9160
9188
  }
@@ -10,16 +10,22 @@ declare class LocalStorageStore implements IClientStateStore {
10
10
  getLastFetchedAt(): Promise<Date | undefined>;
11
11
  setLastFetchedAt(ts: Date): Promise<void>;
12
12
  uploadQueue: Map<string, Uint8Array>;
13
- enqueueUpload: (sha256: string, cipherOp: Uint8Array) => Promise<void>;
14
- dequeueUpload: (sha256: string) => Promise<void>;
15
- peekUpload: (sha256: string) => Promise<Uint8Array | undefined>;
16
- listUploads: () => Promise<string[]>;
13
+ enqueueUpload: (sha256: Uint8Array, cipherOp: Uint8Array) => Promise<void>;
14
+ dequeueUpload: (sha256: Uint8Array) => Promise<void>;
15
+ peekUpload: (sha256: Uint8Array) => Promise<Uint8Array | undefined>;
16
+ listUploads: () => Promise<Uint8Array[]>;
17
17
  numUploads: () => Promise<number>;
18
- downloadQueue: Set<string>;
19
- enqueueDownload: (path: string) => Promise<void>;
20
- dequeueDownload: (path: string) => Promise<void>;
21
- listDownloads: () => Promise<string[]>;
18
+ downloadQueue: Map<string, Date>;
19
+ enqueueDownload: (sha256: Uint8Array, recordedAt: Date) => Promise<void>;
20
+ dequeueDownload: (sha256: Uint8Array) => Promise<void>;
21
+ listDownloads: () => Promise<{
22
+ sha256: Uint8Array;
23
+ recordedAt: Date;
24
+ }[]>;
22
25
  numDownloads: () => Promise<number>;
26
+ ops: Map<string, Uint8Array>;
27
+ storeOp: (sha256: Uint8Array, cipherOp: Uint8Array) => Promise<void>;
28
+ clearOp: (sha256: Uint8Array) => Promise<void>;
23
29
  }
24
30
  export declare const localStorageStore: LocalStorageStore;
25
31
  export {};
@@ -14,16 +14,22 @@ declare class MemoryStore implements IClientStateStore {
14
14
  getLastFetchedAt(): Promise<Date | undefined>;
15
15
  setLastFetchedAt(ts: Date): Promise<void>;
16
16
  uploadQueue: Map<string, Uint8Array>;
17
- enqueueUpload: (sha256: string, cipherOp: Uint8Array) => Promise<void>;
18
- dequeueUpload: (sha256: string) => Promise<void>;
19
- peekUpload: (sha256: string) => Promise<Uint8Array | undefined>;
20
- listUploads: () => Promise<string[]>;
17
+ enqueueUpload: (sha256: Uint8Array, cipherOp: Uint8Array) => Promise<void>;
18
+ dequeueUpload: (sha256: Uint8Array) => Promise<void>;
19
+ peekUpload: (sha256: Uint8Array) => Promise<Uint8Array | undefined>;
20
+ listUploads: () => Promise<Uint8Array[]>;
21
21
  numUploads: () => Promise<number>;
22
- downloadQueue: Set<string>;
23
- enqueueDownload: (path: string) => Promise<void>;
24
- dequeueDownload: (path: string) => Promise<void>;
25
- listDownloads: () => Promise<string[]>;
22
+ downloadQueue: Map<string, Date>;
23
+ enqueueDownload: (sha256: Uint8Array, recordedAt: Date) => Promise<void>;
24
+ dequeueDownload: (sha256: Uint8Array) => Promise<void>;
25
+ listDownloads: () => Promise<{
26
+ sha256: Uint8Array;
27
+ recordedAt: Date;
28
+ }[]>;
26
29
  numDownloads: () => Promise<number>;
30
+ ops: Map<string, Uint8Array>;
31
+ storeOp: (sha256: Uint8Array, cipherOp: Uint8Array) => Promise<void>;
32
+ clearOp: (sha256: Uint8Array) => Promise<void>;
27
33
  }
28
34
  export declare const memoryStore: MemoryStore;
29
35
  export {};
@@ -1,4 +1,4 @@
1
- import type { ICrypto, IGetDeltaPathsResponse, IMsgpackCodec, KeyPair } from "./types.ts";
1
+ import type { ICrypto, IListDeltasResponse, IMsgpackCodec, KeyPair } from "./types.ts";
2
2
  export default class DiplomaticClientAPI {
3
3
  codec: IMsgpackCodec;
4
4
  crypto: ICrypto;
@@ -6,6 +6,6 @@ export default class DiplomaticClientAPI {
6
6
  getHostID(hostURL: URL): Promise<string>;
7
7
  register(hostURL: URL, pubKey: Uint8Array, token: string): Promise<void>;
8
8
  putDelta(hostURL: URL, cipherOp: Uint8Array, keyPair: KeyPair): Promise<string>;
9
- getDelta(hostURL: URL, opPath: string, keyPair: KeyPair): Promise<Uint8Array>;
10
- getDeltaPaths(hostURL: URL, begin: Date, keyPair: KeyPair): Promise<IGetDeltaPathsResponse>;
9
+ getDelta(hostURL: URL, sha256: Uint8Array, keyPair: KeyPair): Promise<Uint8Array>;
10
+ listDeltas(hostURL: URL, begin: Date, keyPair: KeyPair): Promise<IListDeltasResponse>;
11
11
  }
@@ -29,16 +29,20 @@ export interface IRegistrationRequest {
29
29
  export interface IOperationRequest {
30
30
  cipher: Uint8Array;
31
31
  }
32
- export interface IGetDeltaPathsResponse {
33
- paths: string[];
32
+ export interface IDeltaListItem {
33
+ sha256: Uint8Array;
34
+ recordedAt: Date;
35
+ }
36
+ export interface IListDeltasResponse {
37
+ deltas: IDeltaListItem[];
34
38
  fetchedAt: string;
35
39
  }
36
40
  export interface IStorage {
37
41
  addUser: (pubKeyHex: string) => Promise<void>;
38
42
  hasUser: (pubKeyHex: string) => Promise<boolean>;
39
- setOp: (pubKeyHex: string, path: string, op: Uint8Array) => Promise<void>;
40
- getOp: (pubKeyHex: string, path: string) => Promise<Uint8Array | undefined>;
41
- listOps: (pubKeyHex: string, begin: string, end: string) => Promise<string[]>;
43
+ setOp: (pubKeyHex: string, recordedAt: Date, op: Uint8Array) => Promise<void>;
44
+ getOp: (pubKeyHex: string, sha256Hex: string) => Promise<Uint8Array | undefined>;
45
+ listOps: (pubKeyHex: string, begin: string, end: string) => Promise<IDeltaListItem[]>;
42
46
  }
43
47
  export interface KeyPair {
44
48
  keyType: "public" | "private" | "secret";
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { IOp } from "./shared/types";
1
+ import type { IDeltaListItem, IOp } from "./shared/types";
2
2
  export interface IClientStateStore {
3
3
  init?: () => Promise<void>;
4
4
  getSeed: () => Promise<Uint8Array | undefined>;
@@ -10,15 +10,17 @@ export interface IClientStateStore {
10
10
  setLastFetchedAt: (ts: Date) => Promise<void>;
11
11
  getLastFetchedAt: () => Promise<Date | undefined>;
12
12
  wipe: () => Promise<void>;
13
- enqueueUpload: (sha256: string, cipherOp: Uint8Array) => Promise<void>;
14
- dequeueUpload: (sha256: string) => Promise<void>;
15
- peekUpload: (sha256: string) => Promise<Uint8Array | undefined>;
16
- listUploads: () => Promise<string[]>;
13
+ enqueueUpload: (sha256: Uint8Array, cipherOp: Uint8Array) => Promise<void>;
14
+ dequeueUpload: (sha256: Uint8Array) => Promise<void>;
15
+ peekUpload: (sha256: Uint8Array) => Promise<Uint8Array | undefined>;
16
+ listUploads: () => Promise<Uint8Array[]>;
17
17
  numUploads: () => Promise<number>;
18
- enqueueDownload: (path: string) => Promise<void>;
19
- dequeueDownload: (path: string) => Promise<void>;
20
- listDownloads: () => Promise<string[]>;
18
+ enqueueDownload: (sha256: Uint8Array, recordedAt: Date) => Promise<void>;
19
+ dequeueDownload: (sha256: Uint8Array) => Promise<void>;
20
+ listDownloads: () => Promise<IDeltaListItem[]>;
21
21
  numDownloads: () => Promise<number>;
22
+ storeOp: (sha256: Uint8Array, cipherOp: Uint8Array) => Promise<void>;
23
+ clearOp: (sha256: Uint8Array) => Promise<void>;
22
24
  }
23
25
  export interface IDiplomaticClientState {
24
26
  hasSeed: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interncom/diplomatic",
3
- "version": "0.0.33",
3
+ "version": "0.0.35",
4
4
  "type": "module",
5
5
  "description": "Secure sync layer",
6
6
  "main": "dist/index.mjs",