@metalabel/dfos-client 0.29.1 → 0.31.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/README.md +22 -4
- package/dist/siwd.d.ts +149 -3
- package/dist/siwd.js +75 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -6,8 +6,6 @@ If verification logic appears in this package, that is the bug: every proof come
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
> **Not yet published — pre-release.** This package is `private` until it ships with a stamped release; until then it is consumable only inside this workspace.
|
|
10
|
-
|
|
11
9
|
```bash
|
|
12
10
|
npm install @metalabel/dfos-client @metalabel/dfos-protocol @metalabel/dfos-web-relay
|
|
13
11
|
```
|
|
@@ -78,10 +76,30 @@ import { indexedDbStore, memoryStore } from '@metalabel/dfos-client/store';
|
|
|
78
76
|
### `@metalabel/dfos-client/siwd`
|
|
79
77
|
|
|
80
78
|
```typescript
|
|
81
|
-
import {
|
|
79
|
+
import {
|
|
80
|
+
createSiwdLoginRequest,
|
|
81
|
+
readSiwdCallback,
|
|
82
|
+
siwdSigningInput,
|
|
83
|
+
verifySiwd,
|
|
84
|
+
} from '@metalabel/dfos-client/siwd';
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Sign In With DFOS. The three verbs above are the relying-party login kit, in the order a login uses them: `createSiwdLoginRequest` mints the challenge and builds the `/authorize` URL to redirect to, `readSiwdCallback` parses what comes back, and `verifySiwd` verifies it — mint → redirect, read → verify. The `expect` object `createSiwdLoginRequest` returns (nonce, domain, and the DID when the challenge is bound to one) is what `verifySiwd` checks against, so the relying party MUST persist it across the redirect: a verifier that takes its expectation from the callback has implemented the check and none of the protection. See [`examples/siwd-demo`](../../examples/siwd-demo) for the reference consumer.
|
|
88
|
+
|
|
89
|
+
In production, spend the nonce instead of comparing it:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
await verifySiwd(client, jws, {
|
|
93
|
+
domain,
|
|
94
|
+
consumeNonce: async (nonce) => (await store.getdel(nonce)) !== null,
|
|
95
|
+
});
|
|
82
96
|
```
|
|
83
97
|
|
|
84
|
-
|
|
98
|
+
`consumeNonce` replaces `expect.nonce` (supply exactly one) and returns true iff **this** verifier minted the nonce and it was unspent — membership in verifier-minted state is what satisfies the spec's rule that the verifier MUST have minted the nonce it checks, and deleting it in the same operation is what makes it single-use. The atomicity is the caller's: a get-then-delete lets two concurrent replays both win, where a Redis `GETDEL` or a `DELETE … RETURNING` does not. It is called at most once, and only after every other check has passed, so an invalid presentation can never burn a nonce the user is still holding.
|
|
99
|
+
|
|
100
|
+
`createSiwdLoginRequest` throws rather than returning an error on the two things that are RP misconfiguration: an `authorizeUrl` or `redirectUri` that is not an absolute URL, and any scope other than `identity` over a loopback redirect (a local port holds no `client_did` for a credential to be issued to — see [SIWD.md](../../specs/SIWD.md)).
|
|
101
|
+
|
|
102
|
+
`siwdSigningInput(challenge)` is the pure byte contract both the signer and the verifier share (see [SIWD.md](../../specs/SIWD.md)); `createSiwdChallenge` mints a challenge on its own for a caller building its own redirect; `verifySiwd` is a no-throw verifier that accepts only a current `authKeys` entry of a non-deleted identity.
|
|
85
103
|
|
|
86
104
|
## License
|
|
87
105
|
|
package/dist/siwd.d.ts
CHANGED
|
@@ -67,6 +67,134 @@ declare const createSiwdChallenge: (input: CreateChallengeInput) => {
|
|
|
67
67
|
};
|
|
68
68
|
/** Decode a base64url `challenge` query-param body back into a challenge object. */
|
|
69
69
|
declare const decodeSiwdChallenge: (encoded: string) => SiwdChallenge;
|
|
70
|
+
interface SiwdLoginRequestInput {
|
|
71
|
+
/** The host's authorize endpoint, e.g. `https://app.example.com/authorize`. */
|
|
72
|
+
authorizeUrl: string;
|
|
73
|
+
/** The RP's own domain — bound into the signed challenge as a bare hostname. */
|
|
74
|
+
domain: string;
|
|
75
|
+
/** Exact redirect target; must match the RP's registered or served allowlist. */
|
|
76
|
+
redirectUri: string;
|
|
77
|
+
/** Requested scope. `identity` is the only scope implemented today. */
|
|
78
|
+
scope: string;
|
|
79
|
+
/** Consent-screen prose. A host MAY decline to render it; see specs/SIWD.md. */
|
|
80
|
+
statement?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Bind the challenge to ONE identity — "sign in as this DID, or not at all".
|
|
83
|
+
* Threaded into the signed bytes AND into `expect`, because binding a
|
|
84
|
+
* challenge without verifying the binding proves nothing: a host that ignored
|
|
85
|
+
* it would return a signature from whoever was logged in, and an RP checking
|
|
86
|
+
* only the signature would accept it.
|
|
87
|
+
*/
|
|
88
|
+
did?: string;
|
|
89
|
+
/** The RP's own DID. Omitted from the URL automatically for loopback redirects. */
|
|
90
|
+
clientDid?: string;
|
|
91
|
+
/** Supply a nonce minted elsewhere (e.g. by your backend); default: minted here. */
|
|
92
|
+
nonce?: string;
|
|
93
|
+
}
|
|
94
|
+
interface SiwdLoginRequest {
|
|
95
|
+
/** Navigate the browser here, or hand it to the user. */
|
|
96
|
+
url: string;
|
|
97
|
+
/**
|
|
98
|
+
* THE THING TO PERSIST ACROSS THE REDIRECT. One JSON-serializable object that
|
|
99
|
+
* satisfies `SiwdExpectations`, so the whole round trip is: store this before
|
|
100
|
+
* navigating, rehydrate it when the callback lands, and hand it straight to
|
|
101
|
+
* `verifySiwd(client, jws, saved)`. Nothing else has to survive the hop, and
|
|
102
|
+
* nothing has to be threaded to both ends by hand — which is the point, since
|
|
103
|
+
* a `domain` or `did` that drifts between mint and verify is a check that
|
|
104
|
+
* silently stops checking.
|
|
105
|
+
*
|
|
106
|
+
* SINGLE USE: consume the nonce on the way back, pass or fail. A backend
|
|
107
|
+
* holding its minted nonces in shared state should hand `verifySiwd` a
|
|
108
|
+
* `consumeNonce` instead of this object's `nonce`, so the consumption is
|
|
109
|
+
* atomic and is the last thing that happens before a session is granted.
|
|
110
|
+
*
|
|
111
|
+
* WHOEVER VERIFIES MUST HAVE MINTED. A verifier that accepts an expectation
|
|
112
|
+
* supplied by the party presenting the JWS is comparing a value against
|
|
113
|
+
* itself and has verified nothing — the replay guard binds only when the
|
|
114
|
+
* expectation comes from the verifier's own prior state (this object, held
|
|
115
|
+
* server-side or in the session that began the sign-in) or from an
|
|
116
|
+
* independent validation of it.
|
|
117
|
+
*
|
|
118
|
+
* `nonce` is REQUIRED here even though `SiwdExpectations` leaves it optional
|
|
119
|
+
* for the `consumeNonce` form: what this function mints into the challenge is
|
|
120
|
+
* always a string, and an RP persisting this object must get it back.
|
|
121
|
+
*/
|
|
122
|
+
expect: Pick<SiwdExpectations, 'domain' | 'did'> & {
|
|
123
|
+
nonce: string;
|
|
124
|
+
};
|
|
125
|
+
/** base64url canonical challenge bytes, exactly as embedded in `url`. */
|
|
126
|
+
challenge: string;
|
|
127
|
+
/** ISO whole-second mint timestamp, exactly as embedded in the signed bytes. */
|
|
128
|
+
timestamp: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Mint a challenge and build the `/authorize` URL to send the browser to — the
|
|
132
|
+
* OUTBOUND half of profile A. `readSiwdCallback` is the inbound half, and
|
|
133
|
+
* `verifySiwd` is what both compose around: mint → redirect, read → verify.
|
|
134
|
+
*
|
|
135
|
+
* The rule this function exists to own is the LOOPBACK OMISSION. Nothing can
|
|
136
|
+
* prove a client DID for an app on a local port — there is no domain serving a
|
|
137
|
+
* well-known and no registration to check — so a host REFUSES a `client_did`
|
|
138
|
+
* on a loopback redirect rather than displaying an identity it cannot stand
|
|
139
|
+
* behind. A CLI that passed its own DID would have the whole request rejected,
|
|
140
|
+
* not downgraded, so the param is dropped here instead of being forwarded into
|
|
141
|
+
* a guaranteed refusal.
|
|
142
|
+
*
|
|
143
|
+
* The same judgment BOUNDS THE SCOPE. Every scope past `identity` returns a
|
|
144
|
+
* credential issued to a `client_did` — the one param a loopback request cannot
|
|
145
|
+
* carry — so specs/SIWD.md admits a loopback target for `scope=identity` only.
|
|
146
|
+
* Asking for more from a local port is not a downgrade the way `client_did` is;
|
|
147
|
+
* there is nothing to drop, so it throws.
|
|
148
|
+
*
|
|
149
|
+
* It also owns the WIRE PARAM NAMES (`challenge`, `redirect_uri`, `scope`,
|
|
150
|
+
* `client_did`) as their single source in this package. They are snake_case on
|
|
151
|
+
* the wire and camelCase everywhere else, which is exactly the kind of seam
|
|
152
|
+
* every hand-rolled RP re-implements and eventually gets wrong.
|
|
153
|
+
*
|
|
154
|
+
* PURE: no DOM, no storage, no navigation, no fetch — identical in a browser
|
|
155
|
+
* and in Node. Throws on an unparseable `authorizeUrl` or `redirectUri`, and on
|
|
156
|
+
* a non-`identity` scope over a loopback redirect, because those are mistakes
|
|
157
|
+
* in the RP's own configuration rather than runtime conditions a result type
|
|
158
|
+
* would help a caller recover from.
|
|
159
|
+
*/
|
|
160
|
+
declare const createSiwdLoginRequest: (input: SiwdLoginRequestInput) => SiwdLoginRequest;
|
|
161
|
+
/**
|
|
162
|
+
* What came back on the redirect. `none` means this was a plain page load, not
|
|
163
|
+
* a callback at all — the common case on an RP's own landing page.
|
|
164
|
+
*/
|
|
165
|
+
type SiwdCallbackResult = {
|
|
166
|
+
kind: 'success';
|
|
167
|
+
jws: string;
|
|
168
|
+
did: string;
|
|
169
|
+
} | {
|
|
170
|
+
kind: 'denied';
|
|
171
|
+
error: string;
|
|
172
|
+
} | {
|
|
173
|
+
kind: 'none';
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Read a profile-A callback — the INBOUND half of the pair. Pure parse: it
|
|
177
|
+
* decides only what kind of return this is, and hands `jws` to the caller for
|
|
178
|
+
* `verifySiwd`. NOTHING here is trusted; the `did` param is unauthenticated
|
|
179
|
+
* courier convenience, and the DID a caller should act on is the one
|
|
180
|
+
* `verifySiwd` returns from the signature.
|
|
181
|
+
*
|
|
182
|
+
* Takes an absolute URL string, a `URL`, or a bare `?…` query string (so
|
|
183
|
+
* `readSiwdCallback(location.search)` works, including when it is empty).
|
|
184
|
+
*
|
|
185
|
+
* SCRUB THE URL YOURSELF, IMMEDIATELY. A signed JWS is sitting in the query
|
|
186
|
+
* string, which means it is in the address bar, in `history`, in the referrer
|
|
187
|
+
* of anything the page loads next, and in any analytics that samples the
|
|
188
|
+
* location. This function cannot do the scrubbing for you — `history` is
|
|
189
|
+
* environment-owned and this package stays free of the DOM — so a browser RP
|
|
190
|
+
* should follow the read with a `history.replaceState` back to the bare path.
|
|
191
|
+
*
|
|
192
|
+
* A HALF-CALLBACK IS A FAILURE, NOT A NON-EVENT: `jws` without `did` (or the
|
|
193
|
+
* reverse) resolves to `denied` carrying a synthetic reason rather than `none`.
|
|
194
|
+
* Silently treating it as a plain page load would strand the user on a
|
|
195
|
+
* sign-in button with no explanation of why the last attempt vanished.
|
|
196
|
+
*/
|
|
197
|
+
declare const readSiwdCallback: (url: string | URL) => SiwdCallbackResult;
|
|
70
198
|
interface BuildSiwdSignRequestInput {
|
|
71
199
|
/** Requester DID that signs the courier envelope. */
|
|
72
200
|
did: string;
|
|
@@ -102,8 +230,26 @@ declare const validateSiwdSignRequest: (jwsToken: string, options: ValidateSiwdS
|
|
|
102
230
|
interface SiwdExpectations {
|
|
103
231
|
/** The verifier's own origin — MUST match the challenge domain. */
|
|
104
232
|
domain: string;
|
|
105
|
-
/**
|
|
106
|
-
|
|
233
|
+
/**
|
|
234
|
+
* The nonce this verifier issued for the session. Supply EXACTLY ONE of
|
|
235
|
+
* `nonce` and `consumeNonce`.
|
|
236
|
+
*/
|
|
237
|
+
nonce?: string;
|
|
238
|
+
/**
|
|
239
|
+
* Atomically consume the presented nonce against verifier-minted state — a
|
|
240
|
+
* Redis `GETDEL`, a `DELETE … RETURNING` row — returning true iff THIS
|
|
241
|
+
* verifier minted it and it was unspent. Membership in the verifier's own
|
|
242
|
+
* minted state is what proves the verifier minted it, which is the whole
|
|
243
|
+
* check; an equality test against a `nonce` fed in from the presented
|
|
244
|
+
* artifact is a value compared against itself. The ATOMICITY is yours: a
|
|
245
|
+
* get-then-delete lets two concurrent replays both win.
|
|
246
|
+
*
|
|
247
|
+
* Called AT MOST ONCE, and only after every other check has passed —
|
|
248
|
+
* signature, current-key resolution, did binding, domain, timestamp — so an
|
|
249
|
+
* otherwise-invalid presentation can never burn a live nonce, and the
|
|
250
|
+
* consumption is the last gate before the caller grants anything.
|
|
251
|
+
*/
|
|
252
|
+
consumeNonce?: (nonce: string) => boolean | Promise<boolean>;
|
|
107
253
|
/** If set, the challenge (and identity) MUST bind to this DID. */
|
|
108
254
|
did?: string;
|
|
109
255
|
/** If set, the signed challenge's timestamp MUST equal this exact value. */
|
|
@@ -127,4 +273,4 @@ interface SiwdExpectations {
|
|
|
127
273
|
*/
|
|
128
274
|
declare const verifySiwd: (client: Client, jws: string, expect: SiwdExpectations) => Promise<VerifyResult<SiwdSession>>;
|
|
129
275
|
|
|
130
|
-
export { type BuildSiwdSignRequestInput, type CreateChallengeInput, SIWD_JWS_TYP, type SiwdChallenge, type SiwdExpectations, type SiwdSession, type ValidateSiwdSignRequestOptions, type ValidatedSiwdSignRequest, buildSiwdSignRequest, createSiwdChallenge, decodeSiwdChallenge, parseSiwdChallenge, siwdSigningInput, validateSiwdSignRequest, verifySiwd };
|
|
276
|
+
export { type BuildSiwdSignRequestInput, type CreateChallengeInput, SIWD_JWS_TYP, type SiwdCallbackResult, type SiwdChallenge, type SiwdExpectations, type SiwdLoginRequest, type SiwdLoginRequestInput, type SiwdSession, type ValidateSiwdSignRequestOptions, type ValidatedSiwdSignRequest, buildSiwdSignRequest, createSiwdChallenge, createSiwdLoginRequest, decodeSiwdChallenge, parseSiwdChallenge, readSiwdCallback, siwdSigningInput, validateSiwdSignRequest, verifySiwd };
|
package/dist/siwd.js
CHANGED
|
@@ -109,6 +109,69 @@ var createSiwdChallenge = (input) => {
|
|
|
109
109
|
return { challenge, encoded, nonce };
|
|
110
110
|
};
|
|
111
111
|
var decodeSiwdChallenge = (encoded) => parseSiwdChallenge(base64urlDecode(encoded));
|
|
112
|
+
var SIWD_LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
113
|
+
var bareHostname = (url) => {
|
|
114
|
+
const host = url.hostname.toLowerCase();
|
|
115
|
+
return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
116
|
+
};
|
|
117
|
+
var parseUrlOrThrow = (value, field) => {
|
|
118
|
+
try {
|
|
119
|
+
return new URL(value);
|
|
120
|
+
} catch {
|
|
121
|
+
throw new Error(`invalid SIWD login request: ${field} must be an absolute URL`);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var createSiwdLoginRequest = (input) => {
|
|
125
|
+
const authorizeUrl = parseUrlOrThrow(input.authorizeUrl, "authorizeUrl");
|
|
126
|
+
const redirect = parseUrlOrThrow(input.redirectUri, "redirectUri");
|
|
127
|
+
const isLoopback = SIWD_LOOPBACK_HOSTS.has(bareHostname(redirect));
|
|
128
|
+
if (isLoopback && input.scope !== "identity") {
|
|
129
|
+
throw new Error("invalid SIWD login request: loopback redirects support scope=identity only");
|
|
130
|
+
}
|
|
131
|
+
const { challenge, encoded, nonce } = createSiwdChallenge({
|
|
132
|
+
domain: input.domain,
|
|
133
|
+
...input.statement !== void 0 ? { statement: input.statement } : {},
|
|
134
|
+
...input.did !== void 0 ? { did: input.did } : {},
|
|
135
|
+
...input.nonce !== void 0 ? { nonce: input.nonce } : {}
|
|
136
|
+
});
|
|
137
|
+
const url = new URL(authorizeUrl);
|
|
138
|
+
url.searchParams.set("challenge", encoded);
|
|
139
|
+
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
140
|
+
url.searchParams.set("scope", input.scope);
|
|
141
|
+
if (input.clientDid !== void 0 && !isLoopback) {
|
|
142
|
+
url.searchParams.set("client_did", input.clientDid);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
url: url.toString(),
|
|
146
|
+
expect: {
|
|
147
|
+
domain: input.domain,
|
|
148
|
+
nonce,
|
|
149
|
+
...input.did !== void 0 ? { did: input.did } : {}
|
|
150
|
+
},
|
|
151
|
+
challenge: encoded,
|
|
152
|
+
timestamp: challenge.timestamp
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
var callbackParam = (params, key) => {
|
|
156
|
+
const value = params.get(key);
|
|
157
|
+
return value === null || value === "" ? void 0 : value;
|
|
158
|
+
};
|
|
159
|
+
var callbackParams = (url) => {
|
|
160
|
+
if (typeof url !== "string") return url.searchParams;
|
|
161
|
+
if (url === "" || url.startsWith("?")) return new URLSearchParams(url);
|
|
162
|
+
return parseUrlOrThrow(url, "url").searchParams;
|
|
163
|
+
};
|
|
164
|
+
var readSiwdCallback = (url) => {
|
|
165
|
+
const params = callbackParams(url);
|
|
166
|
+
const jws = callbackParam(params, "jws");
|
|
167
|
+
const did = callbackParam(params, "did");
|
|
168
|
+
const error = callbackParam(params, "error");
|
|
169
|
+
if (jws !== void 0 && did !== void 0) return { kind: "success", jws, did };
|
|
170
|
+
if (error !== void 0) return { kind: "denied", error };
|
|
171
|
+
if (jws !== void 0) return { kind: "denied", error: "malformed SIWD callback: missing did" };
|
|
172
|
+
if (did !== void 0) return { kind: "denied", error: "malformed SIWD callback: missing jws" };
|
|
173
|
+
return { kind: "none" };
|
|
174
|
+
};
|
|
112
175
|
var assertSiwdAcceptanceWindow = (seconds) => {
|
|
113
176
|
if (!Number.isSafeInteger(seconds) || seconds <= 0) {
|
|
114
177
|
throw new Error("SIWD acceptanceWindowSeconds must be a positive integer");
|
|
@@ -174,6 +237,9 @@ var validateSiwdSignRequest = async (jwsToken, options) => {
|
|
|
174
237
|
var fail = (error) => ({ ok: false, error });
|
|
175
238
|
var verifySiwd = async (client, jws, expect) => {
|
|
176
239
|
try {
|
|
240
|
+
if (expect.nonce === void 0 === (expect.consumeNonce === void 0)) {
|
|
241
|
+
return fail("provide exactly one of nonce or consumeNonce");
|
|
242
|
+
}
|
|
177
243
|
const decoded = decodeJwsUnsafe(jws);
|
|
178
244
|
if (!decoded) return fail("failed to decode JWS");
|
|
179
245
|
const rawHeader = decoded.header;
|
|
@@ -216,7 +282,6 @@ var verifySiwd = async (client, jws, expect) => {
|
|
|
216
282
|
} catch (err) {
|
|
217
283
|
return fail(err instanceof Error ? err.message : "invalid signature");
|
|
218
284
|
}
|
|
219
|
-
if (payload.nonce !== expect.nonce) return fail("nonce mismatch");
|
|
220
285
|
if (payload.domain !== expect.domain) return fail("domain mismatch");
|
|
221
286
|
if (expect.timestamp !== void 0 && payload.timestamp !== expect.timestamp) {
|
|
222
287
|
return fail("timestamp does not match expected challenge timestamp");
|
|
@@ -229,6 +294,13 @@ var verifySiwd = async (client, jws, expect) => {
|
|
|
229
294
|
if (issuedMs - nowMs > MAX_CLOCK_SKEW_SECONDS * 1e3) {
|
|
230
295
|
return fail("challenge timestamp is in the future");
|
|
231
296
|
}
|
|
297
|
+
if (expect.consumeNonce !== void 0) {
|
|
298
|
+
if (!await expect.consumeNonce(payload.nonce)) {
|
|
299
|
+
return fail("nonce already used or not recognized");
|
|
300
|
+
}
|
|
301
|
+
} else if (payload.nonce !== expect.nonce) {
|
|
302
|
+
return fail("nonce mismatch");
|
|
303
|
+
}
|
|
232
304
|
const session = {
|
|
233
305
|
did,
|
|
234
306
|
domain: payload.domain,
|
|
@@ -252,8 +324,10 @@ export {
|
|
|
252
324
|
SIWD_JWS_TYP,
|
|
253
325
|
buildSiwdSignRequest,
|
|
254
326
|
createSiwdChallenge,
|
|
327
|
+
createSiwdLoginRequest,
|
|
255
328
|
decodeSiwdChallenge,
|
|
256
329
|
parseSiwdChallenge,
|
|
330
|
+
readSiwdCallback,
|
|
257
331
|
siwdSigningInput,
|
|
258
332
|
validateSiwdSignRequest,
|
|
259
333
|
verifySiwd
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metalabel/dfos-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DFOS Client — read-only resolve + verify orchestration over untrusted relays. Fetch, resolve, verify-orchestration, cache; all crypto truth comes from @metalabel/dfos-protocol",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@metalabel/dfos-protocol": "^0.
|
|
47
|
-
"@metalabel/dfos-web-relay": "^0.
|
|
46
|
+
"@metalabel/dfos-protocol": "^0.31.0",
|
|
47
|
+
"@metalabel/dfos-web-relay": "^0.31.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^24.10.4",
|
|
51
51
|
"tsup": "^8.5.1",
|
|
52
52
|
"vitest": "^4.1.8",
|
|
53
|
-
"@metalabel/dfos-protocol": "0.
|
|
54
|
-
"@metalabel/dfos-web-relay": "0.
|
|
53
|
+
"@metalabel/dfos-protocol": "0.31.0",
|
|
54
|
+
"@metalabel/dfos-web-relay": "0.31.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsup",
|