whisper-id 0.1.0 → 0.2.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 +15 -1
- package/index.d.ts +21 -0
- package/index.js +55 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,21 @@ await withEgress(async (e) => {
|
|
|
25
25
|
| `egress({ agent, tier, setEnv })` | Bring up egress bound to your `/128`. → `Egress { port, proxyUrl, socksUrl, proxies, close() }`. `tier: "wireguard"` for a routed `/128`. Sets proxy env unless `setEnv:false`; call `close()` to restore. |
|
|
26
26
|
| `withEgress([opts], fn)` | Run `fn(egress)` with egress up, restoring the environment afterwards. |
|
|
27
27
|
| `verify(address)` | Keyless — is `address` a real Whisper agent? (DANE + DNSSEC + reverse-DNS + JWS) → `boolean` |
|
|
28
|
-
| `
|
|
28
|
+
| `verifyDetails(address)` | Keyless — the full verdict (`is_whisper_agent`, `fqdn`, `operator`, `dane_ok`, `jws_ok`, …) or `null` |
|
|
29
|
+
| `rdap(address)` | Keyless — the public RDAP record for a `/128`, or `null` |
|
|
30
|
+
| `egressIp()` | Keyless — the IP this process leaves from (a `/128` when routed, else the platform's) → `string` |
|
|
31
|
+
| `ip()` | Your current egress IP via the CLI, proving it's your `/128`. → `string` |
|
|
32
|
+
|
|
33
|
+
### Keyless / serverless (no CLI)
|
|
34
|
+
|
|
35
|
+
`verify`, `verifyDetails`, `rdap`, and `egressIp` are **keyless** — pure HTTPS via `fetch`, **no `whisper` CLI and no key** — so they run in Cloudflare Workers, Vercel, Deno, Lambda, etc.:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
import { verify, rdap } from "whisper-id";
|
|
39
|
+
if (await verify(addr)) console.log((await rdap(addr)).name);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`register`, `egress`, `withEgress`, and `ip` need the CLI on the host (egress needs the local proxy; register needs your key).
|
|
29
43
|
|
|
30
44
|
### Using it with `fetch`
|
|
31
45
|
|
package/index.d.ts
CHANGED
|
@@ -26,6 +26,27 @@ export function register(name: string, opts?: RegisterOptions): Promise<Agent>;
|
|
|
26
26
|
export function egress(opts?: EgressOptions): Promise<Egress>;
|
|
27
27
|
export function withEgress<T>(fn: (e: Egress) => Promise<T> | T): Promise<T>;
|
|
28
28
|
export function withEgress<T>(opts: EgressOptions, fn: (e: Egress) => Promise<T> | T): Promise<T>;
|
|
29
|
+
/** Full server-side verify verdict (keyless). */
|
|
30
|
+
export interface VerifyVerdict {
|
|
31
|
+
is_whisper_agent: boolean;
|
|
32
|
+
fqdn?: string;
|
|
33
|
+
operator?: string;
|
|
34
|
+
tenant?: string;
|
|
35
|
+
dane_ok?: boolean;
|
|
36
|
+
jws_ok?: boolean;
|
|
37
|
+
verified_at?: number;
|
|
38
|
+
evidence?: unknown;
|
|
39
|
+
[k: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Keyless, CLI-free: is `address` a real Whisper agent? (server-side DANE+DNSSEC+reverse-DNS+JWS) */
|
|
29
43
|
export function verify(address: string, opts?: { timeout?: number }): Promise<boolean>;
|
|
44
|
+
/** Keyless, CLI-free: the full verify verdict, or null if not an agent. */
|
|
45
|
+
export function verifyDetails(address: string, opts?: { timeout?: number }): Promise<VerifyVerdict | null>;
|
|
46
|
+
/** Keyless, CLI-free: the public RDAP record for a /128, or null. */
|
|
47
|
+
export function rdap(address: string, opts?: { timeout?: number }): Promise<Record<string, unknown> | null>;
|
|
48
|
+
/** Keyless, CLI-free: the caller's current egress IP (what this process leaves from). */
|
|
49
|
+
export function egressIp(opts?: { timeout?: number }): Promise<string>;
|
|
50
|
+
/** Your current egress IP via the CLI (proves your /128). */
|
|
30
51
|
export function ip(opts?: { timeout?: number }): Promise<string>;
|
|
31
52
|
export function firstAddr(obj: unknown): string | null;
|
package/index.js
CHANGED
|
@@ -127,11 +127,63 @@ export async function withEgress(optsOrFn, maybeFn) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
// Keyless public endpoint base (the same surface the CLI uses, server-side). No key, no `whisper`
|
|
131
|
+
// CLI — these work in any runtime that can fetch() (serverless, edge, browser). Override via env.
|
|
132
|
+
function rdapBase() {
|
|
133
|
+
return (process.env.WHISPER_RDAP_URL || "https://rdap.whisper.online").replace(/\/+$/, "");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function httpGetJson(url, { timeout = 60000 } = {}) {
|
|
137
|
+
const ctrl = new AbortController();
|
|
138
|
+
const t = setTimeout(() => ctrl.abort(), timeout);
|
|
139
|
+
let res;
|
|
140
|
+
try {
|
|
141
|
+
res = await fetch(url, { headers: { Accept: "application/rdap+json, application/json, */*", "User-Agent": "whisper-id-node/0.2.0" }, signal: ctrl.signal });
|
|
142
|
+
} catch (err) {
|
|
143
|
+
throw new WhisperError(`could not reach ${url}: ${err && err.message ? err.message : err}`);
|
|
144
|
+
} finally {
|
|
145
|
+
clearTimeout(t);
|
|
146
|
+
}
|
|
147
|
+
let body = null;
|
|
148
|
+
try {
|
|
149
|
+
body = await res.json();
|
|
150
|
+
} catch {
|
|
151
|
+
body = null;
|
|
152
|
+
}
|
|
153
|
+
return { status: res.status, body };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Return true iff `address` is a real Whisper agent. **Keyless and CLI-free** — one HTTPS GET to
|
|
158
|
+
* the public /verify-identity endpoint (server-side DANE+DNSSEC+reverse-DNS+JWS chain). Works in
|
|
159
|
+
* any runtime. 200 ⇒ agent, 404 ⇒ not. Throws only if the endpoint is unreachable.
|
|
160
|
+
*/
|
|
131
161
|
export async function verify(address, { timeout = 60000 } = {}) {
|
|
162
|
+
return (await verifyDetails(address, { timeout })) !== null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Full verify verdict for a real agent (is_whisper_agent, fqdn, operator, tenant, dane_ok, jws_ok, …), else null. Keyless, CLI-free. */
|
|
166
|
+
export async function verifyDetails(address, { timeout = 60000 } = {}) {
|
|
132
167
|
if (!address || !address.trim()) throw new WhisperError("verify() needs an address");
|
|
133
|
-
const
|
|
134
|
-
|
|
168
|
+
const url = `${rdapBase()}/verify-identity?ip=${encodeURIComponent(address.trim())}`;
|
|
169
|
+
const { status, body } = await httpGetJson(url, { timeout });
|
|
170
|
+
if (status !== 200 || !body || !body.is_whisper_agent) return null;
|
|
171
|
+
return body;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Public RDAP record for a Whisper /128 (handle, name, status, entities, …), or null. Keyless, CLI-free. */
|
|
175
|
+
export async function rdap(address, { timeout = 60000 } = {}) {
|
|
176
|
+
if (!address || !address.trim()) throw new WhisperError("rdap() needs an address");
|
|
177
|
+
const url = `${rdapBase()}/ip/${encodeURI(address.trim())}`; // path segment — keep the literal colons
|
|
178
|
+
const { status, body } = await httpGetJson(url, { timeout });
|
|
179
|
+
return status === 200 ? body : null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The caller's current egress IP as seen by Whisper (what this process leaves from). Keyless, CLI-free. */
|
|
183
|
+
export async function egressIp({ timeout = 60000 } = {}) {
|
|
184
|
+
const { status, body } = await httpGetJson(`${rdapBase()}/egress-ip`, { timeout });
|
|
185
|
+
if (status !== 200) throw new WhisperError(`egress-ip endpoint returned HTTP ${status}`);
|
|
186
|
+
return (body && body.ip) || "";
|
|
135
187
|
}
|
|
136
188
|
|
|
137
189
|
/** Return the current egress IP, proving it is your Whisper /128. */
|
package/package.json
CHANGED