@burnt-labs/expo-satya-attest 3.0.0 → 3.1.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/PROVIDER_CONFIG.md +36 -12
  3. package/README.md +68 -19
  4. package/android/build.gradle +2 -2
  5. package/android/src/main/java/expo/modules/satyaattest/SatyaAttestModule.kt +104 -25
  6. package/android/src/main/java/expo/modules/satyaattest/SatyaCompositeReplay.kt +39 -0
  7. package/android/src/main/jniLibs/arm64-v8a/libsatya_ffi.so +0 -0
  8. package/android/src/main/jniLibs/armeabi-v7a/libsatya_ffi.so +0 -0
  9. package/android/src/main/jniLibs/x86_64/libsatya_ffi.so +0 -0
  10. package/build/SatyaAttestModule.d.ts +1 -1
  11. package/build/SatyaAttestModule.d.ts.map +1 -1
  12. package/build/SatyaAttestModule.js.map +1 -1
  13. package/build/SatyaAttestModule.web.d.ts +1 -1
  14. package/build/SatyaAttestModule.web.d.ts.map +1 -1
  15. package/build/SatyaAttestModule.web.js +1 -1
  16. package/build/SatyaAttestModule.web.js.map +1 -1
  17. package/build/SatyaMobileClient.d.ts +7 -1
  18. package/build/SatyaMobileClient.d.ts.map +1 -1
  19. package/build/SatyaMobileClient.js +210 -20
  20. package/build/SatyaMobileClient.js.map +1 -1
  21. package/build/artifactMetadata.js +3 -5
  22. package/build/artifactMetadata.js.map +1 -1
  23. package/build/components/providerAttest/compositeCapture.d.ts +9 -3
  24. package/build/components/providerAttest/compositeCapture.d.ts.map +1 -1
  25. package/build/components/providerAttest/compositeCapture.js +84 -6
  26. package/build/components/providerAttest/compositeCapture.js.map +1 -1
  27. package/build/components/providerAttest/useCompositeCaptureController.d.ts +1 -1
  28. package/build/components/providerAttest/useCompositeCaptureController.d.ts.map +1 -1
  29. package/build/components/providerAttest/useCompositeCaptureController.js +14 -2
  30. package/build/components/providerAttest/useCompositeCaptureController.js.map +1 -1
  31. package/build/components/providerAttest/useNativeReplayRunner.d.ts +1 -1
  32. package/build/components/providerAttest/useNativeReplayRunner.d.ts.map +1 -1
  33. package/build/components/providerAttest/useNativeReplayRunner.js +10 -6
  34. package/build/components/providerAttest/useNativeReplayRunner.js.map +1 -1
  35. package/build/nativeReplayArgs.d.ts +8 -0
  36. package/build/nativeReplayArgs.d.ts.map +1 -0
  37. package/build/nativeReplayArgs.js +14 -0
  38. package/build/nativeReplayArgs.js.map +1 -0
  39. package/build/satyaConfig.d.ts +4 -0
  40. package/build/satyaConfig.d.ts.map +1 -1
  41. package/build/satyaConfig.js +34 -1
  42. package/build/satyaConfig.js.map +1 -1
  43. package/build/sdkMetadata.generated.d.ts +1 -1
  44. package/build/sdkMetadata.generated.js +1 -1
  45. package/build/sdkMetadata.generated.js.map +1 -1
  46. package/build/types/mobile.types.d.ts +4 -0
  47. package/build/types/mobile.types.d.ts.map +1 -1
  48. package/build/types/mobile.types.js.map +1 -1
  49. package/ios/Frameworks/libsatya_ffi-rs.xcframework/Info.plist +5 -5
  50. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/libsatya_ffi.a +0 -0
  51. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64_x86_64-simulator/libsatya_ffi.a +0 -0
  52. package/ios/SatyaAttest.podspec +1 -1
  53. package/ios/SatyaAttestModule.swift +109 -20
  54. package/ios/SatyaCompositeReplay.swift +43 -0
  55. package/package.json +2 -2
  56. package/src/SatyaAttestModule.ts +2 -1
  57. package/src/SatyaAttestModule.web.ts +2 -1
  58. package/src/SatyaMobileClient.ts +287 -28
  59. package/src/artifactMetadata.ts +3 -5
  60. package/src/components/providerAttest/compositeCapture.ts +111 -8
  61. package/src/components/providerAttest/useCompositeCaptureController.ts +18 -3
  62. package/src/components/providerAttest/useNativeReplayRunner.ts +17 -6
  63. package/src/nativeReplayArgs.ts +21 -0
  64. package/src/satyaConfig.ts +52 -1
  65. package/src/sdkMetadata.generated.ts +1 -1
  66. package/src/types/mobile.types.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 3.1.0 - 2026-07-31
6
+
7
+ ### Added
8
+
9
+ - **Provider-selective partial endpoints (BUR-150).** Apps opt in exact providers
10
+ with `initSatyaAttest({ partialEndpointProviderIds: ['provider.id'] })`. Authenticated
11
+ readiness starts the bounded window when received; otherwise the first accepted capture does.
12
+ The SDK attempts the full plan; at the deadline it retains every confirmed
13
+ 2xx endpoint and fails if none succeeded. Provider config and policy hashing
14
+ are unchanged.
15
+ - Schema `2.2.0` binds an explicit partial-completion mode and exact ordered endpoint subset
16
+ into the device-signed statement, semantic hash, aggregate exchange root, and a new
17
+ proof-challenge domain. The verifier validates the subset directly against the current full
18
+ registry proof plan, including policy hash, order, membership, and account-binding
19
+ compatibility. Schemas
20
+ `2.0.0` and strict-all app-bound `2.1.0` remain unchanged.
21
+ - The managed component and public low-level client always verify partial-endpoint artifacts
22
+ online because only the backend has the trusted full provider plan. Strict-all flows continue
23
+ to honor the component's `verifyBackend` setting. Native/local verification returns
24
+ `backend-required` for schema `2.2.0`. Verifier results expose completed and omitted endpoint
25
+ ids plus `planComplete`; relying parties that require the full plan must require
26
+ `planComplete: true`.
27
+ - A stable `no-endpoints-captured` outcome when no endpoint succeeds, plus
28
+ `unconfirmedEndpointIds` diagnostics.
29
+
3
30
  ## 3.0.0 - 2026-07-28
4
31
 
5
32
  Multi-tenant app gating (BREAKING). Verification is now gated per app: every
@@ -425,9 +425,9 @@ accepting an artifact.
425
425
  ## 8.1 `proofPlans[]` (optional — composite proofs)
426
426
 
427
427
  A **proof plan** gathers claims from several governed endpoints in one authenticated
428
- provider session as a single all-or-nothing artifact (schema v2). The app selects a plan
429
- by id (`proofPlanId`), never by URL or an arbitrary endpoint list — every endpoint the
430
- plan replays is a pre-approved `endpoints[]` entry.
428
+ provider session as a single artifact (schema v2). The plan always declares the full
429
+ attempt set. The app selects a plan by id (`proofPlanId`), never by URL or an arbitrary
430
+ endpoint list — every endpoint the plan replays is a pre-approved `endpoints[]` entry.
431
431
 
432
432
  ```json
433
433
  "proofPlans": [
@@ -443,10 +443,32 @@ plan replays is a pre-approved `endpoints[]` entry.
443
443
  | Field | Req? | Type | Description |
444
444
  |---|---|---|---|
445
445
  | `id` | yes | `string` | Plan id. Same character rule as endpoint `id` (starts with a letter; letters/digits/`_`/`-`). Unique per provider. Selected via the `proofPlanId` prop, which is **mutually exclusive** with `endpointId`. |
446
- | `endpointIds` | yes | `string[]` | Ordered, unique set of **1–8** required endpoint ids, each present in `endpoints[]`. Order is order-sensitive and participates in `policyHash`. |
447
- | `maxDurationMs` | yes | `int` | Composite window bound, integer in `[1000, 120000]`. The window starts at the first required-endpoint capture (interactive login before it is excluded) and ends when the challenge is prepared. |
446
+ | `endpointIds` | yes | `string[]` | Ordered, unique set of **1–8** endpoint ids, each present in `endpoints[]`. Order is order-sensitive and participates in `policyHash`. Strict-all requires every endpoint. |
447
+ | `maxDurationMs` | yes | `int` | Composite window bound, integer in `[1000, 120000]`. For partial-enabled sessions, authenticated `__satyaReady` starts the window when received; otherwise the first accepted capture starts it. |
448
448
  | `accountBinding` | yes | `object` | How the artifact proves the endpoints belong to one account. `shared-session` (all endpoints replayed under one proof-local cookie jar) or `claim-equality` (named claims must be equal across endpoints). |
449
449
 
450
+ **SDK partial completion (BUR-150) — no provider change**
451
+
452
+ Partial completion is enabled for exact provider ids with
453
+ `initSatyaAttest({ partialEndpointProviderIds: ['provider.id'] })`. Do not add
454
+ `completionPolicy`, endpoint permutations, minimum counts, or any other partial-completion
455
+ field to the provider. There is no corresponding Devtool, Provider Registry, or verifier
456
+ configuration. An empty SDK list leaves the provider strict-all.
457
+
458
+ The capture layer attempts every plan endpoint through `maxDurationMs`. If the complete plan
459
+ succeeds, native replay follows the existing strict-all path. Otherwise, when the window expires,
460
+ it passes every endpoint with a confirmed 2xx response to native replay and fails if none
461
+ succeeded. Native replay may further omit an availability, timeout, non-2xx, or missing-claim
462
+ failure while preserving fatal TLS/SPKI/origin/policy failures. Schema `2.2.0` signs the explicit
463
+ `partial-endpoint-subset` mode and binds the exact ordered successful endpoints through the
464
+ semantic hash, exchange root, and platform proof challenge.
465
+
466
+ The verifier validates the non-empty proper subset directly against the current full registry
467
+ proof plan. Unknown, duplicate, reordered, full-sized partial, and account-binding-breaking
468
+ subsets are rejected. Relying parties that require the complete plan must require
469
+ `planComplete: true`; `ok: true` alone means the presented subset is valid, not that every
470
+ configured endpoint completed.
471
+
450
472
  **`accountBinding` modes**
451
473
 
452
474
  ```json
@@ -471,11 +493,11 @@ rejected after alias resolution.
471
493
 
472
494
  **Policy hash.** The Provider Registry backend canonicalizes the complete plan object
473
495
  into `policyHash`: the plan `id`, the **ordered** `endpointIds`, each referenced endpoint's
474
- complete policy, `maxDurationMs`, and
475
- the `accountBinding` rule. The byte/count/time caps are fixed by the schema-v2 compatibility
496
+ complete policy, `maxDurationMs`, and the `accountBinding` rule. Partial completion adds no
497
+ provider field or policy-hash input. The byte/count/time caps are fixed by the schema-v2 compatibility
476
498
  profile and are not part of the plan object. The verifier recomputes the policy hash, loads
477
499
  the same plan independently, and rejects the whole artifact if the hash or any exchange,
478
- binding rule, or aggregate invariant fails.
500
+ binding rule, subset constraint, or aggregate invariant fails.
479
501
 
480
502
  > Composite proofs are a signed, bounded **sequential** observation, not an atomic snapshot
481
503
  > of the provider's database. Per-exchange completion times remain visible to verifier
@@ -515,10 +537,10 @@ whether naturally observed or triggered. Enable it when a logged-out response co
515
537
  match the endpoint rules.
516
538
 
517
539
  The WebView readiness flag is document-scoped: injection must emit `__satyaReady` on each
518
- authenticated document instead of persisting it across navigation. For composite plans, login
519
- remains unbounded; `proofPlans[].maxDurationMs` starts at the first accepted endpoint capture.
520
- The SDK then waits through that full interval for all remaining endpoints before failing the
521
- unresolved plan.
540
+ authenticated document instead of persisting it across navigation. In a partial-enabled session,
541
+ the signal starts the bounded window; without it, the first accepted ungated endpoint capture does.
542
+ Expiry retains every confirmed 2xx endpoint or fails with `no-endpoints-captured` when none
543
+ succeeded (§8.1). Strict-all retains its first-capture window behavior.
522
544
 
523
545
  **Provider concealment after login.** Once the SDK receives the `__satyaReady` login signal it
524
546
  covers the provider WebView with a verification screen automatically: the user sees the login
@@ -598,6 +620,8 @@ console.log(body.match(/octolytics-actor-login" content="(.*?)"/)?.[1]);
598
620
  - `capture` (when present) is a non-empty object whose only keys are `trigger`, `headers`,
599
621
  `nativeReplayHeaders`, `requestBody`, `landingUrl`, `pollMs`, `maxAttempts`,
600
622
  `requireLoginSignal`, with the bounds in §6,
623
+ - unsupported proof-plan fields such as `completionPolicy` are rejected; partial completion
624
+ is an SDK/verifier session option (§8.1),
601
625
  - `injection` is a non-empty string / non-empty string array when present; `login.injection`
602
626
  and endpoint `injection` are rejected,
603
627
  - raw `allowedRequestHeaders`, `allowedResponseBodyJsonPaths`, and `privacyPolicy` are
package/README.md CHANGED
@@ -80,7 +80,7 @@ New SDK artifacts also include envelope metadata:
80
80
  "attestationIdVersion": "statement-sha256-96-v1",
81
81
  "producer": {
82
82
  "sdk": "@burnt-labs/expo-satya-attest",
83
- "sdkVersion": "3.0.0",
83
+ "sdkVersion": "3.1.0",
84
84
  "artifactSchemaVersion": "1.1.0",
85
85
  "coreVersion": "0.1.0"
86
86
  }
@@ -227,7 +227,7 @@ is a release tarball produced by `npm pack`, because it contains the same files
227
227
  would publish:
228
228
 
229
229
  ```bash
230
- npm install https://github.com/<owner>/<repo>/releases/download/expo-satya-attest-v2.1.0/burnt-labs-expo-satya-attest-2.1.0.tgz
230
+ npm install https://github.com/<owner>/<repo>/releases/download/expo-satya-attest-v3.1.0/burnt-labs-expo-satya-attest-3.1.0.tgz
231
231
  npx expo install react-native-webview react-native-safe-area-context
232
232
  ```
233
233
 
@@ -235,8 +235,8 @@ Installing from a Git ref also works when that ref has the SDK package at the re
235
235
  root, such as a dedicated SDK repo or a subtree/split branch:
236
236
 
237
237
  ```bash
238
- npm install github:<owner>/<sdk-repo>#v2.1.0
239
- npm install git+https://github.com/<owner>/<sdk-repo>.git#v2.1.0
238
+ npm install github:<owner>/<sdk-repo>#v3.1.0
239
+ npm install git+https://github.com/<owner>/<sdk-repo>.git#v3.1.0
240
240
  npx expo install react-native-webview react-native-safe-area-context
241
241
  ```
242
242
 
@@ -491,19 +491,66 @@ effect, then render the hidden launcher:
491
491
  ## Composite Proofs
492
492
 
493
493
  A **composite proof** attests claims from several governed endpoints gathered in **one**
494
- authenticated provider session, bound into a single all-or-nothing artifact (schema
495
- `2.0.0`). Instead of a single `endpointId`, the app passes a `proofPlanId` — a
494
+ authenticated provider session. Strict-all artifacts are schema `2.0.0` (legacy) or
495
+ app-bound `2.1.0`; signed partial-endpoint artifacts use `2.2.0`. Instead of a
496
+ single `endpointId`, the app passes a `proofPlanId` — a
496
497
  registry-defined **proof plan** that selects an ordered set of **1–8** required endpoint
497
498
  ids. The app never supplies URLs or an endpoint list; every endpoint the plan replays is a
498
499
  pre-approved `endpoints[]` entry, and the plan is folded into the provider `policyHash`.
499
500
 
501
+ ### Provider-selective partial endpoints
502
+
503
+ Some providers cannot guarantee that every proof-plan endpoint fires in every session. An app
504
+ may opt exact provider ids into partial completion without changing provider or proof-plan config:
505
+
506
+ ```ts
507
+ initSatyaAttest({
508
+ partialEndpointProviderIds: ['provider.identity.v1'],
509
+ });
510
+ ```
511
+
512
+ Matching is exact and case-sensitive; entries are never treated as patterns. An empty or
513
+ omitted list keeps every provider strict-all. There is no matching Provider Registry, Devtool,
514
+ or verifier setting. The SDK list is an app availability preference; the backend remains the
515
+ security authority.
516
+
517
+ An authenticated `__satyaReady` signal starts the capture window and releases
518
+ readiness-gated endpoints. Without it, the first accepted ungated capture starts the window.
519
+ The capture layer attempts the full plan until `maxDurationMs`. Full capture success stays on the
520
+ existing strict-all path. Otherwise, it sends the confirmed 2xx request subset to native replay,
521
+ which requires at least one successful exchange and may further omit availability, timeout,
522
+ non-2xx, or missing-claim failures. TLS trust, SPKI, origin, request-policy, and binding failures
523
+ remain fatal.
524
+
525
+ - the signed set must be a non-empty proper subset in proof-plan order;
526
+ - every claim-equality account-binding endpoint must be present in the final subset;
527
+ - schema `2.2.0` signs `completionMode: "partial-endpoint-subset"`; the semantic hash,
528
+ exchange root, and domain-separated proof challenge bind the exact ordered endpoint IDs;
529
+ - schemas `2.0.0` and `2.1.0` have no completion mode and remain strict-all;
530
+ - the verifier directly compares the signed subset with the current full registry proof plan
531
+ and fully verifies every included exchange;
532
+ - both the managed component and public `SatyaMobileClient` API require successful backend
533
+ verification for schema `2.2.0`, even when `verifyBackend` is false;
534
+ - native/local `verifyArtifact*` returns `backend-required` for schema `2.2.0` and is not an
535
+ acceptance verdict;
536
+ - the verifier verdict always distinguishes `strict-all` from `partial-endpoint-subset` and
537
+ returns `endpointIds`, `omittedEndpointIds`, and `planComplete`;
538
+ - relying parties that require full completion must require `planComplete: true` (equivalently,
539
+ `completionMode: "strict-all"` and an empty `omittedEndpointIds`).
540
+
541
+ The verifier proves that every presented exchange is valid and belongs to the configured plan.
542
+ It cannot prove that the omitted endpoints were actually attempted; that is SDK execution
543
+ behavior, not a property recoverable from a subset proof.
544
+
545
+ If the window expires without a successful endpoint, capture fails with
546
+ `no-endpoints-captured`. Timeout diagnostics list `unconfirmedEndpointIds`.
547
+
500
548
  `proofPlanId` is **mutually exclusive** with `endpointId` — pass exactly one. It works on
501
549
  the same `SatyaProviderAttestButton` and low-level client:
502
550
 
503
- Providers without `proofPlans[]` remain single-endpoint compatible: the SDK treats the
504
- plan id as a hint and selects the first entry in `endpoints[]`. If the provider defines
505
- plans, an unknown `proofPlanId` is rejected instead of silently changing the requested
506
- policy.
551
+ Providers without `proofPlans[]` remain single-endpoint compatible when no plan is requested.
552
+ Requesting a `proofPlanId` from a provider with no plans, or requesting an unknown plan id, is
553
+ rejected instead of silently changing the requested policy.
507
554
 
508
555
  ```tsx
509
556
  <SatyaProviderAttestButton
@@ -532,14 +579,15 @@ disabled and only 2xx responses accepted. `onProgress` reports `proofPlanId`, `e
532
579
  - **1–8 endpoints** per plan, ordered; the order is order-sensitive and participates in
533
580
  `policyHash`.
534
581
  - **Per-response 8 MiB** and **aggregate 16 MiB** response-byte caps across the plan.
535
- - The whole plan must complete within the plan's `maxDurationMs` (an integer in
536
- `[1000, 120000]` ms). The window starts at the first required-endpoint capture — the
537
- interactive login before it is excluded.
582
+ - Capture selection runs for at most the plan's `maxDurationMs` (an integer in
583
+ `[1000, 120000]` ms). A readiness signal starts the window when received; otherwise the
584
+ first accepted capture starts it. Later readiness never resets an earlier start.
538
585
 
539
586
  ### What a v2 artifact contains
540
587
 
541
- The Rust core emits a schema-`2.0.0` artifact (`attestationIdVersion`
542
- `statement-sha256-96-v2`) with, per witnessed exchange, its own TLS/handshake commitments,
588
+ The Rust core emits schema `2.0.0` for legacy strict-all, `2.1.0` for app-bound strict-all,
589
+ or `2.2.0` for a signed endpoint subset. Each artifact carries, per witnessed
590
+ exchange, its own TLS/handshake commitments,
543
591
  request/response commitments, record-log evidence, and endpoint-scoped disclosed claims
544
592
  (the same alias may repeat across different endpoint scopes; endpoint provenance is
545
593
  preserved). All exchanges are bound by **one** domain-separated, order- and count-bound
@@ -581,10 +629,11 @@ the preserved single-artifact profile for SDK 1–3, and schema 2 selects
581
629
  `expo-sdk-2-composite` for SDK 2–3. The backend loads the provider, plan, endpoints, reveal allowlists,
582
630
  SNI, pins, freshness, and revocation policy **independently from the registry** (never from
583
631
  the artifact), recomputes the provider's canonical `policyHash`, requires the artifact's
584
- exact hash, and returns a typed verdict with `proofPlanId`, `exchangeCount`, and
585
- `accountBindingAssurance`. Verification is all-or-nothing: any missing, duplicated, unknown,
586
- reordered, failed, or policy-mismatched exchange — or an account-binding, platform-assertion,
587
- freshness, or aggregate-invariant failure — produces **no** artifact.
632
+ exact hash, and returns a typed verdict with `proofPlanId`, `completionMode`, `endpointIds`,
633
+ `omittedEndpointIds`, `planComplete`, `exchangeCount`, and `accountBindingAssurance`.
634
+ Strict-all verification rejects any missing, duplicated, unknown, reordered, failed, or
635
+ policy-mismatched exchange. Partial mode applies the same checks to every endpoint in the
636
+ exact signed subset and validates that subset against the registry's full proof plan.
588
637
 
589
638
  ### Tier and activation gate
590
639
 
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'expo.modules.satyaattest'
7
- version = '3.0.0'
7
+ version = '3.1.0'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.satyaattest"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName "3.0.0"
13
+ versionName "3.1.0"
14
14
  // StrongBox key attestation needs API 28+; basic Keystore attestation API 24+.
15
15
  minSdkVersion 24
16
16
  }
@@ -40,8 +40,11 @@ import java.security.spec.ECGenParameterSpec
40
40
  import java.security.cert.X509Certificate
41
41
  import java.security.KeyStore
42
42
  import java.net.URL
43
+ import java.io.IOException
43
44
  import java.util.concurrent.TimeUnit
44
45
  import javax.net.ssl.SSLContext
46
+ import javax.net.ssl.SSLHandshakeException
47
+ import javax.net.ssl.SSLPeerUnverifiedException
45
48
  import javax.net.ssl.TrustManager
46
49
  import javax.net.ssl.TrustManagerFactory
47
50
  import javax.net.ssl.X509TrustManager
@@ -78,6 +81,9 @@ private const val PLAY_INTEGRITY_COMPOSITE_BIND_DOMAIN = "satya-w-playintegrity-
78
81
 
79
82
  class SatyaAttestModule : Module() {
80
83
  private data class BoundedResponseBody(val bytes: ByteArray, val text: String)
84
+ private data class PartialEndpointSelection(
85
+ val endpointIds: List<String>,
86
+ )
81
87
 
82
88
  @Volatile private var transportConfigJson: String = "{}"
83
89
 
@@ -184,13 +190,20 @@ class SatyaAttestModule : Module() {
184
190
  // exchanges in one attested session, then bind them together with a
185
191
  // device-bound proof challenge. Mirrors attestNativeReplay's shape.
186
192
  AsyncFunction("attestNativeReplayBundle") {
187
- providerTemplateJson: String, capturedRequestsJson: String, promise: Promise ->
193
+ providerTemplateJson: String,
194
+ capturedRequestsJson: String,
195
+ partialEndpointSelectionJson: String?,
196
+ promise: Promise ->
188
197
  try {
189
198
  require(providerTemplateJson.isNotEmpty()) {
190
199
  "attestNativeReplayBundle requires provider template JSON"
191
200
  }
192
201
  val replayBundleJson =
193
- buildCompositeReplayBundle(providerTemplateJson, capturedRequestsJson)
202
+ buildCompositeReplayBundle(
203
+ providerTemplateJson,
204
+ capturedRequestsJson,
205
+ partialEndpointSelectionJson,
206
+ )
194
207
  // Prepare fixes the session id / nonce / issuance window and returns the
195
208
  // exact proof challenge the platform attestation must bind to.
196
209
  val preparedJson = prepareNativeReplayBundle(replayBundleJson)
@@ -480,6 +493,7 @@ class SatyaAttestModule : Module() {
480
493
  private fun buildCompositeReplayBundle(
481
494
  providerTemplateJson: String,
482
495
  capturedRequestsJson: String,
496
+ partialEndpointSelectionJson: String?,
483
497
  ): String {
484
498
  val template = JSONObject(providerTemplateJson)
485
499
  val providerId = template.getString("providerId")
@@ -536,12 +550,23 @@ class SatyaAttestModule : Module() {
536
550
  )
537
551
  }
538
552
 
539
- // Trusted-order resolution; throws for missing/extra BEFORE any network.
553
+ val partialSelection = compositePartialEndpointSelection(partialEndpointSelectionJson)
554
+ require(partialSelection == null || configuredAppId() != null) {
555
+ "partial-endpoint replay requires a configured appId"
556
+ }
557
+
558
+ // The SDK may opt this provider into partial completion. Without an
559
+ // explicit selection, strict-all requires the complete proof plan.
560
+ val effectiveEndpointIds = CompositeCompletion.effectiveEndpointIds(
561
+ planEndpointIds = planEndpointIds,
562
+ capturedEndpointIds = capturedRequests.keys,
563
+ partialEndpointIds = partialSelection?.endpointIds,
564
+ )
540
565
  val orderedRequests =
541
- CompositeReplayPlan(proofPlanId, planEndpointIds).orderedRequests(capturedRequests)
566
+ CompositeReplayPlan(proofPlanId, effectiveEndpointIds).orderedRequests(capturedRequests)
542
567
 
543
568
  // Seed the proof-local jar for every governed replay path.
544
- val cookieSeedUrls = planEndpointIds.map { endpointId ->
569
+ val cookieSeedUrls = effectiveEndpointIds.map { endpointId ->
545
570
  val endpoint = endpointsById[endpointId]
546
571
  ?: error("composite plan endpoint has no template config: $endpointId")
547
572
  val templateUrl = endpoint.optString("url").trim()
@@ -568,9 +593,10 @@ class SatyaAttestModule : Module() {
568
593
  "composite replay start plan=$proofPlanId endpoints=${orderedRequests.size}",
569
594
  )
570
595
  var totalResponseBytes = 0L
571
- val observations =
572
- try {
573
- CompositeOrchestration.replaySequentially(orderedRequests) { exchangeRequest ->
596
+ val observations = ArrayList<CompositeExchangeObservation>(orderedRequests.size)
597
+ try {
598
+ for (exchangeRequest in orderedRequests) {
599
+ try {
574
600
  val endpoint = endpointsById[exchangeRequest.endpointId]
575
601
  ?: error("composite plan endpoint has no template config: ${exchangeRequest.endpointId}")
576
602
  val observation = replayCompositeExchange(
@@ -589,18 +615,33 @@ class SatyaAttestModule : Module() {
589
615
  )
590
616
  totalResponseBytes =
591
617
  CompositeByteBudget.accumulate(totalResponseBytes, observation.responseByteCount)
592
- observation
618
+ observations.add(observation)
619
+ } catch (t: PartialEndpointUnavailableException) {
620
+ if (partialSelection == null) throw t
621
+ Log.i("SatyaComposite", "omitting unavailable endpoint ${exchangeRequest.endpointId}")
593
622
  }
594
- } catch (t: Throwable) {
595
- // Any failed exchange aborts the bundle.
596
- Log.e("SatyaComposite", "composite replay ABORTED plan=$proofPlanId: ${t.message}")
597
- throw t
598
623
  }
624
+ } catch (t: Throwable) {
625
+ Log.e("SatyaComposite", "composite replay ABORTED plan=$proofPlanId: ${t.message}")
626
+ throw t
627
+ }
599
628
  val completedAtUtc = iso8601UtcNow()
600
629
  val elapsedMs = SystemClock.elapsedRealtime() - startedAtMonotonicMs
601
- if (maxDurationMs > 0 && elapsedMs > maxDurationMs) {
630
+ if (partialSelection == null && maxDurationMs > 0 && elapsedMs > maxDurationMs) {
602
631
  error("composite replay exceeded plan maxDurationMs ($elapsedMs > $maxDurationMs)")
603
632
  }
633
+ require(observations.isNotEmpty()) {
634
+ "partial-endpoint replay requires at least one successful endpoint"
635
+ }
636
+ val completedEndpointIds = observations.map { it.endpointId }.toSet()
637
+ if (observations.size < planEndpointIds.size && accountBinding.optString("mode") == "claim-equality") {
638
+ val references = accountBinding.optJSONArray("claimReferences") ?: JSONArray()
639
+ for (index in 0 until references.length()) {
640
+ require(completedEndpointIds.contains(references.getJSONObject(index).getString("endpointId"))) {
641
+ "partial-endpoint replay omitted an account-binding endpoint"
642
+ }
643
+ }
644
+ }
604
645
  Log.i(
605
646
  "SatyaComposite",
606
647
  "composite replay complete plan=$proofPlanId exchanges=${observations.size} bytes=$totalResponseBytes",
@@ -613,10 +654,14 @@ class SatyaAttestModule : Module() {
613
654
  put("providerId", providerId)
614
655
  put("policyHash", policyHash)
615
656
  put("proofPlanId", proofPlanId)
616
- put("endpointIds", JSONArray(planEndpointIds))
657
+ // Exact replay set, bound by the signed statement and verifier.
658
+ put("endpointIds", JSONArray(observations.map { it.endpointId }))
659
+ if (observations.size < planEndpointIds.size) {
660
+ put("completionMode", "partial-endpoint-subset")
661
+ }
617
662
  put("accountBinding", accountBinding)
618
663
  put("startedAtUtc", startedAtUtc)
619
- put("completedAtUtc", completedAtUtc)
664
+ put("completedAtUtc", observations.lastOrNull()?.completedAtUtc ?: completedAtUtc)
620
665
  put("maxDurationMs", maxDurationMs)
621
666
  put("totalResponseBytes", totalResponseBytes)
622
667
  put("exchanges", exchangesJson)
@@ -716,18 +761,36 @@ class SatyaAttestModule : Module() {
716
761
  requestBuilder.method(method, body)
717
762
 
718
763
  val remainingMs = deadlineAtMonotonicMs - SystemClock.elapsedRealtime()
719
- require(remainingMs > 0) {
720
- "composite replay exceeded plan maxDurationMs before ${exchangeRequest.endpointId}"
764
+ if (remainingMs <= 0) {
765
+ throw PartialEndpointUnavailableException(
766
+ "composite replay exceeded plan maxDurationMs before ${exchangeRequest.endpointId}",
767
+ )
721
768
  }
722
769
  Log.i("SatyaComposite", "replaying endpoint ${exchangeRequest.endpointId}")
723
770
  val call = client.newCall(requestBuilder.build())
724
771
  call.timeout().timeout(minOf(remainingMs, 45_000L), TimeUnit.MILLISECONDS)
725
- call.execute().use { response ->
726
- require(SystemClock.elapsedRealtime() <= deadlineAtMonotonicMs) {
727
- "composite replay exceeded plan maxDurationMs during ${exchangeRequest.endpointId}"
772
+ val response = try {
773
+ call.execute()
774
+ } catch (t: SSLPeerUnverifiedException) {
775
+ throw t
776
+ } catch (t: SSLHandshakeException) {
777
+ throw t
778
+ } catch (t: IOException) {
779
+ throw PartialEndpointUnavailableException(
780
+ "composite replay transport unavailable: ${exchangeRequest.endpointId}",
781
+ t,
782
+ )
783
+ }
784
+ response.use {
785
+ if (SystemClock.elapsedRealtime() > deadlineAtMonotonicMs) {
786
+ throw PartialEndpointUnavailableException(
787
+ "composite replay exceeded plan maxDurationMs during ${exchangeRequest.endpointId}",
788
+ )
728
789
  }
729
- require(response.code in 200..299) {
730
- "composite replay returned HTTP ${response.code}: ${exchangeRequest.endpointId}"
790
+ if (response.code !in 200..299) {
791
+ throw PartialEndpointUnavailableException(
792
+ "composite replay returned HTTP ${response.code}: ${exchangeRequest.endpointId}",
793
+ )
731
794
  }
732
795
  require(response.request.url.host == expectedSni) {
733
796
  "composite replay final host does not match expectedSni: ${exchangeRequest.endpointId}"
@@ -765,8 +828,10 @@ class SatyaAttestModule : Module() {
765
828
  value = claim.opt("value"),
766
829
  )
767
830
  }
768
- require(claims.isNotEmpty()) {
769
- "composite replay response had no allowed claim value: ${exchangeRequest.endpointId}"
831
+ if (claims.isEmpty()) {
832
+ throw PartialEndpointUnavailableException(
833
+ "composite replay response had no allowed claim value: ${exchangeRequest.endpointId}",
834
+ )
770
835
  }
771
836
  Log.i("SatyaComposite", "endpoint ${exchangeRequest.endpointId} replayed bytes=$byteCount")
772
837
 
@@ -863,6 +928,20 @@ class SatyaAttestModule : Module() {
863
928
  }
864
929
  }
865
930
 
931
+ /** Validates the untrusted SDK-to-native partial-mode selection. */
932
+ private fun compositePartialEndpointSelection(
933
+ rawJson: String?,
934
+ ): PartialEndpointSelection? {
935
+ if (rawJson.isNullOrBlank()) return null
936
+ val selection = JSONObject(rawJson)
937
+ require(selection.getString("mode") == "partial-endpoint-subset") {
938
+ "partial endpoint selection mode is invalid"
939
+ }
940
+ val endpointIds = stringList(selection.optJSONArray("endpointIds"))
941
+ require(endpointIds.isNotEmpty()) { "partial endpoint selection must not be empty" }
942
+ return PartialEndpointSelection(endpointIds = endpointIds)
943
+ }
944
+
866
945
  private fun compositeRevealRules(endpoint: JSONObject): JSONArray {
867
946
  val rules = endpoint.optJSONArray("revealRules") ?: JSONArray()
868
947
  val reveals = JSONArray()
@@ -5,6 +5,10 @@ import okhttp3.CookieJar
5
5
  import okhttp3.HttpUrl
6
6
  import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
7
7
 
8
+ /** Availability/data failure that partial mode may omit without weakening TLS checks. */
9
+ class PartialEndpointUnavailableException(message: String, cause: Throwable? = null) :
10
+ Exception(message, cause)
11
+
8
12
  // Pure, JVM-only types for the sequential composite (schema-v2) replay.
9
13
  //
10
14
  // This file deliberately imports ONLY okhttp3 (a plain JVM library) and the
@@ -89,6 +93,41 @@ class CompositeReplayPlan(
89
93
  }
90
94
  }
91
95
 
96
+ /** Pure partial-endpoint resolution, unit-testable without the Expo runtime. */
97
+ object CompositeCompletion {
98
+ /**
99
+ * Resolves an exact candidate replay set. No selection means strict-all.
100
+ * Partial mode may start with a subset or the full plan so native replay can
101
+ * sign only the successful endpoints.
102
+ */
103
+ fun effectiveEndpointIds(
104
+ planEndpointIds: List<String>,
105
+ capturedEndpointIds: Set<String>,
106
+ partialEndpointIds: List<String>?,
107
+ ): List<String> {
108
+ if (partialEndpointIds == null) {
109
+ require(capturedEndpointIds == planEndpointIds.toSet()) {
110
+ "strict-all replay requires every proof-plan endpoint"
111
+ }
112
+ return planEndpointIds
113
+ }
114
+ require(partialEndpointIds.isNotEmpty() && partialEndpointIds.size <= planEndpointIds.size) {
115
+ "selected endpoints must be a non-empty proof-plan subset"
116
+ }
117
+ val partialSet = partialEndpointIds.toSet()
118
+ require(partialSet.size == partialEndpointIds.size && partialEndpointIds.all(planEndpointIds.toSet()::contains)) {
119
+ "selected endpoints must be unique proof-plan endpoints"
120
+ }
121
+ require(partialEndpointIds == planEndpointIds.filter(partialSet::contains)) {
122
+ "selected endpoints must follow proof-plan order"
123
+ }
124
+ require(capturedEndpointIds == partialSet) {
125
+ "captured endpoints do not exactly match the selected subset"
126
+ }
127
+ return partialEndpointIds
128
+ }
129
+ }
130
+
92
131
  /** Endpoint-scoped disclosed claim, mirroring the Rust `NativeReplayBundleClaim`. */
93
132
  data class CompositeClaim(
94
133
  val name: String,
@@ -3,7 +3,7 @@ import { VerifyResult } from './types';
3
3
  declare class SatyaAttestModule extends NativeModule<Record<string, never>> {
4
4
  satyaCoreVersion(): string;
5
5
  attestNativeReplay(providerTemplateJson: string, capturedRequestJson?: string | null): Promise<string>;
6
- attestNativeReplayBundle(providerTemplateJson: string, capturedRequestsJson: string): Promise<string>;
6
+ attestNativeReplayBundle(providerTemplateJson: string, capturedRequestsJson: string, partialEndpointSelectionJson?: string | null): Promise<string>;
7
7
  verifyArtifact(artifactJson: string): Promise<boolean>;
8
8
  verifyArtifactDetailed(artifactJson: string): Promise<VerifyResult>;
9
9
  initialize(configJson: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaAttestModule.d.ts","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,OAAO,iBAAkB,SAAQ,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACzE,gBAAgB,IAAI,MAAM;IAE1B,kBAAkB,CAChB,oBAAoB,EAAE,MAAM,EAC5B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,GAClC,OAAO,CAAC,MAAM,CAAC;IAElB,wBAAwB,CACtB,oBAAoB,EAAE,MAAM,EAC5B,oBAAoB,EAAE,MAAM,GAC3B,OAAO,CAAC,MAAM,CAAC;IAElB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACtD,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAEnE,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,iCAAiC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAC3F,sBAAsB,CACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,kBAAkB,EAAE,OAAO,GAC1B,OAAO,CAAC,OAAO,CAAC;IACnB,qFAAqF;IACrF,wBAAwB,CAAC,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAC/D,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;CACtC;;AAED,wBAAqE"}
1
+ {"version":3,"file":"SatyaAttestModule.d.ts","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,OAAO,iBAAkB,SAAQ,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACzE,gBAAgB,IAAI,MAAM;IAE1B,kBAAkB,CAChB,oBAAoB,EAAE,MAAM,EAC5B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,GAClC,OAAO,CAAC,MAAM,CAAC;IAElB,wBAAwB,CACtB,oBAAoB,EAAE,MAAM,EAC5B,oBAAoB,EAAE,MAAM,EAC5B,4BAA4B,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3C,OAAO,CAAC,MAAM,CAAC;IAElB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACtD,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAEnE,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,iCAAiC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAC3F,sBAAsB,CACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,kBAAkB,EAAE,OAAO,GAC1B,OAAO,CAAC,OAAO,CAAC;IACnB,qFAAqF;IACrF,wBAAwB,CAAC,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAC/D,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;CACtC;;AAED,wBAAqE"}
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaAttestModule.js","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAiCzD,eAAe,mBAAmB,CAAoB,aAAa,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport { VerifyResult } from './types';\n\ndeclare class SatyaAttestModule extends NativeModule<Record<string, never>> {\n satyaCoreVersion(): string;\n\n attestNativeReplay(\n providerTemplateJson: string,\n capturedRequestJson?: string | null\n ): Promise<string>;\n\n attestNativeReplayBundle(\n providerTemplateJson: string,\n capturedRequestsJson: string\n ): Promise<string>;\n\n verifyArtifact(artifactJson: string): Promise<boolean>;\n verifyArtifactDetailed(artifactJson: string): Promise<VerifyResult>;\n\n initialize(configJson: string): Promise<void>;\n dispose(): Promise<void>;\n installWebViewDocumentStartScript(viewTag: number, script: string): Promise<boolean | null>;\n setPopupWindowsEnabled(\n viewTag: number,\n enabled: boolean,\n geolocationEnabled: boolean\n ): Promise<boolean>;\n /** Optional for compatibility with native binaries predating privacy concealment. */\n setPopupWindowsConcealed?(concealed: boolean): Promise<boolean>;\n closePopupWindows(): Promise<boolean>;\n}\n\nexport default requireNativeModule<SatyaAttestModule>('SatyaAttest');\n"]}
1
+ {"version":3,"file":"SatyaAttestModule.js","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAkCzD,eAAe,mBAAmB,CAAoB,aAAa,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport { VerifyResult } from './types';\n\ndeclare class SatyaAttestModule extends NativeModule<Record<string, never>> {\n satyaCoreVersion(): string;\n\n attestNativeReplay(\n providerTemplateJson: string,\n capturedRequestJson?: string | null\n ): Promise<string>;\n\n attestNativeReplayBundle(\n providerTemplateJson: string,\n capturedRequestsJson: string,\n partialEndpointSelectionJson?: string | null\n ): Promise<string>;\n\n verifyArtifact(artifactJson: string): Promise<boolean>;\n verifyArtifactDetailed(artifactJson: string): Promise<VerifyResult>;\n\n initialize(configJson: string): Promise<void>;\n dispose(): Promise<void>;\n installWebViewDocumentStartScript(viewTag: number, script: string): Promise<boolean | null>;\n setPopupWindowsEnabled(\n viewTag: number,\n enabled: boolean,\n geolocationEnabled: boolean\n ): Promise<boolean>;\n /** Optional for compatibility with native binaries predating privacy concealment. */\n setPopupWindowsConcealed?(concealed: boolean): Promise<boolean>;\n closePopupWindows(): Promise<boolean>;\n}\n\nexport default requireNativeModule<SatyaAttestModule>('SatyaAttest');\n"]}
@@ -3,7 +3,7 @@ import { VerifyResult } from './types';
3
3
  declare class SatyaAttestModule extends NativeModule<Record<string, never>> {
4
4
  satyaCoreVersion(): string;
5
5
  attestNativeReplay(_providerTemplateJson: string, _capturedRequestJson?: string | null): Promise<string>;
6
- attestNativeReplayBundle(_providerTemplateJson: string, _capturedRequestsJson: string): Promise<string>;
6
+ attestNativeReplayBundle(_providerTemplateJson: string, _capturedRequestsJson: string, _partialEndpointSelectionJson?: string | null): Promise<string>;
7
7
  verifyArtifact(_artifactJson: string): Promise<boolean>;
8
8
  verifyArtifactDetailed(_artifactJson: string): Promise<VerifyResult>;
9
9
  initialize(_configJson: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaAttestModule.web.d.ts","sourceRoot":"","sources":["../src/SatyaAttestModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,cAAM,iBAAkB,SAAQ,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjE,gBAAgB,IAAI,MAAM;IAGpB,kBAAkB,CACtB,qBAAqB,EAAE,MAAM,EAC7B,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,GACnC,OAAO,CAAC,MAAM,CAAC;IAGZ,wBAAwB,CAC5B,qBAAqB,EAAE,MAAM,EAC7B,qBAAqB,EAAE,MAAM,GAC5B,OAAO,CAAC,MAAM,CAAC;IAGZ,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAGvD,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAGpE,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B;;AAMD,wBAAyE"}
1
+ {"version":3,"file":"SatyaAttestModule.web.d.ts","sourceRoot":"","sources":["../src/SatyaAttestModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,cAAM,iBAAkB,SAAQ,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjE,gBAAgB,IAAI,MAAM;IAGpB,kBAAkB,CACtB,qBAAqB,EAAE,MAAM,EAC7B,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,GACnC,OAAO,CAAC,MAAM,CAAC;IAGZ,wBAAwB,CAC5B,qBAAqB,EAAE,MAAM,EAC7B,qBAAqB,EAAE,MAAM,EAC7B,6BAA6B,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5C,OAAO,CAAC,MAAM,CAAC;IAGZ,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAGvD,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAGpE,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B;;AAMD,wBAAyE"}
@@ -8,7 +8,7 @@ class SatyaAttestModule extends NativeModule {
8
8
  async attestNativeReplay(_providerTemplateJson, _capturedRequestJson) {
9
9
  throw unsupportedPlatformError();
10
10
  }
11
- async attestNativeReplayBundle(_providerTemplateJson, _capturedRequestsJson) {
11
+ async attestNativeReplayBundle(_providerTemplateJson, _capturedRequestsJson, _partialEndpointSelectionJson) {
12
12
  throw unsupportedPlatformError();
13
13
  }
14
14
  async verifyArtifact(_artifactJson) {
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaAttestModule.web.js","sourceRoot":"","sources":["../src/SatyaAttestModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjE,MAAM,WAAW,GACf,uFAAuF,CAAC;AAE1F,MAAM,iBAAkB,SAAQ,YAAmC;IACjE,gBAAgB;QACd,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,kBAAkB,CACtB,qBAA6B,EAC7B,oBAAoC;QAEpC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,wBAAwB,CAC5B,qBAA6B,EAC7B,qBAA6B;QAE7B,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,aAAqB;QACxC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,sBAAsB,CAAC,aAAqB;QAChD,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,WAAmB;QAClC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,OAAO;QACX,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;CACF;AAED,SAAS,wBAAwB;IAC/B,MAAM,UAAU,CAAC,cAAc,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;AACrE,CAAC;AAED,eAAe,iBAAiB,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC","sourcesContent":["import { registerWebModule, NativeModule } from 'expo';\n\nimport { VerifyResult } from './types';\nimport { SatyaErrorCode, satyaError } from './types/error.types';\n\nconst UNSUPPORTED =\n 'expo-satya-attest is not available on web (it requires native App Attest / Keystore).';\n\nclass SatyaAttestModule extends NativeModule<Record<string, never>> {\n satyaCoreVersion(): string {\n throw unsupportedPlatformError();\n }\n async attestNativeReplay(\n _providerTemplateJson: string,\n _capturedRequestJson?: string | null\n ): Promise<string> {\n throw unsupportedPlatformError();\n }\n async attestNativeReplayBundle(\n _providerTemplateJson: string,\n _capturedRequestsJson: string\n ): Promise<string> {\n throw unsupportedPlatformError();\n }\n async verifyArtifact(_artifactJson: string): Promise<boolean> {\n throw unsupportedPlatformError();\n }\n async verifyArtifactDetailed(_artifactJson: string): Promise<VerifyResult> {\n throw unsupportedPlatformError();\n }\n async initialize(_configJson: string): Promise<void> {\n throw unsupportedPlatformError();\n }\n async dispose(): Promise<void> {\n throw unsupportedPlatformError();\n }\n}\n\nfunction unsupportedPlatformError(): never {\n throw satyaError(SatyaErrorCode.UNSUPPORTED_PLATFORM, UNSUPPORTED);\n}\n\nexport default registerWebModule(SatyaAttestModule, 'SatyaAttestModule');\n"]}
1
+ {"version":3,"file":"SatyaAttestModule.web.js","sourceRoot":"","sources":["../src/SatyaAttestModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjE,MAAM,WAAW,GACf,uFAAuF,CAAC;AAE1F,MAAM,iBAAkB,SAAQ,YAAmC;IACjE,gBAAgB;QACd,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,kBAAkB,CACtB,qBAA6B,EAC7B,oBAAoC;QAEpC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,wBAAwB,CAC5B,qBAA6B,EAC7B,qBAA6B,EAC7B,6BAA6C;QAE7C,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,aAAqB;QACxC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,sBAAsB,CAAC,aAAqB;QAChD,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,WAAmB;QAClC,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,OAAO;QACX,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC;CACF;AAED,SAAS,wBAAwB;IAC/B,MAAM,UAAU,CAAC,cAAc,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;AACrE,CAAC;AAED,eAAe,iBAAiB,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC","sourcesContent":["import { registerWebModule, NativeModule } from 'expo';\n\nimport { VerifyResult } from './types';\nimport { SatyaErrorCode, satyaError } from './types/error.types';\n\nconst UNSUPPORTED =\n 'expo-satya-attest is not available on web (it requires native App Attest / Keystore).';\n\nclass SatyaAttestModule extends NativeModule<Record<string, never>> {\n satyaCoreVersion(): string {\n throw unsupportedPlatformError();\n }\n async attestNativeReplay(\n _providerTemplateJson: string,\n _capturedRequestJson?: string | null\n ): Promise<string> {\n throw unsupportedPlatformError();\n }\n async attestNativeReplayBundle(\n _providerTemplateJson: string,\n _capturedRequestsJson: string,\n _partialEndpointSelectionJson?: string | null\n ): Promise<string> {\n throw unsupportedPlatformError();\n }\n async verifyArtifact(_artifactJson: string): Promise<boolean> {\n throw unsupportedPlatformError();\n }\n async verifyArtifactDetailed(_artifactJson: string): Promise<VerifyResult> {\n throw unsupportedPlatformError();\n }\n async initialize(_configJson: string): Promise<void> {\n throw unsupportedPlatformError();\n }\n async dispose(): Promise<void> {\n throw unsupportedPlatformError();\n }\n}\n\nfunction unsupportedPlatformError(): never {\n throw satyaError(SatyaErrorCode.UNSUPPORTED_PLATFORM, UNSUPPORTED);\n}\n\nexport default registerWebModule(SatyaAttestModule, 'SatyaAttestModule');\n"]}