@latchway/react-native 0.0.0-bootstrap.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/LatchwayReactNative.podspec +33 -0
- package/NOTICE +7 -0
- package/README.md +293 -3
- package/SECURITY.md +69 -0
- package/android/build.gradle.kts +62 -0
- package/android/consumer-rules.pro +2 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +9 -0
- package/android/gradle.properties +4 -0
- package/android/gradlew +251 -0
- package/android/gradlew.bat +94 -0
- package/android/settings.gradle.kts +33 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/dev/latchway/reactnative/LatchwayReactNativePackage.kt +25 -0
- package/android/src/main/java/dev/latchway/reactnative/NativeLatchwayModule.kt +964 -0
- package/android/src/test/java/dev/latchway/reactnative/NativeLatchwayModuleTest.kt +521 -0
- package/babel.cjs +22 -0
- package/babel.d.cts +7 -0
- package/contract.lock +7 -0
- package/docs/architecture.md +145 -0
- package/docs/conformance.md +68 -0
- package/docs/langchain.md +135 -0
- package/docs/native-installation.md +172 -0
- package/docs/physical-device-evidence.md +559 -0
- package/docs/releasing.md +360 -0
- package/docs/security.md +55 -0
- package/ios/LatchwayNativeBridge.swift +1551 -0
- package/ios/RCTNativeLatchway.h +9 -0
- package/ios/RCTNativeLatchway.mm +175 -0
- package/lib/client.d.ts +29 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/client.js +939 -0
- package/lib/client.js.map +1 -0
- package/lib/component-client.d.ts +15 -0
- package/lib/component-client.d.ts.map +1 -0
- package/lib/component-client.js +141 -0
- package/lib/component-client.js.map +1 -0
- package/lib/config.d.ts +25 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +261 -0
- package/lib/config.js.map +1 -0
- package/lib/coordinator.d.ts +19 -0
- package/lib/coordinator.d.ts.map +1 -0
- package/lib/coordinator.js +167 -0
- package/lib/coordinator.js.map +1 -0
- package/lib/errors.d.ts +5 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +201 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +13 -0
- package/lib/index.js.map +1 -0
- package/lib/native/NativeLatchway.d.ts +25 -0
- package/lib/native/NativeLatchway.d.ts.map +1 -0
- package/lib/native/NativeLatchway.js +3 -0
- package/lib/native/NativeLatchway.js.map +1 -0
- package/lib/native/bridge.d.ts +5 -0
- package/lib/native/bridge.d.ts.map +1 -0
- package/lib/native/bridge.js +17 -0
- package/lib/native/bridge.js.map +1 -0
- package/lib/native-output.d.ts +3 -0
- package/lib/native-output.d.ts.map +1 -0
- package/lib/native-output.js +43 -0
- package/lib/native-output.js.map +1 -0
- package/lib/polyfills.d.ts +8 -0
- package/lib/polyfills.d.ts.map +1 -0
- package/lib/polyfills.js +56 -0
- package/lib/polyfills.js.map +1 -0
- package/lib/request-id.d.ts +2 -0
- package/lib/request-id.d.ts.map +1 -0
- package/lib/request-id.js +5 -0
- package/lib/request-id.js.map +1 -0
- package/lib/runtime-symbols.d.ts +2 -0
- package/lib/runtime-symbols.d.ts.map +1 -0
- package/lib/runtime-symbols.js +9 -0
- package/lib/runtime-symbols.js.map +1 -0
- package/lib/testing.d.ts +7 -0
- package/lib/testing.d.ts.map +1 -0
- package/lib/testing.js +9 -0
- package/lib/testing.js.map +1 -0
- package/lib/types.d.ts +200 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/version.d.ts +8 -0
- package/lib/version.d.ts.map +1 -0
- package/lib/version.js +8 -0
- package/lib/version.js.map +1 -0
- package/package.json +152 -6
- package/react-native.config.cjs +7 -0
- package/release-compatibility.json +64 -0
- package/src/client.ts +1022 -0
- package/src/component-client.ts +158 -0
- package/src/config.ts +368 -0
- package/src/coordinator.ts +195 -0
- package/src/errors.ts +225 -0
- package/src/index.ts +53 -0
- package/src/native/NativeLatchway.ts +75 -0
- package/src/native/bridge.ts +23 -0
- package/src/native-output.ts +43 -0
- package/src/polyfills.ts +50 -0
- package/src/request-id.ts +5 -0
- package/src/runtime-symbols.ts +9 -0
- package/src/testing.ts +11 -0
- package/src/types.ts +241 -0
- package/src/version.ts +7 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# React Native SDK architecture
|
|
2
|
+
|
|
3
|
+
## Dependency and trust boundary
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
React Native application
|
|
7
|
+
└─ @latchway/react-native
|
|
8
|
+
├─ @latchway/client 1.0.0 (errors and shared transport concepts)
|
|
9
|
+
├─ Latchway/AppAttest 1.0.0 (iOS)
|
|
10
|
+
├─ Latchway/AppExtensions 1.0.0 (optional Debug extension target)
|
|
11
|
+
└─ dev.latchway:latchway-okhttp + latchway-play-integrity 1.0.0 (Android)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The core repository owns OpenAPI, error codes, attestation binding, DPoP behavior, and compatibility. This package owns the handwritten React Native API, fetch integration, TurboModule schema, cross-instance lease, abort propagation, stable error projection, and redacted diagnostics. Native SDKs exclusively own installation keys, secure session persistence, platform attestation, DPoP signing, and native single-flight.
|
|
15
|
+
|
|
16
|
+
The gateway, not the SDK, derives user, organization, plan, trust, routing,
|
|
17
|
+
pricing, and quota facts. The SDK never receives an upstream provider
|
|
18
|
+
credential or treats application-supplied values as trusted server facts.
|
|
19
|
+
|
|
20
|
+
## Contract ownership
|
|
21
|
+
|
|
22
|
+
The Latchway core repository exclusively owns the client OpenAPI, error-code
|
|
23
|
+
registry, protocol compatibility manifest, canonical attestation binding, DPoP
|
|
24
|
+
vectors, canonical request examples, and checksummed contract bundle. A
|
|
25
|
+
contract update must verify checksums, update `contract.lock`, regenerate only
|
|
26
|
+
internal types, rerun shared vectors, and pass conformance against the exact
|
|
27
|
+
core revision. Generated bridge and wire types do not become public API.
|
|
28
|
+
|
|
29
|
+
## Operation flow
|
|
30
|
+
|
|
31
|
+
1. JavaScript validates the origin, feature, configuration, request state, and decoded query names; provider-credential names fail before identity acquisition or dispatch.
|
|
32
|
+
2. The application identity callback returns an external identity JWT.
|
|
33
|
+
3. The TurboModule passes that JWT transiently to the native SDK while native session work runs.
|
|
34
|
+
4. Native repeats the exact origin and allowed-path checks, establishes or refreshes a device-bound session, signs a DPoP proof, attaches native-owned protocol headers, and dispatches through its private URLSession or OkHttp client.
|
|
35
|
+
5. Native refuses redirects, retains the credential-bearing request and response task, and returns only an opaque response identifier, status, and allowlisted safe headers.
|
|
36
|
+
6. A WHATWG `ReadableStream` pulls bounded base64 response chunks through the TurboModule. Pull demand supplies bridge backpressure; abort, reader cancellation, EOF, client disposal, and invalid metadata all finish or cancel the native handle. JavaScript never clones or replays an authenticated request. Android's locked authenticator and iOS's locked feature transport exclusively own the contract-safe, one-time pre-dispatch retry; iOS bounds rejection classification to 64 KiB before any response bytes become visible.
|
|
37
|
+
|
|
38
|
+
Authorization, DPoP, access tokens, refresh tokens, private keys, and attestation evidence never appear in a native return value. Response bodies are application data, not credential envelopes, and remain incrementally delivered rather than eagerly buffered.
|
|
39
|
+
|
|
40
|
+
The containing application's root client owns the public descriptor lifecycle:
|
|
41
|
+
prepare, replace, descriptor diagnostics, descriptor revoke, and whole-family
|
|
42
|
+
retirement. Descriptors are normalized and snapshotted before any identity
|
|
43
|
+
await; native results must match the same snapshot. Identity is acquired only
|
|
44
|
+
transiently for prepare, replace, revoke, and family retirement. Root-side
|
|
45
|
+
component diagnostics are identity-free. A 65,536-byte JavaScript serialization
|
|
46
|
+
limit keeps oversized descriptor sets from reaching the native bridge.
|
|
47
|
+
|
|
48
|
+
The component path uses a separate extension-process client, not the containing
|
|
49
|
+
app's root client or lease. JavaScript running inside a signed `.appex` can
|
|
50
|
+
supply one validated public descriptor; native configuration rejects a
|
|
51
|
+
containing-app process, selects `.reactNativeIOS`, and retains a
|
|
52
|
+
`LatchwayExtensionClient` with no direct App Attest provider. iOS extensions
|
|
53
|
+
cannot call `DCAppAttestService.generateKey`, and the containing application
|
|
54
|
+
must not attest on their behalf. The extension can use only independently
|
|
55
|
+
keyed, component-scoped delegated sessions. React Native v1 exposes no
|
|
56
|
+
JavaScript component request operation. A second bridge operation returns only
|
|
57
|
+
`LatchwayComponentDiagnostics`; direct-attestation trust-source decoders are
|
|
58
|
+
retained for protocol compatibility but are not reachable proof claims. The
|
|
59
|
+
identity callback is not invoked, and component credentials and DPoP material
|
|
60
|
+
never cross the TurboModule. Both platforms report `attestation_unsupported`
|
|
61
|
+
for the legacy direct operation.
|
|
62
|
+
|
|
63
|
+
The example App Intents extension has no React Native runtime or TurboModule
|
|
64
|
+
bridge. Its Debug target optionally links `Latchway/AppExtensions` and calls the
|
|
65
|
+
native extension client directly to prove an independently keyed delegated
|
|
66
|
+
session and one fully consumed bounded Responses request. The root publishes a
|
|
67
|
+
nonsecret exact-run shared-Keychain challenge immediately before waiting. The
|
|
68
|
+
intent captures it before client construction, rechecks it immediately before
|
|
69
|
+
echoing the run in a bounded receipt, and cannot publish for a superseded run.
|
|
70
|
+
The containing app accepts only its native-captured exact run and deletes both
|
|
71
|
+
challenge and receipt before descriptor-bound family retirement and sign-out;
|
|
72
|
+
abort also deletes both artifacts. This is local integration proof. In Release the
|
|
73
|
+
AppExtensions pod is absent, no executable Latchway client path is compiled,
|
|
74
|
+
and the intent fails closed. Both variants retain distinct root/extension
|
|
75
|
+
bundle identities and provisioning profiles, private-first/shared-second root
|
|
76
|
+
Keychain entitlements, and a shared-only extension entitlement. The root's
|
|
77
|
+
first/default group keeps its key, identity, and session state outside the
|
|
78
|
+
extension's reach.
|
|
79
|
+
|
|
80
|
+
The bridge intentionally implements a bounded fetch subset: method, headers,
|
|
81
|
+
an at-most-8-MiB buffered request body, cancellation, response metadata, and a
|
|
82
|
+
pull-driven response stream. Browser cookie/cache modes, service workers,
|
|
83
|
+
redirect following, streaming uploads, response trailers, and native response
|
|
84
|
+
URL metadata are outside this transport. Framework compatibility therefore
|
|
85
|
+
depends on a real custom-fetch seam and the framework's React Native support;
|
|
86
|
+
the presence of `fetchFor` alone is not a version-support claim.
|
|
87
|
+
|
|
88
|
+
`fetchFor(feature)` also maps the canonical safe
|
|
89
|
+
`X-Latchway-Request-ID` response header to the conventional `X-Request-ID`
|
|
90
|
+
alias without reading the body, so provider SDK failures retain server
|
|
91
|
+
correlation. The native runtime remains the framework identity reported to the
|
|
92
|
+
gateway (`react-native-fetch`); an underlying JavaScript library cannot spoof a
|
|
93
|
+
different SDK/framework pair through caller headers.
|
|
94
|
+
|
|
95
|
+
Feature binding is also protocol binding: a Responses feature cannot dispatch
|
|
96
|
+
Chat Completions, Embeddings, or Anthropic Messages. A consumer set spanning
|
|
97
|
+
those protocols constructs a separate `fetchFor` transport for each configured
|
|
98
|
+
feature instead of multiplexing incompatible endpoints through one identifier.
|
|
99
|
+
|
|
100
|
+
## Coordination
|
|
101
|
+
|
|
102
|
+
A module-global root lease map is keyed by native-module identity plus gateway/application/environment scope. A separate component lease map adds the component definition and never aliases the root map. Equivalent clients reuse one native client and configuration promise; conflicting security configuration for an active scope is rejected. Reference-counted disposal drops the native object only after the last JavaScript client leaves. The native iOS actor and Android coordinator/mutex prevent session establishment and refresh stampedes.
|
|
103
|
+
|
|
104
|
+
Native persistence namespaces include `react_native_ios` or `react_native_android`. The bridge configures the paired runtime identity, so challenge/grant platform and `X-Latchway-SDK: react-native` cannot disagree. Native compatibility JSON is checked against released contract 1.0.0 and current wire protocol 2 before any operation.
|
|
105
|
+
|
|
106
|
+
## TurboModule boundary
|
|
107
|
+
|
|
108
|
+
The handwritten spec carries root and component configuration as distinct
|
|
109
|
+
operations, a bounded request description, the root client's transient
|
|
110
|
+
application identity token, opaque response-handle start/read/close operations,
|
|
111
|
+
quota/diagnostic results, public component descriptors for lifecycle and
|
|
112
|
+
delegated-session compatibility, cancellation, and disposal. Root lifecycle
|
|
113
|
+
mutations carry identity transiently; root-side and extension-side component
|
|
114
|
+
diagnostics do not. The legacy direct-attestation operation remains
|
|
115
|
+
ABI-compatible but fails closed. Extension-process component operations have no
|
|
116
|
+
identity-token argument. The spec has no authorization-envelope operation and
|
|
117
|
+
does not return or accept provider attestation evidence, Play request hashes,
|
|
118
|
+
App Attest client-data hashes, session tokens, DPoP proofs, or key material.
|
|
119
|
+
Generated Objective-C++ and Java specs are disposable codegen output, not
|
|
120
|
+
public API.
|
|
121
|
+
|
|
122
|
+
## Native dependencies
|
|
123
|
+
|
|
124
|
+
Published package metadata pins release coordinates. CocoaPods consumes `Latchway/AppAttest` 1.0.0. Gradle consumes `dev.latchway:latchway-okhttp:1.0.0` and `dev.latchway:latchway-play-integrity:1.0.0`. Development may point `LATCHWAY_NATIVE_REPOSITORY` or `-PlatchwayNativeRepository` at a locally published Maven repository; local file links never enter npm metadata.
|
|
125
|
+
|
|
126
|
+
## Diagnostics and errors
|
|
127
|
+
|
|
128
|
+
Diagnostics contain version compatibility, platform, secure key-storage category, attestation support/provider, session state/expiration, installation ID/status, server version, and last request/error identifiers. Component diagnostics add only family/component IDs, public definition/access-group identifiers, key/session/grant availability, trust provenance/expiry, and a containing-app action flag. Native key IDs, JWK thumbprints, tokens, proofs, and evidence are excluded. Native errors are bounded, control-character stripped, secret-pattern redacted, and mapped to the shared `LatchwayError` taxonomy. A server-originated native error must carry the exact `https://docs.latchway.dev/errors/<hyphenated-code>` documentation URL; missing or mismatched links fail closed. `operation_indeterminate` alone carries a required canonical reconciliation ID through both native bridges; malformed, missing, contradictory, or otherwise attached operation metadata fails closed.
|
|
129
|
+
|
|
130
|
+
## Verification boundary
|
|
131
|
+
|
|
132
|
+
Unit and Node conformance tests own public request shaping, fail-closed
|
|
133
|
+
credential-output checks, response pull/backpressure, error projection,
|
|
134
|
+
cancellation, coordination, strict-CSP behavior, and
|
|
135
|
+
canonical vectors. Reproducible code generation proves the handwritten schema
|
|
136
|
+
remains valid. Native consumer builds prove released dependency resolution and
|
|
137
|
+
bridge compilation. Physical-device conformance proves real App Attest and Play
|
|
138
|
+
Integrity behavior, session rotation, quota, streaming, diagnostics, and
|
|
139
|
+
revocation against the exact core image.
|
|
140
|
+
|
|
141
|
+
## Non-goals
|
|
142
|
+
|
|
143
|
+
This package does not own server policy, provider routing, quota enforcement,
|
|
144
|
+
user-authentication UI, AI request modeling, upstream secrets, native
|
|
145
|
+
cryptography, native attestation verification, or an independent session store.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Conformance
|
|
2
|
+
|
|
3
|
+
Node conformance validates JavaScript request shaping with the explicit testing bridge, malicious credential-field output rejection, native response pull/backpressure, strict-CSP safety, redaction, cancellation, version mismatch, cross-instance coordination, and canonical contract fixtures. It does not emulate hardware trust or assert that a production native transport attached a valid proof.
|
|
4
|
+
|
|
5
|
+
The framework suite under `Conformance/framework` uses 16 applicable stable
|
|
6
|
+
`FW-*` case IDs from the JavaScript SDK suite and binds them to the canonical
|
|
7
|
+
`react-native-fetch` registry entry. It executes, rather than merely imports,
|
|
8
|
+
the exact OpenAI 7.8.0, Vercel AI 7.0.85, and LangChain OpenAI 1.5.10 consumer
|
|
9
|
+
paths. RN-specific `RN-FW-ANTHROPIC-001` separately runs
|
|
10
|
+
`@ai-sdk/anthropic` 4.0.46 against `/v1/messages`, without claiming a canonical
|
|
11
|
+
Anthropic framework registry entry. The official `@anthropic-ai/sdk` 0.120.0
|
|
12
|
+
client is recorded as unsupported because its credential-chain Node filesystem
|
|
13
|
+
imports fail an actual Metro bundle. The deterministic native-boundary fixture
|
|
14
|
+
returns protocol-shaped Responses, Chat Completions, Anthropic Messages,
|
|
15
|
+
embeddings, SSE usage, tool calls, structured output, quota failures, provider
|
|
16
|
+
failures, and retry responses. Tests prove feature binding, safe headers,
|
|
17
|
+
request-ID correlation, cancellation, fresh native operation IDs for framework
|
|
18
|
+
retries, placeholder stripping, exact-origin refusal, and isolation from global
|
|
19
|
+
fetch. The runnable consumer factory requires distinct Responses, Chat,
|
|
20
|
+
Embeddings, and Anthropic feature identifiers because one active Latchway
|
|
21
|
+
feature is bound to exactly one gateway protocol.
|
|
22
|
+
|
|
23
|
+
`RN-FW-REFRESH-001` separately proves the public explicit-refresh operation
|
|
24
|
+
followed by a framework request. It is intentionally not reported as shared
|
|
25
|
+
`FW-BEH-006`: automatic recovery from a pre-dispatch expired native session is
|
|
26
|
+
owned by the platform SDK and requires native/device evidence.
|
|
27
|
+
|
|
28
|
+
`RN-FW-OPAQUE-001` proves the bounded `/proxy/{feature}/...` pathway keeps its
|
|
29
|
+
feature binding through native dispatch. None of the RN-only cases extends the
|
|
30
|
+
shared `FW-*` registry set.
|
|
31
|
+
|
|
32
|
+
This suite deliberately does not manufacture DPoP proofs, refresh credentials,
|
|
33
|
+
or platform attestation in JavaScript. Fresh-proof generation and native
|
|
34
|
+
pre-dispatch retry classification remain native SDK/device evidence. The local
|
|
35
|
+
framework result therefore supports the registry's `experimental` state; it is
|
|
36
|
+
not hosted or physical-device release evidence.
|
|
37
|
+
|
|
38
|
+
Real platform conformance requires the exact core image and real provider configuration:
|
|
39
|
+
|
|
40
|
+
- iOS: App Attest development and production applications on physical devices; session creation, assertion reuse, nonce, refresh rotation, quota, streaming, diagnostics, and revocation.
|
|
41
|
+
- Android: Play-distributed application with Play Integrity standard requests; hardware/StrongBox policy variants, nonce, refresh rotation, quota, streaming, diagnostics, and revocation.
|
|
42
|
+
- Both: identity token expiry/reauthentication, installation platform validation, no secret material in errors/diagnostics, and published native dependency resolution.
|
|
43
|
+
|
|
44
|
+
Provider credentials and signing material belong in protected CI/device infrastructure and are never committed. Missing provider credentials block device conformance only; they do not justify fake evidence or trusted identity fields.
|
|
45
|
+
|
|
46
|
+
The production TurboModule intentionally cannot expose or mutate DPoP/session
|
|
47
|
+
credentials for a JavaScript evidence harness. Replay, proof tamper, session
|
|
48
|
+
credential rotation, and protocol-header mutation therefore have to be taken
|
|
49
|
+
from the separately linked native SDK physical-device reports. The v2 raw
|
|
50
|
+
React Native record omits those native-only claims. Its protected finalizer
|
|
51
|
+
validates the hash-pinned platform-native report, imports only the five exact
|
|
52
|
+
allowlisted proof objects, and rejects missing, failed, renamed, extended, or
|
|
53
|
+
coordinate-substituted proofs. The React Native report remains ineligible
|
|
54
|
+
unless all of its own runtime checks pass (eight on iOS, including direct
|
|
55
|
+
assertion reuse; seven on Android) and every imported proof passes.
|
|
56
|
+
|
|
57
|
+
The repository's native source jobs build official React Native 0.82 iOS and
|
|
58
|
+
Android hosts against the exact locked SDK source releases. The published
|
|
59
|
+
dependency jobs repeat those builds with local CocoaPods paths and Maven
|
|
60
|
+
repositories removed. Neither is physical-device attestation evidence.
|
|
61
|
+
|
|
62
|
+
Before v1 is called device-conformant, retain redacted run evidence for each
|
|
63
|
+
platform/provider/environment combination covering installation creation,
|
|
64
|
+
session reuse, identity expiry and reauthentication, nonce recovery, refresh
|
|
65
|
+
rotation, quota, an SSE stream, diagnostics, revocation, and a post-revocation
|
|
66
|
+
re-enrollment. The evidence must name the application release, device/OS,
|
|
67
|
+
gateway image digest, contract bundle hash, SDK package versions, and request
|
|
68
|
+
IDs without including tokens, proofs, keys, or raw attestations.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# LangChain on React Native
|
|
2
|
+
|
|
3
|
+
Use `@latchway/react-native@1.1.0` for native authenticated transport and
|
|
4
|
+
`@latchway/langchain@1.1.0` for the optional LangChain adapter. No provider key
|
|
5
|
+
belongs in the application. Secure Enclave/Keystore, App Attest/Play Integrity,
|
|
6
|
+
DPoP and refresh credentials stay native.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
The tested baseline is React Native 0.82 / React 19.1, New Architecture:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install --save-exact @latchway/react-native@1.1.0 @latchway/langchain@1.1.0 \
|
|
14
|
+
@latchway/client@1.0.0 @langchain/core@1.2.9 @langchain/openai@1.5.10 openai@7.8.0
|
|
15
|
+
cd ios && pod install && cd ..
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Keep the lockfile. LangChain is not a dependency of the base React Native SDK.
|
|
19
|
+
The native dependencies remain the public iOS/Android SDKs 1.0.0. Normal native
|
|
20
|
+
signing, Firebase/other identity and gateway platform policy setup still applies.
|
|
21
|
+
|
|
22
|
+
## Two explicit setup lines
|
|
23
|
+
|
|
24
|
+
Put the bootstrap **first** in the application entrypoint, before importing
|
|
25
|
+
React Native, Latchway, LangChain or any stream-dependent application module:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import '@latchway/react-native/polyfills';
|
|
29
|
+
import {AppRegistry} from 'react-native';
|
|
30
|
+
import App from './App';
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use the optional compiler helper in `babel.config.js`:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
const {withLatchwayBabel} = require('@latchway/react-native/babel');
|
|
37
|
+
module.exports = withLatchwayBabel({
|
|
38
|
+
presets: ['module:@react-native/babel-preset'],
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Use standard Metro; no resolver override, source alias or local SDK link is
|
|
43
|
+
needed. Rebuild the native app after installation. Clear Metro's cache if it
|
|
44
|
+
still resolves the old 1.0.0 source entry.
|
|
45
|
+
|
|
46
|
+
The bootstrap installs missing async symbols before stream dependencies,
|
|
47
|
+
incremental UTF-8 decoding, web streams, random values and abort compatibility.
|
|
48
|
+
It only replaces URL when a capability probe fails, preserves working globals,
|
|
49
|
+
and never replaces fetch or native security. The standard SDK import does not
|
|
50
|
+
initialize globals. The decoder currently uses the pinned, deprecated
|
|
51
|
+
`text-encoding@0.7.0` implementation for incremental decoding; this is a known
|
|
52
|
+
maintenance dependency, not a claim of comprehensive runtime compatibility.
|
|
53
|
+
|
|
54
|
+
The Babel helper enables `noClassCalls` and the export-namespace transform while
|
|
55
|
+
preserving the rest of your config. Classes must still be constructed with
|
|
56
|
+
`new`; LangChain's own type checks are not removed.
|
|
57
|
+
|
|
58
|
+
## Create a model
|
|
59
|
+
|
|
60
|
+
Create `latchway` using the ordinary SDK configuration and your current user's
|
|
61
|
+
identity-token callback. Then pass that client directly—no transport wrapper:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import {createLatchwayResponsesModel} from '@latchway/langchain';
|
|
65
|
+
|
|
66
|
+
const model = createLatchwayResponsesModel({
|
|
67
|
+
latchway,
|
|
68
|
+
feature: 'assistant',
|
|
69
|
+
reasoning: {effort: 'none'}, // only if your configured model supports this
|
|
70
|
+
chatOptions: {maxTokens: 1024},
|
|
71
|
+
});
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
for await (const chunk of await model.stream('Explain Latchway', {
|
|
74
|
+
signal: controller.signal,
|
|
75
|
+
})) {
|
|
76
|
+
// Render text chunks; retain original messages for usage/callback metadata.
|
|
77
|
+
}
|
|
78
|
+
// controller.abort() cancels native networking too.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The feature must route `openai_responses`, with the matching trusted accounting
|
|
82
|
+
profile, on gateway 1.0.2+. The server selects the physical model. The helper
|
|
83
|
+
does not guess reasoning capabilities from the placeholder model alias: omit
|
|
84
|
+
`reasoning` for routes that do not accept it. Stateless storage settings do not
|
|
85
|
+
guarantee retention behavior across all upstream providers.
|
|
86
|
+
|
|
87
|
+
For an `openai_chat` feature use the existing `createLatchwayChatOpenAI` instead.
|
|
88
|
+
Both factories default to no automatic framework retries. Native pre-dispatch
|
|
89
|
+
session recovery is separate; uncertain provider dispatches must not be replayed
|
|
90
|
+
casually. Explicit `chatOptions.maxRetries` is an application policy choice.
|
|
91
|
+
|
|
92
|
+
## Tools and local history
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import {bindLatchwayTools, toLatchwayReplayMessage} from '@latchway/langchain';
|
|
96
|
+
import {HumanMessage, type BaseMessage} from '@langchain/core/messages';
|
|
97
|
+
|
|
98
|
+
// weather is your ordinary LangChain tool; the SDK never executes tools for you.
|
|
99
|
+
const modelWithTools = bindLatchwayTools(model, [weather]);
|
|
100
|
+
const history: BaseMessage[] = [new HumanMessage('Weather in Singapore?')];
|
|
101
|
+
const answer = await modelWithTools.invoke(history);
|
|
102
|
+
const replay = toLatchwayReplayMessage(answer);
|
|
103
|
+
history.push(replay);
|
|
104
|
+
// Dispatch only an allowlisted tool with validated arguments. Append the
|
|
105
|
+
// returned ToolMessage, preserving the model's tool_call_id, then invoke again.
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The binding helper
|
|
109
|
+
defaults to `strict:true` and `parallel_tool_calls:false`; ordinary LangChain
|
|
110
|
+
options can override these deliberately. Supply strict-compatible schemas
|
|
111
|
+
(all required fields, `additionalProperties:false`; nullable instead of optional
|
|
112
|
+
properties when necessary).
|
|
113
|
+
|
|
114
|
+
Use `toLatchwayReplayMessage` only after a successful complete response (or
|
|
115
|
+
aggregating all stream chunks). It preserves text and function-call IDs while
|
|
116
|
+
removing provider item IDs and observational metadata. It rejects non-text,
|
|
117
|
+
opaque reasoning/refusal, malformed calls and duplicate IDs. Keep the original
|
|
118
|
+
message separately for usage information. Do not save failed/partial turns as
|
|
119
|
+
history. The helper does not store history, execute tools or enforce loop limits.
|
|
120
|
+
|
|
121
|
+
Set explicit tool-call limits, timeouts, cancellation and history bounds in your
|
|
122
|
+
app. Standard LangChain `withStructuredOutput(..., {method:'jsonSchema', strict:true})`
|
|
123
|
+
is available when the gateway route/model supports the schema.
|
|
124
|
+
|
|
125
|
+
## Example and evidence
|
|
126
|
+
|
|
127
|
+
The repository's `Examples/LatchwayChat` demonstrates Firebase login, a bounded
|
|
128
|
+
streaming weather-tool loop, ephemeral chat, direct fetch, Stop and diagnostics.
|
|
129
|
+
It consumes npm packages. Its verification report distinguishes physical iOS
|
|
130
|
+
evidence from Android build-only evidence. Request framework attribution remains
|
|
131
|
+
native-owned `react-native-fetch`, including when called through LangChain.
|
|
132
|
+
|
|
133
|
+
References: [Metro exports](https://metrobundler.dev/docs/package-exports/),
|
|
134
|
+
[Babel assumptions](https://babeljs.io/docs/assumptions#noclasscalls),
|
|
135
|
+
[LangChain ChatOpenAI](https://docs.langchain.com/oss/javascript/integrations/chat/openai).
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Native installation
|
|
2
|
+
|
|
3
|
+
## iOS
|
|
4
|
+
|
|
5
|
+
Configure `apple.rootKeychainAccessGroup` with the fully resolved private app-ID
|
|
6
|
+
group that appears first in the signed root target. List every explicit
|
|
7
|
+
extension-shared group in `apple.legacySharedKeychainAccessGroups`; the native
|
|
8
|
+
SDK scans only exact root-record coordinates in those groups. Missing,
|
|
9
|
+
wildcard, duplicate, or root-equal groups fail closed, and stale root records
|
|
10
|
+
require an explicit migration.
|
|
11
|
+
|
|
12
|
+
The podspec pins `Latchway/AppAttest` 1.0.0 and React Native codegen dependencies. Run CocoaPods from the host application after installing the npm package. Enable App Attest for the application identifier and use a real device for conformance; simulators report attestation unsupported.
|
|
13
|
+
|
|
14
|
+
The Firebase Authentication example pins React Native Firebase 25.1.0 and
|
|
15
|
+
Firebase Apple SDK 12.15.0 and uses CocoaPods static frameworks. The Latchway
|
|
16
|
+
package itself does not depend on Firebase. Firebase has announced that the
|
|
17
|
+
existing CocoaPods releases remain installable but new Firebase Apple SDK
|
|
18
|
+
versions stop shipping through CocoaPods after October 2026; migrate the
|
|
19
|
+
example to the compatible React Native Firebase SPM path only after its pinned
|
|
20
|
+
RN 0.82 native host build is green.
|
|
21
|
+
|
|
22
|
+
For a source-development run on a physical iPhone or iPad, the example offers a
|
|
23
|
+
separate opt-in Debug bootstrap. `scripts/copy-development-firebase-ios-config.sh`
|
|
24
|
+
validates an external, bundle-matched Firebase plist and copies it only into a
|
|
25
|
+
Debug `iphoneos` build; `scripts/run-development-react-native-ios.sh` keeps the
|
|
26
|
+
custom token and digest out of an allowlisted Xcode build environment, validates
|
|
27
|
+
the complete non-secret deployment coordinates, rechecks grant freshness, and
|
|
28
|
+
force-bundles the exact JavaScript checkout before handing the grant to one
|
|
29
|
+
no-debugger launch. The physical-device run therefore does not require Metro or
|
|
30
|
+
Local Network access, although iOS can still show React Native's one-time Debug
|
|
31
|
+
permission sheet on the first install. Later runs update the existing app so
|
|
32
|
+
that OS consent persists. The app signs in with a new grant, revokes the old
|
|
33
|
+
descriptor-bound family, verifies the root Responses/quota/diagnostics path,
|
|
34
|
+
and prepares the App Intent descriptor. After the one-use grant has been
|
|
35
|
+
destroyed, the root publishes a nonsecret exact-run shared-Keychain challenge
|
|
36
|
+
immediately before the waiting marker. The separately launched Debug App Intent
|
|
37
|
+
captures that challenge before constructing its client, refreshes an
|
|
38
|
+
independently keyed delegated session, and fully consumes one successful bounded
|
|
39
|
+
Responses body. It rechecks the challenge immediately before echoing the run in
|
|
40
|
+
a bounded shared-Keychain receipt. The containing app accepts only its
|
|
41
|
+
native-captured exact run, deletes both artifacts, retires that exact
|
|
42
|
+
descriptor-bound family, and signs out. The runner passes only after retrieving
|
|
43
|
+
the exact random-run
|
|
44
|
+
marker written after terminal cleanup. Its bounded post-wait abort path also
|
|
45
|
+
relaunches the containing app to finish and verify family retirement/sign-out
|
|
46
|
+
after interruption or timeout, deleting both challenge and receipt. The Debug
|
|
47
|
+
native module and marker writer are
|
|
48
|
+
absent from Release. This path verifies local integration only and cannot
|
|
49
|
+
satisfy the protected physical-evidence gate. See the example README for the
|
|
50
|
+
exact runner workflow and possible Shortcuts tap.
|
|
51
|
+
|
|
52
|
+
Firebase Authentication and Firebase App Check are distinct. The checked-in
|
|
53
|
+
example pins Firebase App/Auth but does not install the native App Check module,
|
|
54
|
+
and a Firebase web App Check registration does not apply to an iOS application.
|
|
55
|
+
When App Check enforcement is enabled for the Firebase resource, pin a
|
|
56
|
+
compatible React Native Firebase App Check dependency and activate the Apple
|
|
57
|
+
App Attest provider before the Auth exchange. A debug App Check provider/token
|
|
58
|
+
is never acceptable in a protected Release candidate.
|
|
59
|
+
|
|
60
|
+
A production App Attest run requires all of the following, none of which can be
|
|
61
|
+
substituted by a simulator build:
|
|
62
|
+
|
|
63
|
+
- an App Attest-capable physical iPhone or iPad;
|
|
64
|
+
- a registered App ID with the App Attest capability, a matching Team ID and
|
|
65
|
+
bundle ID, and a provisioning profile containing the entitlement;
|
|
66
|
+
- `development` or `production` selected consistently in the entitlement,
|
|
67
|
+
React Native client configuration, gateway application record, and Apple
|
|
68
|
+
verification policy; and
|
|
69
|
+
- a real application identity token plus the exact gateway/core release named
|
|
70
|
+
by the synchronized contract lock.
|
|
71
|
+
|
|
72
|
+
The root-application bridge constructs the App Attest provider with the exact
|
|
73
|
+
`rootKeychainAccessGroup`, `legacySharedKeychainAccessGroups`, and
|
|
74
|
+
`.reactNativeIOS` runtime, then passes the same groups to
|
|
75
|
+
`LatchwayConfiguration`. Keychain, Secure Enclave, session, and accepted App
|
|
76
|
+
Attest key state are runtime-isolated. An extension bridge constructs no App
|
|
77
|
+
Attest provider: iOS app extensions cannot call
|
|
78
|
+
`DCAppAttestService.generateKey`, so extension sessions remain independently
|
|
79
|
+
keyed and delegated from the already attested root application.
|
|
80
|
+
|
|
81
|
+
The checked-in App Intents target has two intentionally different build
|
|
82
|
+
boundaries. In Debug, its own CocoaPods target links `Latchway/AppExtensions`
|
|
83
|
+
and the native Swift intent performs the local delegated-request proof without
|
|
84
|
+
hosting a React Native JavaScript runtime. In Release, that dependency is not
|
|
85
|
+
linked, no executable Latchway client path is compiled, and the archive/signing
|
|
86
|
+
fixture's intent fails closed. The CocoaPods subspec is imported through module
|
|
87
|
+
`Latchway`, not the SwiftPM-only module name.
|
|
88
|
+
|
|
89
|
+
Candidate production requires a distinct child bundle ID and provisioning
|
|
90
|
+
profile. The signed root target lists its private app-ID Keychain group first
|
|
91
|
+
and the shared component group second; the first position keeps implicit root
|
|
92
|
+
Keychain writes private. The signed extension lists only the shared group and
|
|
93
|
+
therefore cannot read root-private key, credential, identity, or session state.
|
|
94
|
+
Each provisioning profile must authorize every group its target signs, either
|
|
95
|
+
exactly or with a well-formed terminal wildcard. The extension must not carry
|
|
96
|
+
App Attest. The Debug intent constructs its delegated client with
|
|
97
|
+
`.reactNativeIOS`; the gateway component definition must therefore use platform
|
|
98
|
+
`react_native_ios`, kind `app_intent_extension`, delegated-only trust, and the
|
|
99
|
+
same requested feature as the descriptor prepared by the root.
|
|
100
|
+
|
|
101
|
+
The root JavaScript API owns the descriptor lifecycle:
|
|
102
|
+
|
|
103
|
+
- `prepareComponents` provisions one or more exact descriptors;
|
|
104
|
+
- `replaceComponent` rotates/replaces one exact descriptor;
|
|
105
|
+
- root-side `componentDiagnostics` reads redacted local state without acquiring
|
|
106
|
+
application identity;
|
|
107
|
+
- `revokeComponent` retires one descriptor; and
|
|
108
|
+
- no-argument `revokeCurrentInstallationFamily()` retires the root plus every
|
|
109
|
+
component in the native iOS SDK's durable root-private descriptor registry;
|
|
110
|
+
the optional descriptor list additionally covers pre-registry legacy state.
|
|
111
|
+
|
|
112
|
+
Descriptors are normalized and snapshotted before asynchronous identity work.
|
|
113
|
+
The native SDK registers only their public Keychain coordinates before it can
|
|
114
|
+
create component-local state. Successful cleanup removes a coordinate, while a
|
|
115
|
+
failed Keychain erasure keeps it durable for retry after a later app launch.
|
|
116
|
+
Preparation, replacement, and returned diagnostics are checked against that
|
|
117
|
+
same snapshot, and serialized multi-component input larger than 65,536 bytes is
|
|
118
|
+
rejected in JavaScript before crossing the native bridge. Component keys,
|
|
119
|
+
grants, delegated sessions, and the root identity never cross into JavaScript.
|
|
120
|
+
|
|
121
|
+
For local native SDK work, declare the sibling `Latchway.podspec` by path in the
|
|
122
|
+
appropriate host targets. The root path declaration lets the autolinked React
|
|
123
|
+
Native pod resolve its App Attest dependency from that source, while the
|
|
124
|
+
extension subspec is Debug-only:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
target "ContainingApp" do
|
|
128
|
+
pod "Latchway", :path => "../../../latchway-ios-sdk"
|
|
129
|
+
# use_native_modules! / use_react_native! follows here.
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
target "AppIntents" do
|
|
133
|
+
pod "Latchway/AppExtensions", :path => "../../../latchway-ios-sdk",
|
|
134
|
+
:configurations => ["Debug"]
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Local paths belong only in the host Podfile. They are absent from the published React Native podspec.
|
|
139
|
+
|
|
140
|
+
## Android
|
|
141
|
+
|
|
142
|
+
The library pins the native 1.0.0 Maven coordinates. Those AARs publish Kotlin
|
|
143
|
+
2.3 metadata and require compile SDK 37; the React Native 0.82 host therefore
|
|
144
|
+
pins Kotlin 2.3.21 while retaining RN's supported consumer AGP 8.12 baseline.
|
|
145
|
+
Play Integrity requires the decimal Google Cloud project number in
|
|
146
|
+
`android.playIntegrityCloudProjectNumber`; JavaScript never receives or
|
|
147
|
+
supplies the resulting integrity token.
|
|
148
|
+
|
|
149
|
+
A production Play Integrity run requires a physical device and a build whose
|
|
150
|
+
package name and signing-certificate digest match the gateway application
|
|
151
|
+
record. Configure the Play Integrity API and its decimal Google Cloud project
|
|
152
|
+
number, upload the signed build to a Play internal/closed/production track, and
|
|
153
|
+
install it from Google Play. A locally sideloaded debug APK can prove compilation
|
|
154
|
+
and bridge behavior, but it is not production Play Integrity evidence. Exercise
|
|
155
|
+
both hardware-backed/StrongBox-available and fallback policy variants on the
|
|
156
|
+
device matrix required by the deployment.
|
|
157
|
+
|
|
158
|
+
For local native SDK work, publish the native artifacts to a disposable Maven repository and set one of:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
./android/gradlew -p android -PlatchwayNativeRepository=/absolute/path/to/maven check
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
LATCHWAY_NATIVE_REPOSITORY=/absolute/path/to/maven ./android/gradlew -p android check
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The repository is content-filtered to `dev.latchway`. Composite-build substitution is intentionally avoided because the Android SDK and React Native 0.82 currently use incompatible Gradle major versions.
|
|
169
|
+
|
|
170
|
+
## Release dependency check
|
|
171
|
+
|
|
172
|
+
CI must run once against only the published CocoaPods/Maven coordinates, without local path or repository overrides, before npm publication. A local native build proving source compatibility does not replace that consumer check. The manual `Published dependency consumer` workflow performs that gate; the promotion-dispatched release workflow repeats it before npm publication.
|