@latchway/react-native 0.0.0-bootstrap.0 → 1.0.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 (95) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/LatchwayReactNative.podspec +33 -0
  3. package/NOTICE +7 -0
  4. package/README.md +286 -3
  5. package/SECURITY.md +69 -0
  6. package/android/build.gradle.kts +62 -0
  7. package/android/consumer-rules.pro +2 -0
  8. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  9. package/android/gradle/wrapper/gradle-wrapper.properties +9 -0
  10. package/android/gradle.properties +4 -0
  11. package/android/gradlew +251 -0
  12. package/android/gradlew.bat +94 -0
  13. package/android/settings.gradle.kts +33 -0
  14. package/android/src/main/AndroidManifest.xml +3 -0
  15. package/android/src/main/java/dev/latchway/reactnative/LatchwayReactNativePackage.kt +25 -0
  16. package/android/src/main/java/dev/latchway/reactnative/NativeLatchwayModule.kt +964 -0
  17. package/android/src/test/java/dev/latchway/reactnative/NativeLatchwayModuleTest.kt +521 -0
  18. package/contract.lock +7 -0
  19. package/docs/architecture.md +145 -0
  20. package/docs/conformance.md +68 -0
  21. package/docs/native-installation.md +172 -0
  22. package/docs/physical-device-evidence.md +559 -0
  23. package/docs/releasing.md +360 -0
  24. package/docs/security.md +55 -0
  25. package/ios/LatchwayNativeBridge.swift +1551 -0
  26. package/ios/RCTNativeLatchway.h +9 -0
  27. package/ios/RCTNativeLatchway.mm +175 -0
  28. package/lib/client.d.ts +29 -0
  29. package/lib/client.d.ts.map +1 -0
  30. package/lib/client.js +939 -0
  31. package/lib/client.js.map +1 -0
  32. package/lib/component-client.d.ts +15 -0
  33. package/lib/component-client.d.ts.map +1 -0
  34. package/lib/component-client.js +141 -0
  35. package/lib/component-client.js.map +1 -0
  36. package/lib/config.d.ts +25 -0
  37. package/lib/config.d.ts.map +1 -0
  38. package/lib/config.js +261 -0
  39. package/lib/config.js.map +1 -0
  40. package/lib/coordinator.d.ts +19 -0
  41. package/lib/coordinator.d.ts.map +1 -0
  42. package/lib/coordinator.js +167 -0
  43. package/lib/coordinator.js.map +1 -0
  44. package/lib/errors.d.ts +5 -0
  45. package/lib/errors.d.ts.map +1 -0
  46. package/lib/errors.js +201 -0
  47. package/lib/errors.js.map +1 -0
  48. package/lib/index.d.ts +8 -0
  49. package/lib/index.d.ts.map +1 -0
  50. package/lib/index.js +13 -0
  51. package/lib/index.js.map +1 -0
  52. package/lib/native/NativeLatchway.d.ts +25 -0
  53. package/lib/native/NativeLatchway.d.ts.map +1 -0
  54. package/lib/native/NativeLatchway.js +3 -0
  55. package/lib/native/NativeLatchway.js.map +1 -0
  56. package/lib/native/bridge.d.ts +5 -0
  57. package/lib/native/bridge.d.ts.map +1 -0
  58. package/lib/native/bridge.js +17 -0
  59. package/lib/native/bridge.js.map +1 -0
  60. package/lib/native-output.d.ts +3 -0
  61. package/lib/native-output.d.ts.map +1 -0
  62. package/lib/native-output.js +43 -0
  63. package/lib/native-output.js.map +1 -0
  64. package/lib/request-id.d.ts +2 -0
  65. package/lib/request-id.d.ts.map +1 -0
  66. package/lib/request-id.js +5 -0
  67. package/lib/request-id.js.map +1 -0
  68. package/lib/testing.d.ts +7 -0
  69. package/lib/testing.d.ts.map +1 -0
  70. package/lib/testing.js +9 -0
  71. package/lib/testing.js.map +1 -0
  72. package/lib/types.d.ts +200 -0
  73. package/lib/types.d.ts.map +1 -0
  74. package/lib/types.js +2 -0
  75. package/lib/types.js.map +1 -0
  76. package/lib/version.d.ts +8 -0
  77. package/lib/version.d.ts.map +1 -0
  78. package/lib/version.js +8 -0
  79. package/lib/version.js.map +1 -0
  80. package/package.json +130 -6
  81. package/react-native.config.cjs +7 -0
  82. package/release-compatibility.json +64 -0
  83. package/src/client.ts +1022 -0
  84. package/src/component-client.ts +158 -0
  85. package/src/config.ts +368 -0
  86. package/src/coordinator.ts +195 -0
  87. package/src/errors.ts +225 -0
  88. package/src/index.ts +53 -0
  89. package/src/native/NativeLatchway.ts +75 -0
  90. package/src/native/bridge.ts +23 -0
  91. package/src/native-output.ts +43 -0
  92. package/src/request-id.ts +5 -0
  93. package/src/testing.ts +11 -0
  94. package/src/types.ts +241 -0
  95. package/src/version.ts +7 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,97 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format follows Keep a Changelog, and releases will follow Semantic
6
+ Versioning once package publication begins.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0] - 2026-09-01
11
+
12
+ ### Changed
13
+
14
+ - Added the root React Native installation-family component lifecycle:
15
+ descriptor-snapshotted prepare, replace, identity-free diagnostics, revoke,
16
+ and descriptor-bound family retirement, with Android reporting the iOS-only
17
+ operations explicitly unsupported.
18
+ - Added a Debug-only App Intent integration proof that uses an independently
19
+ keyed delegated `react_native_ios` session, fully consumes a Responses request,
20
+ and binds its bounded shared-Keychain receipt to the root's exact-run challenge;
21
+ the Release fixture remains fail-closed with no AppExtensions linkage.
22
+ - Physical React Native evidence now requires the authorization-first HTTP 403
23
+ `component_feature_not_granted` mapping for an ungranted feature instead of
24
+ the feature-enumerating HTTP 404 expectation.
25
+ - Added Metro-runnable OpenAI, Vercel AI OpenAI/Anthropic, and LangChain
26
+ example consumers plus
27
+ a 16-case shared-ID React Native framework conformance matrix covering
28
+ Responses, Chat, embeddings, streaming, tools, structured output, errors,
29
+ retry dispatches, cancellation, and credential isolation, plus a separate
30
+ RN-only explicit-refresh, Anthropic Messages, and opaque-route cases.
31
+ - Exposed the canonical non-secret `gatewayURL` and made `fetchFor` preserve
32
+ Latchway request correlation through the conventional `X-Request-ID` alias
33
+ without buffering response streams.
34
+ - Removed the obsolete React Native CLI `podspecPath` override; React Native
35
+ 0.82 now discovers the root podspec without an invalid-config warning.
36
+ - Added iOS and Android production Metro bundles to the normal `pnpm check`
37
+ gate so Node-only framework imports fail before release.
38
+
39
+ - Replaced JavaScript-owned authorization envelopes and network dispatch with
40
+ native URLSession/OkHttp dispatch, opaque response handles, pull-streamed
41
+ response chunks, cancellation, exact origin/path enforcement, and strict
42
+ safe response metadata. The removed `authorize` API is an intentional
43
+ security-boundary break; `fetchFor(feature)` supplies framework adapters
44
+ without exposing reusable credentials.
45
+ - Advanced the exact JavaScript source pin to the reviewed final source commit
46
+ whose protected release evidence requires both Firebase App Check and
47
+ Cloudflare Turnstile.
48
+ - Synchronized the release to contract 1.0.0, current wire protocol 2,
49
+ and the canonical installation-family and component-attestation binding v2
50
+ fixtures while preserving wire 1 in the core compatibility window.
51
+ - Added an extension-process component client for iOS Action and SSO
52
+ extensions. It performs native App Attest step-up without a containing-app
53
+ identity callback and reports composite `delegated_direct_attested` trust;
54
+ Android reports direct component attestation as unsupported.
55
+
56
+ ### Candidate baseline
57
+
58
+ #### Changed
59
+
60
+ - Native consumers now pin the reviewed JavaScript, iOS, Android, Kotlin,
61
+ React Native, and core source commits through a machine-verified release
62
+ compatibility lock.
63
+ - The example now includes complete React Native 0.82 New Architecture iOS and
64
+ Android hosts, including source-development overrides that are excluded from
65
+ published metadata.
66
+ - The example provider stack now pins the native-build-verified React Native
67
+ Firebase 25.1.0, Firebase Apple 12.15.0, and Firebase Android BoM 34.15.0 set,
68
+ and repository installs use pnpm's hoisted linker for CocoaPods framework
69
+ compatibility.
70
+ - The version 1 release candidate uses the exact reviewed JavaScript, iOS,
71
+ Android, and core source commits recorded by the synchronized compatibility
72
+ lock.
73
+ - The Firebase example now selects the Firebase identity provider explicitly
74
+ and uses the currently supported OpenAI Responses route and request shape.
75
+ - Automatic bodyless retries now require exact canonical, correlated
76
+ pre-dispatch Problem documents and unambiguous nonce semantics; ambiguous or
77
+ duplicate response metadata fails closed.
78
+ - Provider credential headers are comprehensively stripped and decoded
79
+ credential-like query names fail before identity acquisition or dispatch.
80
+ - Native `operation_indeterminate` failures preserve only a canonical
81
+ reconciliation identifier and reject missing, conflicting, or forbidden
82
+ operation metadata.
83
+
84
+ ### Added
85
+
86
+ - Tag-triggered npm trusted publication with provenance, immutable release
87
+ package verification, draft GitHub releases, and published native consumer
88
+ gates.
89
+ - Full contract-bundle verification and clean packed-package consumer gates.
90
+ - Handwritten React Native client API with exact-origin authenticated fetch,
91
+ safe DPoP-nonce/session retries, cancellation, quota, and diagnostics.
92
+ - TurboModule bridges backed by the Latchway iOS and Android SDKs for native
93
+ keys, attestation, DPoP, secure sessions, and installation revocation.
94
+ - Canonical contract-vector tests, native-boundary checks, deterministic
95
+ package verification, CI, security documentation, and an example app.
96
+ - Protocol-specific framework feature bindings; the primary example and
97
+ physical-device path use the setup wizard's OpenAI Responses feature.
@@ -0,0 +1,33 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |spec|
6
+ spec.name = "LatchwayReactNative"
7
+ spec.version = package["version"]
8
+ spec.summary = package["description"]
9
+ spec.homepage = package["homepage"]
10
+ spec.license = { type: "Apache-2.0", file: "LICENSE" }
11
+ spec.author = { "Latchway Contributors" => "security@latchway.dev" }
12
+ spec.source = { git: "https://github.com/Latchway/latchway-react-native-sdk.git", tag: "v#{spec.version}" }
13
+ spec.platforms = { ios: "15.0" }
14
+ spec.source_files = "ios/**/*.{h,m,mm,swift}"
15
+ # React Native's generated TurboModule spec is a C++ header. It is an
16
+ # implementation detail discovered by class name at runtime and must not be
17
+ # exposed through the framework umbrella module, where Clang may scan it as
18
+ # plain Objective-C and fail to resolve C++ standard-library headers.
19
+ spec.private_header_files = "ios/RCTNativeLatchway.h"
20
+ spec.requires_arc = true
21
+ spec.swift_version = "6.0"
22
+ spec.pod_target_xcconfig = {
23
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
24
+ "DEFINES_MODULE" => "YES",
25
+ }
26
+
27
+ spec.dependency "Latchway/AppAttest", "1.0.0"
28
+ if respond_to?(:install_modules_dependencies, true)
29
+ install_modules_dependencies(spec)
30
+ else
31
+ spec.dependency "React-Core"
32
+ end
33
+ end
package/NOTICE ADDED
@@ -0,0 +1,7 @@
1
+ Latchway React Native SDK
2
+ Copyright 2026 Latchway contributors
3
+
4
+ This product includes software developed by Latchway contributors.
5
+
6
+ The Code of Conduct is adapted from Contributor Covenant version 2.1,
7
+ licensed under the Creative Commons Attribution 4.0 International License.
package/README.md CHANGED
@@ -1,5 +1,288 @@
1
- # @latchway/react-native
1
+ # Latchway React Native SDK
2
2
 
3
- This is an inert, one-time namespace bootstrap package. It contains no runtime implementation and must not be used as an SDK dependency.
3
+ `@latchway/react-native` gives iOS and Android applications one fetch-shaped API for a self-hosted Latchway gateway. The JavaScript layer never accepts an upstream AI-provider key. P-256 installation keys, DPoP signing, refresh-token storage, and platform attestation stay in the native Latchway SDKs.
4
4
 
5
- The maintained source repository is https://github.com/Latchway/latchway-react-native-sdk.git.
5
+ > **Release status:** `1.0.0` is the intended stable source candidate, but has not yet been published. Use a workspace dependency or the archive produced by `pnpm pack:check`; do not assume an npm or native artifact exists until the release is announced.
6
+
7
+ ## Requirements
8
+
9
+ - React Native 0.82.x with the New Architecture enabled
10
+ - iOS 15 or newer, an App Attest-capable application entitlement, and `Latchway/AppAttest` 1.0.0
11
+ - Android API 24 or newer, Play Integrity configured for the application, and the `dev.latchway` 1.0.0 artifacts
12
+ - Node 24.19.0 and pnpm 10.15.0 for repository development
13
+
14
+ The repository example additionally pins React Native Firebase 25.1.0, Firebase
15
+ Apple SDK 12.15.0, and Firebase Android BoM 34.15.0. These are example identity
16
+ provider dependencies, not runtime dependencies of the published Latchway
17
+ package. Repository installs use pnpm's hoisted linker because React Native's
18
+ CocoaPods static-framework exclusions require conventional `node_modules`
19
+ paths.
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { createLatchwayClient } from "@latchway/react-native";
25
+
26
+ const latchway = createLatchwayClient({
27
+ baseURL: "https://gateway.example.com",
28
+ // Generated by the Latchway Admin API; names/slugs are not accepted.
29
+ applicationID: "app_01J00000000000000000000000",
30
+ environment: "production",
31
+ getIdentityToken: async () => {
32
+ const token = await applicationIdentity.currentToken();
33
+ if (token === undefined) throw new Error("The user must sign in before calling Latchway.");
34
+ return token;
35
+ },
36
+ apple: {
37
+ rootKeychainAccessGroup: applicationConfiguration.rootPrivateKeychainAccessGroup,
38
+ legacySharedKeychainAccessGroups: applicationConfiguration.extensionSharedKeychainAccessGroups,
39
+ },
40
+ android: {
41
+ playIntegrityCloudProjectNumber: applicationConfiguration.googleCloudProjectNumber,
42
+ },
43
+ });
44
+
45
+ const response = await latchway.fetch("/v1/responses", {
46
+ method: "POST",
47
+ latchwayFeature: "habit_assistant",
48
+ headers: { "Content-Type": "application/json" },
49
+ body: JSON.stringify({
50
+ model: "assistant-default",
51
+ input: "Plan tomorrow",
52
+ }),
53
+ });
54
+
55
+ // OpenAI-compatible clients, Vercel OpenAI/Anthropic providers, and LangChain
56
+ // adapters receive a normal fetch function permanently scoped to one feature.
57
+ const habitAssistantFetch = latchway.fetchFor("habit_assistant");
58
+ ```
59
+
60
+ `gatewayURL` exposes only the canonical non-secret gateway origin for framework
61
+ constructors that require a base URL. `fetchFor` keeps the feature fixed,
62
+ preserves the pull-driven response stream, and aliases the canonical
63
+ `X-Latchway-Request-ID` as `X-Request-ID` for provider SDK error correlation.
64
+ It never uses or replaces global `fetch`.
65
+
66
+ The example contains runnable consumers for the exact locally tested versions
67
+ of the official OpenAI JavaScript SDK (7.8.0), Vercel AI SDK (7.0.85 with
68
+ `@ai-sdk/openai` 4.0.52 and `@ai-sdk/anthropic` 4.0.46), and LangChain
69
+ JavaScript (`@langchain/openai` 1.5.10):
70
+
71
+ ```ts
72
+ import {
73
+ createFrameworkConsumers,
74
+ runFrameworkConsumerSmoke,
75
+ } from "./framework-consumers";
76
+
77
+ const frameworks = createFrameworkConsumers(latchway, {
78
+ responses: "habit_responses",
79
+ chat: "habit_chat",
80
+ embeddings: "habit_embeddings",
81
+ anthropic: "habit_anthropic",
82
+ });
83
+ const result = await runFrameworkConsumerSmoke(frameworks, "Plan tomorrow");
84
+ ```
85
+
86
+ These are compatibility consumers, not another LLM abstraction and not a
87
+ blanket support claim for every package feature. They execute Responses, Chat
88
+ Completions, embeddings, Anthropic Messages, and SSE through native networking.
89
+ Each identifier above must name a separately configured Latchway feature whose
90
+ protocol matches that request family; one feature cannot represent multiple
91
+ protocols.
92
+ Local conformance also covers tools, JSON-schema request preservation, errors,
93
+ framework retry dispatches, explicit refresh, restricted opaque routes, and
94
+ cancellation. Automatic pre-dispatch session recovery remains a native/device
95
+ evidence claim. Audio, images, uploads, Realtime, browser-only middleware,
96
+ streaming request bodies, and any framework path that does not honor the
97
+ injected fetch remain unsupported here. The core
98
+ `compatibility/frameworks.yaml` registry is the canonical release-status and
99
+ version source; `react-native-fetch` remains experimental until hosted and
100
+ physical-device gates pass.
101
+
102
+ The official `@anthropic-ai/sdk` 0.120.0 package is not claimed as a React
103
+ Native consumer. Its credential-chain module contains Node filesystem imports
104
+ that Metro resolves even when a static constructor placeholder is supplied.
105
+ The runnable Anthropic Messages path therefore uses the custom-fetch seam in
106
+ `@ai-sdk/anthropic`; no Node-module shim or provider credential is bundled.
107
+
108
+ `applicationID` is the generated application resource ID returned by the
109
+ Admin API, not an app name, package/bundle identifier, or user-chosen slug.
110
+ On iOS, `apple.rootKeychainAccessGroup` is required, fully resolved, and must
111
+ be the first access group in the signed root application. Put every explicit
112
+ extension-shared group in `legacySharedKeychainAccessGroups`; native code scans
113
+ only exact root-record coordinates and surfaces `storage_unavailable` if stale
114
+ implicit root state requires an explicit migration.
115
+
116
+ Call `dispose()` when the owning application scope is destroyed. Disposal drops the in-memory native client; secure installation state remains available to later instances. `refresh()` explicitly rotates session credentials without exposing them. `revokeCurrentInstallation()` removes only the root installation and leaves independently provisioned family components addressable.
117
+
118
+ The root application manages the complete native iOS component lifecycle with
119
+ public descriptors:
120
+
121
+ ```ts
122
+ import type { ReactNativeIOSComponent } from "@latchway/react-native";
123
+
124
+ const appIntent: ReactNativeIOSComponent = {
125
+ definitionID: "habit_app_intent",
126
+ kind: "app_intent_extension",
127
+ keychainAccessGroup: "ABCDE12345.com.example.app.app-intent",
128
+ requestedFeatures: ["habit_assistant"],
129
+ };
130
+
131
+ const [prepared] = await latchway.prepareComponents([appIntent]);
132
+ const replaced = await latchway.replaceComponent(appIntent);
133
+ const localState = await latchway.componentDiagnostics(appIntent);
134
+ await latchway.revokeComponent(appIntent);
135
+
136
+ // On sign-out, native code retires every durably registered component.
137
+ await latchway.revokeCurrentInstallationFamily();
138
+
139
+ // The descriptor overload remains available for pre-registry legacy state.
140
+ await latchway.revokeCurrentInstallationFamily([appIntent]);
141
+ ```
142
+
143
+ Each descriptor contains exactly a definition ID, kind, fully resolved Keychain
144
+ access group, and requested feature IDs. Its access group must be signed into
145
+ both targets and listed in `apple.legacySharedKeychainAccessGroups`.
146
+ Preparation, replacement, component revocation, and family retirement acquire
147
+ the root identity only transiently inside native code. Root-side
148
+ `componentDiagnostics` reads redacted local state without acquiring identity.
149
+ Inputs are normalized before asynchronous work, and all component credentials,
150
+ keys, grants, and sessions remain native. Before component-local state can be
151
+ created, the iOS SDK records its validated, non-secret Keychain coordinate in
152
+ the root application's private Keychain group. No-argument family retirement
153
+ uses that durable registry across launches, removes entries only after both the
154
+ component credential and key are erased, and retains failed entries for retry.
155
+ The optional descriptor overload can additionally retire legacy component
156
+ state that predates the registry.
157
+
158
+ An independently executing iOS action or SSO extension whose containing app
159
+ has already provisioned its component descriptor can inspect its isolated,
160
+ independently keyed delegated-session state without moving credentials through
161
+ JavaScript:
162
+
163
+ ```ts
164
+ import { createLatchwayComponentClient } from "@latchway/react-native";
165
+
166
+ const component = {
167
+ definitionID: "action_extension",
168
+ kind: "action_extension",
169
+ keychainAccessGroup: "ABCDE12345.com.example.app.action-extension",
170
+ requestedFeatures: ["habit_assistant"],
171
+ } as const;
172
+
173
+ // Construct this only in the JavaScript runtime hosted by the signed .appex.
174
+ // It is deliberately separate from the containing app's root client.
175
+ const componentClient = createLatchwayComponentClient({
176
+ baseURL: "https://gateway.example.com",
177
+ applicationID: "app_01J00000000000000000000000",
178
+ environment: "production",
179
+ component,
180
+ apple: {
181
+ rootKeychainAccessGroup: "ABCDE12345.com.example.app",
182
+ legacySharedKeychainAccessGroups: [component.keychainAccessGroup],
183
+ },
184
+ });
185
+
186
+ const componentState = await componentClient.diagnostics();
187
+ // This is diagnostics only; RN v1 exposes no component request operation.
188
+ ```
189
+
190
+ The access group must be fully resolved and present in both signed entitlement
191
+ sets; build-setting expressions such as `$(AppIdentifierPrefix)` are rejected.
192
+ Configuration fails unless the current process is an iOS `.appex`. The root
193
+ application may establish App Attest only for itself; it must not attest for an
194
+ extension, and an iOS application extension cannot call
195
+ `DCAppAttestService.generateKey`. The extension client therefore constructs no
196
+ App Attest provider. It has no identity callback or root API and retains only
197
+ component-key and delegated-session isolation inside the pinned iOS SDK. The
198
+ legacy `establishDirectAttestation()` entry point and direct-attestation trust
199
+ source decoders remain for wire/API compatibility, but invocation fails closed
200
+ with `attestation_unsupported`; their presence is not a claim that iOS can
201
+ produce that state. Only redacted component diagnostics return. Android also
202
+ fails closed for direct component attestation.
203
+
204
+ The example's `AppIntents.appex` does not host a React Native JavaScript runtime.
205
+ In Debug only, its separate CocoaPods target links `Latchway/AppExtensions`
206
+ (Swift module `Latchway`) and constructs a native `LatchwayExtensionClient` with
207
+ runtime `react_native_ios`. After the root prepares the descriptor, the intent
208
+ publishes a nonsecret exact-run challenge in the shared Keychain immediately
209
+ before its waiting marker. The intent captures that challenge before creating
210
+ its client, proves an independently keyed delegated session, fully consumes one
211
+ successful bounded Responses body, rechecks that the challenge is still current,
212
+ and then echoes it in a bounded shared-Keychain receipt. Resume accepts only the
213
+ native-captured exact run and deletes both challenge and receipt. The receipt
214
+ contains only the nonsecret `dev_<32hex>` run nonce, status booleans, and a
215
+ timestamp; it contains no component, installation, or user IDs, tokens, proofs,
216
+ request body, or response body. The intent never receives the root identity or
217
+ root-private Keychain state.
218
+
219
+ The Release fixture has no AppExtensions dependency or executable Latchway
220
+ client path and its intent fails closed with an unsupported error. Both variants
221
+ retain private-first/shared-second root entitlements and a shared-only extension
222
+ entitlement. The local Debug intent is integration proof only and does not
223
+ broaden or replace the protected Release physical-evidence claim.
224
+
225
+ Equivalent clients in one JavaScript runtime share one native client and contract compatibility check. Native SDK actors/mutexes own session establishment and refresh single-flight. JavaScript never clones or replays an authenticated request; any bounded pre-dispatch retry is exclusively a native transport decision. Android installs the locked Latchway OkHttp interceptor, origin guard, and authenticator. iOS uses the locked feature transport, whose private redirect-rejecting URL session classifies at most 64 KiB of one canonical pre-dispatch rejection before its single safe retry.
226
+
227
+ The fetch surface is intentionally bounded. It dispatches only to the configured origin. The structured paths `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, and `/v1/messages` are POST-only. Opaque integrations may use GET, POST, PUT, PATCH, or DELETE only below `/proxy/{feature}/<safe-relative-path>` with an exact feature match and no query; empty segments, traversal, encoded separators, backslashes, and absolute-URL-shaped suffixes fail closed. Fragments and credential-shaped query names fail before identity acquisition. Request bodies are buffered to cross the New Architecture bridge and are limited to 8 MiB. Response bodies remain pull-streamed in chunks with cancellation and backpressure, while native retains the network task and credential-bearing request. Redirects are refused, and JavaScript receives only status, a strict safe-header allowlist, an opaque response handle, and response bytes.
228
+
229
+ The compatible subset guarantees request method, headers, body, `AbortSignal`, response status/headers, and a pull-driven `ReadableStream`. It does not provide browser cookie or cache modes, service-worker behavior, redirect following, streaming request uploads, response trailers, or native `Response.url`/`redirected` metadata. Frameworks must expose a custom-fetch hook and use one of the allowed data-plane paths; browser-only framework features remain unsupported in React Native. A provider SDK's required placeholder authorization header is discarded, never forwarded. Consult the released framework compatibility registry before claiming support for a specific OpenAI, Vercel AI, or LangChain version.
230
+
231
+ `errorFromResponse` is re-exported for explicit conversion of a returned problem response. Every resulting `LatchwayError.documentationURL` uses `https://docs.latchway.dev/errors/<hyphenated-code>`, and a server-originated native failure is accepted only when its documentation URL matches its code exactly. An `operation_indeterminate` error includes a required canonical `operationID`; preserve it with the request ID and reconcile the operation before deciding whether to retry.
232
+
233
+ ## Security boundary
234
+
235
+ The root client's only application credential sent into the TurboModule is the external identity JWT returned by `getIdentityToken`, and it is retained only for the duration of a native operation that requires identity. Root component descriptors and extension-client descriptors contain only definition ID, kind, fully resolved Keychain access group, and requested feature IDs. Root-side and extension-side component diagnostics do not acquire identity. The separate component client cannot acquire the containing app's native root lease. The bridge does not accept App Attest objects, Play Integrity tokens, `client_data_hash`, request hashes, DPoP private keys, access tokens, or refresh tokens as inputs. Native code attaches Authorization and DPoP, sends the request, and owns the response stream. Authorization, DPoP, access tokens, refresh tokens, private keys, attestation evidence, and reusable credentials never return to JavaScript.
236
+
237
+ Caller-supplied `Authorization`, `DPoP`, cookies, API-key headers, transport-owned headers, and Latchway protocol headers are removed before the request crosses the bridge. This permits SDKs that require a placeholder API-key option without forwarding that placeholder or a real provider credential. Provider-credential query names are rejected, including percent-encoded and case-varied names. Insecure HTTP is limited to explicitly enabled loopback conformance.
238
+
239
+ See [native installation](docs/native-installation.md), [security details](docs/security.md), and [architecture](docs/architecture.md).
240
+ Release ordering and immutable publication gates are in [releasing](docs/releasing.md).
241
+
242
+ ## Development
243
+
244
+ ```sh
245
+ pnpm install --frozen-lockfile
246
+ pnpm check
247
+ pnpm verify:compatibility
248
+ pnpm pack:check
249
+ pnpm verify:reproducible
250
+ ```
251
+
252
+ For a release-candidate checkout, `pnpm verify:compatibility --sources` also
253
+ requires the exact core, JavaScript, iOS, and Android commits recorded in
254
+ `release-compatibility.json`. `pnpm verify:bundle -- /path/to/latchway-contract-<version>.tar.gz`
255
+ verifies the complete immutable contract archive, and `pnpm consumer:check`
256
+ installs the packed JavaScript and React Native archives in a clean temporary
257
+ consumer before compiling it.
258
+
259
+ `pnpm codegen:check` parses the handwritten TurboModule spec and regenerates both platform surfaces in a disposable directory. Node tests use the explicit `@latchway/react-native/testing` bridge; production applications must never install a test bridge.
260
+
261
+ `pnpm example:bundle:check` creates production Metro bundles for both iOS and
262
+ Android in a disposable directory. It is part of `pnpm check` so framework
263
+ packages that typecheck but import Node-only modules fail the normal source
264
+ gate.
265
+
266
+ The example in [`example`](example/README.md) demonstrates Firebase Authentication, environment-supplied deployment configuration, raw fetch, real framework consumers, quota, diagnostics, and lifecycle cleanup without storing or logging credentials.
267
+
268
+ ## Contract lock
269
+
270
+ The final version 1 source candidate consumes released contract checkpoint `1.0.0`,
271
+ current wire protocol `2` (with wire `1` retained in the core compatibility
272
+ window), core commit `d260e3d7485e9e1487b5e03922b79c7089d94ce2`, and
273
+ bundle SHA-256
274
+ `4866aec1ff70e78d70f07847448161c2b59970fe102d95393b051444536d29a4`.
275
+ Core plus all four SDK locks and fixtures are synchronized. This is source
276
+ compatibility evidence, not a claim that the npm package or native dependencies
277
+ have been published. All gates read `release-compatibility.json` and
278
+ `contract.lock`, so later compatible releases do not require rewriting CI.
279
+ `pnpm verify:contracts` checks the active lock and vendored canonical fixtures
280
+ byte-for-byte, including the installation-family and component-attestation
281
+ binding v2 fixtures. The
282
+ promotion-dispatched release workflow still refuses
283
+ publication until exact native registry, provenance, physical-device, and
284
+ immutable-release evidence passes.
285
+
286
+ ## License
287
+
288
+ Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
package/SECURITY.md ADDED
@@ -0,0 +1,69 @@
1
+ # Security Policy
2
+
3
+ ## Release status
4
+
5
+ Latchway React Native SDK is implemented but remains pre-release. Do not depend
6
+ on an unpublished checkout as a supported production security boundary; use a
7
+ published version whose native compatibility entry is recorded.
8
+
9
+ Security fixes will target supported releases once the support matrix is
10
+ established. The unreleased branch may change without compatibility guarantees.
11
+
12
+ ## Reporting a vulnerability
13
+
14
+ Use GitHub private vulnerability reporting for this repository. If that feature
15
+ is unavailable, contact the Latchway organization maintainers privately before
16
+ sharing details. Do not open a public issue for a suspected vulnerability.
17
+
18
+ Do not include live identity tokens, refresh tokens, DPoP proofs, attestation
19
+ evidence, private keys, signing assets, provider credentials, or user data in a
20
+ report. Revoke exposed credentials before continuing.
21
+
22
+ A useful report includes:
23
+
24
+ - The affected revision or released version
25
+ - The React Native, iOS or Android, and device versions
26
+ - Reproduction steps using synthetic or redacted data
27
+ - The expected and observed security behavior
28
+ - Impact, prerequisites, and any known mitigations
29
+
30
+ ## Security-sensitive scope
31
+
32
+ Treat changes to TurboModule boundaries, native-to-JavaScript data exposure,
33
+ installation state, native SDK version resolution, request retry/replay
34
+ behavior, token redaction, and diagnostics as security-sensitive. Hardware keys,
35
+ attestation, DPoP signing, refresh-token storage, and installation state belong
36
+ to the native SDKs and must not be reimplemented in JavaScript or C++ here.
37
+
38
+ The application-owned external identity token crosses the TurboModule boundary
39
+ only for the duration of a native operation. Private keys, refresh tokens, raw
40
+ attestation evidence, attestation challenge bindings, and native session state
41
+ must never enter JavaScript. Access tokens and DPoP proofs also remain native:
42
+ the iOS URL session or Android OkHttp client attaches them, dispatches the
43
+ exact-origin request, rejects redirects, and retains the response handle.
44
+ JavaScript receives only safe response metadata and pull-streamed response
45
+ bytes. There is no public or TurboModule authorization-envelope API.
46
+
47
+ The SDK must never accept an upstream AI-provider secret. Latchway server or
48
+ native-SDK vulnerabilities should be reported to their owning repository, with
49
+ a cross-reference here when bridge behavior is involved.
50
+
51
+ ## Dependency vulnerability gate
52
+
53
+ CI and release jobs bind `package.json`, `pnpm-lock.yaml`, and the scanner policy
54
+ scripts to the exact candidate commit before scanning. They install the
55
+ checksum-and-size-pinned OSV-Scanner 2.4.0 binary, download the public npm
56
+ advisory database, and then match dependencies locally with network resolution
57
+ disabled. No package inventory or repository path is sent to OSV.dev. Native
58
+ SDK dependency graphs are scanned in their owning iOS and Android repositories.
59
+
60
+ `pnpm security:dependencies` reproduces the gate. Critical, high, or
61
+ unknown-severity findings fail the candidate; lower-severity findings remain
62
+ visible for routine remediation. Scanner errors, malformed output, an empty
63
+ inventory, or a missing offline database also fail closed.
64
+
65
+ ## Disclosure
66
+
67
+ Allow maintainers a reasonable opportunity to investigate and coordinate a fix
68
+ before public disclosure. Good-faith research that avoids privacy violations,
69
+ service disruption, and access beyond what is necessary is welcome.
@@ -0,0 +1,62 @@
1
+ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2
+
3
+ plugins {
4
+ id("com.android.library")
5
+ id("org.jetbrains.kotlin.android")
6
+ id("com.facebook.react")
7
+ }
8
+
9
+ kotlin {
10
+ compilerOptions { jvmTarget.set(JvmTarget.JVM_17) }
11
+ }
12
+
13
+ group = "dev.latchway"
14
+ version = "1.0.0"
15
+
16
+ react {
17
+ root.set(file(".."))
18
+ reactNativeDir.set(file("../node_modules/react-native"))
19
+ codegenDir.set(file("../node_modules/@react-native/codegen"))
20
+ libraryName.set("LatchwayReactNativeSpec")
21
+ codegenJavaPackageName.set("dev.latchway.reactnative")
22
+ }
23
+
24
+ android {
25
+ namespace = "dev.latchway.reactnative"
26
+ // The exact native 1.0.0 AAR metadata requires API 37 consumers.
27
+ compileSdk = 37
28
+
29
+ defaultConfig {
30
+ minSdk = 24
31
+ consumerProguardFiles("consumer-rules.pro")
32
+ }
33
+
34
+ compileOptions {
35
+ sourceCompatibility = JavaVersion.VERSION_17
36
+ targetCompatibility = JavaVersion.VERSION_17
37
+ }
38
+
39
+ lint {
40
+ abortOnError = true
41
+ warningsAsErrors = true
42
+ // These versions are compatibility inputs, not opportunistic upgrades.
43
+ // scripts/verify-compatibility.mjs enforces the reviewed exact lock.
44
+ disable += setOf("AndroidGradlePluginVersion", "NewerVersionAvailable")
45
+ }
46
+
47
+ testOptions {
48
+ unitTests.isIncludeAndroidResources = true
49
+ }
50
+ }
51
+
52
+ dependencies {
53
+ // A real host's React plugin forces this to the installed 0.82.x patch. The
54
+ // exact baseline keeps standalone package-consumer builds deterministic.
55
+ implementation("com.facebook.react:react-android:0.82.0")
56
+ implementation("dev.latchway:latchway-okhttp:1.0.0")
57
+ implementation("dev.latchway:latchway-play-integrity:1.0.0")
58
+
59
+ testImplementation("junit:junit:4.13.2")
60
+ testImplementation("org.robolectric:robolectric:4.14.1")
61
+ testImplementation("com.squareup.okhttp3:mockwebserver:5.3.0")
62
+ }
@@ -0,0 +1,2 @@
1
+ -keep class dev.latchway.reactnative.NativeLatchwayModule { *; }
2
+ -keep class dev.latchway.reactnative.LatchwayReactNativePackage { *; }
@@ -0,0 +1,9 @@
1
+ distributionBase=GRADLE_USER_HOME
2
+ distributionPath=wrapper/dists
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4
+ distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
5
+ networkTimeout=10000
6
+ retries=0
7
+ validateDistributionUrl=true
8
+ zipStoreBase=GRADLE_USER_HOME
9
+ zipStorePath=wrapper/dists
@@ -0,0 +1,4 @@
1
+ android.useAndroidX=true
2
+ kotlin.code.style=official
3
+ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
4
+ android.suppressUnsupportedCompileSdk=37,37.0