@burnt-labs/expo-satya-attest 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/PROVIDER_CONFIG.md +66 -0
  3. package/README.md +110 -0
  4. package/android/build.gradle +2 -2
  5. package/build/SatyaProviderAttestButton.d.ts +1 -1
  6. package/build/SatyaProviderAttestButton.d.ts.map +1 -1
  7. package/build/SatyaProviderAttestButton.js +26 -2
  8. package/build/SatyaProviderAttestButton.js.map +1 -1
  9. package/build/components/providerAttest/ProviderAttestModal.d.ts.map +1 -1
  10. package/build/components/providerAttest/ProviderAttestModal.js +34 -10
  11. package/build/components/providerAttest/ProviderAttestModal.js.map +1 -1
  12. package/build/components/providerAttest/copy.d.ts +1 -0
  13. package/build/components/providerAttest/copy.d.ts.map +1 -1
  14. package/build/components/providerAttest/copy.js +15 -0
  15. package/build/components/providerAttest/copy.js.map +1 -1
  16. package/build/components/providerAttest/flowSelection.d.ts +18 -0
  17. package/build/components/providerAttest/flowSelection.d.ts.map +1 -0
  18. package/build/components/providerAttest/flowSelection.js +25 -0
  19. package/build/components/providerAttest/flowSelection.js.map +1 -0
  20. package/build/components/providerAttest/teels/resolveTeelsProvider.d.ts +26 -0
  21. package/build/components/providerAttest/teels/resolveTeelsProvider.d.ts.map +1 -0
  22. package/build/components/providerAttest/teels/resolveTeelsProvider.js +71 -0
  23. package/build/components/providerAttest/teels/resolveTeelsProvider.js.map +1 -0
  24. package/build/components/providerAttest/teels/teelsProtocol.d.ts +41 -0
  25. package/build/components/providerAttest/teels/teelsProtocol.d.ts.map +1 -0
  26. package/build/components/providerAttest/teels/teelsProtocol.js +257 -0
  27. package/build/components/providerAttest/teels/teelsProtocol.js.map +1 -0
  28. package/build/components/providerAttest/teels/useTeelsProviderFlow.d.ts +11 -0
  29. package/build/components/providerAttest/teels/useTeelsProviderFlow.d.ts.map +1 -0
  30. package/build/components/providerAttest/teels/useTeelsProviderFlow.js +489 -0
  31. package/build/components/providerAttest/teels/useTeelsProviderFlow.js.map +1 -0
  32. package/build/components/providerAttest/types.d.ts +12 -1
  33. package/build/components/providerAttest/types.d.ts.map +1 -1
  34. package/build/components/providerAttest/types.js.map +1 -1
  35. package/build/components/providerAttest/useProviderAttestFlow.d.ts +9 -1
  36. package/build/components/providerAttest/useProviderAttestFlow.d.ts.map +1 -1
  37. package/build/components/providerAttest/useProviderAttestFlow.js.map +1 -1
  38. package/build/index.d.ts +1 -1
  39. package/build/index.d.ts.map +1 -1
  40. package/build/index.js.map +1 -1
  41. package/build/satyaConfig.d.ts +7 -0
  42. package/build/satyaConfig.d.ts.map +1 -1
  43. package/build/satyaConfig.js +15 -1
  44. package/build/satyaConfig.js.map +1 -1
  45. package/build/sdkMetadata.generated.d.ts +1 -1
  46. package/build/sdkMetadata.generated.js +1 -1
  47. package/build/sdkMetadata.generated.js.map +1 -1
  48. package/build/types/error.types.d.ts +5 -1
  49. package/build/types/error.types.d.ts.map +1 -1
  50. package/build/types/error.types.js +13 -0
  51. package/build/types/error.types.js.map +1 -1
  52. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/libsatya_ffi.a +0 -0
  53. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64_x86_64-simulator/libsatya_ffi.a +0 -0
  54. package/ios/SatyaAttest.podspec +1 -1
  55. package/package.json +2 -2
  56. package/src/SatyaProviderAttestButton.tsx +56 -2
  57. package/src/components/providerAttest/ProviderAttestModal.tsx +47 -8
  58. package/src/components/providerAttest/copy.ts +16 -0
  59. package/src/components/providerAttest/flowSelection.ts +44 -0
  60. package/src/components/providerAttest/teels/resolveTeelsProvider.ts +135 -0
  61. package/src/components/providerAttest/teels/teelsProtocol.ts +294 -0
  62. package/src/components/providerAttest/teels/useTeelsProviderFlow.ts +648 -0
  63. package/src/components/providerAttest/types.ts +19 -1
  64. package/src/components/providerAttest/useProviderAttestFlow.ts +12 -1
  65. package/src/index.ts +2 -0
  66. package/src/satyaConfig.ts +29 -1
  67. package/src/sdkMetadata.generated.ts +1 -1
  68. package/src/types/error.types.ts +14 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.2.0 - 2026-08-07
6
+
7
+ ### Added
8
+
9
+ - An opt-in, SDK-only TeeLS hosted passthrough selected with
10
+ `initSatyaAttest({ providerId, appId, teelsFlow: true })`. The flow preserves
11
+ normal provider grants, performs one existing provider-policy access preflight,
12
+ opens the registry login URL in an origin-bound WebView bridge, and returns the
13
+ opaque JSON result through `onTeelsComplete` without native replay, SATYA
14
+ artifact construction, local verification, or verifier artifact submission.
15
+ - TeeLS-specific progress phases and errors for null/invalid results, hosted
16
+ failures, timeout, and WebView failure. The feature is disabled by default;
17
+ omitting `teelsFlow` in a later init restores native routing for subsequently
18
+ mounted buttons.
19
+
5
20
  ## 3.1.0 - 2026-07-31
6
21
 
7
22
  ### Added
@@ -564,6 +564,72 @@ unrelated to the registry catalog flag `metadata.isSatyaReady`.)
564
564
  > This is distinct from endpoint `capture`: `injection` is page JavaScript at the provider
565
565
  > level; `capture` is per-endpoint active-fetch/readiness config.
566
566
 
567
+ ### Hosted TeeLS passthrough provider injection
568
+
569
+ An SDK session initialized with `teelsFlow: true` may use a normal granted
570
+ provider as a hosted TeeLS passthrough. There is no Registry flow discriminator:
571
+ the SDK binds TeeLS mode to the configured provider/app pair, performs the
572
+ normal provider-policy access preflight, opens `login.url`, and skips request
573
+ capture and native replay.
574
+
575
+ Keep the provider structurally valid for the normal Registry. Its existing
576
+ endpoint, SNI, pins, grants, and verifier policy remain ordinary provider
577
+ policy even though the TeeLS attempt does not fire or replay that endpoint.
578
+ The hosted page origin may therefore differ from the inert endpoint/SNI host;
579
+ the TeeLS WebView origin is always derived from `login.url`, while the access
580
+ preflight continues to validate the governed provider policy.
581
+
582
+ Generate the canonical adapter injection from the Expo SDK checkout:
583
+
584
+ ```bash
585
+ cd platform_sdks/expo-satya-attest
586
+ node scripts/generate-hosted-teels-provider-injection.js \
587
+ --origin https://hosted-teels.example \
588
+ --json
589
+ ```
590
+
591
+ Put the resulting JSON string in the provider's top-level `injection`. The
592
+ adapter is additive. It reuses, in order:
593
+
594
+ 1. standard `TeeLS.create({ onSuccess, onError })` callbacks by intercepting the
595
+ initial `window.TeeLS` assignment before the hosted bundle runs;
596
+ 2. the `teels:attestation` `CustomEvent`, reading
597
+ `event.detail.attestation` and optional `event.detail.data`; and
598
+ 3. the durable `window.__teels.attestation` snapshot for success recovery when
599
+ the adapter attaches after completion.
600
+
601
+ The adapter calls the page's original callbacks first and forwards to SATYA in
602
+ `finally`, preserving callback arguments, `this`, return values, thrown errors,
603
+ the returned TeeLS handle, and every other TeeLS SDK export. The second argument
604
+ to SATYA is always the attestation; if present, the first `onSuccess` argument is
605
+ forwarded as `claimData` and added to the completion payload as `claimData`.
606
+ Null success maps to `attestation_missing`; all other TeeLS errors map to
607
+ `teels_flow_failed`.
608
+ The SDK bridge and adapter both enforce exactly-once terminal settlement.
609
+
610
+ The terminal API is distinct from native login readiness and supports the optional
611
+ `claimData` passthrough:
612
+
613
+ ```js
614
+ window.__SATYA_TEELS_BRIDGE__.complete(attestation, claimData);
615
+ window.__SATYA_TEELS_BRIDGE__.fail('attestation_missing');
616
+ window.__SATYA_TEELS_BRIDGE__.fail('teels_flow_failed');
617
+ ```
618
+
619
+ Do not emit or overload `__satyaReady` for TeeLS completion. Do not post the
620
+ opaque result through the native-replay interceptor. The adapter must remain
621
+ main-frame and exact-origin scoped and must tolerate being evaluated only once;
622
+ SATYA installs the bridge before executing the Registry injection.
623
+
624
+ Success can be recovered from the durable snapshot after a late injection, but
625
+ a past error cannot. Before using a hosted provider beyond development, prove
626
+ on physical iOS and Android—both with the external TeeLS bundle cached and
627
+ uncached—that `injectedJavaScriptBeforeContentLoaded` wraps `TeeLS.create`
628
+ before the page calls it. If that ordering cannot be established, add a minimal
629
+ page-owned error seam instead of relying on best-effort console interception.
630
+ Also confirm the hosted TeeLS SDK invokes its configured `onError` for every
631
+ terminal failure; a rendered SDK error alone cannot be observed by the adapter.
632
+
567
633
  ---
568
634
 
569
635
  ## 10. Writing regex patterns (HTML / text bodies)
package/README.md CHANGED
@@ -26,6 +26,9 @@ with `CONFIG_INVALID`. An unregistered or unapproved id fails at the verifier
26
26
  with `APP_NOT_REGISTERED` / `APP_DISABLED`, and an ungranted provider fails with
27
27
  `PROVIDER_ACCESS_DENIED`. See
28
28
  [`docs/MULTI_TENANT_VERIFICATION.md`](../../docs/MULTI_TENANT_VERIFICATION.md).
29
+ Flows also fail with `app_platform_not_configured` when the app has no
30
+ registered identity for the current platform, so register every platform you
31
+ develop and test on.
29
32
 
30
33
  ## Current Flow
31
34
 
@@ -51,6 +54,113 @@ The earlier mock and plain `webview-observed` modes are no longer exposed throug
51
54
  high-level Expo client because they do not prove a provider response fetched through a
52
55
  pinned native TLS connection.
53
56
 
57
+ ## TeeLS Hosted Passthrough (Opt-In)
58
+
59
+ The SDK also has an isolated, opt-in TeeLS hosted flow. It uses a normal
60
+ registry provider and the normal app/provider grant, but it does **not** run
61
+ request capture, native replay, SATYA artifact construction, local SATYA
62
+ verification, or verifier artifact submission. SATYA performs one provider-policy
63
+ request before opening the WebView solely to confirm that the configured app is
64
+ active and has access to the provider.
65
+
66
+ Enable the flow for exactly one provider/app pair at initialization:
67
+
68
+ ```tsx
69
+ import {
70
+ initSatyaAttest,
71
+ SatyaProviderAttestButton,
72
+ type SatyaTeelsAttestation,
73
+ type SatyaTeelsClaimData,
74
+ } from '@burnt-labs/expo-satya-attest';
75
+
76
+ initSatyaAttest({
77
+ providerId: 'satya.teels.v1',
78
+ appId: 'app_your-registered-app-id',
79
+ teelsFlow: true,
80
+ });
81
+
82
+ function TeelsAttestButton() {
83
+ const verifyTeelsResult = async (
84
+ attestation: SatyaTeelsAttestation,
85
+ claimData?: SatyaTeelsClaimData
86
+ ) => {
87
+ // Required: verify the TeeLS quote, freshness, and expected data binding
88
+ // in your application or backend before trusting the result.
89
+ // `claimData` is the first argument to TeeLS success, usually the
90
+ // JSON-safe payload forwarded by the hosted provider.
91
+ };
92
+
93
+ return (
94
+ <SatyaProviderAttestButton
95
+ providerId="satya.teels.v1"
96
+ transportConfig={{
97
+ verifierUrl: 'https://satya-verifier-v2-staging.burnt.com',
98
+ appId: 'app_your-registered-app-id',
99
+ }}
100
+ onTeelsComplete={verifyTeelsResult}
101
+ onError={(error) => console.error(error.code, error.message)}
102
+ />
103
+ );
104
+ }
105
+ ```
106
+
107
+ The provider's existing `login.url` opens in the SDK WebView and its normal
108
+ Registry `injection` adapts the hosted page to the SDK-owned bridge. A page that
109
+ already uses the standard TeeLS callbacks and EarnOS success event/snapshot
110
+ does not need frontend changes. See
111
+ [Provider Config](./PROVIDER_CONFIG.md#hosted-teels-passthrough-provider-injection)
112
+ for the adapter and device-validation contract.
113
+
114
+ Only a non-null, JSON-safe attestation object can complete the flow. If provided,
115
+ `claimData` is additionally forwarded as a second argument to `onTeelsComplete`.
116
+ Success is returned only through `onTeelsComplete`; invalid/null data, hosted
117
+ failures, timeout, WebView failure, and access denial use the normal `onError`
118
+ path. Closing the sheet before a terminal result uses `onCancel`.
119
+
120
+ Native-replay-only props are not used by TeeLS. A component may keep an
121
+ `onComplete` handler for normal mode, but TeeLS never calls it. Initialize the
122
+ SDK before mounting the button. A later init with `teelsFlow: false` or with
123
+ the option omitted disables TeeLS for subsequently mounted buttons; normal
124
+ providers continue to use the existing native replay flow unchanged.
125
+
126
+ The returned object is an opaque, unverified TeeLS result and is delivered as
127
+ the first argument. `claimData` is the hosted-provider payload whose inner
128
+ `data` object is hashed into the quote's `report_data`; treat both as
129
+ untrusted input until verified. Do not treat callback delivery as a SATYA
130
+ verification claim, and do not log or persist the result by default.
131
+
132
+ ### Verifying the TeeLS result
133
+
134
+ The SATYA SDK performs no verification of the TeeLS result — it neither
135
+ verifies the quote nor checks that the claim data matches it. Before trusting
136
+ the result, the consuming application or its backend must verify it:
137
+
138
+ 1. **DCAP quote.** `attestation.quote` is a hex-encoded Intel TDX quote. Post
139
+ it to the independent TeeLS verifier service and require `success: true`,
140
+ `quote.verified: true`, and a `tcb_status` your policy accepts:
141
+
142
+ ```bash
143
+ curl -X POST https://attest.burnt.com/verify \
144
+ -H 'content-type: application/json' \
145
+ -d '{"hex":"<quote hex>"}'
146
+ ```
147
+
148
+ 2. **Measurements.** Compare the verified quote's `mrtd` and `rtmr0`–`rtmr3`
149
+ against the expected-measurements registry published independently by the
150
+ verifier (`GET https://attest.burnt.com/measurements`); require a match
151
+ against one of its `valid[]` sets.
152
+ 3. **Data binding.** Use `reportdata` from the **verified quote response**
153
+ (never the advisory `attestation.reportDataHex` field — it is
154
+ server-supplied). Its first 32 bytes must equal
155
+ `SHA-256(JSON.stringify(data))` over the exact result-data object the page
156
+ delivered (in this flow, `claimData.data`; remaining bytes are
157
+ zero-padded). When the provider binds a policy hash, the envelope is
158
+ `{ data, policyHash }` instead of bare `data`. Any mismatch means the claim
159
+ data was substituted after attestation.
160
+
161
+ The result carries no nonce, so enforce your own freshness and replay policy
162
+ wherever the attestation crosses a trust boundary.
163
+
54
164
  ## Native Trust Tiers
55
165
 
56
166
  | Platform | Witness key | Platform evidence | Typical tier |
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'expo.modules.satyaattest'
7
- version = '3.1.0'
7
+ version = '3.2.0'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.satyaattest"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName "3.1.0"
13
+ versionName "3.2.0"
14
14
  // StrongBox key attestation needs API 28+; basic Keystore attestation API 24+.
15
15
  minSdkVersion 24
16
16
  }
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
2
  import type { SatyaProviderAttestButtonProps } from './components/providerAttest/types';
3
- export type { SatyaBackendVerifyResult, SatyaProviderAttestButtonProps, SatyaProviderAttestationResult, SatyaProviderAttestPresentation, SatyaProviderAttestTheme, SatyaProviderFlowPhase, SatyaProviderFlowProgress, } from './components/providerAttest/types';
3
+ export type { SatyaBackendVerifyResult, SatyaProviderAttestButtonProps, SatyaProviderAttestationResult, SatyaProviderAttestPresentation, SatyaProviderAttestTheme, SatyaProviderFlowPhase, SatyaProviderFlowProgress, SatyaTeelsAttestation, SatyaTeelsClaimData, } from './components/providerAttest/types';
4
4
  export declare function SatyaProviderAttestButton(props: SatyaProviderAttestButtonProps): React.JSX.Element;
5
5
  //# sourceMappingURL=SatyaProviderAttestButton.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaProviderAttestButton.d.ts","sourceRoot":"","sources":["../src/SatyaProviderAttestButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AAGxF,YAAY,EACV,wBAAwB,EACxB,8BAA8B,EAC9B,8BAA8B,EAC9B,+BAA+B,EAC/B,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,mCAAmC,CAAC;AAE3C,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,8BAA8B,qBAoC9E"}
1
+ {"version":3,"file":"SatyaProviderAttestButton.d.ts","sourceRoot":"","sources":["../src/SatyaProviderAttestButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAUxC,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AAOxF,YAAY,EACV,wBAAwB,EACxB,8BAA8B,EAC9B,8BAA8B,EAC9B,+BAA+B,EAC/B,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAE3C,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,8BAA8B,qBAiB9E"}
@@ -1,10 +1,34 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { ActivityIndicator, Pressable, Text } from 'react-native';
3
3
  import { ProviderAttestModal } from './components/providerAttest/ProviderAttestModal';
4
+ import { selectProviderFlow, } from './components/providerAttest/flowSelection';
4
5
  import { styles } from './components/providerAttest/styles';
5
- import { useProviderAttestFlow } from './components/providerAttest/useProviderAttestFlow';
6
+ import { useTeelsProviderFlow } from './components/providerAttest/teels/useTeelsProviderFlow';
7
+ import { useProviderAttestFlow, } from './components/providerAttest/useProviderAttestFlow';
8
+ import { getSatyaRuntimeConfig } from './satyaConfig';
6
9
  export function SatyaProviderAttestButton(props) {
10
+ // Bind the runtime config once at mount (the init-before-mount contract),
11
+ // but recompute the flow selection from live props so a providerId/template
12
+ // switch cannot leave the button running a stale flow.
13
+ const [runtime] = useState(() => getSatyaRuntimeConfig());
14
+ const selection = selectProviderFlow({
15
+ providerId: props.providerId,
16
+ providerTemplateId: props.providerTemplate?.providerId,
17
+ }, runtime);
18
+ if (selection.type === 'teels') {
19
+ return <TeelsProviderFlowHost props={props} selection={selection}/>;
20
+ }
21
+ return <NativeProviderFlowHost props={props}/>;
22
+ }
23
+ function NativeProviderFlowHost({ props }) {
7
24
  const flow = useProviderAttestFlow(props);
25
+ return <ProviderAttestButtonView props={props} flow={flow}/>;
26
+ }
27
+ function TeelsProviderFlowHost({ props, selection, }) {
28
+ const flow = useTeelsProviderFlow(props, { selection });
29
+ return <ProviderAttestButtonView props={props} flow={flow}/>;
30
+ }
31
+ function ProviderAttestButtonView({ props, flow, }) {
8
32
  const isDisabled = props.disabled || flow.running || flow.modalVisible;
9
33
  const shouldRenderButton = props.renderButton ?? true;
10
34
  const WarmWebView = flow.ResolvedWebView;
@@ -1 +1 @@
1
- {"version":3,"file":"SatyaProviderAttestButton.js","sourceRoot":"","sources":["../src/SatyaProviderAttestButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iDAAiD,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAE5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,mDAAmD,CAAC;AAY1F,MAAM,UAAU,yBAAyB,CAAC,KAAqC;IAC7E,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC;IACvE,MAAM,kBAAkB,GAAG,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC;IACtD,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC;IAEzC,OAAO,CACL,EACE;MAAA,CAAC,kBAAkB,CAAC,CAAC,CAAC,CACpB,CAAC,SAAS,CACR,KAAK,CAAC,CAAC;gBACL,MAAM,CAAC,MAAM;gBACb,EAAE,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;gBAC3C,UAAU,IAAI,MAAM,CAAC,cAAc;aACpC,CAAC,CACF,QAAQ,CAAC,CAAC,UAAU,CAAC,CACrB,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CACvB;UAAA,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,EAAG,CAAC,CAAC,CAAC,IAAI,CACzF;UAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAC7B;YAAA,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAC7D;UAAA,EAAE,IAAI,CACR;QAAA,EAAE,SAAS,CAAC,CACb,CAAC,CAAC,CAAC,IAAI,CACR;MAAA,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,WAAW,CAAC,CAAC,CAAC,CACpD,CAAC,WAAW,CACV,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAC/B,KAAK,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAC1B,aAAa,CAAC,MAAM,CACpB,iBAAiB,CAAC,CAAC,KAAK,CAAC,CACzB,2BAA2B,CAC3B,yBAAyB,CAAC,qBAAqB,EAC/C,CACH,CAAC,CAAC,CAAC,IAAI,CACR;MAAA,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAClC;IAAA,GAAG,CACJ,CAAC;AACJ,CAAC","sourcesContent":["import React from 'react';\nimport { ActivityIndicator, Pressable, Text } from 'react-native';\n\nimport { ProviderAttestModal } from './components/providerAttest/ProviderAttestModal';\nimport { styles } from './components/providerAttest/styles';\nimport type { SatyaProviderAttestButtonProps } from './components/providerAttest/types';\nimport { useProviderAttestFlow } from './components/providerAttest/useProviderAttestFlow';\n\nexport type {\n SatyaBackendVerifyResult,\n SatyaProviderAttestButtonProps,\n SatyaProviderAttestationResult,\n SatyaProviderAttestPresentation,\n SatyaProviderAttestTheme,\n SatyaProviderFlowPhase,\n SatyaProviderFlowProgress,\n} from './components/providerAttest/types';\n\nexport function SatyaProviderAttestButton(props: SatyaProviderAttestButtonProps) {\n const flow = useProviderAttestFlow(props);\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 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 +1 @@
1
- {"version":3,"file":"ProviderAttestModal.d.ts","sourceRoot":"","sources":["../../../src/components/providerAttest/ProviderAttestModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AA+B3D,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;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"}
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
2
2
  import { AccessibilityInfo, ActivityIndicator, Animated, Easing, Keyboard, LayoutAnimation, Modal, Platform, Pressable, StatusBar, StyleSheet, Text, useWindowDimensions, View, } from 'react-native';
3
3
  import { initialWindowMetrics, SafeAreaInsetsContext, SafeAreaProvider, } from 'react-native-safe-area-context';
4
4
  import { WEBVIEW_BOOTSTRAP_URL } from './constants';
5
- import { friendlyError, isRetryableError, preparingCopy, processingCopy } from './copy';
5
+ import { friendlyError, friendlyTeelsError, isRetryableError, preparingCopy, processingCopy, } from './copy';
6
6
  import { managedPopupKey, managedPopupSource } from './popupRelay';
7
7
  import { defaultMobileBrowserUserAgent, providerLoginUrl } from './providerResolution';
8
8
  import { styles } from './styles';
@@ -143,7 +143,7 @@ function AndroidSheetHost({ flow }) {
143
143
  </Modal>);
144
144
  }
145
145
  function ProviderSheetContent({ flow, insets }) {
146
- const { closeFlow, closeManagedPopup, contentReady, effectiveUserAgent, error, errorCode, errorRetryable, handleHardwareBack, handleMessage, handleNavigationStateChange, handleOpenWindow, handlePopupNavigationStateChange, handlePopupOpenWindow, handlePopupShouldStartLoadWithRequest, handleShouldStartLoadWithRequest, handleReload, isVerificationCovered, mainDocumentScriptReady, mainInjectedScript, managedPopup, markContentReady, markLoadEnd, markLoadStart, markPopupContentReady, maybeTriggerHtmlCapture, openFlow, phase, popupContentReady, popupDocumentScriptReady, popupHostRef, popupInjectedScript, popupRef, preparing, presentation, resolved, ResolvedWebView, secureConnection, secureHost, setBrowserChrome, setPopupChrome, theme, title, useNativePopups, webHostRef, webRef, webViewLoading, webViewProps, webViewUrl, returnToProvider, verificationPresentationStalled, } = flow;
146
+ const { closeFlow, closeManagedPopup, contentReady, effectiveUserAgent, error, errorCode, errorRetryable, handleHardwareBack, handleMessage, handleNavigationStateChange, handleOpenWindow, handlePopupNavigationStateChange, handlePopupOpenWindow, handlePopupShouldStartLoadWithRequest, handleShouldStartLoadWithRequest, handleReload, handleContentProcessDidTerminate, handleRenderProcessGone, handleWebViewError, handleWebViewHttpError, isTeelsFlow, isVerificationCovered, mainDocumentScriptReady, mainInjectedScript, managedPopup, markContentReady, markLoadEnd, markLoadStart, markPopupContentReady, maybeTriggerHtmlCapture, openFlow, phase, popupContentReady, popupDocumentScriptReady, popupHostRef, popupInjectedScript, popupRef, preparing, presentation, resolved, ResolvedWebView, secureConnection, secureHost, setBrowserChrome, setPopupChrome, theme, title, useNativePopups, webHostRef, webRef, webViewLoading, webViewProps, webViewUrl, returnToProvider, verificationPresentationStalled, } = flow;
147
147
  const useSheet = presentation === 'sheet';
148
148
  const [coverOpacity] = useState(() => new Animated.Value(1));
149
149
  const [popupCoverOpacity] = useState(() => new Animated.Value(1));
@@ -249,35 +249,45 @@ function ProviderSheetContent({ flow, insets }) {
249
249
  ]}/>
250
250
  </View>
251
251
  <View ref={webHostRef} style={[styles.webViewHost, { backgroundColor: theme.surfaceColor }]}>
252
- {ResolvedWebView && resolved ? (<ResolvedWebView {...(webViewProps ?? {})} geolocationEnabled={resolved.template.requiresLocation === true} ref={webRef} source={{
252
+ {ResolvedWebView && resolved ? (<ResolvedWebView {...(webViewProps ?? {})} {...(isTeelsFlow
253
+ ? {
254
+ onContentProcessDidTerminate: handleContentProcessDidTerminate,
255
+ onRenderProcessGone: handleRenderProcessGone,
256
+ onError: handleWebViewError,
257
+ onHttpError: handleWebViewHttpError,
258
+ }
259
+ : {})} geolocationEnabled={resolved.template.requiresLocation === true} ref={webRef} source={{
253
260
  uri: mainDocumentScriptReady
254
261
  ? (webViewUrl ?? providerLoginUrl(resolved.template))
255
262
  : WEBVIEW_BOOTSTRAP_URL,
256
263
  }} style={[styles.webView, webViewProps?.style]} accessibilityElementsHidden={providerContentConcealed} importantForAccessibility={providerContentConcealed ? 'no-hide-descendants' : 'auto'} javaScriptEnabled javaScriptCanOpenWindowsAutomatically domStorageEnabled sharedCookiesEnabled thirdPartyCookiesEnabled userAgent={effectiveUserAgent ?? defaultMobileBrowserUserAgent()} incognito={false} cacheEnabled setSupportMultipleWindows={useNativePopups} injectedJavaScriptBeforeContentLoaded={mainInjectedScript} injectedJavaScript={mainInjectedScript} injectedJavaScriptBeforeContentLoadedForMainFrameOnly={false} injectedJavaScriptForMainFrameOnly={false} onMessage={handleMessage} onOpenWindow={handleOpenWindow} onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest} onNavigationStateChange={handleNavigationStateChange} onLoadStart={(event) => {
257
264
  const nextChrome = readBrowserChromeState(event);
258
- invokeWebViewCallback(webViewProps?.onLoadStart, event);
265
+ invokeMainWebViewCallback(webViewProps?.onLoadStart, event, isTeelsFlow, 'before');
259
266
  if (!mainDocumentScriptReady && nextChrome.url === WEBVIEW_BOOTSTRAP_URL)
260
267
  return;
261
- markLoadStart();
268
+ markLoadStart(nextChrome.url);
262
269
  setBrowserChrome((current) => ({
263
270
  ...current,
264
271
  ...nextChrome,
265
272
  loading: true,
266
273
  }));
274
+ invokeMainWebViewCallback(webViewProps?.onLoadStart, event, isTeelsFlow, 'after');
267
275
  }} onLoadEnd={(event) => {
268
276
  const nextChrome = readBrowserChromeState(event);
269
- invokeWebViewCallback(webViewProps?.onLoadEnd, event);
277
+ invokeMainWebViewCallback(webViewProps?.onLoadEnd, event, isTeelsFlow, 'before');
270
278
  if (!mainDocumentScriptReady && nextChrome.url === WEBVIEW_BOOTSTRAP_URL)
271
279
  return;
272
280
  markContentReady();
273
- markLoadEnd();
281
+ markLoadEnd(nextChrome.url);
274
282
  setBrowserChrome((current) => ({
275
283
  ...current,
276
284
  ...nextChrome,
277
285
  loading: false,
278
286
  }));
279
287
  webRef.current?.injectJavaScript?.(mainInjectedScript);
280
- maybeTriggerHtmlCapture(nextChrome.url);
288
+ if (!isTeelsFlow)
289
+ maybeTriggerHtmlCapture(nextChrome.url);
290
+ invokeMainWebViewCallback(webViewProps?.onLoadEnd, event, isTeelsFlow, 'after');
281
291
  }} originWhitelist={['http://*', 'https://*', 'about:*']}/>) : (<View style={styles.webViewFallback}>
282
292
  <Text style={styles.errorText}>react-native-webview is not available.</Text>
283
293
  </View>)}
@@ -376,10 +386,10 @@ function ProviderSheetContent({ flow, insets }) {
376
386
  <Text style={styles.errorIconGlyph}>!</Text>
377
387
  </View>
378
388
  <Text style={[styles.overlayTitle, { color: theme.onSurfaceColor }]}>
379
- Could not verify your account
389
+ {isTeelsFlow ? 'Could not complete attestation' : 'Could not verify your account'}
380
390
  </Text>
381
391
  <Text style={[styles.overlaySubtitle, { color: theme.mutedColor }]}>
382
- {friendlyError(error, errorCode)}
392
+ {isTeelsFlow ? friendlyTeelsError(error, errorCode) : friendlyError(error, errorCode)}
383
393
  </Text>
384
394
  {isRetryableError(errorCode, errorRetryable) ? (<Pressable style={[styles.primaryAction, { backgroundColor: theme.accentColor }]} onPress={openFlow}>
385
395
  <Text style={styles.primaryActionText}>Try again</Text>
@@ -401,4 +411,18 @@ function ProviderSheetContent({ flow, insets }) {
401
411
  </View>
402
412
  </View>);
403
413
  }
414
+ function invokeMainWebViewCallback(callback, event, isTeelsFlow, timing) {
415
+ if ((!isTeelsFlow && timing !== 'before') || (isTeelsFlow && timing !== 'after'))
416
+ return;
417
+ if (!isTeelsFlow) {
418
+ invokeWebViewCallback(callback, event);
419
+ return;
420
+ }
421
+ try {
422
+ invokeWebViewCallback(callback, event);
423
+ }
424
+ catch {
425
+ // Observational host callbacks cannot interrupt or settle the TeeLS flow.
426
+ }
427
+ }
404
428
  //# sourceMappingURL=ProviderAttestModal.js.map