@reclaimprotocol/client 0.1.28 → 0.1.31

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.
Files changed (110) hide show
  1. package/README.md +255 -234
  2. package/lib/api.d.ts +2 -0
  3. package/lib/api.js +2 -0
  4. package/lib/client.js +2 -0
  5. package/lib/enckeys.d.ts +1 -1
  6. package/lib/enckeys.js +2 -2
  7. package/lib/encoding.d.ts +4 -0
  8. package/lib/encoding.js +10 -0
  9. package/lib/feature-flags.d.ts +9 -0
  10. package/lib/feature-flags.js +19 -0
  11. package/lib/index.d.ts +4 -4
  12. package/lib/index.js +4 -4
  13. package/lib/jwks.d.ts +8 -8
  14. package/lib/jwks.js +38 -17
  15. package/lib/launch/destination.d.ts +14 -0
  16. package/lib/launch/destination.js +89 -0
  17. package/lib/launch/device.d.ts +30 -0
  18. package/lib/launch/device.js +168 -0
  19. package/lib/launch/environment.d.ts +224 -0
  20. package/lib/launch/environment.js +98 -0
  21. package/lib/launch/errors.d.ts +20 -0
  22. package/lib/launch/errors.js +25 -0
  23. package/lib/launch/extension.d.ts +42 -0
  24. package/lib/launch/extension.js +72 -0
  25. package/lib/launch/flow.d.ts +82 -0
  26. package/lib/launch/flow.js +222 -0
  27. package/lib/launch/index.d.ts +16 -0
  28. package/lib/launch/index.js +12 -0
  29. package/lib/launch/mobile.d.ts +77 -0
  30. package/lib/launch/mobile.js +124 -0
  31. package/lib/launch/modal.d.ts +50 -0
  32. package/lib/launch/modal.js +258 -0
  33. package/lib/launch/options.d.ts +74 -0
  34. package/lib/launch/options.js +20 -0
  35. package/lib/launch/portal.d.ts +28 -0
  36. package/lib/launch/portal.js +68 -0
  37. package/lib/launch/qr.d.ts +5 -0
  38. package/lib/launch/qr.js +28 -0
  39. package/lib/launch/target.d.ts +46 -0
  40. package/lib/launch/target.js +128 -0
  41. package/lib/launch/vendor/qrcodegen.d.ts +134 -0
  42. package/lib/launch/vendor/qrcodegen.js +822 -0
  43. package/lib/legacy-proof.d.ts +2 -0
  44. package/lib/legacy-proof.js +37 -7
  45. package/lib/proof-hash.d.ts +5 -5
  46. package/lib/proof-hash.js +1 -1
  47. package/lib/proof-recipe.d.ts +3 -3
  48. package/lib/proof-recipe.js +1 -1
  49. package/lib/proof-types.d.ts +35 -8
  50. package/lib/reclaim.d.ts +81 -70
  51. package/lib/reclaim.js +79 -130
  52. package/lib/request-proof.d.ts +60 -0
  53. package/lib/request-proof.js +241 -0
  54. package/lib/result.d.ts +46 -38
  55. package/lib/result.js +160 -100
  56. package/lib/routes.gen.d.ts +76 -28
  57. package/lib/routes.gen.js +24 -11
  58. package/lib/tee-binding.d.ts +1 -1
  59. package/lib/types/openapi.d.ts +1 -0
  60. package/lib/types/openapi.gen.d.ts +1594 -488
  61. package/lib/types/openapi.gen.js +5 -0
  62. package/lib/verification-clients.d.ts +28 -14
  63. package/lib/verification-clients.js +31 -11
  64. package/lib/verify-attestor-tee.d.ts +19 -2
  65. package/lib/verify-attestor-tee.js +40 -4
  66. package/lib/verify-tee.d.ts +10 -1
  67. package/lib/verify-tee.js +34 -10
  68. package/package.json +5 -1
  69. package/src/api.ts +9 -0
  70. package/src/client.ts +2 -0
  71. package/src/enckeys.ts +2 -2
  72. package/src/encoding.ts +12 -0
  73. package/src/feature-flags.ts +39 -0
  74. package/src/index.ts +13 -14
  75. package/src/jwks.ts +52 -30
  76. package/src/launch/destination.ts +111 -0
  77. package/src/launch/device.ts +242 -0
  78. package/src/launch/environment.ts +337 -0
  79. package/src/launch/errors.ts +49 -0
  80. package/src/launch/extension.ts +124 -0
  81. package/src/launch/flow.ts +369 -0
  82. package/src/launch/index.ts +42 -0
  83. package/src/launch/mobile.ts +184 -0
  84. package/src/launch/modal.ts +378 -0
  85. package/src/launch/options.ts +99 -0
  86. package/src/launch/portal.ts +104 -0
  87. package/src/launch/qr.ts +37 -0
  88. package/src/launch/target.ts +150 -0
  89. package/src/launch/vendor/qrcodegen.ts +1034 -0
  90. package/src/proof-hash.ts +6 -6
  91. package/src/proof-recipe.ts +3 -3
  92. package/src/proof-types.ts +36 -8
  93. package/src/reclaim.ts +185 -218
  94. package/src/{legacy-proof.ts → request-proof.ts} +81 -36
  95. package/src/result.ts +238 -172
  96. package/src/routes.gen.ts +24 -11
  97. package/src/tee-binding.ts +1 -1
  98. package/src/types/openapi.gen.ts +1599 -488
  99. package/src/types/openapi.ts +3 -1
  100. package/src/verification-clients.ts +33 -19
  101. package/src/verify-attestor-tee.ts +66 -8
  102. package/src/verify-tee.ts +44 -13
  103. package/lib/api-client.d.ts +0 -31
  104. package/lib/api-client.js +0 -112
  105. package/lib/api-consts.d.ts +0 -4
  106. package/lib/api-consts.js +0 -4
  107. package/lib/api-errors.d.ts +0 -13
  108. package/lib/api-errors.js +0 -30
  109. package/lib/eip712.d.ts +0 -20
  110. package/lib/eip712.js +0 -108
@@ -83,6 +83,8 @@ export type ProblemDetails = components['schemas']['Problem'] & {
83
83
  // `@reclaimprotocol/client` instead of reaching into the generated file.
84
84
  export type VerificationSession =
85
85
  components['schemas']['VerificationSession']
86
+ export type VerificationRuntimeConfig =
87
+ components['schemas']['VerificationRuntimeConfig']
86
88
  export type VerificationStatus = components['schemas']['VerificationStatus']
87
89
  export type VerificationEventRecord =
88
90
  components['schemas']['VerificationEventRecord']
@@ -105,4 +107,4 @@ export type OrgTokenMeta = components['schemas']['OrgTokenMeta']
105
107
  export type Organization = components['schemas']['Organization']
106
108
  export type CreateProviderVersionRequest =
107
109
  components['schemas']['CreateProviderVersionRequest']
108
- export type ProviderVersionRef = components['schemas']['ProviderVersionRef']
110
+ export type ProviderVersionRef = components['schemas']['ProviderVersionRef']
@@ -1,22 +1,36 @@
1
+ import { DEFAULT_BASE_URL } from './consts.ts'
2
+
1
3
  /**
2
- * Stable names for the Verification Clients seeded by Builder.
4
+ * Production Verification Client URLs. Use `custom(uri)` for a separately
5
+ * registered client or a non-production Builder deployment.
6
+ *
7
+ * Builder resolves a session's `verificationClientUrl` by EXACT string match
8
+ * against the registered `uri` and 400s for anything unregistered — these
9
+ * constants MUST track `packages/app/src/verification/builtin-clients.ts`'s
10
+ * `clientUrl()` seed exactly, or a consumer passing one of these gets a
11
+ * failed session creation instead of a warning (see
12
+ * `tests/verification-clients.test.ts`, which pins all six against that
13
+ * seed).
3
14
  *
4
- * A name identifies a registry record; session creation still selects a
5
- * client with that deployment's exact registered URL.
15
+ * As of migration `20260917130000000.share-routes-uris.sql`, all six
16
+ * built-ins (`builder` included) share one uniform shape,
17
+ * `{origin}/share/{name}` — what each `/share/{name}` page actually does
18
+ * differs (a redirect for `builder`/`portals`, the QR/App-Clip/deep-link
19
+ * launcher for `verifier-app`/`reclaimBrowserExtension`, a static
20
+ * informational page for `inAppSdk`/`zkFetch` — see
21
+ * `packages/app/src/routes/SharePage.tsx`), but the registry entry is
22
+ * uniform. `../launch/target.ts`'s `parseFlowUrl` is what turns one of these
23
+ * back into a client.
6
24
  */
7
- export const KNOWN_VERIFICATION_CLIENTS = {
8
- builder: 'builder',
9
- portals: 'portals',
10
- verifierApp: 'verifier-app',
11
- inAppSdk: 'inapp-sdk',
12
- reclaimBrowserExtension: 'reclaim-browser-extension',
13
- zkFetch: 'zkfetch',
14
- } as const
15
-
16
- export type KnownVerificationClientName =
17
- typeof KNOWN_VERIFICATION_CLIENTS[keyof typeof KNOWN_VERIFICATION_CLIENTS]
18
-
19
- /** Deployment-specific registered URLs keyed by a known client name. */
20
- export type KnownVerificationClientUrlMap = Partial<
21
- Record<KnownVerificationClientName, string>
22
- >
25
+ export const VerificationClient = Object.freeze({
26
+ builder: `${DEFAULT_BASE_URL}/share/builder`,
27
+ portals: `${DEFAULT_BASE_URL}/share/portals`,
28
+ verifierApp: `${DEFAULT_BASE_URL}/share/verifier-app`,
29
+ inAppSdk: `${DEFAULT_BASE_URL}/share/inapp-sdk`,
30
+ reclaimBrowserExtension:
31
+ `${DEFAULT_BASE_URL}/share/reclaim-browser-extension`,
32
+ zkFetch: `${DEFAULT_BASE_URL}/share/zkfetch`,
33
+ custom(uri: string) {
34
+ return uri
35
+ },
36
+ })
@@ -7,9 +7,10 @@ import {
7
7
  GCP_CONFIDENTIAL_SPACE_ISSUER,
8
8
  GCP_CONFIDENTIAL_SPACE_ROOT_CA,
9
9
  } from './proof-trust-constants.ts'
10
- import type {
11
- AttestorClaimAttestation,
12
- LegacyProof as Proof
10
+ import {
11
+ type AttestorClaimAttestation,
12
+ type ClaimAttestationType,
13
+ type ReclaimRequestProof as Proof
13
14
  } from './proof-types.ts'
14
15
  import { legacyErrorClass } from './proof-verification-error.ts'
15
16
  const AttestorTeeVerificationError = legacyErrorClass('AttestorTeeVerificationError')
@@ -238,6 +239,23 @@ export type AttestorTeeAttestationConfig = {
238
239
  expectedImageDigests?: string[]
239
240
  }
240
241
 
242
+ /** A witness's attestation kind. Required: every client sets it explicitly. */
243
+ export function claimAttestationType(
244
+ att: AttestorClaimAttestation,
245
+ ): ClaimAttestationType {
246
+ return att.type
247
+ }
248
+
249
+ /** What `runAttestorTeeVerification` actually checked, per witness. */
250
+ export interface AttestorTeeVerificationSummary {
251
+ /** Witnesses whose `attestation_report` was verified (and pinned, when
252
+ * `expectedImageDigests` was set). */
253
+ verified: number
254
+ /** Witnesses that carried no `attestation_report`. Their attestor address
255
+ * and claim signature were still verified. */
256
+ unattested: number
257
+ }
258
+
241
259
  function normalizeAttestorAddress(address: string): string {
242
260
  return address.trim().toLowerCase()
243
261
  }
@@ -248,13 +266,16 @@ function normalizeSignature(sig: string): string {
248
266
 
249
267
  async function verifyAttestorTeeForProof(
250
268
  proof: Proof,
251
- config: AttestorTeeAttestationConfig
269
+ config: AttestorTeeAttestationConfig,
270
+ summary: AttestorTeeVerificationSummary,
252
271
  ): Promise<void> {
253
272
  if(!proof.witnesses || proof.witnesses.length === 0) {
254
273
  throw new AttestorTeeVerificationError('Proof has no witnesses')
255
274
  }
256
275
 
257
- const expectedDigests = config.expectedImageDigests?.map(d => d.trim())
276
+ const expectedDigests = config.expectedImageDigests?.map(
277
+ (digest) => digest.trim().toLowerCase(),
278
+ )
258
279
 
259
280
  const proofSignatures = new Set((proof.signatures || []).map(normalizeSignature))
260
281
  const claimSignData = createSignDataForClaim(proof.claimData)
@@ -295,6 +316,28 @@ async function verifyAttestorTeeForProof(
295
316
  )
296
317
  }
297
318
 
319
+ // A `confidential-space` witness (portals) proves its runtime through
320
+ // `tee_attestation`, which `runTeeVerification` checks against
321
+ // `context.attestationNonce` — a different chain with a different nonce
322
+ // binding. The address and signature bindings above still applied; the
323
+ // attestor-report checks below are not this type's evidence.
324
+ if(claimAttestationType(att) === 'confidential-space') {
325
+ summary.unattested++
326
+ continue
327
+ }
328
+
329
+ // An attestor that signs claims without returning its own Confidential
330
+ // Space attestation is the common production shape — the in-app SDK
331
+ // (via reclaim-tee-operator-flutter) emits witnesses carrying only
332
+ // `attestor_address` and `claim_signature`. The bindings above are what
333
+ // tie the claim to a trusted attestor address; the report, when present,
334
+ // additionally proves that attestor ran a pinned image. Absent one,
335
+ // there is nothing to check and nothing to pin.
336
+ if(!att.attestation_report) {
337
+ summary.unattested++
338
+ continue
339
+ }
340
+
298
341
  const result = await verifyAttestorTeeAttestation(att.attestation_report, witness.id)
299
342
  if(!result.isVerified) {
300
343
  throw new AttestorTeeVerificationError(
@@ -302,6 +345,8 @@ async function verifyAttestorTeeForProof(
302
345
  )
303
346
  }
304
347
 
348
+ summary.verified++
349
+
305
350
  if(expectedDigests && expectedDigests.length > 0) {
306
351
  if(!result.imageDigest) {
307
352
  throw new AttestorTeeVerificationError(
@@ -309,7 +354,7 @@ async function verifyAttestorTeeForProof(
309
354
  )
310
355
  }
311
356
 
312
- if(!expectedDigests.includes(result.imageDigest)) {
357
+ if(!expectedDigests.includes(result.imageDigest.trim().toLowerCase())) {
313
358
  throw new AttestorTeeVerificationError(
314
359
  `Attestor image digest ${result.imageDigest} for witness ${witness.id} is not in expectedImageDigests`
315
360
  )
@@ -329,12 +374,18 @@ async function verifyAttestorTeeForProof(
329
374
  * 3. Recovers the signer of `claim_signature` from the claim data and
330
375
  * asserts it equals `witness.id` (binds the signature to the
331
376
  * attestor that the TEE attestation will cover).
377
+ *
378
+ * Steps 4 and 5 apply only to a witness that carries an
379
+ * `attestation_report`, which is optional — see `AttestorClaimAttestation`:
332
380
  * 4. Calls `verifyAttestorTeeAttestation` to validate the JWT against
333
381
  * the pinned GCP Confidential Space Root CA and the attestor-key
334
382
  * nonce.
335
383
  * 5. If `expectedImageDigests` is provided, asserts the attestation's
336
384
  * container image digest is in the allowlist.
337
385
  *
386
+ * The returned summary says which witnesses were attested, so a caller can
387
+ * tell "every report checked out" from "there were no reports to check".
388
+ *
338
389
  * Node-only (uses node:crypto), like `verifyTeeAttestation`.
339
390
  *
340
391
  * @param proofs - The proofs to verify.
@@ -343,15 +394,22 @@ async function verifyAttestorTeeForProof(
343
394
  export async function runAttestorTeeVerification(
344
395
  proofs: Proof[],
345
396
  config: AttestorTeeAttestationConfig = {}
346
- ): Promise<void> {
397
+ ): Promise<AttestorTeeVerificationSummary> {
347
398
  if(!proofs || proofs.length === 0) {
348
399
  throw new AttestorTeeVerificationError('No proofs provided for attestor TEE verification')
349
400
  }
350
401
 
402
+ const summary: AttestorTeeVerificationSummary = {
403
+ verified: 0,
404
+ unattested: 0,
405
+ }
406
+
351
407
  try {
352
408
  for(const proof of proofs) {
353
- await verifyAttestorTeeForProof(proof, config)
409
+ await verifyAttestorTeeForProof(proof, config, summary)
354
410
  }
411
+
412
+ return summary
355
413
  } catch(error) {
356
414
  if(error instanceof AttestorTeeVerificationError) {
357
415
  throw error
package/src/verify-tee.ts CHANGED
@@ -3,7 +3,10 @@ import { keccak_256 } from '@noble/hashes/sha3.js'
3
3
  import { webcrypto } from 'node:crypto'
4
4
  import { recoverEip191Address } from './eip191.ts'
5
5
  import { GCP_CONFIDENTIAL_SPACE_ISSUER } from './proof-trust-constants.ts'
6
- import type { LegacyProof as Proof, TeeAttestation } from './proof-types.ts'
6
+ import type {
7
+ ReclaimRequestProof as Proof,
8
+ TeeAttestation
9
+ } from './proof-types.ts'
7
10
  import { legacyErrorClass } from './proof-verification-error.ts'
8
11
  import {
9
12
  deriveLegacyTeeNonce,
@@ -352,21 +355,28 @@ function getProofVersion(teeAttestation: TeeAttestation): string | undefined {
352
355
  return teeAttestation.proof_version ?? teeAttestation.proofVersion
353
356
  }
354
357
 
355
- function assertProofShape(teeAttestation: TeeAttestation) {
356
- if(teeAttestation.error) {
357
- throw new Error(`${teeAttestation.error.code}: ${teeAttestation.error.message}`)
358
+ function assertProofShape(
359
+ teeAttestation: Record<string, unknown>,
360
+ ): asserts teeAttestation is Record<string, unknown> & TeeAttestation {
361
+ const reportedError = teeAttestation.error
362
+ if(isRecord(reportedError)) {
363
+ throw new Error(`${reportedError.code}: ${reportedError.message}`)
358
364
  }
359
365
 
360
- const proofVersion = getProofVersion(teeAttestation)
366
+ const proofVersion = teeAttestation.proof_version
367
+ ?? teeAttestation.proofVersion
361
368
  assert(typeof proofVersion === 'string' && SUPPORTED_PROOF_VERSIONS.includes(proofVersion), `unexpected proof version: ${proofVersion}`)
362
369
  assert(teeAttestation.tee_provider === EXPECTED_TEE_PROVIDER, `unexpected tee provider: ${teeAttestation.tee_provider}`)
363
370
  assert(teeAttestation.tee_technology === EXPECTED_TEE_TECHNOLOGY, `unexpected tee technology: ${teeAttestation.tee_technology}`)
364
371
  assert(typeof teeAttestation.nonce === 'string' && teeAttestation.nonce.length > 0, 'tee attestation nonce missing')
365
372
  assert(typeof teeAttestation.timestamp === 'string' && teeAttestation.timestamp.length > 0, 'tee attestation timestamp missing')
366
373
  assert(!Number.isNaN(Date.parse(teeAttestation.timestamp)), 'tee attestation timestamp is invalid')
367
- assert(typeof teeAttestation.workload?.image_digest === 'string' && teeAttestation.workload.image_digest.length > 0, 'workload image digest missing')
368
- assert(typeof teeAttestation.verifier?.image_digest === 'string' && teeAttestation.verifier.image_digest.length > 0, 'verifier image digest missing')
369
- assert(typeof teeAttestation.attestation?.token === 'string' && teeAttestation.attestation.token.length > 0, 'attestation token missing')
374
+ assert(isRecord(teeAttestation.workload), 'workload missing')
375
+ assert(typeof teeAttestation.workload.image_digest === 'string' && teeAttestation.workload.image_digest.length > 0, 'workload image digest missing')
376
+ assert(isRecord(teeAttestation.verifier), 'verifier missing')
377
+ assert(typeof teeAttestation.verifier.image_digest === 'string' && teeAttestation.verifier.image_digest.length > 0, 'verifier image digest missing')
378
+ assert(isRecord(teeAttestation.attestation), 'attestation missing')
379
+ assert(typeof teeAttestation.attestation.token === 'string' && teeAttestation.attestation.token.length > 0, 'attestation token missing')
370
380
  }
371
381
 
372
382
  async function computeDigestBinding(teeAttestation: TeeAttestation): Promise<string> {
@@ -439,28 +449,30 @@ export async function verifyTeeAttestation(
439
449
  try {
440
450
  const appId = legacyApplicationIdFromPrivateKey(appSecret)
441
451
 
442
- let teeAttestation = proof.teeAttestation
452
+ let teeAttestation = teeAttestationOf(proof)
443
453
  if(!teeAttestation) {
444
454
  throw new Error('Missing teeAttestation in proof')
445
455
  }
446
456
 
447
457
  if(typeof teeAttestation === 'string') {
448
- teeAttestation = JSON.parse(teeAttestation) as TeeAttestation
458
+ teeAttestation = JSON.parse(teeAttestation)
449
459
  }
450
460
 
461
+ assert(isRecord(teeAttestation), 'TEE attestation must be an object')
451
462
  assertProofShape(teeAttestation)
463
+ const supportedTeeAttestation = teeAttestation
452
464
 
453
465
  const { parsedContext, nonceDataObj, expectedNonce } = parseProofContext(proof)
454
466
  verifyApplicationAndSessionBinding(proof, parsedContext, nonceDataObj, appId)
455
467
  verifyNonceMaterial(expectedNonce, nonceDataObj, appSecret)
456
468
 
457
469
  const cleanExpectedNonce = normalizeHex(expectedNonce)
458
- const cleanTeeNonce = normalizeHex(teeAttestation.nonce)
470
+ const cleanTeeNonce = normalizeHex(supportedTeeAttestation.nonce)
459
471
  assert(cleanTeeNonce.length > 0, 'TEE attestation nonce is empty')
460
472
  assert(isHex(cleanTeeNonce), 'TEE attestation nonce is not valid hex')
461
473
  assert(cleanTeeNonce === cleanExpectedNonce, `Nonce Mismatch! Expected ${cleanExpectedNonce}, got ${cleanTeeNonce}`)
462
474
 
463
- await verifyGcpClaims(teeAttestation, cleanExpectedNonce)
475
+ await verifyGcpClaims(supportedTeeAttestation, cleanExpectedNonce)
464
476
 
465
477
  return { isVerified: true }
466
478
  } catch(error) {
@@ -479,9 +491,28 @@ export async function verifyTeeAttestation(
479
491
  * @param config - TEE attestation configuration containing the app secret
480
492
  * @throws {TeeVerificationError} When TEE data is missing or verification fails
481
493
  */
494
+ /**
495
+ * The TEE attestation to verify for a proof.
496
+ *
497
+ * Builder-mode proofs carry it on the witness, inside a `confidential-space`
498
+ * `claimAttestation`, so every verification client presents the same envelope.
499
+ * Legacy proofs carry it at the proof's top level. Prefer the witness copy and
500
+ * fall back, so both shapes verify through one path.
501
+ */
502
+ export function teeAttestationOf(proof: Proof): unknown {
503
+ for(const witness of proof.witnesses ?? []) {
504
+ const att = witness.claimAttestation
505
+ if(att?.type === 'confidential-space' && att.tee_attestation) {
506
+ return att.tee_attestation
507
+ }
508
+ }
509
+
510
+ return proof.teeAttestation
511
+ }
512
+
482
513
  export async function runTeeVerification(proofs: Proof[], config: TeeAttestationConfig): Promise<void> {
483
514
  const hasTeeData = proofs.every(proof => {
484
- if(proof.teeAttestation) {
515
+ if(teeAttestationOf(proof)) {
485
516
  return true
486
517
  }
487
518
 
@@ -1,31 +0,0 @@
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
- }
package/lib/api-client.js DELETED
@@ -1,112 +0,0 @@
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();
@@ -1,4 +0,0 @@
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";
package/lib/api-consts.js DELETED
@@ -1,4 +0,0 @@
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';
@@ -1,13 +0,0 @@
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>;
package/lib/api-errors.js DELETED
@@ -1,30 +0,0 @@
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/eip712.d.ts DELETED
@@ -1,20 +0,0 @@
1
- declare const TYPES: {
2
- readonly CreateVerificationSession: readonly [readonly ["providerId", "string"], readonly ["version", "string"], readonly ["context", "string"], readonly ["verificationClientUrl", "string"], readonly ["timestamp", "uint256"]];
3
- readonly CreateCallbackSubscription: readonly [readonly ["callbackUrl", "string"], readonly ["events", "string"], readonly ["publicKey", "string"], readonly ["address", "string"], readonly ["timestamp", "uint256"]];
4
- readonly VerificationAuth: readonly [readonly ["timestamp", "uint256"]];
5
- };
6
- export type Eip712PrimaryType = keyof typeof TYPES;
7
- /** Deterministic JSON via RFC 8785 (JSON Canonicalization Scheme): object
8
- * keys sorted recursively, no whitespace, canonical number/string forms.
9
- * Both signer and verifier canonicalize `context`/`events` this way so the
10
- * string fields hash identically. Single source of truth for canonical
11
- * stringification in the repo — `contentHash` (result binding) uses it too;
12
- * do not reimplement with sorted JSON.stringify. */
13
- export declare function canonicalJson(value: unknown): string;
14
- /** EIP-712 digest: keccak256(0x1901 ‖ domainSeparator ‖ structHash). */
15
- export declare function eip712Digest(primaryType: Eip712PrimaryType, message: Record<string, string | bigint>): Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
16
- /** Recover the signer of a typed message, or null on failure. Used by the
17
- * Builder (`packages/app`) to verify legacy typed-data signatures; the client
18
- * itself no longer signs — all consumer auth is the org bearer token. */
19
- export declare function recoverTypedSigner(primaryType: Eip712PrimaryType, message: Record<string, string | bigint>, signature: string): string | null;
20
- export {};
package/lib/eip712.js DELETED
@@ -1,108 +0,0 @@
1
- /**
2
- * EIP-712 typed-data primitives for the legacy verification-session signature
3
- * scheme — kept for the Builder verifier (`eip712Digest` + `recoverTypedSigner`,
4
- * imported by `packages/app`). The consumer client no longer signs (all auth is
5
- * the org bearer token), so there is no `buildAuthHeaders` here anymore. The
6
- * typed data is tiny (only `string` / `uint256` / `address` fields), so it's
7
- * hashed directly with keccak-256; recovery is standard secp256k1 over the
8
- * digest (`prehash:false`, see ./secp.ts).
9
- */
10
- import { badData } from '@hapi/boom';
11
- import * as canonicalizeMod from 'canonicalize';
12
- import assert from 'node:assert';
13
- import { hexToBytes } from "./encoding.js";
14
- import { keccak_256, recoverAddress } from "./secp.js";
15
- // canonicalize ships as a CJS default export; resolve it across interop
16
- // modes (same dance as ajv-formats in packages/app).
17
- const canonicalize = (canonicalizeMod.default ??
18
- canonicalizeMod);
19
- const DOMAIN = {
20
- name: 'Reclaim Builder',
21
- version: '1',
22
- chainId: 1n,
23
- verifyingContract: '0x0000000000000000000000000000000000000000',
24
- };
25
- const TYPES = {
26
- CreateVerificationSession: [
27
- ['providerId', 'string'],
28
- ['version', 'string'],
29
- ['context', 'string'],
30
- ['verificationClientUrl', 'string'],
31
- ['timestamp', 'uint256'],
32
- ],
33
- CreateCallbackSubscription: [
34
- ['callbackUrl', 'string'],
35
- ['events', 'string'],
36
- ['publicKey', 'string'],
37
- ['address', 'string'],
38
- ['timestamp', 'uint256'],
39
- ],
40
- VerificationAuth: [['timestamp', 'uint256']],
41
- };
42
- /** Deterministic JSON via RFC 8785 (JSON Canonicalization Scheme): object
43
- * keys sorted recursively, no whitespace, canonical number/string forms.
44
- * Both signer and verifier canonicalize `context`/`events` this way so the
45
- * string fields hash identically. Single source of truth for canonical
46
- * stringification in the repo — `contentHash` (result binding) uses it too;
47
- * do not reimplement with sorted JSON.stringify. */
48
- export function canonicalJson(value) {
49
- const out = canonicalize(value);
50
- assert(out !== undefined, badData('value is not JSON-serializable'));
51
- return out;
52
- }
53
- function utf8(s) {
54
- return new TextEncoder().encode(s);
55
- }
56
- function uint256(value) {
57
- const out = new Uint8Array(32);
58
- let v = value;
59
- for (let i = 31; i >= 0 && v > 0n; i--) {
60
- out[i] = Number(v & 0xffn);
61
- v >>= 8n;
62
- }
63
- return out;
64
- }
65
- function addressWord(addr) {
66
- const out = new Uint8Array(32);
67
- out.set(hexToBytes(addr), 12);
68
- return out;
69
- }
70
- function concat(...parts) {
71
- const total = parts.reduce((n, p) => n + p.length, 0);
72
- const out = new Uint8Array(total);
73
- let off = 0;
74
- for (const p of parts) {
75
- out.set(p, off);
76
- off += p.length;
77
- }
78
- return out;
79
- }
80
- function typeHash(primaryType) {
81
- const fields = TYPES[primaryType].map(([n, t]) => `${t} ${n}`).join(',');
82
- return keccak_256(utf8(`${primaryType}(${fields})`));
83
- }
84
- function encodeField(type, value) {
85
- return type === 'string'
86
- ? keccak_256(utf8(value))
87
- : uint256(value);
88
- }
89
- function structHash(primaryType, message) {
90
- const parts = [typeHash(primaryType)];
91
- for (const [name, type] of TYPES[primaryType]) {
92
- parts.push(encodeField(type, message[name]));
93
- }
94
- return keccak_256(concat(...parts));
95
- }
96
- const DOMAIN_SEPARATOR = keccak_256(concat(keccak_256(utf8('EIP712Domain(string name,string version,uint256 chainId,' +
97
- 'address verifyingContract)')), keccak_256(utf8(DOMAIN.name)), keccak_256(utf8(DOMAIN.version)), uint256(DOMAIN.chainId), addressWord(DOMAIN.verifyingContract)));
98
- /** EIP-712 digest: keccak256(0x1901 ‖ domainSeparator ‖ structHash). */
99
- export function eip712Digest(primaryType, message) {
100
- return keccak_256(concat(Uint8Array.from([0x19, 0x01]), DOMAIN_SEPARATOR, structHash(primaryType, message)));
101
- }
102
- /** Recover the signer of a typed message, or null on failure. Used by the
103
- * Builder (`packages/app`) to verify legacy typed-data signatures; the client
104
- * itself no longer signs — all consumer auth is the org bearer token. */
105
- export function recoverTypedSigner(primaryType, message, signature) {
106
- const digest = eip712Digest(primaryType, message);
107
- return recoverAddress(digest, hexToBytes(signature));
108
- }