@didis/client 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ledgerbingo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # @didis/client
2
+
3
+ Official TypeScript/JavaScript SDK for the [DID.is](https://did.is) public API: DID resolution with evidence, W3C DID Resolution, verifiable credentials, explicit policies, and MCP/A2A agent trust. Works anywhere `fetch` exists: Node 18+, Deno, Bun, browsers and edge runtimes. Zero dependencies, fully typed, ESM.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @didis/client
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { DidisClient, DidisError } from "@didis/client";
15
+
16
+ // Defaults to the public API at https://did.is/api.
17
+ // Self-hosting? new DidisClient({ baseUrl: "https://your-host/api" })
18
+ const didis = new DidisClient();
19
+
20
+ // Enriched resolution: verdict, evidence dimensions, graph, telemetry.
21
+ const r = await didis.resolve("did:web:identity.foundation");
22
+ console.log(r.verdict.headline);
23
+ for (const d of r.dimensions) console.log(d.label, d.state);
24
+
25
+ // DID Resolution v1 (W3C CR Draft HTTPS binding, unmodified).
26
+ const w3c = await didis.resolveW3c("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
27
+
28
+ // Live resolution stream (server-sent events).
29
+ for await (const ev of didis.stream("did:web:identity.foundation")) {
30
+ if (ev.event === "stage") console.log(ev.data.label, ev.data.status);
31
+ if (ev.event === "result") console.log(ev.data.verdict.outcome);
32
+ }
33
+
34
+ // Credentials: VC DM 2.0 / 1.1 with Data Integrity (eddsa-jcs-2022, ecdsa-jcs-2019) or VC-JOSE/VC-JWT.
35
+ // RDFC suites, ecdsa-sd-2023 and bbs-2023 are reported as UNSUPPORTED, never as valid.
36
+ const vc = await didis.verifyCredential(credential);
37
+ console.log(vc.status, vc.headline);
38
+
39
+ // Policies are explicit rules, never scores.
40
+ const { evaluation } = await didis.evaluatePolicy("did:web:example.com", {
41
+ rules: { allowedMethods: ["web", "webvh"], domainBinding: "required", tlsMinDaysRemaining: 14 },
42
+ });
43
+
44
+ // Agents.
45
+ const mcp = await didis.inspectMcp("https://mcp.example.com/mcp");
46
+ const card = await didis.inspectA2a("https://agent.example.com");
47
+ const { authorization } = await didis.authorize(chain, "payments.refund", { trustedRoots: ["did:web:example.com"] });
48
+
49
+ try {
50
+ await didis.resolve("did:web:does-not-exist.invalid");
51
+ } catch (e) {
52
+ if (e instanceof DidisError) console.log(e.status, e.problem.code, e.problem.detail);
53
+ }
54
+ ```
55
+
56
+ Identifiers are percent-encoded exactly once by the client. Non-2xx responses throw `DidisError` with the RFC 9457 problem body (including the resolution `trace` where available).
57
+
58
+ ## Webhooks
59
+
60
+ Monitoring routes require an admin token (`new DidisClient({ adminToken })`) and must only be used server-side. Deliveries are signed:
61
+
62
+ ```ts
63
+ import { verifyWebhookSignature } from "@didis/client";
64
+
65
+ const ok = await verifyWebhookSignature(
66
+ secret,
67
+ req.headers["x-didis-timestamp"],
68
+ rawBody,
69
+ req.headers["x-didis-signature-256"],
70
+ );
71
+ ```
72
+
73
+ ## Surface
74
+
75
+ | Method | Route |
76
+ | --- | --- |
77
+ | `health()` | `GET /health` |
78
+ | `resolve(did, { noCache })` | `GET /v1/resolve/{did}` |
79
+ | `resolveW3c(did)` | `GET /1.0/identifiers/{did}` |
80
+ | `dereference(didUrl)` | `GET /v1/dereference/{didUrl}` |
81
+ | `stream(did)` | `GET /v1/stream/{did}` |
82
+ | `history(did)` / `diff(did, from?, to?)` / `graph(did)` | `GET /v1/history`, `/v1/diff`, `/v1/graph` |
83
+ | `verifyCredential(vc)` | `POST /v1/credentials/verify` |
84
+ | `evaluatePolicy(did, policy, vc?)` | `POST /v1/policies/evaluate` |
85
+ | `inspectMcp(endpoint)` / `inspectA2a(url)` | `GET /v1/mcp/inspect`, `/v1/a2a/inspect` |
86
+ | `verifyDelegation(chain, opts)` / `authorize(chain, tool, opts)` / `verifyTool(agent, tool, root?)` | `/v1/agents/*` |
87
+ | `fastVerifyDid(did)` / `fastVerifyJws(jws, relationship?)` | `/v1/fast-verify/*` |
88
+ | `watch` / `watches` / `unwatch` / `events` | `/v1/monitoring/*` (admin) |
89
+
90
+ Public API reference: <https://did.is/developers>. Monitoring routes (`watch`, `watches`, `unwatch`, `events`) need an admin token and are only available on self-hosted deployments; they are not exposed by the public API.
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,676 @@
1
+ /**
2
+ * DID.is client SDK (TypeScript — Node 18+, Deno, Bun, browsers, edge runtimes).
3
+ *
4
+ * Every identifier is percent-encoded exactly once; the resolver decodes it exactly once.
5
+ * Errors are thrown as `DidisError`, carrying the RFC 9457 problem object returned by the API.
6
+ */
7
+ export type DimensionState = "ESTABLISHED" | "SELF_CERTIFYING" | "NOT_ESTABLISHED" | "FAILED" | "INDETERMINATE" | "NOT_APPLICABLE";
8
+ export interface Problem {
9
+ type: string;
10
+ title: string;
11
+ status?: number;
12
+ detail: string;
13
+ code?: string;
14
+ trace?: TraceStage[];
15
+ /** Retry-After delta-seconds or HTTP-date, verbatim from the server. */
16
+ retryAfter?: string;
17
+ }
18
+ export interface TraceStage {
19
+ stage: string;
20
+ label: string;
21
+ status: "PASS" | "FAIL" | "WARN" | "INFO" | "SKIP" | string;
22
+ startedUs: number;
23
+ durationUs: number;
24
+ detail: string;
25
+ }
26
+ export interface ResolutionMetadata {
27
+ contentType?: string;
28
+ error?: Problem;
29
+ retrieved?: string;
30
+ durationMs?: number;
31
+ }
32
+ export interface ResolutionSource {
33
+ kind: "LOCAL_DERIVATION" | "HTTPS" | string;
34
+ url?: string;
35
+ httpStatus?: number;
36
+ contentType?: string;
37
+ bytes?: number;
38
+ sha256?: string;
39
+ peerAddress?: string;
40
+ redirects?: string[];
41
+ latencyMs: number;
42
+ }
43
+ export interface CertificateSummary {
44
+ sha256Fingerprint: string;
45
+ subjectCn?: string;
46
+ subjectO?: string;
47
+ issuerCn?: string;
48
+ issuerO?: string;
49
+ notBefore?: string;
50
+ notAfter?: string;
51
+ serialHex?: string;
52
+ sanDns: string[];
53
+ }
54
+ export interface TlsEvidence {
55
+ host: string;
56
+ validatedBy: string;
57
+ protocol: string;
58
+ certificate?: CertificateSummary;
59
+ daysUntilExpiry?: number;
60
+ hostnameInSan?: boolean;
61
+ }
62
+ export interface MulticodecBreakdown {
63
+ multibasePrefix: string;
64
+ multibaseEncoding: string;
65
+ codec: string;
66
+ codecName: string;
67
+ varintHex: string;
68
+ keyBytesHex: string;
69
+ keyLength: number;
70
+ }
71
+ export interface KeyEvidence {
72
+ id: string;
73
+ type: string;
74
+ controller?: string;
75
+ controllerIsSubject: boolean;
76
+ relationships: string[];
77
+ materialProperty?: string;
78
+ status: "VALID_KEY" | "INVALID_KEY" | "UNSUPPORTED_KEY" | string;
79
+ curve?: string;
80
+ keySizeBits?: number;
81
+ jwkThumbprint?: string;
82
+ multikey?: string;
83
+ jwk?: Record<string, unknown>;
84
+ multicodec?: MulticodecBreakdown;
85
+ detail: string;
86
+ }
87
+ export interface ServiceEvidence {
88
+ id: string;
89
+ type: unknown;
90
+ endpoint: unknown;
91
+ notes: string[];
92
+ }
93
+ export interface DocumentEvidence {
94
+ idMatches: boolean;
95
+ contexts: {
96
+ uri: string;
97
+ known: boolean;
98
+ name?: string;
99
+ }[];
100
+ verificationMethodCount: number;
101
+ serviceCount: number;
102
+ relationships: Record<string, string[]>;
103
+ alsoKnownAs: string[];
104
+ controllers: string[];
105
+ jcsSha256?: string;
106
+ jcsProfile?: string;
107
+ warnings: string[];
108
+ }
109
+ export interface LinkageCredentialEvidence {
110
+ format: string;
111
+ issuer?: string;
112
+ subject?: string;
113
+ origin?: string;
114
+ verificationMethod?: string;
115
+ proofSuite?: string;
116
+ issuanceDate?: string;
117
+ expirationDate?: string;
118
+ status: string;
119
+ detail: string;
120
+ }
121
+ export interface DomainBindingEvidence {
122
+ status: string;
123
+ origin: string;
124
+ configurationUrl: string;
125
+ httpStatus?: number;
126
+ configurationSha256?: string;
127
+ linkedDids: string[];
128
+ credentials: LinkageCredentialEvidence[];
129
+ detail: string;
130
+ }
131
+ export interface LogEntryEvidence {
132
+ versionId: string;
133
+ versionNumber: number;
134
+ versionTime: string;
135
+ entryHash: string;
136
+ entryHashValid: boolean;
137
+ proofValid: boolean;
138
+ signer?: string;
139
+ updateKeys: string[];
140
+ parametersChanged: string[];
141
+ stateSha256?: string;
142
+ detail: string;
143
+ }
144
+ export interface HistoryEvidence {
145
+ scid: string;
146
+ logUrl: string;
147
+ logSha256: string;
148
+ entries: LogEntryEvidence[];
149
+ selectedVersionId: string;
150
+ preRotation: boolean;
151
+ portable: boolean;
152
+ deactivated: boolean;
153
+ witness?: {
154
+ threshold: number;
155
+ witnesses: string[];
156
+ approvals: number;
157
+ satisfied: boolean;
158
+ detail: string;
159
+ };
160
+ ttl?: number;
161
+ }
162
+ export interface EvidenceDimension {
163
+ id: string;
164
+ label: string;
165
+ state: DimensionState;
166
+ statement: string;
167
+ proves: string;
168
+ doesNotProve: string;
169
+ }
170
+ export interface Verdict {
171
+ outcome: "RESOLVED" | "RESOLVED_WITH_WARNINGS" | "DEACTIVATED" | string;
172
+ headline: string;
173
+ statements: string[];
174
+ }
175
+ export interface GraphNode {
176
+ id: string;
177
+ kind: string;
178
+ label: string;
179
+ sublabel?: string;
180
+ state: string;
181
+ layer: number;
182
+ digest?: string;
183
+ facts: [string, string][];
184
+ }
185
+ export interface GraphEdge {
186
+ id: string;
187
+ from: string;
188
+ to: string;
189
+ relation: string;
190
+ state: "VERIFIED" | "OBSERVED" | "DECLARED" | "FAILED" | string;
191
+ }
192
+ export interface Evidence {
193
+ source: ResolutionSource;
194
+ document: DocumentEvidence;
195
+ keys: KeyEvidence[];
196
+ services: ServiceEvidence[];
197
+ domainBinding?: DomainBindingEvidence;
198
+ tls?: TlsEvidence;
199
+ history?: HistoryEvidence;
200
+ }
201
+ export interface EnrichedResolution {
202
+ did: string;
203
+ method: string;
204
+ didDocument: Record<string, unknown> | null;
205
+ didResolutionMetadata: ResolutionMetadata;
206
+ didDocumentMetadata: Record<string, unknown>;
207
+ verdict: Verdict;
208
+ dimensions: EvidenceDimension[];
209
+ evidence: Evidence;
210
+ graph: {
211
+ nodes: GraphNode[];
212
+ edges: GraphEdge[];
213
+ };
214
+ trace: TraceStage[];
215
+ limitations: string[];
216
+ resolver: {
217
+ name: string;
218
+ version: string;
219
+ };
220
+ observedAt: string;
221
+ cached: boolean;
222
+ }
223
+ export interface Check {
224
+ id: string;
225
+ label: string;
226
+ status: "PASS" | "FAIL" | "WARN" | "SKIP" | "UNSUPPORTED" | "INDETERMINATE" | string;
227
+ detail: string;
228
+ }
229
+ export interface CredentialVerification {
230
+ status: string;
231
+ headline: string;
232
+ format: string;
233
+ dataModel: string;
234
+ id?: string;
235
+ issuer?: string;
236
+ issuerName?: string;
237
+ subject?: string;
238
+ types: string[];
239
+ validFrom?: string;
240
+ validUntil?: string;
241
+ checks: Check[];
242
+ proof?: {
243
+ format: string;
244
+ suite?: string;
245
+ verificationMethod?: string;
246
+ proofPurpose?: string;
247
+ created?: string;
248
+ keyCurve?: string;
249
+ signedDigest?: string;
250
+ detail: string;
251
+ };
252
+ statusList: {
253
+ id?: string;
254
+ statusType: string;
255
+ purpose: string;
256
+ index?: number;
257
+ listUrl?: string;
258
+ statusSize: number;
259
+ listCredentialVerified?: boolean;
260
+ listLengthBits?: number;
261
+ value?: number;
262
+ result: string;
263
+ window?: string;
264
+ windowOffset?: number;
265
+ detail: string;
266
+ }[];
267
+ issuerResolution?: {
268
+ id: string;
269
+ outcome?: string;
270
+ headline?: string;
271
+ domainBinding?: string;
272
+ detail: string;
273
+ };
274
+ claims: unknown;
275
+ credential: unknown;
276
+ limitations: string[];
277
+ valid: boolean;
278
+ errors: string[];
279
+ }
280
+ export interface ToolEvidence {
281
+ name: string;
282
+ title?: string;
283
+ description: string;
284
+ definitionSha256: string;
285
+ schemaSha256: string;
286
+ parameters: string[];
287
+ annotations: unknown;
288
+ declaredClass?: string;
289
+ heuristicClass: string;
290
+ riskSignals: string[];
291
+ drift: string;
292
+ inputSchema: unknown;
293
+ }
294
+ export interface McpInspection {
295
+ endpoint: string;
296
+ status: "INSPECTED" | "AUTH_REQUIRED" | string;
297
+ mode: string;
298
+ negotiatedVersion?: string;
299
+ supportedVersions: string[];
300
+ serverInfo: Record<string, unknown> | null;
301
+ capabilities: unknown;
302
+ instructions?: string;
303
+ tools: ToolEvidence[];
304
+ inventoryHash: string;
305
+ jcsProfile?: string;
306
+ classCounts: Record<string, number>;
307
+ drift: {
308
+ status: string;
309
+ previousObservedAt?: string;
310
+ previousInventoryHash?: string;
311
+ added: string[];
312
+ removed: string[];
313
+ changed: string[];
314
+ };
315
+ auth?: {
316
+ required: boolean;
317
+ wwwAuthenticate?: string;
318
+ resourceMetadata?: string;
319
+ };
320
+ transcript: {
321
+ method: string;
322
+ httpStatus?: number;
323
+ contentType?: string;
324
+ latencyMs: number;
325
+ outcome: string;
326
+ }[];
327
+ checks: Check[];
328
+ headline: string;
329
+ tls?: TlsEvidence;
330
+ limitations: string[];
331
+ }
332
+ export interface A2aInspection {
333
+ cardUrl: string;
334
+ httpStatus: number;
335
+ cardSha256: string;
336
+ canonicalSha256: string;
337
+ jcsProfile?: string;
338
+ specVersion: string;
339
+ name: string;
340
+ description: string;
341
+ version?: string;
342
+ provider?: {
343
+ organization?: string;
344
+ url?: string;
345
+ };
346
+ documentationUrl?: string;
347
+ interfaces: {
348
+ url: string;
349
+ protocolBinding: string;
350
+ protocolVersion?: string;
351
+ https: boolean;
352
+ sameOrigin: boolean;
353
+ }[];
354
+ capabilities: Record<string, unknown> | null;
355
+ securitySchemes: {
356
+ name: string;
357
+ kind: string;
358
+ detail: string;
359
+ }[];
360
+ skills: {
361
+ id: string;
362
+ name: string;
363
+ description: string;
364
+ tags: string[];
365
+ examples: string[];
366
+ }[];
367
+ signatures: {
368
+ alg?: string;
369
+ kid?: string;
370
+ jku?: string;
371
+ status: string;
372
+ detail: string;
373
+ }[];
374
+ checks: Check[];
375
+ headline: string;
376
+ card: unknown;
377
+ tls?: TlsEvidence;
378
+ limitations: string[];
379
+ }
380
+ export interface DelegationLink {
381
+ index: number;
382
+ issuer: string;
383
+ audience: string;
384
+ capabilities: string[];
385
+ notBefore?: string;
386
+ expires?: string;
387
+ jti?: string;
388
+ kid?: string;
389
+ alg?: string;
390
+ digest: string;
391
+ signatureValid: boolean;
392
+ status: string;
393
+ issuerHeadline?: string;
394
+ checks: Check[];
395
+ }
396
+ export interface DelegationChainResult {
397
+ status: string;
398
+ headline: string;
399
+ format: string;
400
+ root?: string;
401
+ leaf?: string;
402
+ effectiveCapabilities: string[];
403
+ notAfter?: string;
404
+ trustedRoot?: boolean;
405
+ links: DelegationLink[];
406
+ checks: Check[];
407
+ limitations: string[];
408
+ }
409
+ export interface ToolAuthorization {
410
+ agent: string;
411
+ tool: string;
412
+ decision: "ALLOW" | "DENY";
413
+ authorized: boolean;
414
+ reason: string;
415
+ matchedCapability?: string;
416
+ root?: string;
417
+ expires?: string;
418
+ }
419
+ export interface PolicyRuleEvaluation {
420
+ rule: string;
421
+ status: "PASS" | "FAIL" | "INDETERMINATE" | "NOT_APPLICABLE";
422
+ message: string;
423
+ expected?: unknown;
424
+ observedValue?: unknown;
425
+ }
426
+ export interface PolicyEvaluation {
427
+ did: string;
428
+ policyName?: string;
429
+ status: "PASS" | "FAIL" | "INDETERMINATE" | "NOT_APPLICABLE";
430
+ headline: string;
431
+ rulesEvaluated: number;
432
+ rulesPassed: number;
433
+ evaluations: PolicyRuleEvaluation[];
434
+ evaluatedAt: string;
435
+ }
436
+ export interface ObservationRecord {
437
+ id: number;
438
+ did: string;
439
+ documentHash: string;
440
+ observedAt: string;
441
+ lastSeenAt: string;
442
+ seenCount: number;
443
+ latencyMs: number;
444
+ resolverVersion: string;
445
+ domainBindingStatus: string;
446
+ summary: {
447
+ outcome?: string;
448
+ headline?: string;
449
+ } | null;
450
+ }
451
+ export interface SemanticDiff {
452
+ did: string;
453
+ fromHash: string;
454
+ toHash: string;
455
+ fromObservedAt: string;
456
+ toObservedAt: string;
457
+ identical: boolean;
458
+ keysAdded: string[];
459
+ keysRemoved: string[];
460
+ keysRotated: string[];
461
+ relationshipsChanged: {
462
+ relationship: string;
463
+ added: string[];
464
+ removed: string[];
465
+ }[];
466
+ servicesAdded: string[];
467
+ servicesRemoved: string[];
468
+ servicesChanged: string[];
469
+ controllerChanged: boolean;
470
+ alsoKnownAsChanged: boolean;
471
+ contextsChanged: boolean;
472
+ domainBindingFrom: string;
473
+ domainBindingTo: string;
474
+ domainBindingChanged: boolean;
475
+ changedMembers: string[];
476
+ summary: string[];
477
+ fromDocument: unknown;
478
+ toDocument: unknown;
479
+ }
480
+ export interface ResolutionResult {
481
+ "@context": string;
482
+ didDocument: Record<string, unknown> | null;
483
+ didResolutionMetadata: ResolutionMetadata;
484
+ didDocumentMetadata: Record<string, unknown>;
485
+ }
486
+ export interface DereferencingResult {
487
+ "@context": string;
488
+ dereferencingMetadata: ResolutionMetadata;
489
+ contentStream: unknown;
490
+ contentMetadata: Record<string, unknown>;
491
+ }
492
+ export interface PolicyRules {
493
+ didResolution?: "required" | "optional";
494
+ allowedMethods?: string[];
495
+ allowedCurves?: string[];
496
+ allowedKeySuites?: string[];
497
+ minKeyBits?: number;
498
+ requireKeyRelationship?: string;
499
+ domainBinding?: "required" | "optional";
500
+ tlsMinDaysRemaining?: number;
501
+ verifiableHistory?: "required";
502
+ maxCacheAgeSeconds?: number;
503
+ credentialStatus?: "active";
504
+ credentialIssuerMustBeSubject?: boolean;
505
+ }
506
+ export interface PolicyResponse {
507
+ evaluation: PolicyEvaluation;
508
+ credential: CredentialVerification | null;
509
+ resolution: {
510
+ verdict: Verdict;
511
+ dimensions: EvidenceDimension[];
512
+ } | null;
513
+ }
514
+ export interface HealthStatus {
515
+ status: "ok" | string;
516
+ name: string;
517
+ version: string;
518
+ time: string;
519
+ supportedMethods: string[];
520
+ standards: Record<string, unknown>;
521
+ monitoring?: boolean;
522
+ rateLimited?: boolean;
523
+ }
524
+ export interface HistoryResponse {
525
+ did: string;
526
+ count: number;
527
+ history: ObservationRecord[];
528
+ note: string;
529
+ }
530
+ export interface GraphResponse {
531
+ did: string;
532
+ observedAt: string;
533
+ verdict: Verdict;
534
+ nodes: GraphNode[];
535
+ edges: GraphEdge[];
536
+ }
537
+ export interface FastVerifyDidResult {
538
+ did: string;
539
+ resolved: boolean;
540
+ outcome: Verdict["outcome"];
541
+ headline: string;
542
+ statements: string[];
543
+ dimensions: {
544
+ id: string;
545
+ state: DimensionState;
546
+ }[];
547
+ keys: {
548
+ id: string;
549
+ curve?: string;
550
+ status: KeyEvidence["status"];
551
+ relationships: string[];
552
+ jwkThumbprint?: string;
553
+ }[];
554
+ domainBinding: string | null;
555
+ documentSha256?: string;
556
+ observedAt: string;
557
+ cached: boolean;
558
+ }
559
+ export interface FastVerifyJwsResult {
560
+ status: "VALID" | "INVALID" | "INDETERMINATE" | "UNSUPPORTED" | "MALFORMED";
561
+ valid: boolean;
562
+ headline: string;
563
+ alg?: string;
564
+ kid?: string;
565
+ typ?: string;
566
+ /** Decoded JSON payload, or base64url text when the payload is not JSON. */
567
+ payload?: unknown;
568
+ signingInputSha256?: string;
569
+ signer?: string;
570
+ relationship?: string;
571
+ }
572
+ export interface DelegationVerification {
573
+ result: DelegationChainResult;
574
+ registered: boolean;
575
+ }
576
+ export interface AuthorizationResponse {
577
+ authorization: ToolAuthorization;
578
+ chain: DelegationChainResult;
579
+ }
580
+ export type StreamEvent = {
581
+ event: "stage";
582
+ data: TraceStage;
583
+ } | {
584
+ event: "result";
585
+ data: EnrichedResolution;
586
+ } | {
587
+ event: "error";
588
+ data: Problem;
589
+ } | {
590
+ event: "done";
591
+ data: Record<string, never>;
592
+ };
593
+ export declare class DidisError extends Error {
594
+ readonly problem: Problem;
595
+ readonly status: number;
596
+ constructor(status: number, problem: Problem);
597
+ }
598
+ export interface DidisClientOptions {
599
+ baseUrl?: string;
600
+ /** Bearer token for admin-only monitoring routes. Never ship it to browsers. */
601
+ adminToken?: string;
602
+ timeoutMs?: number;
603
+ fetch?: typeof fetch;
604
+ }
605
+ /** Public DID.is API. Point `baseUrl` at your own deployment when self-hosting. */
606
+ export declare const DEFAULT_BASE_URL = "https://did.is/api";
607
+ export declare class DidisClient {
608
+ private readonly baseUrl;
609
+ private readonly adminToken?;
610
+ private readonly timeoutMs;
611
+ private readonly fetchImpl;
612
+ constructor(options?: string | DidisClientOptions);
613
+ private request;
614
+ private admin;
615
+ health(): Promise<HealthStatus>;
616
+ /** Enriched resolution: verdict, evidence dimensions, graph, telemetry. */
617
+ resolve(did: string, opts?: {
618
+ noCache?: boolean;
619
+ }): Promise<EnrichedResolution>;
620
+ /** DID Resolution v1 HTTPS binding (W3C CR Draft; full resolution result). */
621
+ resolveW3c(did: string): Promise<ResolutionResult>;
622
+ dereference(didUrl: string): Promise<DereferencingResult>;
623
+ /** Live resolution over server-sent events. */
624
+ stream(did: string, opts?: {
625
+ noCache?: boolean;
626
+ }): AsyncGenerator<StreamEvent>;
627
+ history(did: string): Promise<HistoryResponse>;
628
+ /** Semantic diff; defaults to the latest two distinct observations. */
629
+ diff(did: string, from?: string, to?: string): Promise<SemanticDiff>;
630
+ graph(did: string): Promise<GraphResponse>;
631
+ /** Audience is checked only when an expected recipient is supplied. Pass raw JSON as a string to preserve member identity. */
632
+ verifyCredential(credential: unknown, options?: {
633
+ expectedAudience?: string;
634
+ }): Promise<CredentialVerification>;
635
+ evaluatePolicy(did: string, policy: {
636
+ name?: string;
637
+ rules: PolicyRules;
638
+ }, credential?: unknown): Promise<PolicyResponse>;
639
+ inspectMcp(endpoint: string): Promise<McpInspection>;
640
+ inspectA2a(url: string): Promise<A2aInspection>;
641
+ verifyDelegation(chain: string[], opts?: {
642
+ trustedRoots?: string[];
643
+ register?: boolean;
644
+ }): Promise<DelegationVerification>;
645
+ authorize(chain: string[], tool: string, opts?: {
646
+ trustedRoots?: string[];
647
+ }): Promise<AuthorizationResponse>;
648
+ verifyTool(agent: string, tool: string, root?: string): Promise<ToolAuthorization>;
649
+ fastVerifyDid(did: string): Promise<FastVerifyDidResult>;
650
+ fastVerifyJws(jws: string, relationship?: string): Promise<FastVerifyJwsResult>;
651
+ watch(did: string, webhookUrl: string, intervalSeconds?: number): Promise<{
652
+ watch: Record<string, unknown>;
653
+ secret: string;
654
+ }>;
655
+ watches(): Promise<{
656
+ watches: Record<string, unknown>[];
657
+ }>;
658
+ unwatch(id: string): Promise<null>;
659
+ events(watchId?: string): Promise<{
660
+ events: Record<string, unknown>[];
661
+ }>;
662
+ }
663
+ /**
664
+ * Verifies a webhook delivery: `X-Didis-Signature-256 = sha256=HMAC(secret, timestamp + "." + body)`.
665
+ * Rejects deliveries older than `toleranceSeconds`. Uses WebCrypto (Node 18+, browsers, edge).
666
+ */
667
+ export declare function verifyWebhookSignature(secret: string, timestamp: string, body: string, signatureHeader: string, toleranceSeconds?: number): Promise<boolean>;
668
+ /** Verifies the separate durable CUSTOMER event signature, not the admin webhook format.
669
+ * The timestamp is event creation and remains unchanged on retry. The receiver MUST persist
670
+ * event-ID deduplication before side effects. A valid signature is not a billing grant.
671
+ */
672
+ export declare function verifyCustomerMonitorSignature(secret: string, body: string, signature: string, expected: {
673
+ tenantId: string;
674
+ projectId: string;
675
+ eventId: string;
676
+ }, maxAgeSeconds?: number): Promise<boolean>;
package/dist/index.js ADDED
@@ -0,0 +1,225 @@
1
+ /**
2
+ * DID.is client SDK (TypeScript — Node 18+, Deno, Bun, browsers, edge runtimes).
3
+ *
4
+ * Every identifier is percent-encoded exactly once; the resolver decodes it exactly once.
5
+ * Errors are thrown as `DidisError`, carrying the RFC 9457 problem object returned by the API.
6
+ */
7
+ // ---------------------------------------------------------------------------------------------
8
+ // Client
9
+ // ---------------------------------------------------------------------------------------------
10
+ export class DidisError extends Error {
11
+ problem;
12
+ status;
13
+ constructor(status, problem) {
14
+ super(`${problem.title}: ${problem.detail}`);
15
+ this.name = "DidisError";
16
+ this.status = status;
17
+ this.problem = problem;
18
+ }
19
+ }
20
+ /** Public DID.is API. Point `baseUrl` at your own deployment when self-hosting. */
21
+ export const DEFAULT_BASE_URL = "https://did.is/api";
22
+ const enc = (s) => encodeURIComponent(s.trim());
23
+ export class DidisClient {
24
+ baseUrl;
25
+ adminToken;
26
+ timeoutMs;
27
+ fetchImpl;
28
+ constructor(options = {}) {
29
+ const o = typeof options === "string" ? { baseUrl: options } : options;
30
+ this.baseUrl = (o.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
31
+ this.adminToken = o.adminToken;
32
+ this.timeoutMs = o.timeoutMs ?? 30_000;
33
+ this.fetchImpl = o.fetch ?? fetch;
34
+ }
35
+ async request(method, path, body, headers = {}) {
36
+ const controller = new AbortController();
37
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
38
+ try {
39
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
40
+ method,
41
+ signal: controller.signal,
42
+ headers: { accept: "application/json", ...(body !== undefined ? { "content-type": "application/json" } : {}), ...headers },
43
+ body: body !== undefined ? JSON.stringify(body) : undefined,
44
+ });
45
+ const text = await res.text();
46
+ const json = text ? JSON.parse(text) : null;
47
+ if (!res.ok) {
48
+ const p = (json ?? {});
49
+ throw new DidisError(res.status, {
50
+ type: p.type ?? "about:blank",
51
+ title: p.title ?? `HTTP ${res.status}`,
52
+ status: res.status,
53
+ detail: p.detail ?? text,
54
+ code: p.code,
55
+ trace: p.trace,
56
+ retryAfter: res.headers.get("retry-after") ?? undefined,
57
+ });
58
+ }
59
+ return json;
60
+ }
61
+ finally {
62
+ clearTimeout(timer);
63
+ }
64
+ }
65
+ admin() {
66
+ if (!this.adminToken)
67
+ throw new Error("adminToken is required for monitoring routes");
68
+ return { authorization: `Bearer ${this.adminToken}` };
69
+ }
70
+ health() {
71
+ return this.request("GET", "/health");
72
+ }
73
+ /** Enriched resolution: verdict, evidence dimensions, graph, telemetry. */
74
+ resolve(did, opts = {}) {
75
+ return this.request("GET", `/v1/resolve/${enc(did)}${opts.noCache ? "?noCache=true" : ""}`);
76
+ }
77
+ /** DID Resolution v1 HTTPS binding (W3C CR Draft; full resolution result). */
78
+ resolveW3c(did) {
79
+ return this.request("GET", `/1.0/identifiers/${enc(did)}`, undefined, { accept: "application/did-resolution" });
80
+ }
81
+ dereference(didUrl) {
82
+ return this.request("GET", `/v1/dereference/${enc(didUrl)}`);
83
+ }
84
+ /** Live resolution over server-sent events. */
85
+ async *stream(did, opts = { noCache: true }) {
86
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/stream/${enc(did)}${opts.noCache ? "?noCache=true" : ""}`, {
87
+ headers: { accept: "text/event-stream" },
88
+ });
89
+ if (!res.ok || !res.body)
90
+ throw new DidisError(res.status, { type: "about:blank", title: `HTTP ${res.status}`, detail: "stream failed" });
91
+ const reader = res.body.getReader();
92
+ const decoder = new TextDecoder();
93
+ let buffer = "";
94
+ for (;;) {
95
+ const { value, done } = await reader.read();
96
+ if (done)
97
+ break;
98
+ buffer += decoder.decode(value, { stream: true });
99
+ let idx;
100
+ while ((idx = buffer.indexOf("\n\n")) >= 0) {
101
+ const block = buffer.slice(0, idx);
102
+ buffer = buffer.slice(idx + 2);
103
+ let event = "message";
104
+ const data = [];
105
+ for (const line of block.split("\n")) {
106
+ if (line.startsWith("event:"))
107
+ event = line.slice(6).trim();
108
+ else if (line.startsWith("data:"))
109
+ data.push(line.slice(5).replace(/^ /, ""));
110
+ }
111
+ if (data.length === 0)
112
+ continue;
113
+ const raw = data.join("\n");
114
+ let parsed = raw;
115
+ try {
116
+ parsed = JSON.parse(raw);
117
+ }
118
+ catch {
119
+ /* non-JSON payload */
120
+ }
121
+ yield { event, data: parsed };
122
+ if (event === "done")
123
+ return;
124
+ }
125
+ }
126
+ }
127
+ history(did) {
128
+ return this.request("GET", `/v1/history/${enc(did)}`);
129
+ }
130
+ /** Semantic diff; defaults to the latest two distinct observations. */
131
+ diff(did, from, to) {
132
+ const q = from && to ? `?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}` : "";
133
+ return this.request("GET", `/v1/diff/${enc(did)}${q}`);
134
+ }
135
+ graph(did) {
136
+ return this.request("GET", `/v1/graph/${enc(did)}`);
137
+ }
138
+ /** Audience is checked only when an expected recipient is supplied. Pass raw JSON as a string to preserve member identity. */
139
+ verifyCredential(credential, options = {}) {
140
+ return this.request("POST", "/v1/credentials/verify", { credential, expectedAudience: options.expectedAudience });
141
+ }
142
+ evaluatePolicy(did, policy, credential) {
143
+ return this.request("POST", "/v1/policies/evaluate", { did, policy, credential });
144
+ }
145
+ inspectMcp(endpoint) {
146
+ return this.request("GET", `/v1/mcp/inspect?endpoint=${encodeURIComponent(endpoint)}`);
147
+ }
148
+ inspectA2a(url) {
149
+ return this.request("GET", `/v1/a2a/inspect?url=${encodeURIComponent(url)}`);
150
+ }
151
+ verifyDelegation(chain, opts = {}) {
152
+ return this.request("POST", "/v1/agents/verify-delegation", { chain, ...opts });
153
+ }
154
+ authorize(chain, tool, opts = {}) {
155
+ return this.request("POST", "/v1/agents/authorize", { chain, tool, ...opts });
156
+ }
157
+ verifyTool(agent, tool, root) {
158
+ const q = new URLSearchParams({ agent, tool, ...(root ? { root } : {}) });
159
+ return this.request("GET", `/v1/agents/verify-tool?${q.toString()}`);
160
+ }
161
+ fastVerifyDid(did) {
162
+ return this.request("GET", `/v1/fast-verify/did/${enc(did)}`);
163
+ }
164
+ fastVerifyJws(jws, relationship) {
165
+ return this.request("POST", "/v1/fast-verify/jws", { jws, relationship });
166
+ }
167
+ // ---- Monitoring (admin) ------------------------------------------------------------------
168
+ async watch(did, webhookUrl, intervalSeconds) {
169
+ return this.request("POST", "/v1/monitoring/watches", { did, webhookUrl, intervalSeconds }, this.admin());
170
+ }
171
+ async watches() {
172
+ return this.request("GET", "/v1/monitoring/watches", undefined, this.admin());
173
+ }
174
+ async unwatch(id) {
175
+ return this.request("DELETE", `/v1/monitoring/watches/${encodeURIComponent(id)}`, undefined, this.admin());
176
+ }
177
+ async events(watchId) {
178
+ return this.request("GET", `/v1/monitoring/events${watchId ? `?watchId=${encodeURIComponent(watchId)}` : ""}`, undefined, this.admin());
179
+ }
180
+ }
181
+ /**
182
+ * Verifies a webhook delivery: `X-Didis-Signature-256 = sha256=HMAC(secret, timestamp + "." + body)`.
183
+ * Rejects deliveries older than `toleranceSeconds`. Uses WebCrypto (Node 18+, browsers, edge).
184
+ */
185
+ export async function verifyWebhookSignature(secret, timestamp, body, signatureHeader, toleranceSeconds = 300) {
186
+ const ts = Number(timestamp);
187
+ if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > toleranceSeconds)
188
+ return false;
189
+ const te = new TextEncoder();
190
+ const key = await crypto.subtle.importKey("raw", te.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
191
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, te.encode(`${timestamp}.${body}`)));
192
+ const expected = `sha256=${Array.from(mac, (b) => b.toString(16).padStart(2, "0")).join("")}`;
193
+ if (expected.length !== signatureHeader.length)
194
+ return false;
195
+ let diff = 0;
196
+ for (let i = 0; i < expected.length; i++)
197
+ diff |= expected.charCodeAt(i) ^ signatureHeader.charCodeAt(i);
198
+ return diff === 0;
199
+ }
200
+ /** Verifies the separate durable CUSTOMER event signature, not the admin webhook format.
201
+ * The timestamp is event creation and remains unchanged on retry. The receiver MUST persist
202
+ * event-ID deduplication before side effects. A valid signature is not a billing grant.
203
+ */
204
+ export async function verifyCustomerMonitorSignature(secret, body, signature, expected, maxAgeSeconds = 30 * 86400) {
205
+ const match = /^t=(0|[1-9][0-9]{0,15}),v1=([0-9a-f]{64})$/.exec(signature);
206
+ const timestamp = match ? Number(match[1]) : NaN;
207
+ const now = Date.now() / 1000;
208
+ const bytes = new TextEncoder().encode(body);
209
+ if (!match || !Number.isSafeInteger(timestamp) || !Number.isInteger(maxAgeSeconds) || maxAgeSeconds < 1 || maxAgeSeconds > 30 * 86400 || timestamp > now + 60 || now - timestamp >= maxAgeSeconds || bytes.length > 300 * 1024 || !secret)
210
+ return false;
211
+ try {
212
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
213
+ const mac = Uint8Array.from(match[2].match(/../g), value => parseInt(value, 16));
214
+ if (!await crypto.subtle.verify("HMAC", key, mac, new TextEncoder().encode(`${match[1]}.${body}`)))
215
+ return false;
216
+ const event = JSON.parse(body);
217
+ return event.specVersion === "didis.customer-monitor.v1" && event.createdAt === timestamp
218
+ && /^cevent_[0-9a-f]{64}$/.test(expected.eventId) && event.id === expected.eventId
219
+ && /^tenant_[0-9a-f]{64}$/.test(expected.tenantId) && event.tenantId === expected.tenantId
220
+ && /^project_[0-9a-f]{64}$/.test(expected.projectId) && event.projectId === expected.projectId;
221
+ }
222
+ catch {
223
+ return false;
224
+ }
225
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@didis/client",
3
+ "version": "0.1.0",
4
+ "description": "Official TypeScript/JavaScript SDK for the DID.is public API: DID resolution with evidence, credentials, policies and agent trust",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "sideEffects": false,
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "test": "npm run build && node --test test/*.test.js",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": [
30
+ "did",
31
+ "w3c",
32
+ "verifiable-credentials",
33
+ "identity",
34
+ "mcp",
35
+ "a2a",
36
+ "resolver"
37
+ ],
38
+ "author": "DID.is",
39
+ "license": "MIT",
40
+ "homepage": "https://did.is/developers",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/did-is/didis-js.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/did-is/didis-js/issues"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "devDependencies": {
52
+ "typescript": "^5.7.0"
53
+ }
54
+ }