@latchway/react-native 1.0.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 CHANGED
@@ -7,6 +7,31 @@ Versioning once package publication begins.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0] - 2026-09-07
11
+
12
+ ### Added
13
+
14
+ - Explicit `@latchway/react-native/polyfills` bootstrap for Hermes async symbols,
15
+ streaming UTF-8 decoding, web streams, URL and abort compatibility. Complete
16
+ existing globals are retained; the ordinary SDK import does not install it.
17
+ - Optional `withLatchwayBabel` configuration helper for LangChain class and
18
+ export-namespace compatibility without depending on LangChain at runtime.
19
+ - Standalone npm-only LatchwayChat with Firebase authentication, temporary
20
+ multi-turn LangChain weather tools, direct fetch, cancellation and diagnostics.
21
+ - Isolated runtime regressions and a LangChain quickstart.
22
+
23
+ ### Fixed
24
+
25
+ - Metro now resolves the published built JavaScript entry without a custom
26
+ `.js`-to-TypeScript resolver. Native Codegen retains its original TS schema.
27
+
28
+ ### Compatibility
29
+
30
+ - Shared client, iOS App Attest and Android SDK dependencies remain 1.0.0;
31
+ the native authentication/credential boundary and wire protocol are unchanged.
32
+ - Supported baseline remains React Native 0.82 / New Architecture. Android
33
+ physical Play Integrity evidence is deferred, not inferred from a build.
34
+
10
35
  ## [1.0.0] - 2026-09-01
11
36
 
12
37
  ### Changed
package/README.md CHANGED
@@ -2,7 +2,14 @@
2
2
 
3
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
- > **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.
5
+ Version 1.1.0 adds an optional [LangChain quickstart](docs/langchain.md), runtime
6
+ bootstrap and Babel helper, with standard Metro resolution. Native SDK
7
+ dependencies remain 1.0.0 from CocoaPods and Maven Central.
8
+
9
+ For a standalone npm-only consumer with Firebase login, LangChain weather tools,
10
+ streaming chat and direct-fetch Settings, see
11
+ [LatchwayChat](Examples/LatchwayChat/README.md). The existing `example/` remains
12
+ the separate workspace/conformance application.
6
13
 
7
14
  ## Requirements
8
15
 
package/babel.cjs ADDED
@@ -0,0 +1,22 @@
1
+ /** Opt-in compiler compatibility for LangChain on the supported RN baseline. */
2
+ function withLatchwayBabel(config = {}) {
3
+ if (config.assumptions?.noClassCalls === false) {
4
+ throw new Error("LangChain on React Native requires assumptions.noClassCalls=true. Remove the conflicting assumption.");
5
+ }
6
+ const namespacePlugin = require.resolve("@babel/plugin-transform-export-namespace-from");
7
+ const plugins = config.plugins ?? [];
8
+ const hasNamespacePlugin = plugins.some((entry) => {
9
+ const plugin = Array.isArray(entry) ? entry[0] : entry;
10
+ return plugin === namespacePlugin || plugin === "@babel/plugin-transform-export-namespace-from";
11
+ });
12
+ return {
13
+ ...config,
14
+ // LangChain's Symbol.hasInstance reads fields initialized by super(). The
15
+ // Babel pre-constructor instanceof assertion runs too early. Classes must
16
+ // still be called with new; LangChain's own type checks remain unchanged.
17
+ assumptions: { ...config.assumptions, noClassCalls: true },
18
+ plugins: hasNamespacePlugin ? [...plugins] : [namespacePlugin, ...plugins],
19
+ };
20
+ }
21
+
22
+ module.exports = { withLatchwayBabel };
package/babel.d.cts ADDED
@@ -0,0 +1,7 @@
1
+ interface BabelConfiguration {
2
+ assumptions?: Record<string, boolean>;
3
+ plugins?: unknown[];
4
+ [key: string]: unknown;
5
+ }
6
+ /** Preserves existing configuration and adds explicit LangChain compatibility. */
7
+ export function withLatchwayBabel(config?: BabelConfiguration): BabelConfiguration;
@@ -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,8 @@
1
+ /**
2
+ * Explicit, opt-in React Native compatibility bootstrap. Import this before
3
+ * LangChain or any SDK/stream dependency. The ordinary SDK entry has no global
4
+ * initialization side effects. Native keys and attestation are never polyfilled.
5
+ */
6
+ import "./runtime-symbols.js";
7
+ import "react-native-get-random-values";
8
+ //# sourceMappingURL=polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polyfills.d.ts","sourceRoot":"","sources":["../src/polyfills.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,sBAAsB,CAAC;AAC9B,OAAO,gCAAgC,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Explicit, opt-in React Native compatibility bootstrap. Import this before
3
+ * LangChain or any SDK/stream dependency. The ordinary SDK entry has no global
4
+ * initialization side effects. Native keys and attestation are never polyfilled.
5
+ */
6
+ import "./runtime-symbols.js";
7
+ import "react-native-get-random-values";
8
+ import { URL as CompatibleURL, URLSearchParams as CompatibleURLSearchParams } from "react-native-url-polyfill";
9
+ import { ReadableStream, TransformStream, WritableStream } from "web-streams-polyfill";
10
+ import { TextDecoder, TextEncoder } from "text-encoding";
11
+ // React Native's built-in URL is present but incomplete. Replace it only when
12
+ // this small capability check fails, retaining complete application polyfills.
13
+ function hasWorkingURL() {
14
+ try {
15
+ const url = new globalThis.URL("child?q=hello%20world", "https://example.invalid/root/");
16
+ return url.href === "https://example.invalid/root/child?q=hello%20world" &&
17
+ url.searchParams.get("q") === "hello world" &&
18
+ new globalThis.URLSearchParams({ q: "hello world" }).get("q") === "hello world";
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ if (!hasWorkingURL())
25
+ Object.assign(globalThis, { URL: CompatibleURL, URLSearchParams: CompatibleURLSearchParams });
26
+ if (typeof globalThis.TextEncoder === "undefined")
27
+ Object.assign(globalThis, { TextEncoder });
28
+ if (typeof globalThis.TextDecoder === "undefined")
29
+ Object.assign(globalThis, { TextDecoder });
30
+ if (typeof globalThis.ReadableStream === "undefined")
31
+ Object.assign(globalThis, { ReadableStream });
32
+ if (typeof globalThis.TransformStream === "undefined")
33
+ Object.assign(globalThis, { TransformStream });
34
+ if (typeof globalThis.WritableStream === "undefined")
35
+ Object.assign(globalThis, { WritableStream });
36
+ if (typeof AbortSignal.prototype.throwIfAborted === "undefined") {
37
+ Object.defineProperty(AbortSignal.prototype, "throwIfAborted", {
38
+ configurable: true,
39
+ writable: true,
40
+ value() {
41
+ if (!this.aborted)
42
+ return;
43
+ if (this.reason !== undefined)
44
+ throw this.reason;
45
+ const error = new Error("The operation was aborted.");
46
+ error.name = "AbortError";
47
+ throw error;
48
+ },
49
+ });
50
+ }
51
+ // LangChain expects a string when navigator exists. This is runtime detection,
52
+ // not caller-supplied trust or framework attribution in the native protocol.
53
+ if (typeof navigator !== "undefined" && typeof navigator.userAgent !== "string") {
54
+ Object.defineProperty(navigator, "userAgent", { value: "ReactNative/0.82", configurable: true });
55
+ }
56
+ //# sourceMappingURL=polyfills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polyfills.js","sourceRoot":"","sources":["../src/polyfills.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,sBAAsB,CAAC;AAC9B,OAAO,gCAAgC,CAAC;AACxC,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,eAAe,IAAI,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC/G,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEzD,8EAA8E;AAC9E,+EAA+E;AAC/E,SAAS,aAAa;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,uBAAuB,EAAE,+BAA+B,CAAC,CAAC;QACzF,OAAO,GAAG,CAAC,IAAI,KAAK,oDAAoD;YACtE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,aAAa;YAC3C,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,aAAa,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,IAAI,CAAC,aAAa,EAAE;IAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,eAAe,EAAE,yBAAyB,EAAE,CAAC,CAAC;AACpH,IAAI,OAAO,UAAU,CAAC,WAAW,KAAK,WAAW;IAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;AAC9F,IAAI,OAAO,UAAU,CAAC,WAAW,KAAK,WAAW;IAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;AAC9F,IAAI,OAAO,UAAU,CAAC,cAAc,KAAK,WAAW;IAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;AACpG,IAAI,OAAO,UAAU,CAAC,eAAe,KAAK,WAAW;IAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC;AACtG,IAAI,OAAO,UAAU,CAAC,cAAc,KAAK,WAAW;IAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;AAEpG,IAAI,OAAO,WAAW,CAAC,SAAS,CAAC,cAAc,KAAK,WAAW,EAAE,CAAC;IAChE,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,SAAS,EAAE,gBAAgB,EAAE;QAC7D,YAAY,EAAE,IAAI;QAClB,QAAQ,EAAE,IAAI;QACd,KAAK;YACH,IAAI,CAAC,IAAI,CAAC,OAAO;gBAAE,OAAO;YAC1B,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;gBAAE,MAAM,IAAI,CAAC,MAAM,CAAC;YACjD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;YACtD,KAAK,CAAC,IAAI,GAAG,YAAY,CAAC;YAC1B,MAAM,KAAK,CAAC;QACd,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,6EAA6E;AAC7E,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,OAAO,SAAS,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;IAChF,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;AACnG,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runtime-symbols.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-symbols.d.ts","sourceRoot":"","sources":["../src/runtime-symbols.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ // This module deliberately has no imports. Computed stream methods must see
2
+ // these symbols before any dependency is evaluated on Hermes 0.82.
3
+ for (const name of ["asyncIterator", "asyncDispose", "dispose"]) {
4
+ if (typeof Symbol[name] === "undefined") {
5
+ Object.defineProperty(Symbol, name, { value: Symbol.for(`Symbol.${name}`) });
6
+ }
7
+ }
8
+ export {};
9
+ //# sourceMappingURL=runtime-symbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-symbols.js","sourceRoot":"","sources":["../src/runtime-symbols.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,mEAAmE;AACnE,KAAK,MAAM,IAAI,IAAI,CAAC,eAAe,EAAE,cAAc,EAAE,SAAS,CAAU,EAAE,CAAC;IACzE,IAAI,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,WAAW,EAAE,CAAC;QACxC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC"}
package/lib/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "1.0.0";
1
+ export declare const SDK_VERSION = "1.1.0";
2
2
  export declare const CONTRACT_VERSION = "1.0.0";
3
3
  export declare const PROTOCOL_VERSION = 2;
4
4
  export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly [1, 2];
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
- export const SDK_VERSION = "1.0.0";
1
+ export const SDK_VERSION = "1.1.0";
2
2
  export const CONTRACT_VERSION = "1.0.0";
3
3
  export const PROTOCOL_VERSION = 2;
4
4
  export const SUPPORTED_PROTOCOL_VERSIONS = Object.freeze([1, PROTOCOL_VERSION]);
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "@latchway/react-native",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Hardware-backed React Native client for the Latchway AI gateway",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",
8
8
  "module": "./lib/index.js",
9
9
  "types": "./lib/index.d.ts",
10
- "react-native": "./src/index.ts",
11
- "sideEffects": false,
10
+ "react-native": "./lib/index.js",
11
+ "sideEffects": [
12
+ "./lib/polyfills.js",
13
+ "./lib/runtime-symbols.js"
14
+ ],
12
15
  "engines": {
13
16
  "node": ">=24.19.0",
14
17
  "pnpm": "10.15.0"
@@ -27,6 +30,8 @@
27
30
  "ios",
28
31
  "lib",
29
32
  "src",
33
+ "babel.cjs",
34
+ "babel.d.cts",
30
35
  "CHANGELOG.md",
31
36
  "LICENSE",
32
37
  "NOTICE",
@@ -40,10 +45,21 @@
40
45
  "exports": {
41
46
  ".": {
42
47
  "types": "./lib/index.d.ts",
43
- "react-native": "./src/index.ts",
48
+ "react-native": "./lib/index.js",
44
49
  "import": "./lib/index.js",
45
50
  "default": "./lib/index.js"
46
51
  },
52
+ "./polyfills": {
53
+ "types": "./lib/polyfills.d.ts",
54
+ "react-native": "./lib/polyfills.js",
55
+ "import": "./lib/polyfills.js",
56
+ "default": "./lib/polyfills.js"
57
+ },
58
+ "./babel": {
59
+ "types": "./babel.d.cts",
60
+ "require": "./babel.cjs",
61
+ "default": "./babel.cjs"
62
+ },
47
63
  "./testing": {
48
64
  "types": "./lib/testing.d.ts",
49
65
  "import": "./lib/testing.js",
@@ -52,7 +68,11 @@
52
68
  "./package.json": "./package.json"
53
69
  },
54
70
  "dependencies": {
71
+ "@babel/plugin-transform-export-namespace-from": "7.29.7",
55
72
  "@latchway/client": "1.0.0",
73
+ "react-native-get-random-values": "1.11.0",
74
+ "react-native-url-polyfill": "2.0.0",
75
+ "text-encoding": "0.7.0",
56
76
  "web-streams-polyfill": "4.3.0"
57
77
  },
58
78
  "peerDependencies": {
@@ -69,6 +89,7 @@
69
89
  "@react-native/gradle-plugin": "0.82.0",
70
90
  "@types/node": "24.13.3",
71
91
  "@types/react": "19.2.18",
92
+ "@types/text-encoding": "0.0.40",
72
93
  "ai": "7.0.85",
73
94
  "eslint": "10.9.1",
74
95
  "globals": "17.11.0",
@@ -117,7 +138,7 @@
117
138
  ],
118
139
  "scripts": {
119
140
  "build": "node scripts/clean.mjs && tsc -p tsconfig.build.json",
120
- "check": "pnpm verify:contracts && pnpm verify:compatibility && pnpm lint && pnpm typecheck && pnpm test && pnpm codegen:check && pnpm build && pnpm example:check && pnpm example:bundle:check && pnpm native:boundary",
141
+ "check": "pnpm verify:contracts && pnpm verify:compatibility && pnpm lint && pnpm typecheck && pnpm test:runtime && pnpm test && pnpm codegen:check && pnpm build && pnpm example:check && pnpm example:bundle:check && pnpm native:boundary",
121
142
  "codegen:check": "node scripts/verify-codegen.mjs",
122
143
  "docs:bundle": "python3 scripts/build_docs_bundle.py",
123
144
  "example:check": "tsc -p tsconfig.example.json --noEmit",
@@ -134,6 +155,7 @@
134
155
  "consumer:check": "node scripts/verify-consumer.mjs",
135
156
  "test": "vitest run && node --test scripts/test-npm-release-evidence.mjs scripts/test-android-release-evidence.mjs scripts/test-ci-lock-output.mjs scripts/test-package-archive.mjs && python3 -m unittest scripts/test_build_docs_bundle.py scripts/test_maintainer_release.py scripts/test_public_core_release.py && python3 scripts/test-dependency-vulnerability-scan.py",
136
157
  "test:watch": "vitest",
158
+ "test:runtime": "node --test scripts/test-runtime.mjs",
137
159
  "typecheck": "tsc -p tsconfig.json --noEmit",
138
160
  "verify:bundle": "node scripts/verify-contract-bundle.mjs",
139
161
  "verify:compatibility": "node scripts/verify-compatibility.mjs",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "react_native": {
19
19
  "package": "@latchway/react-native",
20
- "version": "1.0.0",
20
+ "version": "1.1.0",
21
21
  "baseline": "0.82.0",
22
22
  "cli": "20.0.0",
23
23
  "react": "19.1.1",
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Explicit, opt-in React Native compatibility bootstrap. Import this before
3
+ * LangChain or any SDK/stream dependency. The ordinary SDK entry has no global
4
+ * initialization side effects. Native keys and attestation are never polyfilled.
5
+ */
6
+ import "./runtime-symbols.js";
7
+ import "react-native-get-random-values";
8
+ import { URL as CompatibleURL, URLSearchParams as CompatibleURLSearchParams } from "react-native-url-polyfill";
9
+ import { ReadableStream, TransformStream, WritableStream } from "web-streams-polyfill";
10
+ import { TextDecoder, TextEncoder } from "text-encoding";
11
+
12
+ // React Native's built-in URL is present but incomplete. Replace it only when
13
+ // this small capability check fails, retaining complete application polyfills.
14
+ function hasWorkingURL(): boolean {
15
+ try {
16
+ const url = new globalThis.URL("child?q=hello%20world", "https://example.invalid/root/");
17
+ return url.href === "https://example.invalid/root/child?q=hello%20world" &&
18
+ url.searchParams.get("q") === "hello world" &&
19
+ new globalThis.URLSearchParams({ q: "hello world" }).get("q") === "hello world";
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ if (!hasWorkingURL()) Object.assign(globalThis, { URL: CompatibleURL, URLSearchParams: CompatibleURLSearchParams });
26
+ if (typeof globalThis.TextEncoder === "undefined") Object.assign(globalThis, { TextEncoder });
27
+ if (typeof globalThis.TextDecoder === "undefined") Object.assign(globalThis, { TextDecoder });
28
+ if (typeof globalThis.ReadableStream === "undefined") Object.assign(globalThis, { ReadableStream });
29
+ if (typeof globalThis.TransformStream === "undefined") Object.assign(globalThis, { TransformStream });
30
+ if (typeof globalThis.WritableStream === "undefined") Object.assign(globalThis, { WritableStream });
31
+
32
+ if (typeof AbortSignal.prototype.throwIfAborted === "undefined") {
33
+ Object.defineProperty(AbortSignal.prototype, "throwIfAborted", {
34
+ configurable: true,
35
+ writable: true,
36
+ value(this: AbortSignal): void {
37
+ if (!this.aborted) return;
38
+ if (this.reason !== undefined) throw this.reason;
39
+ const error = new Error("The operation was aborted.");
40
+ error.name = "AbortError";
41
+ throw error;
42
+ },
43
+ });
44
+ }
45
+
46
+ // LangChain expects a string when navigator exists. This is runtime detection,
47
+ // not caller-supplied trust or framework attribution in the native protocol.
48
+ if (typeof navigator !== "undefined" && typeof navigator.userAgent !== "string") {
49
+ Object.defineProperty(navigator, "userAgent", { value: "ReactNative/0.82", configurable: true });
50
+ }
@@ -0,0 +1,9 @@
1
+ // This module deliberately has no imports. Computed stream methods must see
2
+ // these symbols before any dependency is evaluated on Hermes 0.82.
3
+ for (const name of ["asyncIterator", "asyncDispose", "dispose"] as const) {
4
+ if (typeof Symbol[name] === "undefined") {
5
+ Object.defineProperty(Symbol, name, { value: Symbol.for(`Symbol.${name}`) });
6
+ }
7
+ }
8
+
9
+ export {};
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
- export const SDK_VERSION = "1.0.0";
1
+ export const SDK_VERSION = "1.1.0";
2
2
  export const CONTRACT_VERSION = "1.0.0";
3
3
  export const PROTOCOL_VERSION = 2;
4
4
  export const SUPPORTED_PROTOCOL_VERSIONS = Object.freeze([1, PROTOCOL_VERSION] as const);