@burnt-labs/expo-satya-attest 3.2.0 → 3.3.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 (141) hide show
  1. package/CHANGELOG.md +109 -2
  2. package/PROVIDER_CONFIG.md +134 -1
  3. package/README.md +32 -13
  4. package/android/build.gradle +2 -2
  5. package/android/src/main/java/expo/modules/satyaattest/SatyaAttestModule.kt +6 -0
  6. package/android/src/main/jniLibs/arm64-v8a/libsatya_ffi.so +0 -0
  7. package/android/src/main/jniLibs/armeabi-v7a/libsatya_ffi.so +0 -0
  8. package/android/src/main/jniLibs/x86_64/libsatya_ffi.so +0 -0
  9. package/build/SatyaAttestModule.d.ts +1 -0
  10. package/build/SatyaAttestModule.d.ts.map +1 -1
  11. package/build/SatyaAttestModule.js.map +1 -1
  12. package/build/SatyaAttestModule.web.d.ts +1 -0
  13. package/build/SatyaAttestModule.web.d.ts.map +1 -1
  14. package/build/SatyaAttestModule.web.js +3 -0
  15. package/build/SatyaAttestModule.web.js.map +1 -1
  16. package/build/SatyaProviderAttestButton.js +9 -1
  17. package/build/SatyaProviderAttestButton.js.map +1 -1
  18. package/build/components/providerAttest/ProviderAttestModal.d.ts.map +1 -1
  19. package/build/components/providerAttest/ProviderAttestModal.js +25 -15
  20. package/build/components/providerAttest/ProviderAttestModal.js.map +1 -1
  21. package/build/components/providerAttest/backendVerifier.d.ts +9 -0
  22. package/build/components/providerAttest/backendVerifier.d.ts.map +1 -1
  23. package/build/components/providerAttest/backendVerifier.js +44 -6
  24. package/build/components/providerAttest/backendVerifier.js.map +1 -1
  25. package/build/components/providerAttest/copy.d.ts +12 -0
  26. package/build/components/providerAttest/copy.d.ts.map +1 -1
  27. package/build/components/providerAttest/copy.js +20 -0
  28. package/build/components/providerAttest/copy.js.map +1 -1
  29. package/build/components/providerAttest/loginSubmitDetector.d.ts +24 -0
  30. package/build/components/providerAttest/loginSubmitDetector.d.ts.map +1 -0
  31. package/build/components/providerAttest/loginSubmitDetector.js +65 -0
  32. package/build/components/providerAttest/loginSubmitDetector.js.map +1 -0
  33. package/build/components/providerAttest/popupRelay.d.ts +14 -0
  34. package/build/components/providerAttest/popupRelay.d.ts.map +1 -1
  35. package/build/components/providerAttest/popupRelay.js +27 -0
  36. package/build/components/providerAttest/popupRelay.js.map +1 -1
  37. package/build/components/providerAttest/teels/useTeelsProviderFlow.d.ts.map +1 -1
  38. package/build/components/providerAttest/teels/useTeelsProviderFlow.js +3 -0
  39. package/build/components/providerAttest/teels/useTeelsProviderFlow.js.map +1 -1
  40. package/build/components/providerAttest/types.d.ts +7 -1
  41. package/build/components/providerAttest/types.d.ts.map +1 -1
  42. package/build/components/providerAttest/types.js.map +1 -1
  43. package/build/components/providerAttest/urlUtils.d.ts +12 -0
  44. package/build/components/providerAttest/urlUtils.d.ts.map +1 -1
  45. package/build/components/providerAttest/urlUtils.js +23 -0
  46. package/build/components/providerAttest/urlUtils.js.map +1 -1
  47. package/build/components/providerAttest/useManagedPopupController.d.ts.map +1 -1
  48. package/build/components/providerAttest/useManagedPopupController.js +18 -2
  49. package/build/components/providerAttest/useManagedPopupController.js.map +1 -1
  50. package/build/components/providerAttest/useNativeReplayRunner.d.ts.map +1 -1
  51. package/build/components/providerAttest/useNativeReplayRunner.js +3 -37
  52. package/build/components/providerAttest/useNativeReplayRunner.js.map +1 -1
  53. package/build/components/providerAttest/useProviderAttestFlow.d.ts +1 -0
  54. package/build/components/providerAttest/useProviderAttestFlow.d.ts.map +1 -1
  55. package/build/components/providerAttest/useProviderAttestFlow.js +146 -1
  56. package/build/components/providerAttest/useProviderAttestFlow.js.map +1 -1
  57. package/build/components/providerAttest/useProviderFlowLifecycle.d.ts +6 -2
  58. package/build/components/providerAttest/useProviderFlowLifecycle.d.ts.map +1 -1
  59. package/build/components/providerAttest/useProviderFlowLifecycle.js +5 -1
  60. package/build/components/providerAttest/useProviderFlowLifecycle.js.map +1 -1
  61. package/build/components/providerAttest/useProviderInjectedScripts.d.ts +2 -1
  62. package/build/components/providerAttest/useProviderInjectedScripts.d.ts.map +1 -1
  63. package/build/components/providerAttest/useProviderInjectedScripts.js +3 -3
  64. package/build/components/providerAttest/useProviderInjectedScripts.js.map +1 -1
  65. package/build/components/providerAttest/useProviderMessageHandler.d.ts +10 -3
  66. package/build/components/providerAttest/useProviderMessageHandler.d.ts.map +1 -1
  67. package/build/components/providerAttest/useProviderMessageHandler.js +101 -2
  68. package/build/components/providerAttest/useProviderMessageHandler.js.map +1 -1
  69. package/build/components/providerAttest/useProviderNavigationHandlers.d.ts +4 -2
  70. package/build/components/providerAttest/useProviderNavigationHandlers.d.ts.map +1 -1
  71. package/build/components/providerAttest/useProviderNavigationHandlers.js +40 -8
  72. package/build/components/providerAttest/useProviderNavigationHandlers.js.map +1 -1
  73. package/build/components/providerAttest/useVerificationPresentation.d.ts +5 -0
  74. package/build/components/providerAttest/useVerificationPresentation.d.ts.map +1 -1
  75. package/build/components/providerAttest/useVerificationPresentation.js +63 -1
  76. package/build/components/providerAttest/useVerificationPresentation.js.map +1 -1
  77. package/build/components/providerAttest/verificationPresentation.d.ts +34 -1
  78. package/build/components/providerAttest/verificationPresentation.d.ts.map +1 -1
  79. package/build/components/providerAttest/verificationPresentation.js +42 -4
  80. package/build/components/providerAttest/verificationPresentation.js.map +1 -1
  81. package/build/providerRegistry/index.d.ts +1 -0
  82. package/build/providerRegistry/index.d.ts.map +1 -1
  83. package/build/providerRegistry/index.js +1 -0
  84. package/build/providerRegistry/index.js.map +1 -1
  85. package/build/providerRegistry/loginSubmitSignal.d.ts +27 -0
  86. package/build/providerRegistry/loginSubmitSignal.d.ts.map +1 -0
  87. package/build/providerRegistry/loginSubmitSignal.js +385 -0
  88. package/build/providerRegistry/loginSubmitSignal.js.map +1 -0
  89. package/build/providerRegistry/rules.js +15 -0
  90. package/build/providerRegistry/rules.js.map +1 -1
  91. package/build/providerRegistry/template.d.ts.map +1 -1
  92. package/build/providerRegistry/template.js +57 -2
  93. package/build/providerRegistry/template.js.map +1 -1
  94. package/build/sdkMetadata.generated.d.ts +1 -1
  95. package/build/sdkMetadata.generated.js +1 -1
  96. package/build/sdkMetadata.generated.js.map +1 -1
  97. package/build/types/provider.types.d.ts +72 -1
  98. package/build/types/provider.types.d.ts.map +1 -1
  99. package/build/types/provider.types.js.map +1 -1
  100. package/build/webview/index.d.ts +3 -3
  101. package/build/webview/index.d.ts.map +1 -1
  102. package/build/webview/index.js +4 -4
  103. package/build/webview/index.js.map +1 -1
  104. package/build/webview/scripts/requestInterceptorScript.d.ts +3 -1
  105. package/build/webview/scripts/requestInterceptorScript.d.ts.map +1 -1
  106. package/build/webview/scripts/requestInterceptorScript.js +346 -23
  107. package/build/webview/scripts/requestInterceptorScript.js.map +1 -1
  108. package/ios/Frameworks/libsatya_ffi-rs.xcframework/Info.plist +5 -5
  109. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/libsatya_ffi.a +0 -0
  110. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64_x86_64-simulator/libsatya_ffi.a +0 -0
  111. package/ios/SatyaAttest.podspec +1 -1
  112. package/ios/SatyaAttestModule.swift +13 -0
  113. package/package.json +11 -1
  114. package/src/SatyaAttestModule.ts +1 -0
  115. package/src/SatyaAttestModule.web.ts +3 -0
  116. package/src/SatyaProviderAttestButton.tsx +8 -1
  117. package/src/components/providerAttest/ProviderAttestModal.tsx +30 -18
  118. package/src/components/providerAttest/backendVerifier.ts +52 -6
  119. package/src/components/providerAttest/copy.ts +25 -0
  120. package/src/components/providerAttest/loginSubmitDetector.ts +91 -0
  121. package/src/components/providerAttest/popupRelay.ts +30 -0
  122. package/src/components/providerAttest/teels/useTeelsProviderFlow.ts +3 -0
  123. package/src/components/providerAttest/types.ts +8 -0
  124. package/src/components/providerAttest/urlUtils.ts +22 -0
  125. package/src/components/providerAttest/useManagedPopupController.ts +18 -2
  126. package/src/components/providerAttest/useNativeReplayRunner.ts +16 -42
  127. package/src/components/providerAttest/useProviderAttestFlow.ts +159 -2
  128. package/src/components/providerAttest/useProviderFlowLifecycle.ts +9 -0
  129. package/src/components/providerAttest/useProviderInjectedScripts.ts +10 -4
  130. package/src/components/providerAttest/useProviderMessageHandler.ts +141 -4
  131. package/src/components/providerAttest/useProviderNavigationHandlers.ts +55 -7
  132. package/src/components/providerAttest/useVerificationPresentation.ts +73 -0
  133. package/src/components/providerAttest/verificationPresentation.ts +53 -5
  134. package/src/providerRegistry/index.ts +12 -0
  135. package/src/providerRegistry/loginSubmitSignal.ts +532 -0
  136. package/src/providerRegistry/rules.ts +13 -0
  137. package/src/providerRegistry/template.ts +59 -1
  138. package/src/sdkMetadata.generated.ts +1 -1
  139. package/src/types/provider.types.ts +56 -1
  140. package/src/webview/index.ts +10 -4
  141. package/src/webview/scripts/requestInterceptorScript.ts +359 -23
package/CHANGELOG.md CHANGED
@@ -1,6 +1,113 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 3.3.0 - 2026-08-14
4
+
5
+ ### Fixed
6
+
7
+ - **iOS crash on launch for any consumer using `prefetch`.** The hidden WebView
8
+ that warms the WebKit process was sourced from `{ uri: 'about:blank' }`. A
9
+ source URL with no host is loaded by react-native-webview through
10
+ `-[WKWebView loadFileURL:]`, which raises `NSInvalidArgumentException`
11
+ ("about:blank is not a file URL") and terminates the app. It mounts as soon
12
+ as the provider resolves, so the crash landed at startup, before any provider
13
+ flow. It now warms via an `html` source, which takes `loadHTMLString` instead.
14
+ - Three further routes could hand the same hostless URL to a WebView and crash
15
+ it mid-flow: a managed popup opened for the common
16
+ `window.open('about:blank')` + `w.location = url` pattern; a popup navigating
17
+ its opener; and a main-frame navigation fed back into the source prop. All
18
+ three now require a loadable http(s) document URL. `isWebViewSafeUrl` was not
19
+ sufficient — it is a navigation-policy check that deliberately permits
20
+ `about:`. As a backstop, the source prop itself refuses a hostless URL rather
21
+ than trusting its input. `about:blank` is preserved on Android, where it is
22
+ the deliberate bootstrap URL and loads without incident.
23
+
24
+ ### Added
25
+
26
+ - **Pre-emptive login cover (`auth.submitSignal`).** The privacy cover can now
27
+ appear the moment a login is submitted, instead of waiting for the provider
28
+ injection's `__satyaReady` signal. That signal can only fire once the
29
+ authenticated DOM exists — and on Android provider injection is deliberately
30
+ excluded from the document-start payload — so the destination page was
31
+ briefly visible after every sign-in. A provider opts in with
32
+ an ordered `auth.submitSignal[]`. Each entry has a unique stable `id`, a
33
+ canonical HTTPS `url`, and declares `requestMatchRules` matched against the
34
+ already-intercepted fetch/XHR traffic (no new injected JavaScript) and/or a
35
+ `navigationUrlPattern` for form-POST logins that never reach the interceptor.
36
+ Rules must include a `urlContains` or `urlRegex` so a signal cannot match
37
+ every request.
38
+ - Providers can declare up to 16 login routes (for example password, OAuth,
39
+ and magic-link endpoints). Entries are ORed in declaration order; the first
40
+ matching entry owns the attempt, and only that entry's response rules and
41
+ timeout apply. Request and response evidence cannot be mixed across entries.
42
+ - Login request matching now also supports `bodyContains` and `bodyRegex` for
43
+ endpoints whose URL is shared across operations. Optional
44
+ `responseMatchRules` support `bodyContains` and `bodyRegex` success markers.
45
+ Response matching scans a bounded clone inside the WebView and sends only a
46
+ one private boolean over the React Native bridge; response contents remain local.
47
+ - Login-submit validation caps every trimmed matcher string—including header
48
+ keys and JSON paths—at 2048 characters and each rule list at 16 entries,
49
+ plus 32 regexes / 8192 regex characters across the provider. All regex
50
+ matchers must compile; backreferences and expressions with unsafe
51
+ backtracking are rejected by bounded static analysis before they can run
52
+ on-device. Body regexes should be anchored, with `bodyContains` used for
53
+ substring checks.
54
+ - Authenticates interceptor request/response messages with a native-generated,
55
+ per-flow capability so provider subframes cannot forge login-cover signals.
56
+ - Normalizes fetch and URL-encoded request bodies for login matching; bounded
57
+ XHR matching fails closed when a faithful body representation is unavailable.
58
+ - The pre-emptive cover is **provisional**, because logins fail routinely. It
59
+ reverts on its own rather than trapping the user behind a spinner: a non-2xx
60
+ response to the matched login request uncovers immediately, so a wrong
61
+ password, MFA prompt, or captcha stays visible; a 2xx extends the wait to
62
+ `max(timeoutMs, 8s)`
63
+ for `__satyaReady`; an unanswered submission lapses after 4s
64
+ (the selected `auth.submitSignal[].timeoutMs`, 1000–15000). Reverting is not sticky, so a
65
+ retry covers again. `__satyaReady` promotes the provisional cover to the
66
+ confirmed one, which keeps its existing 12s stalled watchdog.
67
+ - A main-frame navigation while the provisional cover is up promotes it to the
68
+ same `max(timeoutMs, 8s)` window an accepted 2xx grants, bounded at 3 extensions. This keeps
69
+ supported main-frame redirect chains covered independently of response-body
70
+ semantics. A rejection that re-renders in place still lapses on `timeoutMs`;
71
+ a provider that navigates on rejection may consume a bounded extension.
72
+ A late accepted response followed by all three extensions can keep the cover
73
+ up for 36s at the defaults, or 75s at `timeoutMs: 15000`. Exhausting the
74
+ windows uncovers and continues; it never fails the run, because a slow
75
+ provider must not lose a capture that is still progressing.
76
+ - `auth.submitSignal.ignoreResponseStatus` covers login endpoints whose status
77
+ is not a reliable success signal. Without `responseMatchRules`, the response
78
+ remains uncorrelated and the cover lapses on `timeoutMs`. With response rules,
79
+ the in-WebView body result decides acceptance while the status is ignored.
80
+ When the flag is absent/false, both a 2xx status and all configured response
81
+ rules must match.
82
+ - While provisional, the cover reads "Signing you in..." rather than
83
+ "Verifying your account..." — nothing has been verified at that point — and
84
+ offers no actions, since only the confirmed cover can stall.
85
+
86
+ ### Notes
87
+
88
+ - The cover remains **presentation-only**. The pre-emptive signal never marks
89
+ the session logged in, releases `capture.requireLoginSignal`, authorizes a
90
+ capture, or affects replay, disclosure, or verifier policy; a matched login
91
+ request still passes through every normal capture rule. Only `__satyaReady`
92
+ gates capture.
93
+ - `auth` remains copied verbatim into the hashed security policy, so
94
+ `auth.submitSignal` is covered without adding a new policy projection.
95
+ The Rust verifier's canonical object-key ordering now follows JavaScript's
96
+ UTF-16 ordering, and SDK/Devtool validation rejects unpaired surrogate
97
+ strings, preventing cross-language hash drift in additive `auth` metadata.
98
+ Exact TypeScript/Rust golden vectors cover ordered signal arrays. Providers
99
+ that do not adopt the field keep an **unchanged policy hash**.
100
+ - `auth` was previously documented as preserved-but-uninterpreted metadata and
101
+ was not validated at all. Sibling keys are still preserved untouched, but a
102
+ non-object `auth` is now rejected instead of passing through silently — it
103
+ now carries configuration the SDK acts on, and a malformed value would
104
+ otherwise disable the cover with nothing to explain why.
105
+ - Popup close is not treated as authentication. This release does not observe
106
+ provisional submit matchers inside native Android popup WebViews, and managed
107
+ popup form navigations do not feed `navigationUrlPattern` into the detector.
108
+ Providers can match an observed session/status request issued by the opener
109
+ after popup close; otherwise concealment waits for the opener's
110
+ `__satyaReady`, preserving the previous popup timing.
4
111
 
5
112
  ## 3.2.0 - 2026-08-07
6
113
 
@@ -155,7 +262,7 @@ default-deny provider allowlist.
155
262
  id was swallowed by the policy-refresh `try` and resurfaced after the user
156
263
  had already completed login, mislabeled as a native-module failure.
157
264
  - **Empty overrides no longer erase configuration.** `initSatyaAttest({ appId:
158
- '' })` (a common `props.appId ?? ''` pattern) wiped a valid env-derived app
265
+ '' })` (a common `props.appId ?? ''` pattern) wiped a valid env-derived app
159
266
  id, and an empty `providerRegistryApi` silently repointed a staging build at
160
267
  production.
161
268
 
@@ -25,6 +25,7 @@ inside a provider:
25
25
  Registry { schemaVersion?, providers[] }
26
26
  └─ Provider top-level proof config
27
27
  ├─ login { url } → page opened in the WebView
28
+ ├─ auth? { submitSignal? } → pre-emptive login cover (optional)
28
29
  ├─ endpoints[] one replayable request each
29
30
  │ ├─ requestMatchRules[] which WebView request to capture
30
31
  │ ├─ revealRules[] what the artifact may disclose
@@ -102,7 +103,7 @@ field table (`Req?` = required `yes` / optional `no`).
102
103
  | `requiresLocation` | `boolean` | Enables geolocation only for this provider's managed WebView. Defaults to `false`; the host app must opt in with the config plugin's `enableWebViewGeolocation` option. Android runtime access is requested after the provider resolves and before its WebView loads. |
103
104
  | `userAgent` | `{ ios, android }` | Per-platform UA (each `string` or `null`) for the WebView when no `webViewUserAgent` prop is set. Use when the provider serves different pages to mobile vs desktop and replay must match. |
104
105
  | `injection` | `string` / `string[]` / `null` | Trusted WebView JavaScript run on every page. See §9. |
105
- | `auth` | `object` | Provider-auth metadata. Preserved but not interpreted by the managed flow. |
106
+ | `auth` | `object` | Authentication metadata. Only `submitSignal` (§2.1) is interpreted; every sibling key is preserved untouched. Must be an object when present. |
106
107
 
107
108
  Unknown top-level keys are cloned through but ignored by native replay/verification —
108
109
  never put security-critical behavior in an undocumented key.
@@ -125,6 +126,123 @@ The page opened in the managed WebView. Use the provider's normal sign-in / acco
125
126
  > `login.injection` is **invalid** — provider JavaScript always lives at the top-level
126
127
  > `injection` (§9).
127
128
 
129
+ ### 2.1 `auth.submitSignal` (optional) — covering before the landing page paints
130
+
131
+ `__satyaReady` (§9) can only fire once the authenticated DOM exists, so the provider's
132
+ destination page is briefly visible after sign-in. `submitSignal` moves the cover earlier:
133
+ the SDK covers the WebView as soon as it sees the login being submitted.
134
+
135
+ ```json
136
+ "login": { "url": "https://acme.example/login" },
137
+ "auth": {
138
+ "submitSignal": [
139
+ {
140
+ "id": "PasswordLogin",
141
+ "url": "https://acme.example/api/v1/auth/login",
142
+ "requestMatchRules": [
143
+ { "type": "methodEquals", "pattern": "POST" },
144
+ { "type": "urlContains", "pattern": "/api/v1/auth/login" },
145
+ { "type": "bodyContains", "pattern": "\"grantType\":\"password\"" }
146
+ ],
147
+ "responseMatchRules": [
148
+ { "type": "bodyContains", "pattern": "\"authenticated\":true" },
149
+ { "type": "bodyRegex", "pattern": "^\\{\"authenticated\":true,\"sessionId\":\"[^\"]+\"\\}$" }
150
+ ],
151
+ "ignoreResponseStatus": true,
152
+ "timeoutMs": 4000
153
+ }
154
+ ]
155
+ }
156
+ ```
157
+
158
+ | Field | Req? | Type | Description |
159
+ |---|---|---|---|
160
+ | `id` | yes | `string` | Unique stable signal identifier. Starts with a letter, then letters/numbers/`_`/`-`; maximum 64 characters. |
161
+ | `url` | yes | `string` | Canonical HTTPS login endpoint, without credentials or surrounding whitespace; maximum 2048 characters. Every request URL rule must match it, and navigation matching is restricted to this origin. |
162
+ | `requestMatchRules` | no\* | `array` | ANDed rules matched against intercepted `fetch`/`XHR` traffic. Supports `methodEquals`, `urlContains`, `urlRegex`, `headerEquals`, `bodyJsonPathEquals`, `bodyContains`, and `bodyRegex`. Must include a `urlContains` (≥4 chars) or `urlRegex` rule. |
163
+ | `responseMatchRules` | no | `array` | Optional ANDed success-envelope rules. Supports `bodyContains` and `bodyRegex`. Requires `requestMatchRules` so the response can be correlated to a URL-bound login request. Matching reads at most the first 65,536 response characters inside the WebView; only a private boolean crosses the React Native bridge. |
164
+ | `navigationUrlPattern` | no\* | `string` | Regex matched against same-origin main-frame navigations. For classic `<form>` POST logins, which never reach the request interceptor. |
165
+ | `timeoutMs` | no | `integer` | 1000–15000, default 4000. How long an unanswered submission stays covered. |
166
+ | `ignoreResponseStatus` | no | `boolean` | Without `responseMatchRules`, `true` skips response correlation and the cover lapses on `timeoutMs`. With `responseMatchRules`, status is ignored but the response-body boolean still decides acceptance. When false/absent, acceptance requires both 2xx and all configured response rules. |
167
+
168
+ `submitSignal` is a non-empty array with at most 16 entries. A provider can list one entry
169
+ per login route (password, OAuth callback, magic link, and so on). Entries are ORed in
170
+ declaration order and the first matching entry owns the attempt; rules within that entry
171
+ are ANDed. The selected entry alone supplies response rules and `timeoutMs`, so request and
172
+ response evidence is never mixed across login routes. IDs must be unique.
173
+
174
+ \* At least one of `requestMatchRules` / `navigationUrlPattern` is required for each entry.
175
+ Sibling `auth` keys are preserved and never interpreted, so a provider may keep its own
176
+ metadata alongside it. A non-object `auth`, an object-form `submitSignal`, and unknown keys
177
+ inside a signal are rejected outright. Every trimmed matcher string—including header keys
178
+ and JSON paths—is capped at 2048 characters, every rule array is capped at 16 entries, and
179
+ the complete provider may contain at most 32 regexes totaling 8192 characters. Every regex
180
+ must compile. To keep matchers safe for on-device request/response processing,
181
+ backreferences and expressions with unsafe backtracking are rejected by bounded static analysis.
182
+ Anchor body regexes to the complete expected body; use `bodyContains` for substring checks.
183
+ Fetch bodies are normalized through the browser `Request` API. XHR supports text,
184
+ `URLSearchParams`, and byte-buffer request bodies; XHR `FormData`/`Blob` request matching and
185
+ `responseType="json"` response matching fail closed because no bounded faithful wire encoding is
186
+ available. Prefer `fetch` or a text XHR response when body rules are required.
187
+
188
+ **The cover is provisional and reverts on its own** — logins fail routinely, and a wrong
189
+ password must not sit behind a spinner:
190
+
191
+ | Outcome | Behavior |
192
+ |---|---|
193
+ | Matched request answered non-2xx | Uncovers immediately, so the provider's error, MFA prompt, or captcha is visible |
194
+ | Matched request answered 2xx | Waits for `max(timeoutMs, 8s)` for `__satyaReady` |
195
+ | `responseMatchRules` configured | Accepts only when every body rule matches; also requires 2xx unless `ignoreResponseStatus` is true |
196
+ | No response observed | Lapses after `timeoutMs` |
197
+ | `ignoreResponseStatus: true`, no response rules | Response is not correlated; always lapses after `timeoutMs` |
198
+ | Main-frame navigation while covered | Grants the same `max(timeoutMs, 8s)` accepted window (up to 3 times) for supported redirect chains |
199
+ | `__satyaReady` arrives | Promotes to the confirmed cover, with its usual 12s stalled watchdog |
200
+
201
+ Reverting is not sticky: a retried login covers again.
202
+
203
+ > **Check what a failed login actually returns.** Many login APIs answer `200` with an
204
+ > error envelope rather than a 4xx. Add `responseMatchRules` for a stable success marker so
205
+ > a rejected 200 response uncovers immediately. Use `ignoreResponseStatus: true` together
206
+ > with response rules only when the HTTP status is unreliable. The response is cloned and
207
+ > scanned inside the WebView; its contents never cross the bridge, only the match boolean.
208
+
209
+ `timeoutMs` does not have to cover the provider's slowest sign-in. A main-frame navigation
210
+ while the cover is up promotes it to the same `max(timeoutMs, 8s)` window an accepted 2xx would grant
211
+ (bounded at 3 extensions), which keeps supported redirect chains covered. A rejected login
212
+ that re-renders in place still uncovers after one `timeoutMs`; a provider that navigates on
213
+ rejection may consume a bounded extension. Set `timeoutMs` to how long you are willing to
214
+ hide a **failed** login, not to how long a successful one takes.
215
+
216
+ If every window still expires without `__satyaReady`, the cover simply lifts and the flow
217
+ continues. A late accepted response followed by all three navigation extensions can keep it
218
+ covered for up to 36s at the defaults, or 75s with `timeoutMs: 15000`. It never fails the run:
219
+ the cover is presentation-only, and a slow provider must not lose a capture that is still
220
+ progressing.
221
+
222
+ > The remaining latency is the provider injection's own detection speed. Between the last
223
+ > navigation and `__satyaReady`, only `timeoutMs` is holding the cover. An injection that
224
+ > polls slowly (say `setInterval(…, 1500)` and waits for full DOM hydration) can exceed it
225
+ > and let the destination page flash. Prefer an auth-cookie check at ≤1s (§9).
226
+
227
+ > Prefer `requestMatchRules` — the SDK already intercepts every `fetch`/`XHR`, so no new
228
+ > injected JavaScript is involved. Scope the rules to the login endpoint itself; a signal
229
+ > that also matches ordinary page traffic will cover the user at random moments.
230
+ > `navigationUrlPattern` must match the **post-login** URL, not the login page's own loads.
231
+
232
+ > **Popup limitation.** Popup close is not an authentication signal. The current SDK does
233
+ > not observe `submitSignal` requests inside native Android popup WebViews, and managed-popup
234
+ > form navigations do not feed `navigationUrlPattern` into the provisional-cover detector.
235
+ > If the opener/main WebView immediately issues an observed session/status `fetch`/`XHR`
236
+ > after close, match that request instead. Otherwise the popup may remain visible until the
237
+ > opener emits `__satyaReady`; do not invent a popup-close heuristic.
238
+
239
+ `submitSignal` is presentation-only. It never releases `capture.requireLoginSignal`,
240
+ authorizes a capture, or affects replay or verification — a matched login request still
241
+ passes through every normal capture rule. **Still emit `__satyaReady`** (§9): it is what
242
+ gates capture and what promotes the cover. Because `auth` is copied verbatim into the
243
+ hashed security policy, adding `submitSignal` changes the provider's `policyHash`; not
244
+ adding it leaves the hash unchanged.
245
+
128
246
  ---
129
247
 
130
248
  ## 3. `endpoints[]` — the Endpoint object
@@ -561,6 +679,13 @@ it while a logged-out state is still possible: a premature signal covers the log
561
679
  although the stalled cover offers a way back, that is a degraded experience. (This signal is
562
680
  unrelated to the registry catalog flag `metadata.isSatyaReady`.)
563
681
 
682
+ Because `__satyaReady` cannot fire until the authenticated DOM exists, even a fast detector
683
+ leaves the destination page briefly visible. To close that gap, also declare
684
+ `auth.submitSignal` (§2.1), which covers the WebView the moment the login is submitted and
685
+ reverts on its own if the login turns out to have failed. The two are complementary:
686
+ `submitSignal` decides how early the cover appears, `__satyaReady` still gates capture and
687
+ promotes the provisional cover to the confirmed one.
688
+
564
689
  > This is distinct from endpoint `capture`: `injection` is page JavaScript at the provider
565
690
  > level; `capture` is per-endpoint active-fetch/readiness config.
566
691
 
@@ -690,6 +815,14 @@ console.log(body.match(/octolytics-actor-login" content="(.*?)"/)?.[1]);
690
815
  is an SDK/verifier session option (§8.1),
691
816
  - `injection` is a non-empty string / non-empty string array when present; `login.injection`
692
817
  and endpoint `injection` are rejected,
818
+ - `auth.submitSignal` (when present) is an array of 1–16 entries with unique `id` values and
819
+ canonical HTTPS `url` values; each entry has only `id`, `url`, `requestMatchRules`,
820
+ `responseMatchRules`, `navigationUrlPattern`, `timeoutMs`, `ignoreResponseStatus`; declares at
821
+ least one request/navigation detector; request rules use supported request types, include a
822
+ URL-bounding `urlContains`/`urlRegex`, and match the entry's `url`; response rules use only
823
+ `bodyContains`/`bodyRegex` and require request rules; request and response rule arrays contain
824
+ at most 16 entries; every trimmed matcher string is ≤2048 characters; patterns compile;
825
+ `timeoutMs` is an integer in 1000–15000 (§2.1),
693
826
  - raw `allowedRequestHeaders`, `allowedResponseBodyJsonPaths`, and `privacyPolicy` are
694
827
  rejected (the loader derives them from `revealRules`),
695
828
  - SPKI pins are SHA-256 base64,
package/README.md CHANGED
@@ -190,7 +190,7 @@ New SDK artifacts also include envelope metadata:
190
190
  "attestationIdVersion": "statement-sha256-96-v1",
191
191
  "producer": {
192
192
  "sdk": "@burnt-labs/expo-satya-attest",
193
- "sdkVersion": "3.1.0",
193
+ "sdkVersion": "3.3.0",
194
194
  "artifactSchemaVersion": "1.1.0",
195
195
  "coreVersion": "0.1.0"
196
196
  }
@@ -337,7 +337,7 @@ is a release tarball produced by `npm pack`, because it contains the same files
337
337
  would publish:
338
338
 
339
339
  ```bash
340
- npm install https://github.com/<owner>/<repo>/releases/download/expo-satya-attest-v3.1.0/burnt-labs-expo-satya-attest-3.1.0.tgz
340
+ npm install https://github.com/<owner>/<repo>/releases/download/expo-satya-attest-v3.3.0/burnt-labs-expo-satya-attest-3.3.0.tgz
341
341
  npx expo install react-native-webview react-native-safe-area-context
342
342
  ```
343
343
 
@@ -345,8 +345,8 @@ Installing from a Git ref also works when that ref has the SDK package at the re
345
345
  root, such as a dedicated SDK repo or a subtree/split branch:
346
346
 
347
347
  ```bash
348
- npm install github:<owner>/<sdk-repo>#v3.1.0
349
- npm install git+https://github.com/<owner>/<sdk-repo>.git#v3.1.0
348
+ npm install github:<owner>/<sdk-repo>#v3.3.0
349
+ npm install git+https://github.com/<owner>/<sdk-repo>.git#v3.3.0
350
350
  npx expo install react-native-webview react-native-safe-area-context
351
351
  ```
352
352
 
@@ -423,15 +423,34 @@ pill (lock icon + domain), a reload button, and a page-load line that advances w
423
423
  loads and clears when it finishes; both the main page and the managed OAuth popup fade in
424
424
  over their first load instead of flashing white.
425
425
 
426
- Once the provider's injection confirms the session is authenticated (the `__satyaReady`
427
- login signal), the sheet conceals the provider page behind a "Verifying your account…"
428
- cover while capture and native replay continue underneath, so users see only the login
429
- window and never the SDK driving the authenticated site. The cover is presentation-only
430
- (capture gating and verification are unchanged); if capture does not start within a
431
- short watchdog window it offers a "Return to provider" path that keeps the flow alive.
432
- Providers whose injection does not emit `__satyaReady` keep the previous behavior and
433
- are covered from request capture onward. See `PROVIDER_CONFIG.md` for the signal
434
- contract.
426
+ SDK 3.3.0 providers can declare an ordered `auth.submitSignal[]` to start a provisional
427
+ "Signing you in..." cover as soon as a tightly scoped main-WebView login request or
428
+ navigation is observed. Request matching supports URL-, header-, JSON-path-, and raw-body
429
+ rules. Each entry has a unique `id` and canonical HTTPS `url`; entries are ORed in order,
430
+ the first match wins, and that same entry owns response matching and timeout behavior.
431
+ Optional response body rules are evaluated on a bounded clone inside the WebView; only
432
+ a private boolean crosses the React Native bridge. A rejection uncovers, an unanswered
433
+ submission lapses on timeout, and bounded main-frame navigation extensions keep supported
434
+ redirect chains concealed.
435
+
436
+ Login-cover configuration is capped at 32 regexes totaling 8192 characters per provider,
437
+ in addition to the per-pattern and per-rule-list limits. Body matching supports normalized
438
+ fetch bodies and text XHR; bounded matching fails closed for XHR `responseType="json"` and
439
+ XHR `FormData`/`Blob` request bodies.
440
+
441
+ Once the provider's injection confirms authentication with `__satyaReady`, the sheet
442
+ promotes to the "Verifying your account…" cover while capture and native replay continue
443
+ underneath. Only `__satyaReady` releases readiness-gated capture; both covers remain
444
+ presentation-only and never change replay or verification policy. If confirmed capture
445
+ does not start within a short watchdog window, the cover offers a "Return to provider"
446
+ path that keeps the flow alive. Providers without `auth.submitSignal` retain the previous
447
+ timing and policy hash.
448
+
449
+ Popup close is not an authentication signal. This release does not observe provisional
450
+ submit matchers inside native Android popup WebViews or managed-popup form navigations. A
451
+ provider may instead match an observed session/status request that the opener issues after
452
+ popup close; otherwise concealment waits for the opener's `__satyaReady`. See
453
+ `PROVIDER_CONFIG.md` §2.1 for the complete schema and limitations.
435
454
 
436
455
  Pass `presentation="fullScreen"` to keep the legacy full-window takeover. Set `prefetch`
437
456
  to resolve the provider policy and warm the WebView engine at mount so the sheet opens
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'expo.modules.satyaattest'
7
- version = '3.2.0'
7
+ version = '3.3.0'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.satyaattest"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName "3.2.0"
13
+ versionName "3.3.0"
14
14
  // StrongBox key attestation needs API 28+; basic Keystore attestation API 24+.
15
15
  minSdkVersion 24
16
16
  }
@@ -107,6 +107,12 @@ class SatyaAttestModule : Module() {
107
107
 
108
108
  Function("satyaCoreVersion") { satyaCoreVersion() }
109
109
 
110
+ Function("createWebViewBridgeCapability") {
111
+ val bytes = ByteArray(32)
112
+ SecureRandom().nextBytes(bytes)
113
+ Base64.encodeToString(bytes, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)
114
+ }
115
+
110
116
  AsyncFunction("initialize") { configJson: String -> transportConfigJson = configJson }
111
117
 
112
118
  AsyncFunction("dispose") { transportConfigJson = "{}" }
@@ -2,6 +2,7 @@ import { NativeModule } from 'expo';
2
2
  import { VerifyResult } from './types';
3
3
  declare class SatyaAttestModule extends NativeModule<Record<string, never>> {
4
4
  satyaCoreVersion(): string;
5
+ createWebViewBridgeCapability(): string;
5
6
  attestNativeReplay(providerTemplateJson: string, capturedRequestJson?: string | null): Promise<string>;
6
7
  attestNativeReplayBundle(providerTemplateJson: string, capturedRequestsJson: string, partialEndpointSelectionJson?: string | null): Promise<string>;
7
8
  verifyArtifact(artifactJson: string): Promise<boolean>;
@@ -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,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
+ {"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;IAC1B,6BAA6B,IAAI,MAAM;IAEvC,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;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"]}
1
+ {"version":3,"file":"SatyaAttestModule.js","sourceRoot":"","sources":["../src/SatyaAttestModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAmCzD,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 createWebViewBridgeCapability(): 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"]}
@@ -2,6 +2,7 @@ import { NativeModule } from 'expo';
2
2
  import { VerifyResult } from './types';
3
3
  declare class SatyaAttestModule extends NativeModule<Record<string, never>> {
4
4
  satyaCoreVersion(): string;
5
+ createWebViewBridgeCapability(): string;
5
6
  attestNativeReplay(_providerTemplateJson: string, _capturedRequestJson?: string | null): Promise<string>;
6
7
  attestNativeReplayBundle(_providerTemplateJson: string, _capturedRequestsJson: string, _partialEndpointSelectionJson?: string | null): Promise<string>;
7
8
  verifyArtifact(_artifactJson: string): Promise<boolean>;
@@ -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,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"}
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;IAG1B,6BAA6B,IAAI,MAAM;IAGjC,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"}
@@ -5,6 +5,9 @@ class SatyaAttestModule extends NativeModule {
5
5
  satyaCoreVersion() {
6
6
  throw unsupportedPlatformError();
7
7
  }
8
+ createWebViewBridgeCapability() {
9
+ throw unsupportedPlatformError();
10
+ }
8
11
  async attestNativeReplay(_providerTemplateJson, _capturedRequestJson) {
9
12
  throw unsupportedPlatformError();
10
13
  }
@@ -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,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"]}
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,6BAA6B;QAC3B,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 createWebViewBridgeCapability(): 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"]}
@@ -43,7 +43,15 @@ function ProviderAttestButtonView({ props, flow, }) {
43
43
  {flow.running ? 'Generating Attestation' : flow.buttonLabel}
44
44
  </Text>
45
45
  </Pressable>) : null}
46
- {flow.prefetch && !flow.modalVisible && WarmWebView ? (<WarmWebView source={{ uri: 'about:blank' }} style={styles.warmWebView} pointerEvents="none" javaScriptEnabled={false} accessibilityElementsHidden importantForAccessibility="no-hide-descendants"/>) : null}
46
+ {flow.prefetch && !flow.modalVisible && WarmWebView ? (<WarmWebView
47
+ // Warms the WebKit process before the real WebView opens. This must be
48
+ // an `html` source rather than a blank-page URI: react-native-webview
49
+ // loads any hostless URI with `-[WKWebView loadFileURL:]`, which
50
+ // raises NSInvalidArgumentException and crashes the app on iOS. An
51
+ // html source takes the `loadHTMLString` branch and warms the same
52
+ // way. This view mounts outside the modal as soon as the provider
53
+ // resolves, so a crash here lands at launch, not during the flow.
54
+ source={{ html: '<html></html>' }} style={styles.warmWebView} pointerEvents="none" javaScriptEnabled={false} accessibilityElementsHidden importantForAccessibility="no-hide-descendants"/>) : null}
47
55
  <ProviderAttestModal flow={flow}/>
48
56
  </>);
49
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaProviderAttestButton.js","sourceRoot":"","sources":["../src/SatyaProviderAttestButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useState } from 'react';\nimport { ActivityIndicator, Pressable, Text } from 'react-native';\n\nimport { ProviderAttestModal } from './components/providerAttest/ProviderAttestModal';\nimport {\n selectProviderFlow,\n type SatyaProviderFlowSelection,\n} from './components/providerAttest/flowSelection';\nimport { styles } from './components/providerAttest/styles';\nimport { useTeelsProviderFlow } from './components/providerAttest/teels/useTeelsProviderFlow';\nimport type { SatyaProviderAttestButtonProps } from './components/providerAttest/types';\nimport {\n useProviderAttestFlow,\n type ProviderAttestFlow,\n} from './components/providerAttest/useProviderAttestFlow';\nimport { getSatyaRuntimeConfig, type SatyaRuntimeConfig } from './satyaConfig';\n\nexport type {\n SatyaBackendVerifyResult,\n SatyaProviderAttestButtonProps,\n SatyaProviderAttestationResult,\n SatyaProviderAttestPresentation,\n SatyaProviderAttestTheme,\n SatyaProviderFlowPhase,\n SatyaProviderFlowProgress,\n SatyaTeelsAttestation,\n SatyaTeelsClaimData,\n} from './components/providerAttest/types';\n\nexport function SatyaProviderAttestButton(props: SatyaProviderAttestButtonProps) {\n // Bind the runtime config once at mount (the init-before-mount contract),\n // but recompute the flow selection from live props so a providerId/template\n // switch cannot leave the button running a stale flow.\n const [runtime] = useState<SatyaRuntimeConfig>(() => getSatyaRuntimeConfig());\n const selection = selectProviderFlow(\n {\n providerId: props.providerId,\n providerTemplateId: props.providerTemplate?.providerId,\n },\n runtime\n );\n\n if (selection.type === 'teels') {\n return <TeelsProviderFlowHost props={props} selection={selection} />;\n }\n return <NativeProviderFlowHost props={props} />;\n}\n\ninterface ProviderFlowHostProps {\n props: SatyaProviderAttestButtonProps;\n}\n\nfunction NativeProviderFlowHost({ props }: ProviderFlowHostProps) {\n const flow = useProviderAttestFlow(props);\n return <ProviderAttestButtonView props={props} flow={flow} />;\n}\n\nfunction TeelsProviderFlowHost({\n props,\n selection,\n}: ProviderFlowHostProps & {\n selection: Extract<SatyaProviderFlowSelection, { type: 'teels' }>;\n}) {\n const flow = useTeelsProviderFlow(props, { selection });\n return <ProviderAttestButtonView props={props} flow={flow} />;\n}\n\nfunction ProviderAttestButtonView({\n props,\n flow,\n}: {\n props: SatyaProviderAttestButtonProps;\n flow: ProviderAttestFlow;\n}) {\n const isDisabled = props.disabled || flow.running || flow.modalVisible;\n const shouldRenderButton = props.renderButton ?? true;\n const WarmWebView = flow.ResolvedWebView;\n\n return (\n <>\n {shouldRenderButton ? (\n <Pressable\n style={[\n styles.button,\n { backgroundColor: flow.theme.accentColor },\n isDisabled && styles.buttonDisabled,\n ]}\n disabled={isDisabled}\n onPress={flow.openFlow}>\n {flow.running ? <ActivityIndicator color=\"#ffffff\" style={styles.buttonSpinner} /> : null}\n <Text style={styles.buttonText}>\n {flow.running ? 'Generating Attestation' : flow.buttonLabel}\n </Text>\n </Pressable>\n ) : null}\n {flow.prefetch && !flow.modalVisible && WarmWebView ? (\n <WarmWebView\n source={{ uri: 'about:blank' }}\n style={styles.warmWebView}\n pointerEvents=\"none\"\n javaScriptEnabled={false}\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n />\n ) : null}\n <ProviderAttestModal flow={flow} />\n </>\n );\n}\n"]}
1
+ {"version":3,"file":"SatyaProviderAttestButton.js","sourceRoot":"","sources":["../src/SatyaProviderAttestButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useState } from 'react';\nimport { ActivityIndicator, Pressable, Text } from 'react-native';\n\nimport { ProviderAttestModal } from './components/providerAttest/ProviderAttestModal';\nimport {\n selectProviderFlow,\n type SatyaProviderFlowSelection,\n} from './components/providerAttest/flowSelection';\nimport { styles } from './components/providerAttest/styles';\nimport { useTeelsProviderFlow } from './components/providerAttest/teels/useTeelsProviderFlow';\nimport type { SatyaProviderAttestButtonProps } from './components/providerAttest/types';\nimport {\n useProviderAttestFlow,\n type ProviderAttestFlow,\n} from './components/providerAttest/useProviderAttestFlow';\nimport { getSatyaRuntimeConfig, type SatyaRuntimeConfig } from './satyaConfig';\n\nexport type {\n SatyaBackendVerifyResult,\n SatyaProviderAttestButtonProps,\n SatyaProviderAttestationResult,\n SatyaProviderAttestPresentation,\n SatyaProviderAttestTheme,\n SatyaProviderFlowPhase,\n SatyaProviderFlowProgress,\n SatyaTeelsAttestation,\n SatyaTeelsClaimData,\n} from './components/providerAttest/types';\n\nexport function SatyaProviderAttestButton(props: SatyaProviderAttestButtonProps) {\n // Bind the runtime config once at mount (the init-before-mount contract),\n // but recompute the flow selection from live props so a providerId/template\n // switch cannot leave the button running a stale flow.\n const [runtime] = useState<SatyaRuntimeConfig>(() => getSatyaRuntimeConfig());\n const selection = selectProviderFlow(\n {\n providerId: props.providerId,\n providerTemplateId: props.providerTemplate?.providerId,\n },\n runtime\n );\n\n if (selection.type === 'teels') {\n return <TeelsProviderFlowHost props={props} selection={selection} />;\n }\n return <NativeProviderFlowHost props={props} />;\n}\n\ninterface ProviderFlowHostProps {\n props: SatyaProviderAttestButtonProps;\n}\n\nfunction NativeProviderFlowHost({ props }: ProviderFlowHostProps) {\n const flow = useProviderAttestFlow(props);\n return <ProviderAttestButtonView props={props} flow={flow} />;\n}\n\nfunction TeelsProviderFlowHost({\n props,\n selection,\n}: ProviderFlowHostProps & {\n selection: Extract<SatyaProviderFlowSelection, { type: 'teels' }>;\n}) {\n const flow = useTeelsProviderFlow(props, { selection });\n return <ProviderAttestButtonView props={props} flow={flow} />;\n}\n\nfunction ProviderAttestButtonView({\n props,\n flow,\n}: {\n props: SatyaProviderAttestButtonProps;\n flow: ProviderAttestFlow;\n}) {\n const isDisabled = props.disabled || flow.running || flow.modalVisible;\n const shouldRenderButton = props.renderButton ?? true;\n const WarmWebView = flow.ResolvedWebView;\n\n return (\n <>\n {shouldRenderButton ? (\n <Pressable\n style={[\n styles.button,\n { backgroundColor: flow.theme.accentColor },\n isDisabled && styles.buttonDisabled,\n ]}\n disabled={isDisabled}\n onPress={flow.openFlow}>\n {flow.running ? <ActivityIndicator color=\"#ffffff\" style={styles.buttonSpinner} /> : null}\n <Text style={styles.buttonText}>\n {flow.running ? 'Generating Attestation' : flow.buttonLabel}\n </Text>\n </Pressable>\n ) : null}\n {flow.prefetch && !flow.modalVisible && WarmWebView ? (\n <WarmWebView\n // Warms the WebKit process before the real WebView opens. This must be\n // an `html` source rather than a blank-page URI: react-native-webview\n // loads any hostless URI with `-[WKWebView loadFileURL:]`, which\n // raises NSInvalidArgumentException and crashes the app on iOS. An\n // html source takes the `loadHTMLString` branch and warms the same\n // way. This view mounts outside the modal as soon as the provider\n // resolves, so a crash here lands at launch, not during the flow.\n source={{ html: '<html></html>' }}\n style={styles.warmWebView}\n pointerEvents=\"none\"\n javaScriptEnabled={false}\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n />\n ) : null}\n <ProviderAttestModal flow={flow} />\n </>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderAttestModal.d.ts","sourceRoot":"","sources":["../../../src/components/providerAttest/ProviderAttestModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAqC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,UAAU,wBAAwB;IAChC,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,EAAE,wBAAwB,qBAMrE"}
1
+ {"version":3,"file":"ProviderAttestModal.d.ts","sourceRoot":"","sources":["../../../src/components/providerAttest/ProviderAttestModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAuC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,UAAU,wBAAwB;IAChC,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,EAAE,wBAAwB,qBAMrE"}