signet-verify 0.7.1 → 0.7.3
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 +119 -0
- package/README.md +225 -51
- package/dist/signet-verify.d.ts +3 -2
- package/dist/signet-verify.js +1 -1
- package/llms.txt +66 -16
- package/package.json +6 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.7.3 (2026-10-18)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- close the re-audit's medium-severity doc gaps (#51) (docs)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## 0.7.2 (2026-10-16)
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
- correct the published docs against the source (#49) (docs)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## 0.7.1 (2026-09-25)
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
- default cross-device relay to nos.lol (#46)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## 0.7.0 (2026-09-11)
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
- default to the Signet relay and export it as DEFAULT_RELAY_URL (#44) (verify)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## 0.6.1 (2026-09-11)
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
- fail with relay-refused when the relay closes the subscription (#43) (auth)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## 0.6.0 (2026-09-11)
|
|
44
|
+
|
|
45
|
+
### Features
|
|
46
|
+
|
|
47
|
+
- one anchor for the sign-in deadline, and tell the user about it (#42) (auth)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## 0.5.2 (2026-09-10)
|
|
52
|
+
|
|
53
|
+
### Bug Fixes
|
|
54
|
+
|
|
55
|
+
- let a restarted wait keep its original subscription window (#41) (auth)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## 0.5.1 (2026-09-08)
|
|
60
|
+
|
|
61
|
+
### Bug Fixes
|
|
62
|
+
|
|
63
|
+
- replay auth responses after mobile app switching (#39)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## 0.5.0 (2026-06-05)
|
|
68
|
+
|
|
69
|
+
### Features
|
|
70
|
+
|
|
71
|
+
- surface bunkerUri from the auth response (cross-device signer upgrade)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## 0.3.1 (2026-05-03)
|
|
76
|
+
|
|
77
|
+
### Bug Fixes
|
|
78
|
+
|
|
79
|
+
- bump to publish 0.3.0 displayName feature to npm (#11) (release)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## 0.3.0 (2026-05-03)
|
|
84
|
+
|
|
85
|
+
### Features
|
|
86
|
+
|
|
87
|
+
- surface displayName from AuthResponse, sanitised (#9) (auth)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## 0.2.2 (2026-05-03)
|
|
92
|
+
|
|
93
|
+
### Features
|
|
94
|
+
|
|
95
|
+
- `SignetAuthResult.displayName?: string` — surfaces the user's persona handle when they opted to share it on the approval screen. Mirrors the `display_name` URL param delivered by the redirect-back flow. Sanitised on receipt: control + bidi characters stripped, capped at 64 chars, empty values dropped.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## 0.2.1 (2026-04-18)
|
|
100
|
+
|
|
101
|
+
### Bug Fixes
|
|
102
|
+
|
|
103
|
+
- use published signet-protocol@^1.6.0, not file:../signet (deps)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## 0.2.0 (2026-04-18)
|
|
108
|
+
|
|
109
|
+
### Features
|
|
110
|
+
|
|
111
|
+
- reshape signet-verify as a proper ESM npm package
|
|
112
|
+
- waitForAuthResponse SDK helper for cross-device login
|
|
113
|
+
- initial release of signet-verify — drop-in age verification SDK
|
|
114
|
+
|
|
115
|
+
### Bug Fixes
|
|
116
|
+
|
|
117
|
+
- bump esbuild to ^0.25.12 (GHSA-67mh-4wv8-2f99) (deps)
|
|
118
|
+
|
|
119
|
+
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Add privacy-preserving age verification to any website. One script tag, one function call. No personal data collected.
|
|
4
4
|
|
|
5
|
+
`verifyAge` is the listening half of a same-browser exchange: it opens a modal and waits for a signed response message from a sender in the same origin. This package does not include that sender, and `verifyAge` does not hand the page the request ID the sender needs. See [What posts the response](#what-posts-the-response).
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -10,8 +12,11 @@ npm install signet-verify
|
|
|
10
12
|
|
|
11
13
|
## Quick Start
|
|
12
14
|
|
|
15
|
+
Script tag (the IIFE bundle exposes `window.Signet`; the URL pins a version, so change the version number in it to upgrade):
|
|
16
|
+
|
|
13
17
|
```html
|
|
14
|
-
<
|
|
18
|
+
<button id="verify-btn">Verify your age</button>
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/signet-verify@0.7.2/dist/signet-verify.iife.js"></script>
|
|
15
20
|
<script>
|
|
16
21
|
document.getElementById('verify-btn').addEventListener('click', async () => {
|
|
17
22
|
const result = await Signet.verifyAge('18+');
|
|
@@ -23,17 +28,69 @@ npm install signet-verify
|
|
|
23
28
|
}
|
|
24
29
|
});
|
|
25
30
|
</script>
|
|
26
|
-
<button id="verify-btn">Verify your age</button>
|
|
27
31
|
```
|
|
28
32
|
|
|
33
|
+
ESM (bundler, browser only, since `verifyAge` opens a modal):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { verifyAge } from 'signet-verify';
|
|
37
|
+
|
|
38
|
+
const result = await verifyAge('18+');
|
|
39
|
+
if (!result.verified) console.log('Not verified:', result.error);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Both samples show the call and the result handling. `result.verified` becomes `true` only after a same-origin sender posts a valid `signet-verify-response` message for that call (see [The response message](#the-response-message)). Without one, the modal keeps waiting and the call resolves with `error: 'timeout'` after 120 seconds (the default `timeout`), or `error: 'cancelled'` if the user closes the modal.
|
|
43
|
+
|
|
44
|
+
### Node, SSR and tests
|
|
45
|
+
|
|
46
|
+
The package is ESM only and has no CommonJS build. Importing it in Node, during server-side rendering or in a test runner is safe: loading it touches no browser global, and it sets `window.Signet` only where a `window` exists. It needs Node 20.19 or later, the floor set by the `engines` fields of its `@noble/curves` and `@noble/hashes` dependencies.
|
|
47
|
+
|
|
48
|
+
- `verifyAge` is browser only. It needs `document`, `window.matchMedia`, `BroadcastChannel` and the `<dialog>` element. In Node it rejects with `ReferenceError: document is not defined` rather than resolving with `result.error`. The one exception is an invalid `requiredAgeRange`, which resolves with `error: 'invalid-age-range'` before touching the DOM. Call it from client-side code only, and stub it or use a real browser in tests.
|
|
49
|
+
- `waitForAuthResponse` needs no DOM, only a global `WebSocket`. Node 22 and later has one. Where there is none (Node 20, for example) the call rejects with `relay-error`. Outside a browser it never reconnects: the first socket error or close rejects with `relay-error` or `relay-closed`.
|
|
50
|
+
|
|
29
51
|
## How It Works
|
|
30
52
|
|
|
31
53
|
1. Website calls `Signet.verifyAge('18+')`
|
|
32
|
-
2. A modal
|
|
33
|
-
3.
|
|
34
|
-
4. The
|
|
35
|
-
5.
|
|
36
|
-
|
|
54
|
+
2. A modal opens. The SDK builds a `signet-verify-request` (request ID, required age range, `relayUrl`, timestamp) but the modal shows only a truncated text placeholder, `signet:verify:` plus the first 40 characters of the base64 payload, not a rendered QR code. Those 40 characters stop before the request ID and the relay URL
|
|
55
|
+
3. The modal listens on a `BroadcastChannel` named `signet-verify-<requestId>`. It opens no WebSocket and never reads from a relay. The proof must arrive as a `signet-verify-response` message carrying that request ID on that channel, so whatever posts it has to run in the same origin and browser profile as the page. Nothing in this package posts it (see below)
|
|
56
|
+
4. The SDK checks the credential's signature, expiry and age range, then asks the verification bot about the verifier's professional registration (skipped when `verifierCheckUrl` is `null`)
|
|
57
|
+
5. Returns the result
|
|
58
|
+
|
|
59
|
+
To receive a response from another device over a relay, use `waitForAuthResponse` (see below); `verifyAge` does not do that.
|
|
60
|
+
|
|
61
|
+
### What posts the response
|
|
62
|
+
|
|
63
|
+
Nothing in this package does. `signet-verify` contains the listener only, and the sender is code you provide, running in the same origin and browser profile as the page that called `verifyAge`.
|
|
64
|
+
|
|
65
|
+
The sender needs the channel name, which contains the request ID. `verifyAge` generates that ID internally and does not expose it: it is not returned, not passed to a callback and not dispatched as an event, and the modal shows only the first 40 characters of the base64 request, which stop before the ID. The package therefore offers no supported way for the page, or for a Signet app on the user's phone, to learn it. Until you have wired up a sender that can, a real user cannot reach `verified: true` through `verifyAge`.
|
|
66
|
+
|
|
67
|
+
### The response message
|
|
68
|
+
|
|
69
|
+
The listener accepts one object on the channel `signet-verify-<requestId>`, where `<requestId>` is the 32-character hex ID (16 random bytes) generated for that call. The shape is `VerifyResponse` from the `signet-protocol` package, which is the canonical type. Install `signet-protocol` only if you want that type for your sender: `signet-verify` does not depend on it at runtime, and its published type declarations do not reference it.
|
|
70
|
+
|
|
71
|
+
The shape:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
{
|
|
75
|
+
type: 'signet-verify-response',
|
|
76
|
+
requestId: string, // must equal the ID of the open modal
|
|
77
|
+
credential: { // a signed Nostr event
|
|
78
|
+
id: string, // 64 hex chars: SHA-256 of JSON [0, pubkey, created_at, kind, tags, content]
|
|
79
|
+
kind: 30470, // anything else is rejected
|
|
80
|
+
pubkey: string, // 64 hex chars: the verifier's key, returned as verifierPubkey
|
|
81
|
+
created_at: number, // unix seconds, returned as issuedAt
|
|
82
|
+
tags: string[][], // must include 'tier', 'age-range' and 'entity-type'; optional 'expires' (unix seconds)
|
|
83
|
+
content: string, // at most 65536 characters
|
|
84
|
+
sig: string, // 128 hex chars: BIP-340 Schnorr signature over id by pubkey
|
|
85
|
+
},
|
|
86
|
+
subjectPubkey: string, // part of the protocol type; verifyAge does not read it
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
How the listener treats a message:
|
|
91
|
+
|
|
92
|
+
- It silently drops anything that is not an object, whose `type` or `requestId` does not match, or whose `credential` is missing or has no `tags` array. The modal keeps waiting.
|
|
93
|
+
- Any other message closes the modal and settles the call, so the first one wins and later messages are ignored. The credential is then checked: a failed structural or signature check (including more than 100 tags, a tag value over 1024 characters, or a missing `tier`, `age-range` or `entity-type` tag) gives `invalid-credential`, a past `expires` gives `credential-expired`, and an `age-range` tag that is not equal to the required range gives `age-range-not-met`. The verifier check then follows, as described under Verifier Trust.
|
|
37
94
|
|
|
38
95
|
## Verifier Trust
|
|
39
96
|
|
|
@@ -44,71 +101,119 @@ By default, the SDK checks the verifier's professional registration against publ
|
|
|
44
101
|
- **Decentralised:** the verification bot is open source — anyone can run their own
|
|
45
102
|
|
|
46
103
|
```javascript
|
|
47
|
-
// Default: safe
|
|
48
|
-
const
|
|
104
|
+
// Default: safe, only confirmed verifiers pass
|
|
105
|
+
const strict = await Signet.verifyAge('18+');
|
|
49
106
|
|
|
50
107
|
// Accept unconfirmed verifiers (less safe, more permissive)
|
|
51
|
-
const
|
|
108
|
+
const lenient = await Signet.verifyAge('18+', { acceptUnconfirmed: true });
|
|
52
109
|
|
|
53
110
|
// Use your own verification bot
|
|
54
|
-
const
|
|
111
|
+
const ownBot = await Signet.verifyAge('18+', { verifierCheckUrl: 'https://my-bot.example.com' });
|
|
55
112
|
|
|
56
|
-
// Skip verifier
|
|
57
|
-
|
|
113
|
+
// Skip the verifier check. On its own this never verifies: the result is
|
|
114
|
+
// `verified: false` with `error: 'verifier-check-unavailable'`.
|
|
115
|
+
// To accept the credential without a check, also set acceptUnconfirmed.
|
|
116
|
+
const unchecked = await Signet.verifyAge('18+', { verifierCheckUrl: null, acceptUnconfirmed: true });
|
|
58
117
|
```
|
|
59
118
|
|
|
119
|
+
### What `verified` is when the check is skipped or fails
|
|
120
|
+
|
|
121
|
+
The credential must first be valid, unexpired and in the required age range. The table then shows how the verifier check decides `verified`:
|
|
122
|
+
|
|
123
|
+
| Verifier check | `verifierConfirmed` | Default (`acceptUnconfirmed: false`) | `acceptUnconfirmed: true` |
|
|
124
|
+
|---|---|---|---|
|
|
125
|
+
| Bot says confirmed | `true` | `verified: true` | `verified: true` |
|
|
126
|
+
| Bot says not confirmed | `false` | `verified: false`, `verifier-not-confirmed` | `verified: true`, no error |
|
|
127
|
+
| Skipped or failed | `null` | `verified: false`, `verifier-check-unavailable` | `verified: true`, no error |
|
|
128
|
+
|
|
129
|
+
The check counts as skipped when `verifierCheckUrl` is `null`, and as failed when the URL does not start with `https://`, the bot is unreachable or does not answer within 5 seconds, it returns a non-2xx status, or the body is not JSON. With `acceptUnconfirmed: true` the bot is still queried (unless `verifierCheckUrl` is `null`), so `verifierConfirmed` is still reported.
|
|
130
|
+
|
|
131
|
+
When the check is skipped, or `acceptUnconfirmed` is `true`, `verified: true` means only that a well-formed credential signed by some key meets the age range. It does not show that the signing key belongs to a registered professional.
|
|
132
|
+
|
|
60
133
|
## Cross-device Sign-in-with-Signet
|
|
61
134
|
|
|
62
|
-
`verifyAge`
|
|
135
|
+
`verifyAge` resolves with a `SignetVerifyResult` and reports failures in `result.error` rather than rejecting. `waitForAuthResponse` resolves with a `SignetAuthResult` on success and rejects with an `Error` whose `message` and `code` are the failure code (see below). Their `timeout` options also differ: for `verifyAge` it is how long the modal waits before resolving with `error: 'timeout'`; for `waitForAuthResponse` it is an optional, earlier give-up.
|
|
136
|
+
|
|
137
|
+
`verifyAge` is the same-browser modal flow: its response arrives on a
|
|
138
|
+
`BroadcastChannel`, never over a relay. For a cross-device sign-in,
|
|
63
139
|
a consumer minting its own challenge, opening the auth URL or QR on one
|
|
64
140
|
device, and waiting on a relay for the response published from the user's
|
|
65
|
-
phone
|
|
141
|
+
phone, use `waitForAuthResponse` directly.
|
|
142
|
+
|
|
143
|
+
The example below imports `@noble/curves` and `@noble/hashes` itself. Both are dependencies of `signet-verify`, but the SDK does not re-export them, so install the ones your code imports (version 2 of each: in `@noble/curves` 2 the key generator is `schnorr.utils.randomSecretKey()`, and `randomPrivateKey()` no longer exists):
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install signet-verify @noble/curves@2 @noble/hashes@2
|
|
147
|
+
```
|
|
66
148
|
|
|
67
149
|
```ts
|
|
68
|
-
import {
|
|
150
|
+
import { schnorr } from '@noble/curves/secp256k1.js';
|
|
151
|
+
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
152
|
+
import { waitForAuthResponse, DEFAULT_RELAY_URL } from 'signet-verify';
|
|
153
|
+
|
|
154
|
+
// 1. Ephemeral session keypair. The private key must be a 32-byte Uint8Array;
|
|
155
|
+
// a hex string is rejected with `invalid-session-privkey`.
|
|
156
|
+
const sessionPrivKey = schnorr.utils.randomSecretKey();
|
|
157
|
+
const sessionPubkey = bytesToHex(schnorr.getPublicKey(sessionPrivKey));
|
|
69
158
|
|
|
70
|
-
//
|
|
159
|
+
// 2. The challenge: 32 random bytes as 64-character hex. This is the `requestId`.
|
|
160
|
+
const requestId = bytesToHex(crypto.getRandomValues(new Uint8Array(32)));
|
|
161
|
+
|
|
162
|
+
// 3. Stamp this ONCE, when you mint the challenge and open the auth URL / show the QR.
|
|
71
163
|
const issuedAt = Math.floor(Date.now() / 1000);
|
|
72
164
|
|
|
165
|
+
// 4. The auth URL (render it as a QR code or a link). The parameters mirror
|
|
166
|
+
// examples/cross-device-login.html; `relay` must match `relayUrl` below. The
|
|
167
|
+
// Signet app README documents them: `origin` is https (or http://localhost),
|
|
168
|
+
// `callback` is on that origin, `t` is within 300 seconds of the phone's
|
|
169
|
+
// clock, and `relay` and `sessionPubkey` go together.
|
|
170
|
+
const signetAppUrl = 'https://mysignet.app'; // the Signet app's production origin (its dev server is https://localhost:5174)
|
|
171
|
+
const authUrl = signetAppUrl + '/?' + new URLSearchParams({
|
|
172
|
+
auth: '1',
|
|
173
|
+
challenge: requestId,
|
|
174
|
+
origin: location.origin,
|
|
175
|
+
name: 'My Site',
|
|
176
|
+
callback: location.origin + '/callback', // unused in relay mode, still required
|
|
177
|
+
t: String(issuedAt),
|
|
178
|
+
relay: DEFAULT_RELAY_URL,
|
|
179
|
+
sessionPubkey,
|
|
180
|
+
}).toString();
|
|
181
|
+
|
|
182
|
+
// 5. Wait for the response. Call controller.abort() to cancel.
|
|
183
|
+
const controller = new AbortController();
|
|
73
184
|
const result = await waitForAuthResponse({
|
|
74
|
-
requestId,
|
|
75
|
-
relayUrl,
|
|
76
|
-
sessionPrivKey,
|
|
185
|
+
requestId,
|
|
186
|
+
relayUrl: DEFAULT_RELAY_URL,
|
|
187
|
+
sessionPrivKey,
|
|
77
188
|
expectedOrigin: location.origin,
|
|
78
|
-
issuedAt, //
|
|
189
|
+
issuedAt, // pass on EVERY call for this sign-in, including retries
|
|
190
|
+
abortSignal: controller.signal,
|
|
79
191
|
});
|
|
80
192
|
```
|
|
81
193
|
|
|
82
194
|
### The sign-in deadline
|
|
83
195
|
|
|
84
196
|
A cross-device sign-in has exactly one deadline: `issuedAt + AUTH_FRESHNESS_WINDOW_SEC`.
|
|
85
|
-
Past that point, this library
|
|
86
|
-
to it.
|
|
197
|
+
`AUTH_FRESHNESS_WINDOW_SEC` is `300` seconds (5 minutes). Past that point, this library
|
|
198
|
+
stops listening and reports `expired`, so show a countdown to it.
|
|
87
199
|
|
|
88
200
|
```ts
|
|
89
|
-
import {
|
|
90
|
-
|
|
91
|
-
// Stamp this ONCE, when you mint the challenge and open the auth URL / show the QR.
|
|
92
|
-
const issuedAt = Math.floor(Date.now() / 1000);
|
|
93
|
-
const expiresAt = issuedAt + AUTH_FRESHNESS_WINDOW_SEC; // show a countdown to this
|
|
201
|
+
import { AUTH_FRESHNESS_WINDOW_SEC } from 'signet-verify';
|
|
94
202
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
relayUrl: DEFAULT_RELAY_URL, // the Signet relay — must match the relay= in your auth URL
|
|
98
|
-
sessionPrivKey,
|
|
99
|
-
expectedOrigin: location.origin,
|
|
100
|
-
issuedAt, // ← pass on EVERY call for this sign-in, retries included
|
|
101
|
-
abortSignal, // ← optional; cancels the wait and closes the subscription
|
|
102
|
-
});
|
|
203
|
+
const expiresAt = issuedAt + AUTH_FRESHNESS_WINDOW_SEC; // unix seconds; count down to this
|
|
204
|
+
const secondsLeft = () => Math.max(0, expiresAt - Math.floor(Date.now() / 1000));
|
|
103
205
|
```
|
|
104
206
|
|
|
207
|
+
The `timeout` option is in milliseconds. Leave it out when you pass `issuedAt`: the
|
|
208
|
+
wait then lasts until the deadline above.
|
|
209
|
+
|
|
105
210
|
**Pass `issuedAt` on every call, including a restart.** On mobile the page is
|
|
106
211
|
suspended the moment it hands off to the signer app. A wait restarted without the
|
|
107
212
|
anchor asks the relay only for responses newer than the restart — which silently
|
|
108
213
|
excludes the one published while you were backgrounded, and it is never asked for
|
|
109
214
|
again. This was a real, 100%-reproducible failure before 0.5.2.
|
|
110
215
|
|
|
111
|
-
**Distinguish the failures.** The rejection message is:
|
|
216
|
+
**Distinguish the failures.** The rejection is an `Error` whose message is one of:
|
|
112
217
|
|
|
113
218
|
| Code | Meaning | What to offer |
|
|
114
219
|
|---|---|---|
|
|
@@ -116,9 +221,11 @@ again. This was a real, 100%-reproducible failure before 0.5.2.
|
|
|
116
221
|
| `timeout` | The wait gave up early — an explicit shorter `timeout`, or no `issuedAt` was supplied at all | A retry, anchored to the same `issuedAt` |
|
|
117
222
|
| `denied` | The user rejected the request | Nothing; respect it |
|
|
118
223
|
| `aborted` | You aborted via `abortSignal` | Nothing |
|
|
119
|
-
| `relay-error` | The socket failed | A retry, anchored to the same `issuedAt` |
|
|
224
|
+
| `relay-error` | The socket failed. Rejected only where there is no `document` (a non-browser runtime); in a browser the wait reconnects while the page is visible and gives up with `timeout` or `expired`. A runtime with no global `WebSocket` (Node 20, for example) gets it at once; see Node, SSR and tests | A retry, anchored to the same `issuedAt` |
|
|
225
|
+
| `relay-closed` | The socket closed. Same rule as `relay-error` | A retry, anchored to the same `issuedAt` |
|
|
120
226
|
| `relay-refused` | The relay closed the subscription — `err.reason` has its text (e.g. `auth-required`) | A relay that serves gift wraps to an unauthenticated reader. `relay.damus.io` currently does not; `nos.lol` (the default) does |
|
|
121
|
-
| `invalid-issued-at` | `issuedAt` looks like the wrong unit (e.g. milliseconds) or is too far in the future | A bug in your code, not a retry
|
|
227
|
+
| `invalid-issued-at` | `issuedAt` looks like the wrong unit (e.g. milliseconds) or is too far in the future | A bug in your code, not a retry; check you're passing unix **seconds** |
|
|
228
|
+
| `invalid-request-id`, `invalid-session-privkey`, `invalid-relay-url`, `invalid-expected-origin` | The matching option failed validation before any connection was made | A bug in your code, not a retry; see the option table below |
|
|
122
229
|
|
|
123
230
|
The code is on both `err.message` and `err.code` — branch on either.
|
|
124
231
|
|
|
@@ -127,30 +234,31 @@ precedence over `issuedAt`. Prefer `issuedAt`, which derives it correctly.
|
|
|
127
234
|
|
|
128
235
|
## API
|
|
129
236
|
|
|
130
|
-
### `
|
|
237
|
+
### `verifyAge(requiredAgeRange, options?)` (`Signet.verifyAge` in the IIFE build)
|
|
131
238
|
|
|
132
|
-
Returns `Promise<SignetVerifyResult
|
|
239
|
+
Returns `Promise<SignetVerifyResult>`. Failures are reported in `result.error`, not as a rejection.
|
|
133
240
|
|
|
134
241
|
**Options:**
|
|
135
242
|
|
|
136
243
|
| Option | Type | Default | Description |
|
|
137
244
|
|--------|------|---------|-------------|
|
|
138
|
-
| `relayUrl` | `string` | `DEFAULT_RELAY_URL` (`wss://nos.lol`) |
|
|
245
|
+
| `relayUrl` | `string` | `DEFAULT_RELAY_URL` (`wss://nos.lol`) | Written into the `relayUrl` field of the request object `verifyAge` builds. `verifyAge` never connects to it, the response does not travel over it (it arrives on a `BroadcastChannel`), and the text the modal shows does not include it. The kind-1059 gift-wrap requirement under `waitForAuthResponse` therefore does not apply to this option |
|
|
139
246
|
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Modal colour scheme |
|
|
140
|
-
| `timeout` | `number` | `120000` | Timeout in milliseconds |
|
|
141
|
-
| `verifierCheckUrl` | `string \| null` | `'https://verify.signet.forgesworn.dev'` | Verification bot URL. Set to `null` to skip
|
|
142
|
-
| `acceptUnconfirmed` | `boolean` | `false` | Accept credentials from unconfirmed verifiers |
|
|
247
|
+
| `timeout` | `number` | `120000` | Timeout in milliseconds, clamped to 5000 to 600000 |
|
|
248
|
+
| `verifierCheckUrl` | `string \| null` | `'https://verify.signet.forgesworn.dev'` | Verification bot URL. Set to `null` to skip the check; unless `acceptUnconfirmed` is also `true`, `verified` is then `false` with `verifier-check-unavailable`. A URL that does not start with `https://` counts as a failed check |
|
|
249
|
+
| `acceptUnconfirmed` | `boolean` | `false` | Accept credentials from unconfirmed verifiers, and when the check is skipped or fails (see What `verified` is when the check is skipped or fails) |
|
|
143
250
|
|
|
144
251
|
**Result:**
|
|
145
252
|
|
|
146
253
|
| Field | Type | Description |
|
|
147
254
|
|-------|------|-------------|
|
|
148
|
-
| `verified` | `boolean` | `true` if credential valid, age range met, AND verifier confirmed (or `acceptUnconfirmed` is true) |
|
|
255
|
+
| `verified` | `boolean` | `true` if credential valid, unexpired, age range met, AND verifier confirmed (or `acceptUnconfirmed` is true). With `verifierCheckUrl: null`, or when the check fails, `verifierConfirmed` is `null` and `verified` is `false` unless `acceptUnconfirmed` is true |
|
|
149
256
|
| `ageRange` | `string \| null` | Age range from the credential (e.g. `'18+'`) |
|
|
150
257
|
| `tier` | `number \| null` | Verification tier (1–4) |
|
|
151
258
|
| `entityType` | `string \| null` | `'natural_person'`, `'persona'`, etc. |
|
|
259
|
+
| `credentialId` | `string \| null` | ID of the credential event (`credential.id`) |
|
|
152
260
|
| `verifierPubkey` | `string \| null` | Professional keypair of the issuing verifier |
|
|
153
|
-
| `verifierConfirmed` | `boolean \| null` | Whether the verifier is confirmed against a public register. `null` if check unavailable. |
|
|
261
|
+
| `verifierConfirmed` | `boolean \| null` | Whether the verifier is confirmed against a public register. `null` if the check was skipped or unavailable. |
|
|
154
262
|
| `verifierMethod` | `'A' \| 'B' \| 'C' \| 'D' \| null` | How the verifier was confirmed: A=NIP-05 on body domain, B=body-issued, C=cross-verified, D=website |
|
|
155
263
|
| `issuedAt` | `number \| null` | Unix timestamp of credential issuance |
|
|
156
264
|
| `expiresAt` | `number \| null` | Unix timestamp of credential expiry |
|
|
@@ -162,10 +270,75 @@ Returns `Promise<SignetVerifyResult>`
|
|
|
162
270
|
|-------|---------|
|
|
163
271
|
| `'cancelled'` | User cancelled the verification |
|
|
164
272
|
| `'timeout'` | Verification timed out (default: 2 minutes) |
|
|
273
|
+
| `'invalid-age-range'` | `requiredAgeRange` is not one of the five values under Age Range Values; no modal is shown |
|
|
165
274
|
| `'invalid-credential'` | Credential signature failed verification |
|
|
275
|
+
| `'credential-expired'` | The credential's `expires` time has passed |
|
|
166
276
|
| `'age-range-not-met'` | Credential age range doesn't satisfy the requirement |
|
|
167
277
|
| `'verifier-not-confirmed'` | Verifier is not confirmed against a public register |
|
|
168
|
-
| `'verifier-check-unavailable'` |
|
|
278
|
+
| `'verifier-check-unavailable'` | The verifier check was skipped (`verifierCheckUrl: null`) or failed (non-`https://` URL, bot unreachable, non-2xx or non-JSON answer). The credential is valid but the verifier status is unknown, so `verified` is `false` unless `acceptUnconfirmed` is true |
|
|
279
|
+
|
|
280
|
+
### `waitForAuthResponse(options)` (`Signet.waitForAuthResponse` in the IIFE build)
|
|
281
|
+
|
|
282
|
+
Returns `Promise<SignetAuthResult>`. It resolves with the verified result and rejects with an `Error` on failure; `err.code` is one of the codes in the failure table under Cross-device Sign-in-with-Signet.
|
|
283
|
+
|
|
284
|
+
**Options (`WaitForAuthOptions`):**
|
|
285
|
+
|
|
286
|
+
| Option | Type | Required | Default | Description |
|
|
287
|
+
|--------|------|----------|---------|-------------|
|
|
288
|
+
| `requestId` | `string` | yes | none | The challenge sent in the auth URL: 64-character hex (32 bytes) |
|
|
289
|
+
| `relayUrl` | `string` | yes | none | The relay you subscribed on. `wss://`, or `ws://` for `localhost` and `127.0.0.1` only |
|
|
290
|
+
| `sessionPrivKey` | `Uint8Array` | yes | none | Ephemeral session private key, 32 bytes. Used to unwrap the NIP-17 response |
|
|
291
|
+
| `expectedOrigin` | `string` | yes | none | Your origin (scheme, host and optional port), verbatim. Must match the `origin` tag of the signed auth event |
|
|
292
|
+
| `issuedAt` | `number` | no, but pass it | none | Unix **seconds** when you minted the challenge. Anchors the relay query and the deadline. More than 300 seconds in the future rejects with `invalid-issued-at` |
|
|
293
|
+
| `timeout` | `number` | no | Remaining validity (`issuedAt + 300` seconds minus now) when `issuedAt` is set, otherwise `120000` | Milliseconds, clamped to 5000 to 600000. Use it only to give up earlier than the default |
|
|
294
|
+
| `since` | `number` | no | `issuedAt - 60` when `issuedAt` is set, otherwise now minus 60 | Raw relay query anchor in unix seconds. Takes precedence over `issuedAt` |
|
|
295
|
+
| `abortSignal` | `AbortSignal` | no | none | Aborts the wait and closes the subscription; rejects with `aborted` |
|
|
296
|
+
|
|
297
|
+
**Result (`SignetAuthResult`):**
|
|
298
|
+
|
|
299
|
+
| Field | Type | Description |
|
|
300
|
+
|-------|------|-------------|
|
|
301
|
+
| `pubkey` | `string` | The user's pubkey (64-character hex); a copy of `authEvent.pubkey` |
|
|
302
|
+
| `authEvent` | `SignetAuthEvent` | The signed kind-21236 event, verified in the browser by this package: `id`, `pubkey`, `kind`, `created_at`, `tags`, `content`, `sig` |
|
|
303
|
+
| `credential` | `unknown` (optional) | Credential included in the response (Signet Login flows), passed through as received, not checked by this package |
|
|
304
|
+
| `displayName` | `string` (optional) | Persona handle the user chose to share. Control and bidi characters are stripped and it is capped at 64 characters; still escape it when rendering |
|
|
305
|
+
| `bunkerUri` | `string` (optional) | A `bunker://` URI for upgrading to a live signer. Absent on auth-only flows. Only its `bunker://` prefix is checked |
|
|
306
|
+
| `createdAt` | `number` | Unix seconds at which the auth event was signed |
|
|
307
|
+
|
|
308
|
+
**Constants:** `AUTH_FRESHNESS_WINDOW_SEC` (`300`, seconds) and `DEFAULT_RELAY_URL` (`'wss://nos.lol'`).
|
|
309
|
+
|
|
310
|
+
### Verify the result on your server
|
|
311
|
+
|
|
312
|
+
`waitForAuthResponse` verifies the response in the browser, and a browser result is not evidence for your server: the user, or script running in the page, can send your server any `pubkey` or `SignetAuthResult` it likes. Re-verify on the server before you create a session. Send the whole `authEvent`, not only `pubkey`, and check all of the following there:
|
|
313
|
+
|
|
314
|
+
- `kind` is `21236` and the event's `id` and `sig` verify (`verifyEvent` from `nostr-tools/pure` recomputes the NIP-01 id and checks the BIP-340 signature).
|
|
315
|
+
- The `challenge` tag equals the challenge your server issued for this attempt. Accept each challenge once, then discard it.
|
|
316
|
+
- The `origin` tag equals your own origin exactly.
|
|
317
|
+
- `created_at` is within `AUTH_FRESHNESS_WINDOW_SEC` of the server clock.
|
|
318
|
+
- Take the user's identity from the verified `authEvent.pubkey`.
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
import { verifyEvent } from 'nostr-tools/pure';
|
|
322
|
+
import { AUTH_FRESHNESS_WINDOW_SEC, type SignetAuthEvent } from 'signet-verify';
|
|
323
|
+
|
|
324
|
+
// `pending` is what your server stored when it issued this sign-in.
|
|
325
|
+
function authEventIsValid(authEvent: SignetAuthEvent, pending: { challenge: string; origin: string }): boolean {
|
|
326
|
+
const tag = (name: string) => authEvent.tags.find((t) => t[0] === name)?.[1];
|
|
327
|
+
return (
|
|
328
|
+
authEvent.kind === 21236 &&
|
|
329
|
+
verifyEvent(authEvent) &&
|
|
330
|
+
tag('challenge')?.toLowerCase() === pending.challenge.toLowerCase() &&
|
|
331
|
+
tag('origin') === pending.origin &&
|
|
332
|
+
Math.abs(Date.now() / 1000 - authEvent.created_at) <= AUTH_FRESHNESS_WINDOW_SEC
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Mint the challenge and `issuedAt` on the server and keep them against the pending sign-in. The cross-device example above mints them in the browser for brevity, and a challenge the client chose cannot be tied to an attempt your server started.
|
|
338
|
+
|
|
339
|
+
`credential` is `unknown` and is forwarded exactly as the response carried it. This package does not check its shape, signature or expiry, so validate it yourself or ignore it.
|
|
340
|
+
|
|
341
|
+
Alternatively, run `waitForAuthResponse` on the server (see Node, SSR and tests) and keep `sessionPrivKey` there. The result then never passes through the client.
|
|
169
342
|
|
|
170
343
|
## Age Range Values
|
|
171
344
|
|
|
@@ -177,11 +350,12 @@ Returns `Promise<SignetVerifyResult>`
|
|
|
177
350
|
| `'13-17'` | Ages 13–17 |
|
|
178
351
|
| `'18+'` | Adult (18 and over) |
|
|
179
352
|
|
|
353
|
+
Matching is exact: the credential's age range must equal `requiredAgeRange`. An `'18+'` credential does not satisfy `'13-17'`, and a `'13-17'` credential does not satisfy `'18+'`. Any other `requiredAgeRange` is rejected: `verifyAge` resolves at once with `error: 'invalid-age-range'` and shows no modal.
|
|
354
|
+
|
|
180
355
|
## Privacy
|
|
181
356
|
|
|
182
357
|
- No personal data is transmitted — only a cryptographic proof of age range
|
|
183
358
|
- No account creation required on the website
|
|
184
359
|
- No cookies beyond session management
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
- Compliant with GDPR, COPPA, UK Online Safety Act, France SREN law
|
|
360
|
+
- Proof verification runs in the browser. `verifyAge` makes one network request, to the verification bot, unless `verifierCheckUrl` is `null`; it opens no relay connection, and the proof reaches the modal over a same-origin `BroadcastChannel`, not the network. `waitForAuthResponse` connects only to the relay you give it
|
|
361
|
+
- The verifier check is a single GET request that sends only the verifier's public key and returns confirmed or not
|
package/dist/signet-verify.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* import { verifyAge, waitForAuthResponse } from 'signet-verify';
|
|
6
6
|
*
|
|
7
7
|
* Script-tag / CDN usage (IIFE bundle exposes `window.Signet`):
|
|
8
|
-
* <script src="https://cdn.signet.
|
|
8
|
+
* <script src="https://cdn.jsdelivr.net/npm/signet-verify@0.7.2/dist/signet-verify.iife.js"></script>
|
|
9
9
|
* <script>
|
|
10
10
|
* const result = await Signet.verifyAge('18+');
|
|
11
11
|
* if (result.verified) { // allow access }
|
|
@@ -40,7 +40,8 @@ export interface SignetVerifyOptions {
|
|
|
40
40
|
/**
|
|
41
41
|
* URL of the verification bot that checks verifier credentials against public registers.
|
|
42
42
|
* Default: 'https://verify.signet.forgesworn.dev'
|
|
43
|
-
* Set to null to skip
|
|
43
|
+
* Set to null to skip the bot: verifierConfirmed is then null, so the result fails
|
|
44
|
+
* with verifier-check-unavailable unless acceptUnconfirmed is true.
|
|
44
45
|
* Anyone can run their own bot — the URL is configurable, not a central authority.
|
|
45
46
|
*/
|
|
46
47
|
verifierCheckUrl?: string | null;
|
package/dist/signet-verify.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* import { verifyAge, waitForAuthResponse } from 'signet-verify';
|
|
6
6
|
*
|
|
7
7
|
* Script-tag / CDN usage (IIFE bundle exposes `window.Signet`):
|
|
8
|
-
* <script src="https://cdn.signet.
|
|
8
|
+
* <script src="https://cdn.jsdelivr.net/npm/signet-verify@0.7.2/dist/signet-verify.iife.js"></script>
|
|
9
9
|
* <script>
|
|
10
10
|
* const result = await Signet.verifyAge('18+');
|
|
11
11
|
* if (result.verified) { // allow access }
|
package/llms.txt
CHANGED
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
For websites that need to gate content by age range (adult content, age
|
|
9
9
|
brackets for minors) without running their own verification flow. A single
|
|
10
|
-
function call opens a
|
|
11
|
-
and checks both the credential and
|
|
12
|
-
registration.
|
|
10
|
+
function call opens a modal, waits for a signed response on a same-origin
|
|
11
|
+
`BroadcastChannel` (no relay connection), and checks both the credential and
|
|
12
|
+
the issuing verifier's professional registration. The package contains the
|
|
13
|
+
listener only: nothing in it posts that response, and `verifyAge` does not
|
|
14
|
+
expose the request ID a sender needs, so a real user cannot reach
|
|
15
|
+
`verified: true` until you wire up a same-origin sender of your own.
|
|
13
16
|
|
|
14
17
|
## Getting Started
|
|
15
18
|
|
|
@@ -26,8 +29,14 @@ if (result.verified) {
|
|
|
26
29
|
}
|
|
27
30
|
```
|
|
28
31
|
|
|
32
|
+
This resolves with `verified: true` only after a same-origin sender posts a
|
|
33
|
+
valid `signet-verify-response` message for the call. Otherwise the modal keeps
|
|
34
|
+
waiting and the call resolves with `error: 'timeout'` after 120 seconds, or
|
|
35
|
+
`error: 'cancelled'` if the user closes it.
|
|
36
|
+
|
|
29
37
|
For a script tag / CDN build, use the IIFE export (`signet-verify/iife`),
|
|
30
|
-
which exposes `window.Signet
|
|
38
|
+
which exposes `window.Signet`, for example
|
|
39
|
+
`https://cdn.jsdelivr.net/npm/signet-verify@0.7.2/dist/signet-verify.iife.js`.
|
|
31
40
|
|
|
32
41
|
## Key Concepts
|
|
33
42
|
|
|
@@ -37,31 +46,72 @@ which exposes `window.Signet`.
|
|
|
37
46
|
when the issuing verifier is checked against a public professional
|
|
38
47
|
register (GMC, SRA, TRA, etc.) via a verification bot; this can be relaxed
|
|
39
48
|
with `acceptUnconfirmed` or pointed at a different bot with
|
|
40
|
-
`verifierCheckUrl`.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
`verifierCheckUrl`. Setting `verifierCheckUrl: null` skips the check but
|
|
50
|
+
does not accept the credential on its own: `verifierConfirmed` is then
|
|
51
|
+
`null` and the result is `verified: false` with
|
|
52
|
+
`error: 'verifier-check-unavailable'` (as it is when the bot is
|
|
53
|
+
unreachable or the URL is not `https://`) unless `acceptUnconfirmed` is
|
|
54
|
+
also `true`.
|
|
55
|
+
- **Same-browser vs cross-device**: `verifyAge` covers the modal flow and
|
|
56
|
+
receives its response on a `BroadcastChannel` named
|
|
57
|
+
`signet-verify-<requestId>`; it makes one network request, to the
|
|
58
|
+
verification bot, and never connects to a relay (its `relayUrl` option is
|
|
59
|
+
only written into the request object). The message it accepts is
|
|
60
|
+
`{ type: 'signet-verify-response', requestId, credential, subjectPubkey }`
|
|
61
|
+
(the `VerifyResponse` type from `signet-protocol`; `subjectPubkey` is not
|
|
62
|
+
read), where `requestId` is the 32-character hex ID generated for that call
|
|
63
|
+
and `credential` is a signed kind-30470 Nostr event whose tags include
|
|
64
|
+
`tier`, `age-range` and `entity-type` (and optionally `expires`). The README
|
|
65
|
+
has the full field list and how each failure maps to an error code.
|
|
66
|
+
`waitForAuthResponse` is the
|
|
67
|
+
lower-level cross-device primitive for a consumer that mints its own
|
|
68
|
+
challenge and waits on a relay for the signed response; only it needs a
|
|
69
|
+
relay that serves kind-1059 gift wraps to an unauthenticated reader.
|
|
45
70
|
- **The sign-in deadline**: a cross-device wait has one deadline,
|
|
46
71
|
`issuedAt + AUTH_FRESHNESS_WINDOW_SEC`. `issuedAt` must be passed on every
|
|
47
72
|
call for the same sign-in, including retries after the page was
|
|
48
73
|
backgrounded, or the relay query anchor is wrong.
|
|
49
74
|
|
|
75
|
+
- **Node, SSR and tests**: the package is ESM only and loads safely in Node
|
|
76
|
+
(20.19 or later, the floor set by its `@noble/curves` and `@noble/hashes`
|
|
77
|
+
dependencies) because it touches no browser global at import time.
|
|
78
|
+
`verifyAge` is browser only: in Node it rejects with a `ReferenceError`
|
|
79
|
+
(except for an invalid age range, which resolves with
|
|
80
|
+
`invalid-age-range`). `waitForAuthResponse` needs only a global
|
|
81
|
+
`WebSocket` (Node 22 and later); without one it rejects with
|
|
82
|
+
`relay-error`.
|
|
83
|
+
- **Verify on your server**: `waitForAuthResponse` checks the response in the
|
|
84
|
+
browser only. Before creating a session, re-verify `authEvent` on your
|
|
85
|
+
server (event id and signature, `challenge` tag equal to the challenge your
|
|
86
|
+
server issued and accepted once, `origin` tag, `created_at` within
|
|
87
|
+
`AUTH_FRESHNESS_WINDOW_SEC`). `credential` is passed through unchecked.
|
|
88
|
+
The README section "Verify the result on your server" has a worked check.
|
|
89
|
+
|
|
50
90
|
## API Surface
|
|
51
91
|
|
|
52
92
|
- `verifyAge(requiredAgeRange, options?) => Promise<SignetVerifyResult>`:
|
|
53
|
-
the same-
|
|
93
|
+
the same-browser modal flow. `requiredAgeRange` must be one of
|
|
94
|
+
`'0-3'`, `'4-7'`, `'8-12'`, `'13-17'`, `'18+'` and is matched exactly.
|
|
95
|
+
Failures are reported in `result.error`, not as a rejection.
|
|
54
96
|
- `waitForAuthResponse(options) => Promise<SignetAuthResult>`: the
|
|
55
|
-
cross-device primitive
|
|
56
|
-
|
|
57
|
-
`
|
|
58
|
-
|
|
59
|
-
|
|
97
|
+
cross-device primitive. It resolves on success and rejects with an `Error`
|
|
98
|
+
whose `message` and `code` are the failure code (`expired`, `timeout`,
|
|
99
|
+
`denied`, `aborted`, `relay-refused`, and others listed in the README).
|
|
100
|
+
Options: `requestId`, `relayUrl`, `sessionPrivKey` (a 32-byte
|
|
101
|
+
`Uint8Array`) and `expectedOrigin` are required; `issuedAt` (unix
|
|
102
|
+
seconds), `timeout` (milliseconds), `since` (unix seconds) and
|
|
103
|
+
`abortSignal` are optional. The README has the full option and result
|
|
104
|
+
tables, and its cross-device example needs `@noble/curves@2` and
|
|
105
|
+
`@noble/hashes@2` installed (it imports them directly) and uses
|
|
106
|
+
`https://mysignet.app`, the Signet app's production origin, in the auth URL.
|
|
107
|
+
- `AUTH_FRESHNESS_WINDOW_SEC` (`300`, seconds), `DEFAULT_RELAY_URL`
|
|
108
|
+
(`'wss://nos.lol'`): constants used to compute and check the sign-in
|
|
109
|
+
deadline.
|
|
60
110
|
- Result and option types: `SignetVerifyResult`, `SignetVerifyOptions`,
|
|
61
111
|
`WaitForAuthOptions`, `SignetAuthResult`, `SignetAuthEvent`.
|
|
62
112
|
|
|
63
113
|
## Optional
|
|
64
114
|
|
|
65
115
|
- Full API reference, error codes and age range values: [README](README.md)
|
|
66
|
-
- Cross-device example: [examples/cross-device-login.html](examples/cross-device-login.html)
|
|
116
|
+
- Cross-device example: [examples/cross-device-login.html](https://github.com/forgesworn/signet-verify/blob/main/examples/cross-device-login.html)
|
|
67
117
|
- Release history: [CHANGELOG.md](CHANGELOG.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signet-verify",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Signet SDK — website age verification and cross-device Sign-in-with-Signet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/signet-verify.js",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
-
"llms.txt"
|
|
18
|
+
"llms.txt",
|
|
19
|
+
"CHANGELOG.md"
|
|
19
20
|
],
|
|
20
21
|
"homepage": "https://github.com/forgesworn/signet-verify#readme",
|
|
21
22
|
"keywords": [
|
|
@@ -34,6 +35,9 @@
|
|
|
34
35
|
"sdk",
|
|
35
36
|
"browser"
|
|
36
37
|
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20.19.0"
|
|
40
|
+
},
|
|
37
41
|
"publishConfig": {
|
|
38
42
|
"provenance": true
|
|
39
43
|
},
|