@reclaimprotocol/client 0.1.27 → 0.1.28
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 +634 -42
- package/lib/api-client.d.ts +31 -0
- package/lib/api-client.js +112 -0
- package/lib/api-consts.d.ts +4 -0
- package/lib/api-consts.js +4 -0
- package/lib/api-errors.d.ts +13 -0
- package/lib/api-errors.js +30 -0
- package/lib/api.d.ts +7 -0
- package/lib/api.js +3 -0
- package/lib/attestor-proof.d.ts +6 -0
- package/lib/attestor-proof.js +45 -18
- package/lib/client.d.ts +1 -6
- package/lib/client.js +12 -33
- package/lib/consts.d.ts +1 -1
- package/lib/consts.js +1 -1
- package/lib/eip191.d.ts +4 -2
- package/lib/eip191.js +17 -1
- package/lib/errors.d.ts +38 -4
- package/lib/errors.js +99 -16
- package/lib/index.d.ts +9 -7
- package/lib/index.js +8 -4
- package/lib/jwks.d.ts +25 -1
- package/lib/jwks.js +69 -4
- package/lib/legacy-proof.d.ts +57 -0
- package/lib/legacy-proof.js +211 -0
- package/lib/proof-hash.d.ts +35 -0
- package/lib/proof-hash.js +216 -0
- package/lib/proof-recipe.d.ts +18 -0
- package/lib/proof-recipe.js +37 -0
- package/lib/proof-trust-constants.d.ts +3 -0
- package/lib/proof-trust-constants.js +43 -0
- package/lib/proof-types.d.ts +62 -0
- package/lib/proof-types.js +1 -0
- package/lib/proof-verification-error.d.ts +13 -0
- package/lib/proof-verification-error.js +11 -0
- package/lib/reclaim.d.ts +27 -5
- package/lib/reclaim.js +140 -14
- package/lib/result.d.ts +58 -6
- package/lib/result.js +252 -61
- package/lib/routes.gen.d.ts +54 -6
- package/lib/routes.gen.js +15 -3
- package/lib/tee-binding.d.ts +82 -0
- package/lib/tee-binding.js +112 -0
- package/lib/types/openapi.gen.d.ts +1343 -206
- package/lib/verification-clients.d.ts +17 -0
- package/lib/verification-clients.js +14 -0
- package/lib/verify-attestor-tee.d.ts +76 -0
- package/lib/verify-attestor-tee.js +243 -0
- package/lib/verify-tee.d.ts +32 -0
- package/lib/verify-tee.js +347 -0
- package/package.json +5 -1
- package/src/api.ts +15 -0
- package/src/attestor-proof.ts +57 -20
- package/src/client.ts +15 -38
- package/src/consts.ts +1 -2
- package/src/eip191.ts +31 -3
- package/src/errors.ts +132 -18
- package/src/index.ts +50 -5
- package/src/jwks.ts +128 -5
- package/src/legacy-proof.ts +367 -0
- package/src/proof-hash.ts +316 -0
- package/src/proof-recipe.ts +75 -0
- package/src/proof-trust-constants.ts +45 -0
- package/src/proof-types.ts +57 -0
- package/src/proof-verification-error.ts +13 -0
- package/src/reclaim.ts +206 -19
- package/src/result.ts +403 -68
- package/src/routes.gen.ts +15 -3
- package/src/tee-binding.ts +192 -0
- package/src/types/openapi.gen.ts +1343 -206
- package/src/verification-clients.ts +22 -0
- package/src/verify-attestor-tee.ts +365 -0
- package/src/verify-tee.ts +507 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { IOperationArgs, IOperationId, IOperationResponse } from './types/openapi.ts';
|
|
2
|
+
export type ReclaimApiClientOptions = {
|
|
3
|
+
/** Backend base URL. Defaults to the public Builder API. */
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
/** Bearer token for authenticated operations. */
|
|
6
|
+
token?: string;
|
|
7
|
+
/** Custom fetch implementation. Defaults to global `fetch`. */
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
/** Extra headers merged into every request. */
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
/** Error mapper for unsuccessful responses. */
|
|
12
|
+
handleErrorResponse?: (response: Response) => Promise<never>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Browser-safe typed HTTP client generated from the Builder OpenAPI contract.
|
|
16
|
+
*
|
|
17
|
+
* This transport doesn't import proof, signing, attestor, or Node-only code.
|
|
18
|
+
* Validation remains in the OpenAPI schema; this client only serializes the
|
|
19
|
+
* typed request and maps unsuccessful responses.
|
|
20
|
+
*/
|
|
21
|
+
export declare class ReclaimApiClient {
|
|
22
|
+
#private;
|
|
23
|
+
readonly baseUrl: string;
|
|
24
|
+
constructor(opts?: ReclaimApiClientOptions);
|
|
25
|
+
setToken(token: string | undefined): void;
|
|
26
|
+
getToken(): string | undefined;
|
|
27
|
+
call<T extends IOperationId>(operationId: T, ...argsRest: {} extends IOperationArgs<T> ? [args?: IOperationArgs<T>] : [args: IOperationArgs<T>]): Promise<{
|
|
28
|
+
data: IOperationResponse<T>;
|
|
29
|
+
response: Response;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _ReclaimApiClient_token, _ReclaimApiClient_fetch, _ReclaimApiClient_headers, _ReclaimApiClient_handleErrorResponse;
|
|
13
|
+
import { API_DEFAULT_BASE_URL } from "./api-consts.js";
|
|
14
|
+
import { throwApiProblemError } from "./api-errors.js";
|
|
15
|
+
import { ROUTES } from "./routes.gen.js";
|
|
16
|
+
/**
|
|
17
|
+
* Browser-safe typed HTTP client generated from the Builder OpenAPI contract.
|
|
18
|
+
*
|
|
19
|
+
* This transport doesn't import proof, signing, attestor, or Node-only code.
|
|
20
|
+
* Validation remains in the OpenAPI schema; this client only serializes the
|
|
21
|
+
* typed request and maps unsuccessful responses.
|
|
22
|
+
*/
|
|
23
|
+
export class ReclaimApiClient {
|
|
24
|
+
constructor(opts = {}) {
|
|
25
|
+
_ReclaimApiClient_token.set(this, void 0);
|
|
26
|
+
_ReclaimApiClient_fetch.set(this, void 0);
|
|
27
|
+
_ReclaimApiClient_headers.set(this, void 0);
|
|
28
|
+
_ReclaimApiClient_handleErrorResponse.set(this, void 0);
|
|
29
|
+
this.baseUrl = (opts.baseUrl || API_DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
30
|
+
__classPrivateFieldSet(this, _ReclaimApiClient_token, opts.token, "f");
|
|
31
|
+
__classPrivateFieldSet(this, _ReclaimApiClient_fetch, opts.fetch || fetch, "f");
|
|
32
|
+
__classPrivateFieldSet(this, _ReclaimApiClient_headers, {
|
|
33
|
+
Accept: 'application/json',
|
|
34
|
+
...opts.headers,
|
|
35
|
+
}, "f");
|
|
36
|
+
__classPrivateFieldSet(this, _ReclaimApiClient_handleErrorResponse, opts.handleErrorResponse
|
|
37
|
+
|| throwApiProblemError, "f");
|
|
38
|
+
}
|
|
39
|
+
setToken(token) {
|
|
40
|
+
__classPrivateFieldSet(this, _ReclaimApiClient_token, token, "f");
|
|
41
|
+
}
|
|
42
|
+
getToken() {
|
|
43
|
+
return __classPrivateFieldGet(this, _ReclaimApiClient_token, "f");
|
|
44
|
+
}
|
|
45
|
+
async call(operationId, ...argsRest) {
|
|
46
|
+
const args = argsRest[0] ?? {};
|
|
47
|
+
const op = ROUTES[operationId];
|
|
48
|
+
// A browser entry point can't depend on node:assert. This guard also
|
|
49
|
+
// protects JavaScript callers that bypass the generated operation type.
|
|
50
|
+
if (!op) {
|
|
51
|
+
throw Object.assign(new Error(`Unknown operation: ${String(operationId)}`), { name: 'UnknownOperationError' });
|
|
52
|
+
}
|
|
53
|
+
let url = `${this.baseUrl}${op.path}`;
|
|
54
|
+
if (args.params) {
|
|
55
|
+
for (const [key, value] of Object.entries(args.params)) {
|
|
56
|
+
url = url.replace(`{${key}}`, String(value));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (args.query && Object.keys(args.query).length > 0) {
|
|
60
|
+
const search = new URLSearchParams();
|
|
61
|
+
for (const [key, value] of Object.entries(args.query)) {
|
|
62
|
+
if (value === undefined || value === null) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
for (const item of value) {
|
|
67
|
+
search.append(key, String(item));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
search.append(key, String(value));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
url += `?${search.toString()}`;
|
|
75
|
+
}
|
|
76
|
+
const headers = {
|
|
77
|
+
...__classPrivateFieldGet(this, _ReclaimApiClient_headers, "f"),
|
|
78
|
+
Accept: 'application/json',
|
|
79
|
+
};
|
|
80
|
+
if (__classPrivateFieldGet(this, _ReclaimApiClient_token, "f")) {
|
|
81
|
+
headers['Authorization'] = `Bearer ${__classPrivateFieldGet(this, _ReclaimApiClient_token, "f")}`;
|
|
82
|
+
}
|
|
83
|
+
// Spec-declared headers win over client defaults, but not authorization.
|
|
84
|
+
if (args.headers) {
|
|
85
|
+
for (const [key, value] of Object.entries(args.headers)) {
|
|
86
|
+
if (value === undefined || value === null) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
headers[key] = String(value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const init = {
|
|
93
|
+
method: op.method.toUpperCase(),
|
|
94
|
+
headers,
|
|
95
|
+
};
|
|
96
|
+
const method = op.method.toLowerCase();
|
|
97
|
+
if (method !== 'get' && method !== 'head') {
|
|
98
|
+
init.body = JSON.stringify(args.body ?? {});
|
|
99
|
+
headers['Content-Type'] = 'application/json';
|
|
100
|
+
}
|
|
101
|
+
const res = await __classPrivateFieldGet(this, _ReclaimApiClient_fetch, "f").call(this, url, init);
|
|
102
|
+
if (!res.ok) {
|
|
103
|
+
await __classPrivateFieldGet(this, _ReclaimApiClient_handleErrorResponse, "f").call(this, res);
|
|
104
|
+
}
|
|
105
|
+
if (res.status === 204 || res.headers.get('content-length') === '0') {
|
|
106
|
+
return { data: undefined, response: res };
|
|
107
|
+
}
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
return { data, response: res };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
_ReclaimApiClient_token = new WeakMap(), _ReclaimApiClient_fetch = new WeakMap(), _ReclaimApiClient_headers = new WeakMap(), _ReclaimApiClient_handleErrorResponse = new WeakMap();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Public Builder API origin used when a caller doesn't provide `baseUrl`. */
|
|
2
|
+
export declare const API_DEFAULT_BASE_URL = "https://build.reclaimprotocol.org";
|
|
3
|
+
/** Media type defined by RFC 9457 for HTTP problem details. */
|
|
4
|
+
export declare const PROBLEM_CONTENT_TYPE = "application/problem+json";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Public Builder API origin used when a caller doesn't provide `baseUrl`. */
|
|
2
|
+
export const API_DEFAULT_BASE_URL = 'https://build.reclaimprotocol.org';
|
|
3
|
+
/** Media type defined by RFC 9457 for HTTP problem details. */
|
|
4
|
+
export const PROBLEM_CONTENT_TYPE = 'application/problem+json';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProblemDetails } from './types/openapi.ts';
|
|
2
|
+
/** A browser-safe HTTP error with the Builder's RFC 9457 response attached. */
|
|
3
|
+
export type ApiProblemError = Error & {
|
|
4
|
+
readonly name: 'ApiProblemError';
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly problem: ProblemDetails;
|
|
7
|
+
};
|
|
8
|
+
/** Returns whether an unknown thrown value is an {@link ApiProblemError}. */
|
|
9
|
+
export declare function isApiProblemError(error: unknown): error is ApiProblemError;
|
|
10
|
+
/** Reads an RFC 9457 response, with a status-only fallback for other bodies. */
|
|
11
|
+
export declare function readProblemDetails(res: Response): Promise<ProblemDetails>;
|
|
12
|
+
/** Throws a browser-safe error for an unsuccessful Builder response. */
|
|
13
|
+
export declare function throwApiProblemError(res: Response): Promise<never>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PROBLEM_CONTENT_TYPE } from "./api-consts.js";
|
|
2
|
+
/** Returns whether an unknown thrown value is an {@link ApiProblemError}. */
|
|
3
|
+
export function isApiProblemError(error) {
|
|
4
|
+
return error instanceof Error
|
|
5
|
+
&& error.name === 'ApiProblemError'
|
|
6
|
+
&& 'status' in error
|
|
7
|
+
&& typeof error.status === 'number'
|
|
8
|
+
&& 'problem' in error;
|
|
9
|
+
}
|
|
10
|
+
/** Reads an RFC 9457 response, with a status-only fallback for other bodies. */
|
|
11
|
+
export async function readProblemDetails(res) {
|
|
12
|
+
const contentType = res.headers.get('Content-Type') || '';
|
|
13
|
+
if (contentType.includes(PROBLEM_CONTENT_TYPE)) {
|
|
14
|
+
return await res.json();
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
type: 'about:blank',
|
|
18
|
+
status: res.status,
|
|
19
|
+
title: res.statusText,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Throws a browser-safe error for an unsuccessful Builder response. */
|
|
23
|
+
export async function throwApiProblemError(res) {
|
|
24
|
+
const problem = await readProblemDetails(res);
|
|
25
|
+
throw Object.assign(new Error(problem.title), {
|
|
26
|
+
name: 'ApiProblemError',
|
|
27
|
+
status: res.status,
|
|
28
|
+
problem,
|
|
29
|
+
});
|
|
30
|
+
}
|
package/lib/api.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ReclaimClient } from './client.ts';
|
|
2
|
+
export type { ReclaimClientOptions } from './client.ts';
|
|
3
|
+
export { isProblemDetails, isProblemError, readProblemDetails, throwProblemError, } from './errors.ts';
|
|
4
|
+
export type { ProblemError } from './errors.ts';
|
|
5
|
+
export { DEFAULT_BASE_URL, PROBLEM_CONTENT_TYPE, } from './consts.ts';
|
|
6
|
+
export type * from './types/openapi.ts';
|
|
7
|
+
export type * from './types/openapi.gen.ts';
|
package/lib/api.js
ADDED
package/lib/attestor-proof.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ export interface NestedProofOutcome {
|
|
|
5
5
|
attestorAddress?: string;
|
|
6
6
|
/** Decoded claim data. */
|
|
7
7
|
claim?: Record<string, unknown>;
|
|
8
|
+
/** Recovered signers, in nested JWS signature order. */
|
|
9
|
+
attestorAddresses?: string[];
|
|
10
|
+
/** Decoded protected headers, in nested JWS signature order. */
|
|
11
|
+
headers?: Record<string, unknown>[];
|
|
12
|
+
/** The raw nested signatures, in hex form. */
|
|
13
|
+
signatures?: string[];
|
|
8
14
|
}
|
|
9
15
|
/**
|
|
10
16
|
* Verify a base64url-encoded nested attestor proof JWS: recover the attestor
|
package/lib/attestor-proof.js
CHANGED
|
@@ -65,45 +65,72 @@ export function verifyNestedProof(proofB64, opts = {}) {
|
|
|
65
65
|
catch {
|
|
66
66
|
return { valid: false, reason: 'proof-not-jws' };
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
if (!jws.payload || !sig) {
|
|
68
|
+
if (!jws.payload || !jws.signatures?.length) {
|
|
70
69
|
return { valid: false, reason: 'proof-malformed' };
|
|
71
70
|
}
|
|
72
|
-
let header;
|
|
73
71
|
let claim;
|
|
74
72
|
try {
|
|
75
|
-
header = decodeJson(sig.protected);
|
|
76
73
|
claim = decodeJson(jws.payload);
|
|
77
74
|
}
|
|
78
75
|
catch {
|
|
79
76
|
return { valid: false, reason: 'proof-decode-failed' };
|
|
80
77
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
const recoveredAddresses = [];
|
|
79
|
+
const headers = [];
|
|
80
|
+
const signatures = [];
|
|
81
|
+
let firstFailure;
|
|
82
|
+
for (const sig of jws.signatures) {
|
|
83
|
+
let header;
|
|
84
|
+
let recovered;
|
|
85
|
+
try {
|
|
86
|
+
header = decodeJson(sig.protected);
|
|
87
|
+
recovered = recoverEthAddress(eip191Digest(createSignData(claim)), base64urlToBytes(sig.signature));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
firstFailure || (firstFailure = { valid: false, reason: 'recover-failed', claim });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const kid = String(header.kid ?? '').toLowerCase();
|
|
94
|
+
if (recovered.toLowerCase() !== kid) {
|
|
95
|
+
firstFailure || (firstFailure = {
|
|
96
|
+
valid: false,
|
|
97
|
+
reason: 'attestor-signature-mismatch',
|
|
98
|
+
attestorAddress: recovered,
|
|
99
|
+
claim,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
recoveredAddresses.push(recovered);
|
|
104
|
+
headers.push(header);
|
|
105
|
+
signatures.push('0x' + bytesToHex(base64urlToBytes(sig.signature)));
|
|
87
106
|
}
|
|
88
|
-
|
|
89
|
-
if (recovered.toLowerCase() !== kid) {
|
|
107
|
+
if (!recoveredAddresses.length) {
|
|
90
108
|
return {
|
|
91
|
-
valid: false,
|
|
92
|
-
reason: 'attestor-signature-mismatch',
|
|
93
|
-
attestorAddress: recovered,
|
|
109
|
+
...(firstFailure ?? { valid: false, reason: 'recover-failed' }),
|
|
94
110
|
claim,
|
|
95
111
|
};
|
|
96
112
|
}
|
|
97
113
|
if (opts.attestorAddresses) {
|
|
98
114
|
const allow = opts.attestorAddresses.map((a) => a.toLowerCase());
|
|
99
|
-
|
|
115
|
+
const allowedIndex = recoveredAddresses.findIndex((a) => allow.includes(a.toLowerCase()));
|
|
116
|
+
if (allowedIndex < 0) {
|
|
100
117
|
return {
|
|
101
118
|
valid: false,
|
|
102
119
|
reason: 'attestor-not-allowed',
|
|
103
|
-
attestorAddress:
|
|
120
|
+
attestorAddress: recoveredAddresses[0],
|
|
121
|
+
attestorAddresses: recoveredAddresses,
|
|
122
|
+
headers,
|
|
123
|
+
signatures,
|
|
104
124
|
claim,
|
|
105
125
|
};
|
|
106
126
|
}
|
|
107
127
|
}
|
|
108
|
-
return {
|
|
128
|
+
return {
|
|
129
|
+
valid: true,
|
|
130
|
+
attestorAddress: recoveredAddresses[0],
|
|
131
|
+
attestorAddresses: recoveredAddresses,
|
|
132
|
+
headers,
|
|
133
|
+
signatures,
|
|
134
|
+
claim,
|
|
135
|
+
};
|
|
109
136
|
}
|
package/lib/client.d.ts
CHANGED
|
@@ -10,12 +10,7 @@ export type ReclaimClientOptions = {
|
|
|
10
10
|
headers?: Record<string, string>;
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* extend behavior here rather than duplicating it across surfaces.
|
|
15
|
-
*
|
|
16
|
-
* Per @reclaimprotocol/app conventions, validation lives in the openapi
|
|
17
|
-
* spec; this client forwards requests and maps RFC 9457 problem responses
|
|
18
|
-
* to {@link ProblemError}.
|
|
13
|
+
* Browser-safe typed HTTP client generated from the Builder OpenAPI contract.
|
|
19
14
|
*/
|
|
20
15
|
export declare class ReclaimClient {
|
|
21
16
|
#private;
|
package/lib/client.js
CHANGED
|
@@ -10,22 +10,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
12
|
var _ReclaimClient_token, _ReclaimClient_fetch, _ReclaimClient_headers;
|
|
13
|
-
import {
|
|
14
|
-
import assert from 'node:assert';
|
|
15
|
-
import { DEFAULT_BASE_URL, USER_AGENT } from "./consts.js";
|
|
13
|
+
import { DEFAULT_BASE_URL } from "./consts.js";
|
|
16
14
|
import { throwProblemError } from "./errors.js";
|
|
17
|
-
// `ROUTES` is generated from `packages/app/openapi.yaml` and is the
|
|
18
|
-
// single source of method/path mapping for every operation. New
|
|
19
|
-
// endpoints added to the spec are picked up by re-running
|
|
20
|
-
// `npm run openapi:typegen --workspace=packages/client`.
|
|
21
15
|
import { ROUTES } from "./routes.gen.js";
|
|
22
16
|
/**
|
|
23
|
-
*
|
|
24
|
-
* extend behavior here rather than duplicating it across surfaces.
|
|
25
|
-
*
|
|
26
|
-
* Per @reclaimprotocol/app conventions, validation lives in the openapi
|
|
27
|
-
* spec; this client forwards requests and maps RFC 9457 problem responses
|
|
28
|
-
* to {@link ProblemError}.
|
|
17
|
+
* Browser-safe typed HTTP client generated from the Builder OpenAPI contract.
|
|
29
18
|
*/
|
|
30
19
|
export class ReclaimClient {
|
|
31
20
|
constructor(opts = {}) {
|
|
@@ -36,7 +25,6 @@ export class ReclaimClient {
|
|
|
36
25
|
__classPrivateFieldSet(this, _ReclaimClient_token, opts.token, "f");
|
|
37
26
|
__classPrivateFieldSet(this, _ReclaimClient_fetch, opts.fetch || fetch, "f");
|
|
38
27
|
__classPrivateFieldSet(this, _ReclaimClient_headers, {
|
|
39
|
-
'User-Agent': USER_AGENT,
|
|
40
28
|
Accept: 'application/json',
|
|
41
29
|
...opts.headers,
|
|
42
30
|
}, "f");
|
|
@@ -48,24 +36,23 @@ export class ReclaimClient {
|
|
|
48
36
|
return __classPrivateFieldGet(this, _ReclaimClient_token, "f");
|
|
49
37
|
}
|
|
50
38
|
async call(operationId, ...argsRest) {
|
|
51
|
-
const args = argsRest[0]
|
|
39
|
+
const args = argsRest[0];
|
|
52
40
|
const op = ROUTES[operationId];
|
|
53
|
-
assert(op, notFound(`Unknown operation: ${String(operationId)}`));
|
|
54
41
|
let url = `${this.baseUrl}${op.path}`;
|
|
55
|
-
if (args
|
|
42
|
+
if (args?.params) {
|
|
56
43
|
for (const [key, value] of Object.entries(args.params)) {
|
|
57
44
|
url = url.replace(`{${key}}`, String(value));
|
|
58
45
|
}
|
|
59
46
|
}
|
|
60
|
-
if (args
|
|
47
|
+
if (args?.query && Object.keys(args.query).length > 0) {
|
|
61
48
|
const search = new URLSearchParams();
|
|
62
49
|
for (const [key, value] of Object.entries(args.query)) {
|
|
63
50
|
if (value === undefined || value === null) {
|
|
64
51
|
continue;
|
|
65
52
|
}
|
|
66
53
|
if (Array.isArray(value)) {
|
|
67
|
-
for (const
|
|
68
|
-
search.append(key, String(
|
|
54
|
+
for (const item of value) {
|
|
55
|
+
search.append(key, String(item));
|
|
69
56
|
}
|
|
70
57
|
}
|
|
71
58
|
else {
|
|
@@ -81,10 +68,7 @@ export class ReclaimClient {
|
|
|
81
68
|
if (__classPrivateFieldGet(this, _ReclaimClient_token, "f")) {
|
|
82
69
|
headers['Authorization'] = `Bearer ${__classPrivateFieldGet(this, _ReclaimClient_token, "f")}`;
|
|
83
70
|
}
|
|
84
|
-
|
|
85
|
-
// concurrency). Applied last so spec-declared headers win over the
|
|
86
|
-
// client-wide defaults, but never override Authorization.
|
|
87
|
-
if (args.headers) {
|
|
71
|
+
if (args?.headers) {
|
|
88
72
|
for (const [key, value] of Object.entries(args.headers)) {
|
|
89
73
|
if (value === undefined || value === null) {
|
|
90
74
|
continue;
|
|
@@ -96,23 +80,18 @@ export class ReclaimClient {
|
|
|
96
80
|
method: op.method.toUpperCase(),
|
|
97
81
|
headers,
|
|
98
82
|
};
|
|
99
|
-
// Write methods always carry a JSON body (defaulting to `{}`) plus a
|
|
100
|
-
// Content-Type. Some endpoints — for example, the auth-only attach session
|
|
101
|
-
// — take an empty body, and omitting Content-Type makes the server reject
|
|
102
|
-
// the request with `415 unsupported media type`.
|
|
103
83
|
const method = op.method.toLowerCase();
|
|
104
84
|
if (method !== 'get' && method !== 'head') {
|
|
105
|
-
init.body = JSON.stringify(args
|
|
85
|
+
init.body = JSON.stringify(args?.body ?? {});
|
|
106
86
|
headers['Content-Type'] = 'application/json';
|
|
107
87
|
}
|
|
108
88
|
const res = await __classPrivateFieldGet(this, _ReclaimClient_fetch, "f").call(this, url, init);
|
|
109
89
|
if (!res.ok) {
|
|
110
90
|
await throwProblemError(res);
|
|
111
91
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const data = await res.json();
|
|
92
|
+
const empty = res.status === 204
|
|
93
|
+
|| res.headers.get('content-length') === '0';
|
|
94
|
+
const data = (empty ? undefined : await res.json());
|
|
116
95
|
return { data, response: res };
|
|
117
96
|
}
|
|
118
97
|
}
|
package/lib/consts.d.ts
CHANGED
package/lib/consts.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export const DEFAULT_BASE_URL =
|
|
1
|
+
export const DEFAULT_BASE_URL = 'https://build.reclaimprotocol.org';
|
|
2
2
|
export const USER_AGENT = '@reclaimprotocol/client';
|
|
3
3
|
export const PROBLEM_CONTENT_TYPE = 'application/problem+json';
|
package/lib/eip191.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/** Standard EIP-191 / personal_sign digest: keccak256 of
|
|
2
2
|
* `0x19 ‖ "Ethereum Signed Message:\n" ‖ len ‖ data`. */
|
|
3
|
-
export declare function eip191Digest(data: string): Uint8Array;
|
|
3
|
+
export declare function eip191Digest(data: string | Uint8Array): Uint8Array;
|
|
4
4
|
/** EIP-191 personal_sign over `message` with a raw 0x-hex eth private key.
|
|
5
5
|
* Returns the standard 65-byte `r ‖ s ‖ v` hex signature that
|
|
6
6
|
* `ethers.verifyMessage(message, signature)` accepts — noble's `'recovered'`
|
|
7
7
|
* format prepends the recovery byte instead, so it's reordered to the end
|
|
8
8
|
* here (a raw `0`/`1` recovery byte is fine there, no `+27` needed). */
|
|
9
|
-
export declare function signEip191(message: string, privateKeyHex: string): string;
|
|
9
|
+
export declare function signEip191(message: string | Uint8Array, privateKeyHex: string): string;
|
|
10
|
+
/** Recover the lowercase Ethereum address from an EIP-191 signature. */
|
|
11
|
+
export declare function recoverEip191Address(message: string | Uint8Array, signatureHex: string): string;
|
package/lib/eip191.js
CHANGED
|
@@ -11,7 +11,7 @@ const ENC = new TextEncoder();
|
|
|
11
11
|
/** Standard EIP-191 / personal_sign digest: keccak256 of
|
|
12
12
|
* `0x19 ‖ "Ethereum Signed Message:\n" ‖ len ‖ data`. */
|
|
13
13
|
export function eip191Digest(data) {
|
|
14
|
-
const bytes = ENC.encode(data);
|
|
14
|
+
const bytes = typeof data === 'string' ? ENC.encode(data) : data;
|
|
15
15
|
const prefix = ENC.encode('\x19Ethereum Signed Message:\n');
|
|
16
16
|
const len = ENC.encode(String(bytes.length));
|
|
17
17
|
const merged = new Uint8Array(prefix.length + len.length + bytes.length);
|
|
@@ -36,3 +36,19 @@ export function signEip191(message, privateKeyHex) {
|
|
|
36
36
|
const rs = recovered.slice(1);
|
|
37
37
|
return '0x' + bytesToHex(rs) + recovery.toString(16).padStart(2, '0');
|
|
38
38
|
}
|
|
39
|
+
/** Recover the lowercase Ethereum address from an EIP-191 signature. */
|
|
40
|
+
export function recoverEip191Address(message, signatureHex) {
|
|
41
|
+
const signature = hexToBytes(signatureHex);
|
|
42
|
+
if (signature.length !== 65) {
|
|
43
|
+
throw new Error('EIP-191 signature must contain 65 bytes');
|
|
44
|
+
}
|
|
45
|
+
const recovery = signature[64] >= 27
|
|
46
|
+
? signature[64] - 27
|
|
47
|
+
: signature[64];
|
|
48
|
+
const recovered = new Uint8Array(65);
|
|
49
|
+
recovered[0] = recovery;
|
|
50
|
+
recovered.set(signature.subarray(0, 64), 1);
|
|
51
|
+
const compressed = secp.recoverPublicKey(recovered, eip191Digest(message), { prehash: false });
|
|
52
|
+
const uncompressed = secp.Point.fromBytes(compressed).toBytes(false);
|
|
53
|
+
return '0x' + bytesToHex(keccak_256(uncompressed.slice(1))).slice(-40);
|
|
54
|
+
}
|
package/lib/errors.d.ts
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
|
-
import { Boom } from '@hapi/boom';
|
|
2
1
|
import type { ProblemDetails } from './types/openapi.ts';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
type ProblemErrorOutput = {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
payload: ProblemDetails & {
|
|
6
|
+
statusCode: number;
|
|
7
|
+
error: string;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* RFC 9457 error with the fields expected by Boom consumers.
|
|
13
|
+
*
|
|
14
|
+
* `@hapi/boom`'s `isBoom()` recognizes this structural contract. The client
|
|
15
|
+
* doesn't import Boom at runtime, so this error remains browser-safe.
|
|
16
|
+
*/
|
|
17
|
+
export type ProblemError = Error & {
|
|
18
|
+
readonly isBoom: true;
|
|
19
|
+
readonly isServer: boolean;
|
|
20
|
+
readonly data: ProblemDetails;
|
|
21
|
+
readonly output: ProblemErrorOutput;
|
|
22
|
+
};
|
|
23
|
+
/** Returns whether a thrown value is a Builder {@link ProblemError}. */
|
|
24
|
+
export declare function isProblemError(error: unknown): error is ProblemError;
|
|
25
|
+
/** Returns whether a value follows the RFC 9457 problem-details schema. */
|
|
26
|
+
export declare function isProblemDetails(value: unknown): value is ProblemDetails;
|
|
27
|
+
/** Reads an RFC 9457 response, with a status-only fallback for other bodies. */
|
|
28
|
+
export declare function readProblemDetails(res: Response): Promise<{
|
|
29
|
+
type: string;
|
|
30
|
+
title: string;
|
|
31
|
+
status: number;
|
|
32
|
+
detail?: string;
|
|
33
|
+
instance?: string;
|
|
34
|
+
event?: string;
|
|
35
|
+
sessionId?: import("./api.ts").components["schemas"]["SessionID"];
|
|
36
|
+
}>;
|
|
37
|
+
/** Throws a browser-safe, Boom-compatible RFC 9457 error. */
|
|
38
|
+
export declare function throwProblemError(res: Response): Promise<never>;
|
|
39
|
+
export {};
|