@interncom/diplomatic 0.0.29 → 0.0.33

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/client.d.ts CHANGED
@@ -16,7 +16,6 @@ export default class DiplomaticClient {
16
16
  encKey?: Uint8Array;
17
17
  hostURL?: URL;
18
18
  hostKeyPair?: KeyPair;
19
- lastFetchedAt?: string;
20
19
  constructor(params: IDiplomaticClientParams);
21
20
  wipe: () => Promise<void>;
22
21
  websocket?: WebSocket;
@@ -37,5 +36,6 @@ export default class DiplomaticClient {
37
36
  pushQueuedOps(): Promise<void>;
38
37
  sync(): Promise<void>;
39
38
  apply(op: IOp): Promise<void>;
40
- upsert<T>(type: string, body: T, version?: number): Promise<void>;
39
+ upsert<T>(type: string, body: T, eid?: Uint8Array, version?: number): Promise<void>;
40
+ delete<T>(type: string, eid: Uint8Array, version?: number): Promise<void>;
41
41
  }
@@ -0,0 +1,23 @@
1
+ import { type DBSchema } from 'idb';
2
+ import { Applier } from './types';
3
+ import { StateManager } from './state';
4
+ export declare const entityTableName = "entities";
5
+ export declare const typeIndexName = "entity_type";
6
+ interface IEntityDB extends DBSchema {
7
+ entities: {
8
+ key: Uint8Array;
9
+ value: {
10
+ eid: Uint8Array;
11
+ type: string;
12
+ updatedAt: Date;
13
+ body: unknown;
14
+ };
15
+ indexes: {
16
+ "entity_type": "string";
17
+ };
18
+ };
19
+ }
20
+ export declare const db: import("idb").IDBPDatabase<IEntityDB>;
21
+ export declare const applier: Applier;
22
+ export declare const stateManager: StateManager;
23
+ export {};
@@ -3,6 +3,7 @@ declare class IDBStore implements IClientStateStore {
3
3
  seed?: Uint8Array;
4
4
  hostURL?: string;
5
5
  hostID?: string;
6
+ lastFetchedAt?: Date;
6
7
  wipe(): Promise<void>;
7
8
  getSeed(): Promise<Uint8Array | undefined>;
8
9
  setSeed(seed: Uint8Array): Promise<void>;
@@ -10,6 +11,8 @@ declare class IDBStore implements IClientStateStore {
10
11
  setHostURL(url: string): Promise<void>;
11
12
  getHostID(): Promise<string | undefined>;
12
13
  setHostID(id: string): Promise<void>;
14
+ getLastFetchedAt(): Promise<Date | undefined>;
15
+ setLastFetchedAt(ts: Date): Promise<void>;
13
16
  uploadQueue: Map<string, Uint8Array>;
14
17
  enqueueUpload: (sha256: string, cipherOp: Uint8Array) => Promise<void>;
15
18
  dequeueUpload: (sha256: string) => Promise<void>;
package/dist/index.d.ts CHANGED
@@ -6,9 +6,10 @@ import type { IDiplomaticClientState } from "./types";
6
6
  import DiplomaticClient from "./client";
7
7
  import libsodiumCrypto from "./crypto";
8
8
  import { btoh, htob } from "./shared/lib";
9
- import { type IOp, Verb } from "./shared/types";
9
+ import { type IOp, type IUpsertOp, Verb } from "./shared/types";
10
10
  import useStateWatcher from "./react/useStateWatcher";
11
11
  import InitSeedView from "./react/initSeedView";
12
12
  import ClientStatusBar from "./react/statusBar";
13
- export { useClientState, useSyncOnResume, StateManager, useStateWatcher, opMapApplier, localStorageStore, idbStore, DiplomaticClient, libsodiumCrypto, btoh, htob, Verb, InitSeedView, ClientStatusBar, };
14
- export type { IOp, IDiplomaticClientState, };
13
+ import * as EntityDB from "./entityDB";
14
+ export { useClientState, useSyncOnResume, StateManager, useStateWatcher, opMapApplier, localStorageStore, idbStore, DiplomaticClient, libsodiumCrypto, btoh, htob, Verb, InitSeedView, ClientStatusBar, EntityDB, };
15
+ export type { IOp, IUpsertOp, IDiplomaticClientState, };
package/dist/index.mjs CHANGED
@@ -69,8 +69,8 @@ var StateManager = class {
69
69
  emitter = new EventEmitter();
70
70
  applier;
71
71
  clear;
72
- constructor(applier, clear) {
73
- this.applier = applier;
72
+ constructor(applier2, clear) {
73
+ this.applier = applier2;
74
74
  this.clear = clear;
75
75
  }
76
76
  apply = async (op) => {
@@ -86,9 +86,9 @@ var StateManager = class {
86
86
  };
87
87
  function opMapApplier(appliers) {
88
88
  return async (op) => {
89
- const applier = appliers[op.type];
90
- if (applier?.check(op)) {
91
- await applier.apply(op);
89
+ const applier2 = appliers[op.type];
90
+ if (applier2?.check(op)) {
91
+ await applier2.apply(op);
92
92
  }
93
93
  };
94
94
  }
@@ -113,6 +113,7 @@ function htob(hex) {
113
113
  var seedKey = "seedHex";
114
114
  var hostURLKey = "hostURL";
115
115
  var hostIDKey = "hostID";
116
+ var lastFetchedAtKey = "lastFetchedAt";
116
117
  var LocalStorageStore = class {
117
118
  async wipe() {
118
119
  localStorage.clear();
@@ -140,6 +141,17 @@ var LocalStorageStore = class {
140
141
  async setHostID(id) {
141
142
  return localStorage.setItem(hostIDKey, id);
142
143
  }
144
+ async getLastFetchedAt() {
145
+ const res = localStorage.getItem(lastFetchedAtKey);
146
+ if (!res) {
147
+ return;
148
+ }
149
+ return new Date(res);
150
+ }
151
+ async setLastFetchedAt(ts) {
152
+ const str = ts.toISOString();
153
+ localStorage.setItem(lastFetchedAtKey, str);
154
+ }
143
155
  uploadQueue = /* @__PURE__ */ new Map();
144
156
  enqueueUpload = async (sha256, cipherOp) => {
145
157
  this.uploadQueue.set(sha256, cipherOp);
@@ -312,11 +324,11 @@ function openDB(name, version, { blocked, upgrade, blocking, terminated } = {})
312
324
  event
313
325
  ));
314
326
  }
315
- openPromise.then((db2) => {
327
+ openPromise.then((db3) => {
316
328
  if (terminated)
317
- db2.addEventListener("close", () => terminated());
329
+ db3.addEventListener("close", () => terminated());
318
330
  if (blocking) {
319
- db2.addEventListener("versionchange", (event) => blocking(event.oldVersion, event.newVersion, event));
331
+ db3.addEventListener("versionchange", (event) => blocking(event.oldVersion, event.newVersion, event));
320
332
  }
321
333
  }).catch(() => {
322
334
  });
@@ -409,12 +421,12 @@ replaceTraps((oldTraps) => ({
409
421
 
410
422
  // src/idbStore.ts
411
423
  var db = await openDB("client-store-db", 2, {
412
- upgrade(db2) {
413
- db2.createObjectStore("metaKV");
414
- db2.createObjectStore("uploadQueue", {
424
+ upgrade(db3) {
425
+ db3.createObjectStore("metaKV");
426
+ db3.createObjectStore("uploadQueue", {
415
427
  keyPath: "sha256"
416
428
  });
417
- db2.createObjectStore("downloadQueue", {
429
+ db3.createObjectStore("downloadQueue", {
418
430
  keyPath: "path"
419
431
  });
420
432
  }
@@ -423,6 +435,7 @@ var IDBStore = class {
423
435
  seed;
424
436
  hostURL;
425
437
  hostID;
438
+ lastFetchedAt;
426
439
  async wipe() {
427
440
  await db.clear("metaKV");
428
441
  await db.clear("uploadQueue");
@@ -455,6 +468,17 @@ var IDBStore = class {
455
468
  async setHostID(id) {
456
469
  await db.put("metaKV", id, "hostID");
457
470
  }
471
+ async getLastFetchedAt() {
472
+ const res = await db.get("metaKV", "lastFetchedAt");
473
+ if (!res) {
474
+ return;
475
+ }
476
+ return new Date(res);
477
+ }
478
+ async setLastFetchedAt(ts) {
479
+ const str = ts.toISOString();
480
+ await db.put("metaKV", str, "lastFetchedAt");
481
+ }
458
482
  uploadQueue = /* @__PURE__ */ new Map();
459
483
  enqueueUpload = async (sha256, cipherOp) => {
460
484
  await db.put("uploadQueue", { sha256, cipherOp });
@@ -8578,6 +8602,9 @@ var LibsodiumCrypto = class {
8578
8602
  constructor(sodium) {
8579
8603
  this.sodium = sodium;
8580
8604
  }
8605
+ async gen128BitRandomID() {
8606
+ return this.sodium.randombytes_buf(16);
8607
+ }
8581
8608
  async gen256BitSecureRandomSeed() {
8582
8609
  return this.sodium.crypto_secretbox_keygen();
8583
8610
  }
@@ -8759,8 +8786,9 @@ var Verb = /* @__PURE__ */ ((Verb2) => {
8759
8786
  })(Verb || {});
8760
8787
 
8761
8788
  // ../shared/ops.ts
8762
- function genUpsertOp(type, body, version = 0) {
8789
+ function genUpsertOp(eid, type, body, version = 0) {
8763
8790
  return {
8791
+ eid,
8764
8792
  ts: (/* @__PURE__ */ new Date()).toISOString(),
8765
8793
  type,
8766
8794
  verb: 1 /* UPSERT */,
@@ -8768,6 +8796,15 @@ function genUpsertOp(type, body, version = 0) {
8768
8796
  body
8769
8797
  };
8770
8798
  }
8799
+ function genDeleteOp(eid, type, version = 0) {
8800
+ return {
8801
+ eid,
8802
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
8803
+ type,
8804
+ verb: 0 /* DELETE */,
8805
+ ver: version
8806
+ };
8807
+ }
8771
8808
 
8772
8809
  // src/client.ts
8773
8810
  var DiplomaticClient = class {
@@ -8778,7 +8815,6 @@ var DiplomaticClient = class {
8778
8815
  encKey;
8779
8816
  hostURL;
8780
8817
  hostKeyPair;
8781
- lastFetchedAt;
8782
8818
  constructor(params) {
8783
8819
  this.store = params.store;
8784
8820
  this.stateManager = params.stateManager;
@@ -8789,7 +8825,6 @@ var DiplomaticClient = class {
8789
8825
  this.encKey = void 0;
8790
8826
  this.hostURL = void 0;
8791
8827
  this.hostKeyPair = void 0;
8792
- this.lastFetchedAt = void 0;
8793
8828
  this.websocket?.close();
8794
8829
  this.websocket = void 0;
8795
8830
  await this.stateManager.clear();
@@ -8920,7 +8955,8 @@ var DiplomaticClient = class {
8920
8955
  if (!this.hostURL || !this.hostKeyPair || !this.encKey) {
8921
8956
  return [];
8922
8957
  }
8923
- const begin = new Date(this.lastFetchedAt ?? 0);
8958
+ const lastFetchedAt = await this.store.getLastFetchedAt();
8959
+ const begin = lastFetchedAt ?? /* @__PURE__ */ new Date(0);
8924
8960
  const { hostURL, hostKeyPair } = this;
8925
8961
  const pathResp = await api_default.getDeltaPaths(hostURL, begin, hostKeyPair);
8926
8962
  const paths = pathResp.paths;
@@ -8928,7 +8964,7 @@ var DiplomaticClient = class {
8928
8964
  await this.store.enqueueDownload(path);
8929
8965
  this.emitUpdate();
8930
8966
  }
8931
- this.lastFetchedAt = pathResp.fetchedAt;
8967
+ await this.store.setLastFetchedAt(new Date(pathResp.fetchedAt));
8932
8968
  }
8933
8969
  async fetchAndExecQueuedOps() {
8934
8970
  if (!this.hostURL || !this.hostKeyPair || !this.encKey) {
@@ -8986,7 +9022,7 @@ var DiplomaticClient = class {
8986
9022
  this.emitUpdate();
8987
9023
  try {
8988
9024
  await this.stateManager.apply(op);
8989
- } catch {
9025
+ } catch (err) {
8990
9026
  await this.store.dequeueUpload(shaHex);
8991
9027
  this.emitUpdate();
8992
9028
  }
@@ -8996,8 +9032,13 @@ var DiplomaticClient = class {
8996
9032
  console.info("failed to push");
8997
9033
  }
8998
9034
  }
8999
- async upsert(type, body, version = 0) {
9000
- const op = genUpsertOp(type, body, version);
9035
+ async upsert(type, body, eid, version = 0) {
9036
+ const id = eid ?? await crypto_default.gen128BitRandomID();
9037
+ const op = genUpsertOp(id, type, body, version);
9038
+ return this.apply(op);
9039
+ }
9040
+ async delete(type, eid, version = 0) {
9041
+ const op = genDeleteOp(eid, type, version);
9001
9042
  return this.apply(op);
9002
9043
  }
9003
9044
  };
@@ -9005,12 +9046,13 @@ var DiplomaticClient = class {
9005
9046
  // src/react/useStateWatcher.ts
9006
9047
  import { useState as useState2, useEffect as useEffect2 } from "react";
9007
9048
  function useStateWatcher(mgr, opType, callback) {
9008
- const [val, setVal] = useState2(callback());
9049
+ const [val, setVal] = useState2();
9009
9050
  useEffect2(() => {
9010
- function update() {
9011
- const newVal = callback();
9051
+ async function update() {
9052
+ const newVal = await callback();
9012
9053
  setVal(newVal);
9013
9054
  }
9055
+ update();
9014
9056
  mgr.on(opType, update);
9015
9057
  return () => {
9016
9058
  mgr.off(opType, update);
@@ -9089,9 +9131,44 @@ function ClientStatusBar({ state }) {
9089
9131
  }
9090
9132
  );
9091
9133
  }
9134
+
9135
+ // src/entityDB.ts
9136
+ var entityDB_exports = {};
9137
+ __export(entityDB_exports, {
9138
+ applier: () => applier,
9139
+ db: () => db2,
9140
+ entityTableName: () => entityTableName,
9141
+ stateManager: () => stateManager,
9142
+ typeIndexName: () => typeIndexName
9143
+ });
9144
+ var entityTableName = "entities";
9145
+ var typeIndexName = "entity_type";
9146
+ var db2 = await openDB("db", 4, {
9147
+ upgrade(db3) {
9148
+ const store = db3.createObjectStore("entities", { keyPath: "eid", autoIncrement: false });
9149
+ store.createIndex("entity_type", "type");
9150
+ }
9151
+ });
9152
+ var applier = async (op) => {
9153
+ switch (op.verb) {
9154
+ case 1 /* UPSERT */: {
9155
+ const curr = await db2.get("entities", op.eid);
9156
+ 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 });
9158
+ }
9159
+ break;
9160
+ }
9161
+ case 0 /* DELETE */: {
9162
+ await db2.delete("entities", op.eid);
9163
+ break;
9164
+ }
9165
+ }
9166
+ };
9167
+ var stateManager = new StateManager(applier, () => db2.clear("entities"));
9092
9168
  export {
9093
9169
  ClientStatusBar,
9094
9170
  DiplomaticClient,
9171
+ entityDB_exports as EntityDB,
9095
9172
  InitSeedView,
9096
9173
  StateManager,
9097
9174
  Verb,
@@ -7,6 +7,8 @@ declare class LocalStorageStore implements IClientStateStore {
7
7
  setHostURL(url: string): Promise<void>;
8
8
  getHostID(): Promise<string | undefined>;
9
9
  setHostID(id: string): Promise<void>;
10
+ getLastFetchedAt(): Promise<Date | undefined>;
11
+ setLastFetchedAt(ts: Date): Promise<void>;
10
12
  uploadQueue: Map<string, Uint8Array>;
11
13
  enqueueUpload: (sha256: string, cipherOp: Uint8Array) => Promise<void>;
12
14
  dequeueUpload: (sha256: string) => Promise<void>;
@@ -3,6 +3,7 @@ declare class MemoryStore implements IClientStateStore {
3
3
  seed?: Uint8Array;
4
4
  hostURL?: string;
5
5
  hostID?: string;
6
+ lastFetchedAt?: Date;
6
7
  wipe(): Promise<void>;
7
8
  getSeed(): Promise<Uint8Array | undefined>;
8
9
  setSeed(seed: Uint8Array): Promise<void>;
@@ -10,6 +11,8 @@ declare class MemoryStore implements IClientStateStore {
10
11
  setHostURL(url: string): Promise<void>;
11
12
  getHostID(): Promise<string | undefined>;
12
13
  setHostID(id: string): Promise<void>;
14
+ getLastFetchedAt(): Promise<Date | undefined>;
15
+ setLastFetchedAt(ts: Date): Promise<void>;
13
16
  uploadQueue: Map<string, Uint8Array>;
14
17
  enqueueUpload: (sha256: string, cipherOp: Uint8Array) => Promise<void>;
15
18
  dequeueUpload: (sha256: string) => Promise<void>;
@@ -1,2 +1,2 @@
1
1
  import type { StateManager } from "../state";
2
- export default function useStateWatcher<T>(mgr: StateManager, opType: string, callback: () => T): T;
2
+ export default function useStateWatcher<T>(mgr: StateManager, opType: string, callback: () => Promise<T>): T | undefined;
@@ -3,6 +3,7 @@ type Libsodium = any;
3
3
  export declare class LibsodiumCrypto implements ICrypto {
4
4
  sodium: Libsodium;
5
5
  constructor(sodium: Libsodium);
6
+ gen128BitRandomID(): Promise<Uint8Array>;
6
7
  gen256BitSecureRandomSeed(): Promise<Uint8Array>;
7
8
  deriveXSalsa20Poly1305Key(seed: Uint8Array): Promise<Uint8Array>;
8
9
  encryptXSalsa20Poly1305Combined(data: Uint8Array, key: Uint8Array): Promise<Uint8Array>;
@@ -1,3 +1,4 @@
1
- import { type IOp } from './types.ts';
2
- export declare function genUpsertOp<T>(type: string, body: T, version?: number): IOp;
1
+ import { IDeleteOp, type IOp, IUpsertOp } from './types.ts';
2
+ export declare function genUpsertOp<T>(eid: Uint8Array, type: string, body: T, version?: number): IUpsertOp;
3
+ export declare function genDeleteOp<T>(eid: Uint8Array, type: string, version?: number): IDeleteOp;
3
4
  export declare function isOp(op: any): op is IOp;
@@ -3,13 +3,20 @@ export declare enum Verb {
3
3
  UPSERT = 1
4
4
  }
5
5
  type Timestamp = string;
6
- export interface IOp {
6
+ export interface IBaseOp {
7
+ eid: Uint8Array;
7
8
  ts: Timestamp;
8
9
  type: string;
9
- verb: Verb;
10
10
  ver: number;
11
+ }
12
+ export interface IUpsertOp extends IBaseOp {
13
+ verb: Verb.UPSERT;
11
14
  body: unknown;
12
15
  }
16
+ export interface IDeleteOp extends IBaseOp {
17
+ verb: Verb.DELETE;
18
+ }
19
+ export type IOp = IUpsertOp | IDeleteOp;
13
20
  export type CipherOp = Uint8Array;
14
21
  export interface ISyncRequest {
15
22
  ops: CipherOp[];
@@ -43,6 +50,7 @@ export interface IHostCrypto {
43
50
  sha256Hash: (data: Uint8Array) => Promise<Uint8Array>;
44
51
  }
45
52
  export interface ICrypto extends IHostCrypto {
53
+ gen128BitRandomID: () => Promise<Uint8Array>;
46
54
  gen256BitSecureRandomSeed: () => Promise<Uint8Array>;
47
55
  deriveXSalsa20Poly1305Key: (seed: Uint8Array, derivationIndex: number) => Promise<Uint8Array>;
48
56
  encryptXSalsa20Poly1305Combined: (plaintext: Uint8Array, key: Uint8Array) => Promise<Uint8Array>;
package/dist/types.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface IClientStateStore {
7
7
  setHostURL: (url: string) => Promise<void>;
8
8
  getHostID: () => Promise<string | undefined>;
9
9
  setHostID: (id: string) => Promise<void>;
10
+ setLastFetchedAt: (ts: Date) => Promise<void>;
11
+ getLastFetchedAt: () => Promise<Date | undefined>;
10
12
  wipe: () => Promise<void>;
11
13
  enqueueUpload: (sha256: string, cipherOp: Uint8Array) => Promise<void>;
12
14
  dequeueUpload: (sha256: string) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interncom/diplomatic",
3
- "version": "0.0.29",
3
+ "version": "0.0.33",
4
4
  "type": "module",
5
5
  "description": "Secure sync layer",
6
6
  "main": "dist/index.mjs",