@decentrl/sdk 0.0.1
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 +36 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +192 -0
- package/dist/contract-manager.d.ts +23 -0
- package/dist/contract-manager.d.ts.map +1 -0
- package/dist/contract-manager.js +91 -0
- package/dist/define-app.d.ts +8 -0
- package/dist/define-app.d.ts.map +1 -0
- package/dist/define-app.js +7 -0
- package/dist/direct-transport.d.ts +69 -0
- package/dist/direct-transport.d.ts.map +1 -0
- package/dist/direct-transport.js +450 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/event-processor.d.ts +19 -0
- package/dist/event-processor.d.ts.map +1 -0
- package/dist/event-processor.js +93 -0
- package/dist/identity-manager.d.ts +22 -0
- package/dist/identity-manager.d.ts.map +1 -0
- package/dist/identity-manager.js +62 -0
- package/dist/identity-serialization.d.ts +5 -0
- package/dist/identity-serialization.d.ts.map +1 -0
- package/dist/identity-serialization.js +30 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/persistence.d.ts +11 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +82 -0
- package/dist/state-store.d.ts +12 -0
- package/dist/state-store.d.ts.map +1 -0
- package/dist/state-store.js +32 -0
- package/dist/sync-manager.d.ts +33 -0
- package/dist/sync-manager.d.ts.map +1 -0
- package/dist/sync-manager.js +244 -0
- package/dist/tag-templates.d.ts +2 -0
- package/dist/tag-templates.d.ts.map +1 -0
- package/dist/tag-templates.js +23 -0
- package/dist/test-helpers.d.ts +15 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +65 -0
- package/dist/transport.d.ts +41 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +1 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/websocket-transport.d.ts +36 -0
- package/dist/websocket-transport.d.ts.map +1 -0
- package/dist/websocket-transport.js +160 -0
- package/package.json +35 -0
- package/src/client.ts +277 -0
- package/src/contract-manager.test.ts +207 -0
- package/src/contract-manager.ts +130 -0
- package/src/define-app.ts +25 -0
- package/src/direct-transport.test.ts +460 -0
- package/src/direct-transport.ts +729 -0
- package/src/errors.ts +23 -0
- package/src/event-processor.ts +133 -0
- package/src/identity-manager.ts +91 -0
- package/src/identity-serialization.ts +33 -0
- package/src/index.ts +43 -0
- package/src/persistence.ts +103 -0
- package/src/sdk.e2e.test.ts +367 -0
- package/src/state-store.ts +42 -0
- package/src/sync-manager.test.ts +414 -0
- package/src/sync-manager.ts +308 -0
- package/src/tag-templates.test.ts +111 -0
- package/src/tag-templates.ts +30 -0
- package/src/test-helpers.ts +88 -0
- package/src/transport.ts +65 -0
- package/src/types.ts +191 -0
- package/src/websocket-transport.ts +233 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DecentrlSDKError } from './errors.js';
|
|
2
|
+
import { deserializeKeys, serializeIdentity } from './identity-serialization.js';
|
|
3
|
+
export class IdentityManager {
|
|
4
|
+
identity = null;
|
|
5
|
+
listeners = new Set();
|
|
6
|
+
transport = null;
|
|
7
|
+
setTransport(transport) {
|
|
8
|
+
this.transport = transport;
|
|
9
|
+
}
|
|
10
|
+
getIdentity() {
|
|
11
|
+
return this.identity;
|
|
12
|
+
}
|
|
13
|
+
requireIdentity() {
|
|
14
|
+
if (!this.identity) {
|
|
15
|
+
throw new DecentrlSDKError('Identity not initialized. Call create() or load() first.', 'IDENTITY_NOT_INITIALIZED');
|
|
16
|
+
}
|
|
17
|
+
return this.identity;
|
|
18
|
+
}
|
|
19
|
+
async create(options) {
|
|
20
|
+
if (this.identity) {
|
|
21
|
+
throw new DecentrlSDKError('Identity already exists. Call reset() first to create a new one.', 'IDENTITY_ALREADY_EXISTS');
|
|
22
|
+
}
|
|
23
|
+
if (!this.transport) {
|
|
24
|
+
throw new DecentrlSDKError('Transport not set', 'NO_TRANSPORT');
|
|
25
|
+
}
|
|
26
|
+
const serialized = await this.transport.createIdentity(options);
|
|
27
|
+
return this.load(serialized);
|
|
28
|
+
}
|
|
29
|
+
load(serialized) {
|
|
30
|
+
this.identity = {
|
|
31
|
+
did: serialized.did,
|
|
32
|
+
alias: serialized.alias,
|
|
33
|
+
mediatorDid: serialized.mediatorDid,
|
|
34
|
+
mediatorEndpoint: serialized.mediatorEndpoint,
|
|
35
|
+
keys: deserializeKeys(serialized),
|
|
36
|
+
mediatorContract: serialized.mediatorContract,
|
|
37
|
+
};
|
|
38
|
+
// Sync with transport
|
|
39
|
+
this.transport?.loadIdentity?.(serialized);
|
|
40
|
+
this.notify();
|
|
41
|
+
return this.identity;
|
|
42
|
+
}
|
|
43
|
+
serialize() {
|
|
44
|
+
const identity = this.requireIdentity();
|
|
45
|
+
return serializeIdentity(identity);
|
|
46
|
+
}
|
|
47
|
+
reset() {
|
|
48
|
+
this.identity = null;
|
|
49
|
+
this.notify();
|
|
50
|
+
}
|
|
51
|
+
onChange(listener) {
|
|
52
|
+
this.listeners.add(listener);
|
|
53
|
+
return () => {
|
|
54
|
+
this.listeners.delete(listener);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
notify() {
|
|
58
|
+
for (const listener of this.listeners) {
|
|
59
|
+
listener(this.identity);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type DecentrlIdentityKeys } from '@decentrl/crypto';
|
|
2
|
+
import type { IdentityState, SerializedIdentity } from './types.js';
|
|
3
|
+
export declare const serializeIdentity: (identity: IdentityState) => SerializedIdentity;
|
|
4
|
+
export declare const deserializeKeys: (serialized: SerializedIdentity) => DecentrlIdentityKeys;
|
|
5
|
+
//# sourceMappingURL=identity-serialization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-serialization.d.ts","sourceRoot":"","sources":["../src/identity-serialization.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpE,eAAO,MAAM,iBAAiB,GAAI,UAAU,aAAa,KAAG,kBAiB1D,CAAC;AAEH,eAAO,MAAM,eAAe,GAAI,YAAY,kBAAkB,KAAG,oBAU/D,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { base64Decode, base64Encode } from '@decentrl/crypto';
|
|
2
|
+
export const serializeIdentity = (identity) => ({
|
|
3
|
+
did: identity.did,
|
|
4
|
+
alias: identity.alias,
|
|
5
|
+
mediatorDid: identity.mediatorDid,
|
|
6
|
+
mediatorEndpoint: identity.mediatorEndpoint,
|
|
7
|
+
keys: {
|
|
8
|
+
signing: {
|
|
9
|
+
privateKey: base64Encode(identity.keys.signing.privateKey),
|
|
10
|
+
publicKey: base64Encode(identity.keys.signing.publicKey),
|
|
11
|
+
},
|
|
12
|
+
encryption: {
|
|
13
|
+
privateKey: base64Encode(identity.keys.encryption.privateKey),
|
|
14
|
+
publicKey: base64Encode(identity.keys.encryption.publicKey),
|
|
15
|
+
},
|
|
16
|
+
storageKey: base64Encode(identity.keys.storageKey),
|
|
17
|
+
},
|
|
18
|
+
mediatorContract: identity.mediatorContract,
|
|
19
|
+
});
|
|
20
|
+
export const deserializeKeys = (serialized) => ({
|
|
21
|
+
signing: {
|
|
22
|
+
privateKey: base64Decode(serialized.keys.signing.privateKey),
|
|
23
|
+
publicKey: base64Decode(serialized.keys.signing.publicKey),
|
|
24
|
+
},
|
|
25
|
+
encryption: {
|
|
26
|
+
privateKey: base64Decode(serialized.keys.encryption.privateKey),
|
|
27
|
+
publicKey: base64Decode(serialized.keys.encryption.publicKey),
|
|
28
|
+
},
|
|
29
|
+
storageKey: base64Decode(serialized.keys.storageKey),
|
|
30
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { DecentrlClient } from './client.js';
|
|
2
|
+
export { ContractManager } from './contract-manager.js';
|
|
3
|
+
export type { DecentrlApp } from './define-app.js';
|
|
4
|
+
export { defineDecentrlApp } from './define-app.js';
|
|
5
|
+
export type { DirectTransportOptions, HttpPost } from './direct-transport.js';
|
|
6
|
+
export { DirectTransport } from './direct-transport.js';
|
|
7
|
+
export type { DecentrlSDKErrorCode } from './errors.js';
|
|
8
|
+
export { DecentrlSDKError } from './errors.js';
|
|
9
|
+
export { EventProcessor } from './event-processor.js';
|
|
10
|
+
export { IdentityManager } from './identity-manager.js';
|
|
11
|
+
export type { PersistOptions } from './persistence.js';
|
|
12
|
+
export { StateStore } from './state-store.js';
|
|
13
|
+
export { SyncManager } from './sync-manager.js';
|
|
14
|
+
export type { DecentrlTransport } from './transport.js';
|
|
15
|
+
export type { ArchivedContract, DecentrlAppConfig, DecentrlClientConfig, EventDefinition, EventDefinitions, EventEnvelope, EventMeta, IdentityState, InferState, PaginatedResult, PaginationMeta, PendingContractRequest, PublishOptions, QueryOptions, SerializedIdentity, SerializedKeyPair, StateDefinitions, StateListener, StateSliceDefinition, StoredSignedContract, SyncOptions, } from './types.js';
|
|
16
|
+
export type { ConnectionStatus, WebSocketTransportCallbacks, WebSocketTransportIdentity, } from './websocket-transport.js';
|
|
17
|
+
export { deriveWsUrl, WebSocketTransport } from './websocket-transport.js';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EACX,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,aAAa,EACb,UAAU,EACV,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,WAAW,GACX,MAAM,YAAY,CAAC;AACpB,YAAY,EACX,gBAAgB,EAChB,2BAA2B,EAC3B,0BAA0B,GAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { DecentrlClient } from './client.js';
|
|
2
|
+
export { ContractManager } from './contract-manager.js';
|
|
3
|
+
export { defineDecentrlApp } from './define-app.js';
|
|
4
|
+
export { DirectTransport } from './direct-transport.js';
|
|
5
|
+
export { DecentrlSDKError } from './errors.js';
|
|
6
|
+
export { EventProcessor } from './event-processor.js';
|
|
7
|
+
export { IdentityManager } from './identity-manager.js';
|
|
8
|
+
export { StateStore } from './state-store.js';
|
|
9
|
+
export { SyncManager } from './sync-manager.js';
|
|
10
|
+
export { deriveWsUrl, WebSocketTransport } from './websocket-transport.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SerializedIdentity } from './types.js';
|
|
2
|
+
export interface PersistOptions {
|
|
3
|
+
/** localStorage key prefix. Identity stored at `${key}:identity`, state at `${key}:state` */
|
|
4
|
+
key: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function loadPersistedIdentity(opts: PersistOptions): SerializedIdentity | null;
|
|
7
|
+
export declare function persistIdentity(opts: PersistOptions, identity: SerializedIdentity | null): void;
|
|
8
|
+
export declare function loadPersistedState<T extends Record<string, unknown>>(opts: PersistOptions): T | null;
|
|
9
|
+
export declare function persistState(opts: PersistOptions, state: Record<string, unknown>): void;
|
|
10
|
+
export declare function clearPersisted(opts: PersistOptions): void;
|
|
11
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,cAAc;IAC9B,6FAA6F;IAC7F,GAAG,EAAE,MAAM,CAAC;CACZ;AAUD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,kBAAkB,GAAG,IAAI,CAkBrF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAgB/F;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,IAAI,EAAE,cAAc,GAClB,CAAC,GAAG,IAAI,CAkBV;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAYvF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAazD"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function getStorage() {
|
|
2
|
+
try {
|
|
3
|
+
return typeof localStorage !== 'undefined' ? localStorage : null;
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function loadPersistedIdentity(opts) {
|
|
10
|
+
const storage = getStorage();
|
|
11
|
+
if (!storage) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const raw = storage.getItem(`${opts.key}:identity`);
|
|
16
|
+
if (!raw) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function persistIdentity(opts, identity) {
|
|
26
|
+
const storage = getStorage();
|
|
27
|
+
if (!storage) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
if (identity) {
|
|
32
|
+
storage.setItem(`${opts.key}:identity`, JSON.stringify(identity));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
storage.removeItem(`${opts.key}:identity`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Silently fail (quota exceeded, etc.)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function loadPersistedState(opts) {
|
|
43
|
+
const storage = getStorage();
|
|
44
|
+
if (!storage) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const raw = storage.getItem(`${opts.key}:state`);
|
|
49
|
+
if (!raw) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function persistState(opts, state) {
|
|
59
|
+
const storage = getStorage();
|
|
60
|
+
if (!storage) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
storage.setItem(`${opts.key}:state`, JSON.stringify(state));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Silently fail
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function clearPersisted(opts) {
|
|
71
|
+
const storage = getStorage();
|
|
72
|
+
if (!storage) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
storage.removeItem(`${opts.key}:identity`);
|
|
77
|
+
storage.removeItem(`${opts.key}:state`);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Silently fail
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StateListener } from './types.js';
|
|
2
|
+
export declare class StateStore<TState extends Record<string, unknown>> {
|
|
3
|
+
private state;
|
|
4
|
+
private listeners;
|
|
5
|
+
constructor(initialState: TState);
|
|
6
|
+
getState: () => TState;
|
|
7
|
+
setSlice<K extends keyof TState>(key: K, value: TState[K]): void;
|
|
8
|
+
subscribe: (listener: StateListener<TState>) => (() => void);
|
|
9
|
+
reset(initialState: TState): void;
|
|
10
|
+
private notify;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=state-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../src/state-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,UAAU,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAoC;gBAEzC,YAAY,EAAE,MAAM;IAIhC,QAAQ,QAAO,MAAM,CAEnB;IAEF,QAAQ,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI;IAShE,SAAS,GAAI,UAAU,aAAa,CAAC,MAAM,CAAC,KAAG,CAAC,MAAM,IAAI,CAAC,CAMzD;IAEF,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAKjC,OAAO,CAAC,MAAM;CAKd"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class StateStore {
|
|
2
|
+
state;
|
|
3
|
+
listeners = new Set();
|
|
4
|
+
constructor(initialState) {
|
|
5
|
+
this.state = { ...initialState };
|
|
6
|
+
}
|
|
7
|
+
getState = () => {
|
|
8
|
+
return this.state;
|
|
9
|
+
};
|
|
10
|
+
setSlice(key, value) {
|
|
11
|
+
if (this.state[key] === value) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
this.state = { ...this.state, [key]: value };
|
|
15
|
+
this.notify();
|
|
16
|
+
}
|
|
17
|
+
subscribe = (listener) => {
|
|
18
|
+
this.listeners.add(listener);
|
|
19
|
+
return () => {
|
|
20
|
+
this.listeners.delete(listener);
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
reset(initialState) {
|
|
24
|
+
this.state = { ...initialState };
|
|
25
|
+
this.notify();
|
|
26
|
+
}
|
|
27
|
+
notify() {
|
|
28
|
+
for (const listener of this.listeners) {
|
|
29
|
+
listener(this.state);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ContractManager } from './contract-manager.js';
|
|
2
|
+
import type { EventProcessor } from './event-processor.js';
|
|
3
|
+
import type { IdentityManager } from './identity-manager.js';
|
|
4
|
+
import type { DecentrlTransport } from './transport.js';
|
|
5
|
+
import type { EventDefinitions, StateDefinitions, SyncOptions } from './types.js';
|
|
6
|
+
import { type ConnectionStatus } from './websocket-transport.js';
|
|
7
|
+
type StatusListener = (status: ConnectionStatus) => void;
|
|
8
|
+
export declare class SyncManager<TEvents extends EventDefinitions, TState extends StateDefinitions<TEvents>> {
|
|
9
|
+
private eventProcessor;
|
|
10
|
+
private contractManager;
|
|
11
|
+
private identityManager;
|
|
12
|
+
private transport;
|
|
13
|
+
private intervalId;
|
|
14
|
+
private isSyncing;
|
|
15
|
+
private wsTransport;
|
|
16
|
+
private connectionStatus;
|
|
17
|
+
private statusListeners;
|
|
18
|
+
private unsubscribePush;
|
|
19
|
+
private unsubscribeContractsPush;
|
|
20
|
+
private currentOptions;
|
|
21
|
+
constructor(eventProcessor: EventProcessor<TEvents, TState>, contractManager: ContractManager, identityManager: IdentityManager, transport: DecentrlTransport);
|
|
22
|
+
start(options?: SyncOptions): void;
|
|
23
|
+
stop(): void;
|
|
24
|
+
tick(onError?: (error: unknown) => void): Promise<void>;
|
|
25
|
+
getConnectionStatus: () => ConnectionStatus;
|
|
26
|
+
onConnectionStatusChange: (listener: StatusListener) => (() => void);
|
|
27
|
+
get isRunning(): boolean;
|
|
28
|
+
private loadStoredEvents;
|
|
29
|
+
private processUntaggedEvents;
|
|
30
|
+
private startWebSocket;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=sync-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../src/sync-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAiB,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,KAAK,gBAAgB,EAAmC,MAAM,0BAA0B,CAAC;AAElG,KAAK,cAAc,GAAG,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEzD,qBAAa,WAAW,CACvB,OAAO,SAAS,gBAAgB,EAChC,MAAM,SAAS,gBAAgB,CAAC,OAAO,CAAC;IAYvC,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,SAAS;IAblB,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,gBAAgB,CAAoC;IAC5D,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,wBAAwB,CAA6B;IAC7D,OAAO,CAAC,cAAc,CAAmB;gBAGhC,cAAc,EAAE,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,EAC/C,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,iBAAiB;IAGrC,KAAK,CAAC,OAAO,GAAE,WAAgB,GAAG,IAAI;IA+DtC,IAAI,IAAI,IAAI;IAsBN,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAyC7D,mBAAmB,QAAO,gBAAgB,CAExC;IAEF,wBAAwB,GAAI,UAAU,cAAc,KAAG,CAAC,MAAM,IAAI,CAAC,CAMjE;IAEF,IAAI,SAAS,IAAI,OAAO,CAEvB;YAEa,gBAAgB;YA6BhB,qBAAqB;IA4CnC,OAAO,CAAC,cAAc;CA+DtB"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { deriveWsUrl, WebSocketTransport } from './websocket-transport.js';
|
|
2
|
+
export class SyncManager {
|
|
3
|
+
eventProcessor;
|
|
4
|
+
contractManager;
|
|
5
|
+
identityManager;
|
|
6
|
+
transport;
|
|
7
|
+
intervalId = null;
|
|
8
|
+
isSyncing = false;
|
|
9
|
+
wsTransport = null;
|
|
10
|
+
connectionStatus = 'disconnected';
|
|
11
|
+
statusListeners = new Set();
|
|
12
|
+
unsubscribePush = null;
|
|
13
|
+
unsubscribeContractsPush = null;
|
|
14
|
+
currentOptions = {};
|
|
15
|
+
constructor(eventProcessor, contractManager, identityManager, transport) {
|
|
16
|
+
this.eventProcessor = eventProcessor;
|
|
17
|
+
this.contractManager = contractManager;
|
|
18
|
+
this.identityManager = identityManager;
|
|
19
|
+
this.transport = transport;
|
|
20
|
+
}
|
|
21
|
+
start(options = {}) {
|
|
22
|
+
this.stop();
|
|
23
|
+
this.currentOptions = options;
|
|
24
|
+
// If transport supports push-based events, subscribe
|
|
25
|
+
if (this.transport.onEvents) {
|
|
26
|
+
this.unsubscribePush = this.transport.onEvents((events) => {
|
|
27
|
+
try {
|
|
28
|
+
this.eventProcessor.processBatch(events);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.error('[Decentrl] Failed to process pushed events:', err);
|
|
32
|
+
}
|
|
33
|
+
// Tag enrichment for non-ephemeral pushed events
|
|
34
|
+
if (events.some((e) => !e.meta.ephemeral)) {
|
|
35
|
+
this.processUntaggedEvents().catch((err) => console.debug('[Decentrl] Failed to process untagged events:', err));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
if (this.transport.onContractsChanged) {
|
|
39
|
+
this.unsubscribeContractsPush = this.transport.onContractsChanged(() => {
|
|
40
|
+
this.contractManager.refresh();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Still do an initial sync tick + slower polling for contracts
|
|
44
|
+
this.tick(options.onError);
|
|
45
|
+
const intervalMs = options.fallbackIntervalMs ?? 30_000;
|
|
46
|
+
this.intervalId = setInterval(() => {
|
|
47
|
+
this.tick(options.onError);
|
|
48
|
+
}, intervalMs);
|
|
49
|
+
// Load stored events from mediator to catch up on offline period
|
|
50
|
+
this.loadStoredEvents().catch((err) => console.debug('[Decentrl] loadStoredEvents() failed:', err));
|
|
51
|
+
// Process any events that haven't been tagged by the app yet
|
|
52
|
+
this.processUntaggedEvents().catch((err) => console.debug('[Decentrl] processUntaggedEvents() failed:', err));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const intervalMs = options.intervalMs ?? 3000;
|
|
56
|
+
// Run immediately
|
|
57
|
+
this.tick(options.onError);
|
|
58
|
+
this.intervalId = setInterval(() => {
|
|
59
|
+
this.tick(options.onError);
|
|
60
|
+
}, intervalMs);
|
|
61
|
+
// Start WebSocket if available and enabled
|
|
62
|
+
const useWebSocket = options.websocket !== false;
|
|
63
|
+
if (useWebSocket && typeof globalThis.WebSocket !== 'undefined') {
|
|
64
|
+
this.startWebSocket(options);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
stop() {
|
|
68
|
+
if (this.intervalId !== null) {
|
|
69
|
+
clearInterval(this.intervalId);
|
|
70
|
+
this.intervalId = null;
|
|
71
|
+
}
|
|
72
|
+
if (this.wsTransport) {
|
|
73
|
+
this.wsTransport.disconnect();
|
|
74
|
+
this.wsTransport = null;
|
|
75
|
+
}
|
|
76
|
+
if (this.unsubscribePush) {
|
|
77
|
+
this.unsubscribePush();
|
|
78
|
+
this.unsubscribePush = null;
|
|
79
|
+
}
|
|
80
|
+
if (this.unsubscribeContractsPush) {
|
|
81
|
+
this.unsubscribeContractsPush();
|
|
82
|
+
this.unsubscribeContractsPush = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async tick(onError) {
|
|
86
|
+
if (this.isSyncing) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.isSyncing = true;
|
|
90
|
+
try {
|
|
91
|
+
const processedEvents = await this.transport.processPendingEvents();
|
|
92
|
+
this.eventProcessor.processBatch(processedEvents);
|
|
93
|
+
// Enrich any unprocessed events with app-computed tags
|
|
94
|
+
if (processedEvents.length > 0) {
|
|
95
|
+
await this.processUntaggedEvents();
|
|
96
|
+
}
|
|
97
|
+
// Refresh contracts
|
|
98
|
+
await this.contractManager.refresh();
|
|
99
|
+
// Auto-renew expiring contracts
|
|
100
|
+
const autoRenew = this.currentOptions.autoRenew;
|
|
101
|
+
if (autoRenew?.enabled) {
|
|
102
|
+
await this.contractManager.processAutoRenewals(autoRenew.threshold);
|
|
103
|
+
}
|
|
104
|
+
// Clean up old superseded contracts
|
|
105
|
+
try {
|
|
106
|
+
await this.contractManager.processContractCleanup();
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.error('[Decentrl] Contract cleanup failed:', err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (onError) {
|
|
114
|
+
onError(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
this.isSyncing = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
getConnectionStatus = () => {
|
|
122
|
+
return this.connectionStatus;
|
|
123
|
+
};
|
|
124
|
+
onConnectionStatusChange = (listener) => {
|
|
125
|
+
this.statusListeners.add(listener);
|
|
126
|
+
return () => {
|
|
127
|
+
this.statusListeners.delete(listener);
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
get isRunning() {
|
|
131
|
+
return this.intervalId !== null;
|
|
132
|
+
}
|
|
133
|
+
async loadStoredEvents() {
|
|
134
|
+
if (!this.transport.queryEvents) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
let page = 0;
|
|
139
|
+
const pageSize = 50;
|
|
140
|
+
while (true) {
|
|
141
|
+
const result = await this.transport.queryEvents({
|
|
142
|
+
pagination: { page, pageSize },
|
|
143
|
+
});
|
|
144
|
+
if (result.data.length > 0) {
|
|
145
|
+
this.eventProcessor.processBatch(result.data);
|
|
146
|
+
}
|
|
147
|
+
if ((page + 1) * pageSize >= result.pagination.total) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
page++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.debug('[Decentrl] loadStoredEvents() failed:', err);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async processUntaggedEvents() {
|
|
158
|
+
if (!this.transport.queryUnprocessedEvents) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!this.transport.updateEventTags) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
let page = 0;
|
|
166
|
+
const pageSize = 50;
|
|
167
|
+
while (true) {
|
|
168
|
+
const result = await this.transport.queryUnprocessedEvents({ page, pageSize });
|
|
169
|
+
if (result.data.length === 0) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
this.eventProcessor.processBatch(result.data);
|
|
173
|
+
const tagUpdates = result.data
|
|
174
|
+
.filter((e) => !e.meta.ephemeral)
|
|
175
|
+
.map((e) => ({
|
|
176
|
+
eventId: e._mediatorEventId ?? e.meta.eventId,
|
|
177
|
+
tags: this.eventProcessor.computeTagsSafe(e.type, e.data),
|
|
178
|
+
}));
|
|
179
|
+
if (tagUpdates.length > 0) {
|
|
180
|
+
await this.transport.updateEventTags(tagUpdates);
|
|
181
|
+
}
|
|
182
|
+
if ((page + 1) * pageSize >= result.pagination.total) {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
page++;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.debug('[Decentrl] processUntaggedEvents failed:', err);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
startWebSocket(options) {
|
|
193
|
+
const identity = this.identityManager.getIdentity();
|
|
194
|
+
if (!identity) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const wsUrl = deriveWsUrl(identity.mediatorEndpoint);
|
|
198
|
+
this.wsTransport = new WebSocketTransport(() => {
|
|
199
|
+
const id = this.identityManager.getIdentity();
|
|
200
|
+
return id ? { did: id.did, keys: id.keys } : null;
|
|
201
|
+
}, () => wsUrl, {
|
|
202
|
+
onPendingEvents: async (events) => {
|
|
203
|
+
try {
|
|
204
|
+
if (this.transport.processPreFetchedPendingEvents) {
|
|
205
|
+
const processed = await this.transport.processPreFetchedPendingEvents(events);
|
|
206
|
+
this.eventProcessor.processBatch(processed);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
options.onError?.(error);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
onContractsUpdated: async () => {
|
|
214
|
+
try {
|
|
215
|
+
await this.contractManager.refresh();
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
options.onError?.(error);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
onStatusChange: (status) => {
|
|
222
|
+
this.connectionStatus = status;
|
|
223
|
+
// Adjust polling interval based on WS connection
|
|
224
|
+
if (this.intervalId !== null) {
|
|
225
|
+
clearInterval(this.intervalId);
|
|
226
|
+
const intervalMs = status === 'connected'
|
|
227
|
+
? (options.fallbackIntervalMs ?? 30_000)
|
|
228
|
+
: (options.intervalMs ?? 3000);
|
|
229
|
+
this.intervalId = setInterval(() => {
|
|
230
|
+
this.tick(options.onError);
|
|
231
|
+
}, intervalMs);
|
|
232
|
+
// Trigger immediate catch-up tick when WS connects
|
|
233
|
+
if (status === 'connected') {
|
|
234
|
+
this.tick(options.onError);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const listener of this.statusListeners) {
|
|
238
|
+
listener(status);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
this.wsTransport.connect();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag-templates.d.ts","sourceRoot":"","sources":["../src/tag-templates.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,CAEjF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function evaluateTagTemplates(templates, data) {
|
|
2
|
+
return templates.map((t) => evaluateTemplate(t, data));
|
|
3
|
+
}
|
|
4
|
+
function evaluateTemplate(template, data) {
|
|
5
|
+
return template.replace(/\$\{([^}]+)\}/g, (_, path) => {
|
|
6
|
+
return String(resolveProperty(data, path) ?? '');
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
10
|
+
function resolveProperty(obj, path) {
|
|
11
|
+
const parts = path.split(/[.[\]]+/).filter(Boolean);
|
|
12
|
+
let value = obj;
|
|
13
|
+
for (const part of parts) {
|
|
14
|
+
if (BLOCKED_KEYS.has(part)) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
if (value == null || typeof value !== 'object') {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
value = value[part];
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ContractManager } from './contract-manager.js';
|
|
2
|
+
import type { EventProcessor } from './event-processor.js';
|
|
3
|
+
import type { IdentityManager } from './identity-manager.js';
|
|
4
|
+
import type { DecentrlTransport } from './transport.js';
|
|
5
|
+
import type { EventDefinitions, StateDefinitions, StoredSignedContract } from './types.js';
|
|
6
|
+
export declare const makeContract: (overrides: Partial<StoredSignedContract> & {
|
|
7
|
+
participantDid: string;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}) => StoredSignedContract;
|
|
11
|
+
export declare const createMockIdentityManager: (overrides?: Partial<Record<keyof IdentityManager, unknown>>) => IdentityManager;
|
|
12
|
+
export declare const createMockTransport: (overrides?: Partial<DecentrlTransport>) => DecentrlTransport;
|
|
13
|
+
export declare const createMockEventProcessor: (overrides?: Partial<Record<string, unknown>>) => EventProcessor<EventDefinitions, StateDefinitions<EventDefinitions>>;
|
|
14
|
+
export declare const createMockContractManager: (overrides?: Partial<Record<string, unknown>>) => ContractManager;
|
|
15
|
+
//# sourceMappingURL=test-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE3F,eAAO,MAAM,YAAY,GACxB,WAAW,OAAO,CAAC,oBAAoB,CAAC,GAAG;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB,KACC,oBAsBD,CAAC;AAEH,eAAO,MAAM,yBAAyB,GACrC,YAAW,OAAO,CAAC,MAAM,CAAC,MAAM,eAAe,EAAE,OAAO,CAAC,CAAM,KAqB9C,eAAe,CAAC;AAElC,eAAO,MAAM,mBAAmB,GAAI,YAAW,OAAO,CAAC,iBAAiB,CAAM,KAW5D,iBAAiB,CAAC;AAEpC,eAAO,MAAM,wBAAwB,GAAI,YAAW,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,KAKvE,cAAc,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAEvF,eAAO,MAAM,yBAAyB,GAAI,YAAW,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,KAMxE,eAAe,CAAC"}
|