@didis/client 0.2.2 → 0.2.4
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/CHANGELOG.md +19 -1
- package/README.md +2 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +49 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to `@didis/client` are listed here. The format follows [Keep
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.4] - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Automatic wait-and-retry for `429` (request budget used up) and `503` (temporarily overloaded) responses that carry `Retry-After`. By default the client retries up to 2 times and never waits longer than 10 seconds per attempt; a short random delay is added so clients refused together do not return together. Both statuses are refused before the request is processed, so retrying is safe for every call. Tune with `new DidisClient({ retry: { maxRetries, maxWaitMs } })` or turn off with `retry: false`. When retries run out, the last `DidisError` is thrown as before, with `problem.retryAfter`.
|
|
12
|
+
|
|
13
|
+
## [0.2.3] - 2026-10-18
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Requests now carry `user-agent: didis-client/<version>` (Node, Deno, Bun and other non-browser runtimes; browsers keep their own user agent). Only the package name and version are sent, so DID.is can count SDK use separately from other HTTP clients. New export `VERSION`.
|
|
18
|
+
|
|
19
|
+
- Type `LinkedAccountEvidence` and `Evidence.linkedAccounts`: each `alsoKnownAs` value with whether that website links back to the DID (`CONFIRMED`, `NOT_CONFIRMED`, `FAILED`, `UNSUPPORTED`, `UNREACHABLE`, `NOT_CHECKABLE`). A confirmation proves the same party controls the website origin and the DID; it does not prove who that party is.
|
|
20
|
+
|
|
21
|
+
- Policy rule `linkedAccounts` in `PolicyRules`: 1–4 https origins (for example `["https://shop.example"]`) that must be listed in the DID's `alsoKnownAs` and confirm the DID back. Use it to require "this DID belongs to our official website" in one policy call.
|
|
22
|
+
|
|
7
23
|
## [0.2.2] - 2026-10-18
|
|
8
24
|
|
|
9
25
|
### Added
|
|
@@ -46,7 +62,9 @@ All notable changes to `@didis/client` are listed here. The format follows [Keep
|
|
|
46
62
|
- `DidisError` carrying the RFC 9457 problem body and `Retry-After`.
|
|
47
63
|
- Webhook helpers `verifyWebhookSignature` and `verifyCustomerMonitorSignature`.
|
|
48
64
|
|
|
49
|
-
[Unreleased]: https://github.com/did-is/didis-js/compare/v0.2.
|
|
65
|
+
[Unreleased]: https://github.com/did-is/didis-js/compare/v0.2.4...HEAD
|
|
66
|
+
[0.2.4]: https://github.com/did-is/didis-js/compare/v0.2.3...v0.2.4
|
|
67
|
+
[0.2.3]: https://github.com/did-is/didis-js/compare/v0.2.2...v0.2.3
|
|
50
68
|
[0.2.2]: https://github.com/did-is/didis-js/compare/v0.2.1...v0.2.2
|
|
51
69
|
[0.2.1]: https://github.com/did-is/didis-js/compare/v0.2.0...v0.2.1
|
|
52
70
|
[0.2.0]: https://github.com/did-is/didis-js/compare/v0.1.0...v0.2.0
|
package/README.md
CHANGED
|
@@ -63,6 +63,8 @@ try {
|
|
|
63
63
|
|
|
64
64
|
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).
|
|
65
65
|
|
|
66
|
+
**Rate limits.** When the public budget is used up the API answers `429` with `Retry-After`. The client waits that long and retries (up to 2 times, at most 10 s per wait), so a loop of calls slows down instead of failing. Tune with `new DidisClient({ retry: { maxRetries: 3, maxWaitMs: 5000 } })` or turn it off with `retry: false`.
|
|
67
|
+
|
|
66
68
|
## Guard agent tools
|
|
67
69
|
|
|
68
70
|
Wrap a tool handler so it runs only when (1) the tool definition the agent received still matches the one you approved and (2) DID.is says the agent's registered delegation grants the tool. Every failure throws `DidisGuardError` and the handler does not run (fail-closed).
|
package/dist/index.d.ts
CHANGED
|
@@ -128,6 +128,19 @@ export interface DomainBindingEvidence {
|
|
|
128
128
|
credentials: LinkageCredentialEvidence[];
|
|
129
129
|
detail: string;
|
|
130
130
|
}
|
|
131
|
+
/** One `alsoKnownAs` value checked for a reciprocal link back to the DID. */
|
|
132
|
+
export interface LinkedAccountEvidence {
|
|
133
|
+
/** The `alsoKnownAs` value exactly as declared. */
|
|
134
|
+
value: string;
|
|
135
|
+
/** `WEB_ORIGIN` | `WEB_PAGE` | `SAME_ORIGIN` | `DID` | `OTHER` */
|
|
136
|
+
kind: string;
|
|
137
|
+
origin?: string;
|
|
138
|
+
/** `CONFIRMED` | `NOT_CONFIRMED` | `FAILED` | `UNSUPPORTED` | `UNREACHABLE` | `NOT_CHECKABLE` */
|
|
139
|
+
status: string;
|
|
140
|
+
configurationStatus?: string;
|
|
141
|
+
configurationSha256?: string;
|
|
142
|
+
detail: string;
|
|
143
|
+
}
|
|
131
144
|
export interface LogEntryEvidence {
|
|
132
145
|
versionId: string;
|
|
133
146
|
versionNumber: number;
|
|
@@ -195,6 +208,8 @@ export interface Evidence {
|
|
|
195
208
|
keys: KeyEvidence[];
|
|
196
209
|
services: ServiceEvidence[];
|
|
197
210
|
domainBinding?: DomainBindingEvidence;
|
|
211
|
+
/** Reciprocal checks of `alsoKnownAs`; absent when nothing is declared. */
|
|
212
|
+
linkedAccounts?: LinkedAccountEvidence[];
|
|
198
213
|
tls?: TlsEvidence;
|
|
199
214
|
history?: HistoryEvidence;
|
|
200
215
|
}
|
|
@@ -552,6 +567,8 @@ export interface PolicyRules {
|
|
|
552
567
|
requireKeyRelationship?: string;
|
|
553
568
|
domainBinding?: "required" | "optional";
|
|
554
569
|
tlsMinDaysRemaining?: number;
|
|
570
|
+
/** 1–4 bare https origins that must be declared in `alsoKnownAs` and link back to the DID. */
|
|
571
|
+
linkedAccounts?: string[];
|
|
555
572
|
verifiableHistory?: "required";
|
|
556
573
|
maxCacheAgeSeconds?: number;
|
|
557
574
|
credentialStatus?: "active";
|
|
@@ -655,16 +672,38 @@ export interface DidisClientOptions {
|
|
|
655
672
|
adminToken?: string;
|
|
656
673
|
timeoutMs?: number;
|
|
657
674
|
fetch?: typeof fetch;
|
|
675
|
+
/**
|
|
676
|
+
* Automatic wait-and-retry when the API answers 429 (request budget used up) or 503
|
|
677
|
+
* (temporarily overloaded) with a `Retry-After` header. Such refusals happen before the
|
|
678
|
+
* request is processed, so retrying is safe for every call. Default: up to 2 retries,
|
|
679
|
+
* never waiting longer than 10 s for one retry. Pass `false` to turn it off.
|
|
680
|
+
*/
|
|
681
|
+
retry?: false | {
|
|
682
|
+
maxRetries?: number;
|
|
683
|
+
maxWaitMs?: number;
|
|
684
|
+
};
|
|
658
685
|
}
|
|
686
|
+
/** Milliseconds to wait from a `Retry-After` value (delta-seconds or HTTP-date); null if unusable. */
|
|
687
|
+
export declare function retryAfterMs(value: string | null | undefined, now?: number): number | null;
|
|
659
688
|
/** Public DID.is API. Point `baseUrl` at your own deployment when self-hosting. */
|
|
660
689
|
export declare const DEFAULT_BASE_URL = "https://did.is/api";
|
|
690
|
+
/** Package version, kept equal to package.json (checked by the test suite). */
|
|
691
|
+
export declare const VERSION = "0.2.4";
|
|
661
692
|
export declare class DidisClient {
|
|
662
693
|
private readonly baseUrl;
|
|
663
694
|
private readonly adminToken?;
|
|
664
695
|
private readonly timeoutMs;
|
|
665
696
|
private readonly fetchImpl;
|
|
697
|
+
private readonly maxRetries;
|
|
698
|
+
private readonly maxWaitMs;
|
|
666
699
|
constructor(options?: string | DidisClientOptions);
|
|
700
|
+
/**
|
|
701
|
+
* Waits out a 429/503 refusal when the server says how long, within the configured bounds.
|
|
702
|
+
* A small random delay spreads out many clients that were refused at the same moment.
|
|
703
|
+
*/
|
|
704
|
+
private backoff;
|
|
667
705
|
private request;
|
|
706
|
+
private once;
|
|
668
707
|
private admin;
|
|
669
708
|
health(): Promise<HealthStatus>;
|
|
670
709
|
/** Enriched resolution: verdict, evidence dimensions, graph, telemetry. */
|
package/dist/index.js
CHANGED
|
@@ -12,29 +12,76 @@ export class DidisError extends Error {
|
|
|
12
12
|
this.problem = problem;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
/** Milliseconds to wait from a `Retry-After` value (delta-seconds or HTTP-date); null if unusable. */
|
|
16
|
+
export function retryAfterMs(value, now = Date.now()) {
|
|
17
|
+
if (!value)
|
|
18
|
+
return null;
|
|
19
|
+
const v = value.trim();
|
|
20
|
+
if (/^\d+$/.test(v))
|
|
21
|
+
return Number(v) * 1000;
|
|
22
|
+
const at = Date.parse(v);
|
|
23
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
24
|
+
}
|
|
25
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
15
26
|
/** Public DID.is API. Point `baseUrl` at your own deployment when self-hosting. */
|
|
16
27
|
export const DEFAULT_BASE_URL = "https://did.is/api";
|
|
28
|
+
/** Package version, kept equal to package.json (checked by the test suite). */
|
|
29
|
+
export const VERSION = "0.2.4";
|
|
30
|
+
/**
|
|
31
|
+
* Identifies the SDK to the API so DID.is can count SDK use apart from other HTTP clients.
|
|
32
|
+
* Only the product name and version are sent. Browsers are left alone: there the browser
|
|
33
|
+
* controls the user agent and an extra header would add a CORS preflight to every call.
|
|
34
|
+
*/
|
|
35
|
+
const SDK_HEADERS = typeof globalThis.document === "undefined" ? { "user-agent": `didis-client/${VERSION}` } : {};
|
|
17
36
|
const enc = (s) => encodeURIComponent(s.trim());
|
|
18
37
|
export class DidisClient {
|
|
19
38
|
baseUrl;
|
|
20
39
|
adminToken;
|
|
21
40
|
timeoutMs;
|
|
22
41
|
fetchImpl;
|
|
42
|
+
maxRetries;
|
|
43
|
+
maxWaitMs;
|
|
23
44
|
constructor(options = {}) {
|
|
24
45
|
const o = typeof options === "string" ? { baseUrl: options } : options;
|
|
25
46
|
this.baseUrl = (o.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
26
47
|
this.adminToken = o.adminToken;
|
|
27
48
|
this.timeoutMs = o.timeoutMs ?? 30_000;
|
|
28
49
|
this.fetchImpl = o.fetch ?? fetch;
|
|
50
|
+
this.maxRetries = o.retry === false ? 0 : Math.max(0, Math.floor(o.retry?.maxRetries ?? 2));
|
|
51
|
+
this.maxWaitMs = o.retry === false ? 0 : Math.max(0, o.retry?.maxWaitMs ?? 10_000);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Waits out a 429/503 refusal when the server says how long, within the configured bounds.
|
|
55
|
+
* A small random delay spreads out many clients that were refused at the same moment.
|
|
56
|
+
*/
|
|
57
|
+
async backoff(attempt, status, retryAfter) {
|
|
58
|
+
if (attempt >= this.maxRetries || (status !== 429 && status !== 503))
|
|
59
|
+
return false;
|
|
60
|
+
const wait = retryAfterMs(retryAfter);
|
|
61
|
+
if (wait === null || wait > this.maxWaitMs)
|
|
62
|
+
return false;
|
|
63
|
+
await sleep(wait + Math.floor(Math.random() * 250));
|
|
64
|
+
return true;
|
|
29
65
|
}
|
|
30
66
|
async request(method, path, body, headers = {}) {
|
|
67
|
+
for (let attempt = 0;; attempt++) {
|
|
68
|
+
try {
|
|
69
|
+
return await this.once(method, path, body, headers);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
if (!(e instanceof DidisError) || !(await this.backoff(attempt, e.status, e.problem.retryAfter ?? null)))
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async once(method, path, body, headers = {}) {
|
|
31
78
|
const controller = new AbortController();
|
|
32
79
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
33
80
|
try {
|
|
34
81
|
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
35
82
|
method,
|
|
36
83
|
signal: controller.signal,
|
|
37
|
-
headers: { accept: "application/json", ...(body !== undefined ? { "content-type": "application/json" } : {}), ...headers },
|
|
84
|
+
headers: { ...SDK_HEADERS, accept: "application/json", ...(body !== undefined ? { "content-type": "application/json" } : {}), ...headers },
|
|
38
85
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
39
86
|
});
|
|
40
87
|
const text = await res.text();
|
|
@@ -79,7 +126,7 @@ export class DidisClient {
|
|
|
79
126
|
/** Live resolution over server-sent events. */
|
|
80
127
|
async *stream(did, opts = { noCache: true }) {
|
|
81
128
|
const res = await this.fetchImpl(`${this.baseUrl}/v1/stream/${enc(did)}${opts.noCache ? "?noCache=true" : ""}`, {
|
|
82
|
-
headers: { accept: "text/event-stream" },
|
|
129
|
+
headers: { ...SDK_HEADERS, accept: "text/event-stream" },
|
|
83
130
|
});
|
|
84
131
|
if (!res.ok || !res.body)
|
|
85
132
|
throw new DidisError(res.status, { type: "about:blank", title: `HTTP ${res.status}`, detail: "stream failed" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@didis/client",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Official TypeScript/JavaScript SDK for the DID.is public API: DID resolution with evidence, credentials, policies and agent trust",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|