@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.
Files changed (74) hide show
  1. package/dist/client.d.ts +36 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +192 -0
  4. package/dist/contract-manager.d.ts +23 -0
  5. package/dist/contract-manager.d.ts.map +1 -0
  6. package/dist/contract-manager.js +91 -0
  7. package/dist/define-app.d.ts +8 -0
  8. package/dist/define-app.d.ts.map +1 -0
  9. package/dist/define-app.js +7 -0
  10. package/dist/direct-transport.d.ts +69 -0
  11. package/dist/direct-transport.d.ts.map +1 -0
  12. package/dist/direct-transport.js +450 -0
  13. package/dist/errors.d.ts +7 -0
  14. package/dist/errors.d.ts.map +1 -0
  15. package/dist/errors.js +10 -0
  16. package/dist/event-processor.d.ts +19 -0
  17. package/dist/event-processor.d.ts.map +1 -0
  18. package/dist/event-processor.js +93 -0
  19. package/dist/identity-manager.d.ts +22 -0
  20. package/dist/identity-manager.d.ts.map +1 -0
  21. package/dist/identity-manager.js +62 -0
  22. package/dist/identity-serialization.d.ts +5 -0
  23. package/dist/identity-serialization.d.ts.map +1 -0
  24. package/dist/identity-serialization.js +30 -0
  25. package/dist/index.d.ts +18 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +10 -0
  28. package/dist/persistence.d.ts +11 -0
  29. package/dist/persistence.d.ts.map +1 -0
  30. package/dist/persistence.js +82 -0
  31. package/dist/state-store.d.ts +12 -0
  32. package/dist/state-store.d.ts.map +1 -0
  33. package/dist/state-store.js +32 -0
  34. package/dist/sync-manager.d.ts +33 -0
  35. package/dist/sync-manager.d.ts.map +1 -0
  36. package/dist/sync-manager.js +244 -0
  37. package/dist/tag-templates.d.ts +2 -0
  38. package/dist/tag-templates.d.ts.map +1 -0
  39. package/dist/tag-templates.js +23 -0
  40. package/dist/test-helpers.d.ts +15 -0
  41. package/dist/test-helpers.d.ts.map +1 -0
  42. package/dist/test-helpers.js +65 -0
  43. package/dist/transport.d.ts +41 -0
  44. package/dist/transport.d.ts.map +1 -0
  45. package/dist/transport.js +1 -0
  46. package/dist/types.d.ts +131 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +1 -0
  49. package/dist/websocket-transport.d.ts +36 -0
  50. package/dist/websocket-transport.d.ts.map +1 -0
  51. package/dist/websocket-transport.js +160 -0
  52. package/package.json +35 -0
  53. package/src/client.ts +277 -0
  54. package/src/contract-manager.test.ts +207 -0
  55. package/src/contract-manager.ts +130 -0
  56. package/src/define-app.ts +25 -0
  57. package/src/direct-transport.test.ts +460 -0
  58. package/src/direct-transport.ts +729 -0
  59. package/src/errors.ts +23 -0
  60. package/src/event-processor.ts +133 -0
  61. package/src/identity-manager.ts +91 -0
  62. package/src/identity-serialization.ts +33 -0
  63. package/src/index.ts +43 -0
  64. package/src/persistence.ts +103 -0
  65. package/src/sdk.e2e.test.ts +367 -0
  66. package/src/state-store.ts +42 -0
  67. package/src/sync-manager.test.ts +414 -0
  68. package/src/sync-manager.ts +308 -0
  69. package/src/tag-templates.test.ts +111 -0
  70. package/src/tag-templates.ts +30 -0
  71. package/src/test-helpers.ts +88 -0
  72. package/src/transport.ts +65 -0
  73. package/src/types.ts +191 -0
  74. package/src/websocket-transport.ts +233 -0
@@ -0,0 +1,65 @@
1
+ import { vi } from 'vitest';
2
+ export const makeContract = (overrides) => ({
3
+ id: overrides.id ?? `contract-${Math.random().toString(36).slice(2)}`,
4
+ participantDid: overrides.participantDid,
5
+ signedCommunicationContract: {
6
+ communication_contract: {
7
+ requestor_did: overrides.signedCommunicationContract?.communication_contract?.requestor_did ??
8
+ 'did:decentrl:alice',
9
+ recipient_did: overrides.participantDid,
10
+ requestor_signing_key_id: 'did:decentrl:alice#signing',
11
+ recipient_signing_key_id: `${overrides.participantDid}#signing`,
12
+ requestor_encryption_public_key: 'key-a',
13
+ recipient_encryption_public_key: 'key-b',
14
+ expires_at: overrides.expiresAt,
15
+ timestamp: overrides.timestamp,
16
+ },
17
+ requestor_signature: 'sig-a',
18
+ recipient_signature: 'sig-b',
19
+ },
20
+ rootSecret: overrides.rootSecret ?? 'c2VjcmV0',
21
+ createdAt: overrides.createdAt ?? Date.now(),
22
+ status: overrides.status ?? 'active',
23
+ });
24
+ export const createMockIdentityManager = (overrides = {}) => ({
25
+ requireIdentity: vi.fn(() => ({
26
+ did: 'did:decentrl:alice',
27
+ mediatorDid: 'did:web:mediator',
28
+ mediatorEndpoint: 'http://mediator',
29
+ keys: {
30
+ signing: {
31
+ privateKey: new Uint8Array(32),
32
+ publicKey: new Uint8Array(32),
33
+ },
34
+ encryption: {
35
+ privateKey: new Uint8Array(32),
36
+ publicKey: new Uint8Array(32),
37
+ },
38
+ storageKey: new Uint8Array(32),
39
+ },
40
+ })),
41
+ getIdentity: vi.fn(() => null),
42
+ ...overrides,
43
+ });
44
+ export const createMockTransport = (overrides = {}) => ({
45
+ refreshContracts: vi.fn(async () => []),
46
+ processAutoRenewals: vi.fn(async () => { }),
47
+ processContractCleanup: vi.fn(async () => { }),
48
+ requestContract: vi.fn(async () => { }),
49
+ getPendingContracts: vi.fn(async () => []),
50
+ acceptContract: vi.fn(async () => { }),
51
+ getActiveContracts: vi.fn(() => []),
52
+ processPendingEvents: vi.fn(async () => []),
53
+ ...overrides,
54
+ });
55
+ export const createMockEventProcessor = (overrides = {}) => ({
56
+ processBatch: vi.fn(() => 0),
57
+ computeTagsSafe: vi.fn((type) => [`tag:${type}`]),
58
+ ...overrides,
59
+ });
60
+ export const createMockContractManager = (overrides = {}) => ({
61
+ refresh: vi.fn(async () => { }),
62
+ processAutoRenewals: vi.fn(async () => { }),
63
+ processContractCleanup: vi.fn(async () => { }),
64
+ ...overrides,
65
+ });
@@ -0,0 +1,41 @@
1
+ import type { EventEnvelope, PendingContractRequest, SerializedIdentity, StoredSignedContract } from './types.js';
2
+ export interface DecentrlTransport {
3
+ createIdentity(options: {
4
+ alias: string;
5
+ mediatorDid: string;
6
+ }): Promise<SerializedIdentity>;
7
+ getIdentity(): SerializedIdentity | null;
8
+ loadIdentity?(serialized: SerializedIdentity): void;
9
+ publishEvent(envelope: EventEnvelope, options: {
10
+ tags: string[];
11
+ recipient?: string;
12
+ ephemeral?: boolean;
13
+ }): Promise<void>;
14
+ processPendingEvents(): Promise<EventEnvelope[]>;
15
+ processPreFetchedPendingEvents?(rawEvents: Array<{
16
+ id: string;
17
+ sender_did: string;
18
+ payload: string;
19
+ }>): Promise<EventEnvelope[]>;
20
+ requestContract(recipientDid: string, expiresIn?: number): Promise<void>;
21
+ getPendingContracts(): Promise<PendingContractRequest[]>;
22
+ acceptContract(pendingId: string, encryptedPayload: string, requestorEphemeralPublicKey: string): Promise<void>;
23
+ refreshContracts(): Promise<StoredSignedContract[]>;
24
+ getActiveContracts(): StoredSignedContract[];
25
+ processAutoRenewals?(threshold: number): Promise<void>;
26
+ processContractCleanup?(): Promise<void>;
27
+ getContractHistory?(): Promise<import('./types.js').ArchivedContract[]>;
28
+ queryEvents?(options?: import('./types.js').QueryOptions): Promise<import('./types.js').PaginatedResult<EventEnvelope>>;
29
+ onEvents?(listener: (events: EventEnvelope[]) => void): () => void;
30
+ onContractsChanged?(listener: () => void): () => void;
31
+ updateEventTags?(events: Array<{
32
+ eventId: string;
33
+ tags: string[];
34
+ }>): Promise<void>;
35
+ queryUnprocessedEvents?(pagination?: {
36
+ page: number;
37
+ pageSize: number;
38
+ }): Promise<import('./types.js').PaginatedResult<EventEnvelope>>;
39
+ dispose(): void;
40
+ }
41
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,aAAa,EACb,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAEjC,cAAc,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC7F,WAAW,IAAI,kBAAkB,GAAG,IAAI,CAAC;IACzC,YAAY,CAAC,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAGpD,YAAY,CACX,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAClE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,oBAAoB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACjD,8BAA8B,CAAC,CAC9B,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GACnE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAG5B,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,mBAAmB,IAAI,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IACzD,cAAc,CACb,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,2BAA2B,EAAE,MAAM,GACjC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,gBAAgB,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACpD,kBAAkB,IAAI,oBAAoB,EAAE,CAAC;IAG7C,mBAAmB,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvD,sBAAsB,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAGzC,kBAAkB,CAAC,IAAI,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAGxE,WAAW,CAAC,CACX,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,YAAY,GACzC,OAAO,CAAC,OAAO,YAAY,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC;IAGhE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAGnE,kBAAkB,CAAC,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAGtD,eAAe,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAGpF,sBAAsB,CAAC,CAAC,UAAU,CAAC,EAAE;QACpC,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,OAAO,YAAY,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC;IAEjE,OAAO,IAAI,IAAI,CAAC;CAChB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,131 @@
1
+ import type { DecentrlIdentityKeys } from '@decentrl/crypto';
2
+ import type { SignedCommunicationContract } from '@decentrl/identity/communication-contract/communication-contract.schema';
3
+ import type { z } from 'zod';
4
+ export interface EventDefinition<TSchema extends z.ZodType = z.ZodType> {
5
+ schema: TSchema;
6
+ tags: string[];
7
+ }
8
+ export type EventDefinitions = Record<string, {
9
+ schema: z.ZodType;
10
+ tags: string[];
11
+ }>;
12
+ export interface StateSliceDefinition<TSlice = unknown, TEvents extends EventDefinitions = EventDefinitions> {
13
+ initial: TSlice;
14
+ reduce: {
15
+ [K in keyof TEvents]?: (state: TSlice, data: z.infer<TEvents[K]['schema']>, meta: EventMeta) => TSlice;
16
+ };
17
+ }
18
+ export type StateDefinitions<TEvents extends EventDefinitions = EventDefinitions> = Record<string, StateSliceDefinition<any, TEvents>>;
19
+ export type InferState<TState extends StateDefinitions> = {
20
+ [K in keyof TState]: TState[K]['initial'];
21
+ };
22
+ export interface EventMeta {
23
+ senderDid: string;
24
+ timestamp: number;
25
+ eventId: string;
26
+ ephemeral?: boolean;
27
+ }
28
+ export interface EventEnvelope {
29
+ type: string;
30
+ data: unknown;
31
+ meta: EventMeta;
32
+ tags?: string[];
33
+ _mediatorEventId?: string;
34
+ }
35
+ export interface DecentrlClientConfig {
36
+ mediatorDid: string;
37
+ /** Enable localStorage persistence. Pass a key prefix (e.g. "decentrl-myapp"). */
38
+ persist?: {
39
+ key: string;
40
+ };
41
+ /** Optional transport layer. Defaults to DirectTransport (direct HTTP to mediator). */
42
+ transport?: import('./transport.js').DecentrlTransport;
43
+ }
44
+ export interface DecentrlAppConfig<TEvents extends EventDefinitions = EventDefinitions, TState extends StateDefinitions<TEvents> = StateDefinitions<TEvents>> {
45
+ events: TEvents;
46
+ state: TState;
47
+ }
48
+ export interface SerializedKeyPair {
49
+ privateKey: string;
50
+ publicKey: string;
51
+ }
52
+ export interface SerializedIdentity {
53
+ did: string;
54
+ alias: string;
55
+ mediatorDid: string;
56
+ mediatorEndpoint: string;
57
+ keys: {
58
+ signing: SerializedKeyPair;
59
+ encryption: SerializedKeyPair;
60
+ storageKey: string;
61
+ };
62
+ mediatorContract: SignedCommunicationContract | null;
63
+ }
64
+ export interface IdentityState {
65
+ did: string;
66
+ alias: string;
67
+ mediatorDid: string;
68
+ mediatorEndpoint: string;
69
+ keys: DecentrlIdentityKeys;
70
+ mediatorContract: SignedCommunicationContract | null;
71
+ }
72
+ export interface StoredSignedContract {
73
+ id: string;
74
+ participantDid: string;
75
+ participantAlias?: string;
76
+ signedCommunicationContract: SignedCommunicationContract;
77
+ rootSecret: string;
78
+ createdAt: number;
79
+ status: 'active' | 'expired' | 'superseded';
80
+ }
81
+ export interface ArchivedContract {
82
+ contractId: string;
83
+ participantDid: string;
84
+ participantAlias?: string;
85
+ timestamp: number;
86
+ expiresAt: number;
87
+ supersededAt: number;
88
+ supersededBy: string;
89
+ }
90
+ export interface PendingContractRequest {
91
+ id: string;
92
+ senderDid: string;
93
+ encryptedPayload: string;
94
+ requestorEphemeralPublicKey: string;
95
+ }
96
+ export interface QueryOptions {
97
+ tags?: string[];
98
+ participantDid?: string;
99
+ afterTimestamp?: number;
100
+ beforeTimestamp?: number;
101
+ pagination?: {
102
+ page: number;
103
+ pageSize: number;
104
+ };
105
+ }
106
+ export interface PaginationMeta {
107
+ page: number;
108
+ pageSize: number;
109
+ total: number;
110
+ }
111
+ export interface PaginatedResult<T> {
112
+ data: T[];
113
+ pagination: PaginationMeta;
114
+ }
115
+ export interface PublishOptions {
116
+ recipient?: string;
117
+ ephemeral?: boolean;
118
+ }
119
+ export interface SyncOptions {
120
+ intervalMs?: number;
121
+ fallbackIntervalMs?: number;
122
+ onError?: (error: unknown) => void;
123
+ websocket?: boolean;
124
+ autoRenew?: {
125
+ enabled: boolean;
126
+ threshold?: number;
127
+ };
128
+ }
129
+ export type { ConnectionStatus } from './websocket-transport.js';
130
+ export type StateListener<TState> = (state: TState) => void;
131
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { type DecentrlIdentityKeys } from '@decentrl/crypto';
2
+ export type ConnectionStatus = 'disconnected' | 'connecting' | 'authenticating' | 'connected';
3
+ export interface WebSocketTransportCallbacks {
4
+ onPendingEvents: (events: Array<{
5
+ id: string;
6
+ sender_did: string;
7
+ payload: string;
8
+ }>) => void;
9
+ onContractsUpdated: () => void;
10
+ onStatusChange: (status: ConnectionStatus) => void;
11
+ }
12
+ export interface WebSocketTransportIdentity {
13
+ did: string;
14
+ keys: DecentrlIdentityKeys;
15
+ }
16
+ export declare class WebSocketTransport {
17
+ private getIdentity;
18
+ private getWsUrl;
19
+ private callbacks;
20
+ private ws;
21
+ private status;
22
+ private reconnectAttempt;
23
+ private reconnectTimer;
24
+ private shouldReconnect;
25
+ constructor(getIdentity: () => WebSocketTransportIdentity | null, getWsUrl: () => string | null, callbacks: WebSocketTransportCallbacks);
26
+ connect(): void;
27
+ disconnect(): void;
28
+ getStatus(): ConnectionStatus;
29
+ private sendAuthenticate;
30
+ private handleMessage;
31
+ private send;
32
+ private setStatus;
33
+ private scheduleReconnect;
34
+ }
35
+ export declare function deriveWsUrl(httpEndpoint: string): string;
36
+ //# sourceMappingURL=websocket-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-transport.d.ts","sourceRoot":"","sources":["../src/websocket-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAkB,MAAM,kBAAkB,CAAC;AAE7E,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,YAAY,GAAG,gBAAgB,GAAG,WAAW,CAAC;AAE9F,MAAM,WAAW,2BAA2B;IAC3C,eAAe,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;IAC9F,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,cAAc,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,0BAA0B;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,oBAAoB,CAAC;CAC3B;AAcD,qBAAa,kBAAkB;IAQ7B,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,SAAS;IATlB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,eAAe,CAAS;gBAGvB,WAAW,EAAE,MAAM,0BAA0B,GAAG,IAAI,EACpD,QAAQ,EAAE,MAAM,MAAM,GAAG,IAAI,EAC7B,SAAS,EAAE,2BAA2B;IAG/C,OAAO,IAAI,IAAI;IAoEf,UAAU,IAAI,IAAI;IAkBlB,SAAS,IAAI,gBAAgB;IAI7B,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,iBAAiB;CA2BzB;AAED,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAMxD"}
@@ -0,0 +1,160 @@
1
+ import { signJsonObject } from '@decentrl/crypto';
2
+ const MAX_RECONNECT_ATTEMPTS = 10;
3
+ const BASE_RECONNECT_MS = 1000;
4
+ const MAX_RECONNECT_MS = 30_000;
5
+ const JITTER_FACTOR = 0.2;
6
+ export class WebSocketTransport {
7
+ getIdentity;
8
+ getWsUrl;
9
+ callbacks;
10
+ ws = null;
11
+ status = 'disconnected';
12
+ reconnectAttempt = 0;
13
+ reconnectTimer = null;
14
+ shouldReconnect = false;
15
+ constructor(getIdentity, getWsUrl, callbacks) {
16
+ this.getIdentity = getIdentity;
17
+ this.getWsUrl = getWsUrl;
18
+ this.callbacks = callbacks;
19
+ }
20
+ connect() {
21
+ if (this.ws) {
22
+ return;
23
+ }
24
+ const wsUrl = this.getWsUrl();
25
+ const identity = this.getIdentity();
26
+ if (!wsUrl || !identity) {
27
+ console.warn('[Decentrl:WS] connect() skipped — wsUrl:', !!wsUrl, 'identity:', !!identity);
28
+ return;
29
+ }
30
+ console.debug('[Decentrl:WS] Connecting to', wsUrl);
31
+ this.shouldReconnect = true;
32
+ this.setStatus('connecting');
33
+ try {
34
+ this.ws = new WebSocket(wsUrl);
35
+ }
36
+ catch (err) {
37
+ console.error('[Decentrl:WS] WebSocket constructor failed:', err);
38
+ this.setStatus('disconnected');
39
+ this.scheduleReconnect();
40
+ return;
41
+ }
42
+ this.ws.onopen = () => {
43
+ console.debug('[Decentrl:WS] Socket opened, authenticating...');
44
+ this.setStatus('authenticating');
45
+ this.sendAuthenticate(identity);
46
+ };
47
+ this.ws.onmessage = (event) => {
48
+ let data;
49
+ try {
50
+ data = JSON.parse(event.data);
51
+ }
52
+ catch {
53
+ return;
54
+ }
55
+ this.handleMessage(data);
56
+ };
57
+ this.ws.onclose = (event) => {
58
+ console.debug('[Decentrl:WS] Socket closed — code:', event.code, 'reason:', event.reason, 'willReconnect:', this.shouldReconnect);
59
+ this.ws = null;
60
+ this.setStatus('disconnected');
61
+ if (this.shouldReconnect) {
62
+ this.scheduleReconnect();
63
+ }
64
+ };
65
+ this.ws.onerror = () => {
66
+ console.error('[Decentrl:WS] Socket error (onclose will follow)');
67
+ };
68
+ }
69
+ disconnect() {
70
+ this.shouldReconnect = false;
71
+ if (this.reconnectTimer) {
72
+ clearTimeout(this.reconnectTimer);
73
+ this.reconnectTimer = null;
74
+ }
75
+ if (this.ws) {
76
+ this.ws.onclose = null;
77
+ this.ws.close();
78
+ this.ws = null;
79
+ }
80
+ this.reconnectAttempt = 0;
81
+ this.setStatus('disconnected');
82
+ }
83
+ getStatus() {
84
+ return this.status;
85
+ }
86
+ sendAuthenticate(identity) {
87
+ const payload = {
88
+ did: identity.did,
89
+ signing_key_id: `${identity.did}#signing`,
90
+ timestamp: Date.now(),
91
+ nonce: crypto.randomUUID(),
92
+ };
93
+ const signature = signJsonObject(payload, identity.keys.signing.privateKey);
94
+ this.send({
95
+ type: 'AUTHENTICATE',
96
+ ...payload,
97
+ signature,
98
+ });
99
+ }
100
+ handleMessage(data) {
101
+ if (data.type !== 'PING') {
102
+ console.debug('[Decentrl:WS] Received:', data.type, data.type === 'PENDING_EVENTS' ? `(${data.events.length} events)` : '');
103
+ }
104
+ switch (data.type) {
105
+ case 'AUTH_SUCCESS':
106
+ this.reconnectAttempt = 0;
107
+ this.setStatus('connected');
108
+ break;
109
+ case 'AUTH_FAILED':
110
+ console.error('[Decentrl:WS] Auth failed:', data.error_code);
111
+ // Don't reconnect on auth failure — would fail again
112
+ this.shouldReconnect = false;
113
+ this.ws?.close();
114
+ break;
115
+ case 'PENDING_EVENTS':
116
+ this.callbacks.onPendingEvents(data.events);
117
+ break;
118
+ case 'CONTRACTS_UPDATED':
119
+ this.callbacks.onContractsUpdated();
120
+ break;
121
+ case 'PING':
122
+ this.send({ type: 'PONG', timestamp: Date.now() });
123
+ break;
124
+ }
125
+ }
126
+ send(message) {
127
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
128
+ this.ws.send(JSON.stringify(message));
129
+ }
130
+ }
131
+ setStatus(status) {
132
+ if (this.status !== status) {
133
+ console.debug('[Decentrl:WS] Status:', this.status, '→', status);
134
+ this.status = status;
135
+ this.callbacks.onStatusChange(status);
136
+ }
137
+ }
138
+ scheduleReconnect() {
139
+ if (this.reconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
140
+ console.warn('[Decentrl:WS] Max reconnect attempts reached (%d), giving up', MAX_RECONNECT_ATTEMPTS);
141
+ return;
142
+ }
143
+ const backoff = Math.min(BASE_RECONNECT_MS * 2 ** this.reconnectAttempt, MAX_RECONNECT_MS);
144
+ const jitter = backoff * JITTER_FACTOR * (Math.random() * 2 - 1);
145
+ const delay = Math.max(0, backoff + jitter);
146
+ this.reconnectAttempt++;
147
+ console.debug('[Decentrl:WS] Reconnecting in %dms (attempt %d/%d)', Math.round(delay), this.reconnectAttempt, MAX_RECONNECT_ATTEMPTS);
148
+ this.reconnectTimer = setTimeout(() => {
149
+ this.reconnectTimer = null;
150
+ this.ws = null;
151
+ this.connect();
152
+ }, delay);
153
+ }
154
+ }
155
+ export function deriveWsUrl(httpEndpoint) {
156
+ const url = new URL(httpEndpoint);
157
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
158
+ url.pathname = `${url.pathname.replace(/\/$/, '')}/ws`;
159
+ return url.toString();
160
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@decentrl/sdk",
3
+ "version": "0.0.1",
4
+ "description": "Declarative SDK for building Decentrl protocol applications",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "dependencies": {
9
+ "zod": "^4.0.10",
10
+ "axios": "1.11.0",
11
+ "@decentrl/crypto": "0.0.1",
12
+ "@decentrl/identity": "0.0.1",
13
+ "@decentrl/event-store": "0.0.1"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.8.3",
17
+ "vitest": "3.2.4"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "src"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "test": "bash scripts/test-e2e.sh",
29
+ "test:unit": "vitest run --exclude 'src/**/*.e2e.test.ts'",
30
+ "test:watch": "vitest --exclude 'src/**/*.e2e.test.ts'",
31
+ "typecheck": "tsc --noEmit",
32
+ "clean": "rm -rf dist",
33
+ "dev": "tsc --watch"
34
+ }
35
+ }