@strand-js/react-native 0.1.0 → 0.1.2
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/README.md +35 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +122 -0
- package/dist/index.mjs +94 -0
- package/package.json +11 -10
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @strand-js/react-native
|
|
2
|
+
|
|
3
|
+
React Native transport adapter for [Strand](https://github.com/strand-js/strand).
|
|
4
|
+
|
|
5
|
+
React Native's built-in `fetch` does not support streaming response bodies. This package patches it transparently so all Strand hooks work identically to web.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @strand-js/react-native
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// App.tsx — must be the first import
|
|
17
|
+
import '@strand-js/react-native'
|
|
18
|
+
|
|
19
|
+
// Everything else works as normal
|
|
20
|
+
import { StrandProvider } from '@strand-js/react'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That's it. All hooks (`useConversation`, `useToolCall`, `useAgentSession`) work identically after this import.
|
|
24
|
+
|
|
25
|
+
## Compatibility
|
|
26
|
+
|
|
27
|
+
| Platform | Status |
|
|
28
|
+
|---|---|
|
|
29
|
+
| React Native 0.73+ | ✅ |
|
|
30
|
+
| Expo SDK 50+ | ✅ |
|
|
31
|
+
| Expo Web | ✅ (no-op) |
|
|
32
|
+
| Node.js | ✅ (no-op) |
|
|
33
|
+
| Browsers | ✅ (no-op) |
|
|
34
|
+
|
|
35
|
+
[Full documentation →](https://github.com/strand-js/strand)
|
package/dist/index.d.mts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
applyStreamingFetchPatch: () => applyStreamingFetchPatch,
|
|
24
|
+
createXHRStreamingFetch: () => createXHRStreamingFetch
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/patch.ts
|
|
29
|
+
var encoder = new TextEncoder();
|
|
30
|
+
function parseResponseHeaders(headersStr) {
|
|
31
|
+
const headers = {};
|
|
32
|
+
for (const line of headersStr.trim().split("\r\n")) {
|
|
33
|
+
const idx = line.indexOf(": ");
|
|
34
|
+
if (idx > 0) headers[line.slice(0, idx).toLowerCase()] = line.slice(idx + 2);
|
|
35
|
+
}
|
|
36
|
+
return headers;
|
|
37
|
+
}
|
|
38
|
+
function createXHRStreamingFetch() {
|
|
39
|
+
return (input, init) => {
|
|
40
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
41
|
+
const method = init?.method ?? (typeof input !== "string" && !(input instanceof URL) ? input.method : "GET");
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const xhr = new XMLHttpRequest();
|
|
44
|
+
xhr.open(method, url, true);
|
|
45
|
+
xhr.responseType = "text";
|
|
46
|
+
const reqHeaders = init?.headers ? new Headers(init.headers) : new Headers();
|
|
47
|
+
reqHeaders.forEach((value, key) => xhr.setRequestHeader(key, value));
|
|
48
|
+
let resolved = false;
|
|
49
|
+
let streamController = null;
|
|
50
|
+
let processedLength = 0;
|
|
51
|
+
const readableStream = new ReadableStream({
|
|
52
|
+
start(controller) {
|
|
53
|
+
streamController = controller;
|
|
54
|
+
},
|
|
55
|
+
cancel() {
|
|
56
|
+
xhr.abort();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
function pushChunk() {
|
|
60
|
+
const text = xhr.responseText;
|
|
61
|
+
const newText = text.slice(processedLength);
|
|
62
|
+
processedLength = text.length;
|
|
63
|
+
if (newText && streamController) {
|
|
64
|
+
streamController.enqueue(encoder.encode(newText));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function resolveResponse() {
|
|
68
|
+
if (!resolved) {
|
|
69
|
+
resolved = true;
|
|
70
|
+
resolve(
|
|
71
|
+
new Response(readableStream, {
|
|
72
|
+
status: xhr.status,
|
|
73
|
+
statusText: xhr.statusText,
|
|
74
|
+
headers: parseResponseHeaders(xhr.getAllResponseHeaders())
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
xhr.onprogress = () => {
|
|
80
|
+
pushChunk();
|
|
81
|
+
resolveResponse();
|
|
82
|
+
};
|
|
83
|
+
xhr.onload = () => {
|
|
84
|
+
pushChunk();
|
|
85
|
+
streamController?.close();
|
|
86
|
+
resolveResponse();
|
|
87
|
+
};
|
|
88
|
+
xhr.onerror = () => {
|
|
89
|
+
const err = new TypeError("Network request failed");
|
|
90
|
+
streamController?.error(err);
|
|
91
|
+
if (!resolved) reject(err);
|
|
92
|
+
};
|
|
93
|
+
xhr.onabort = () => {
|
|
94
|
+
const err = new DOMException("Request aborted", "AbortError");
|
|
95
|
+
streamController?.error(err);
|
|
96
|
+
if (!resolved) reject(err);
|
|
97
|
+
};
|
|
98
|
+
if (init?.signal) {
|
|
99
|
+
init.signal.addEventListener("abort", () => xhr.abort());
|
|
100
|
+
}
|
|
101
|
+
xhr.send(init?.body ?? null);
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
var patched = false;
|
|
106
|
+
function applyStreamingFetchPatch() {
|
|
107
|
+
if (patched) return;
|
|
108
|
+
patched = true;
|
|
109
|
+
const nav = typeof navigator !== "undefined" ? navigator : null;
|
|
110
|
+
const isReactNative = nav?.product === "ReactNative";
|
|
111
|
+
if (isReactNative) {
|
|
112
|
+
globalThis.fetch = createXHRStreamingFetch();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/index.ts
|
|
117
|
+
applyStreamingFetchPatch();
|
|
118
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
119
|
+
0 && (module.exports = {
|
|
120
|
+
applyStreamingFetchPatch,
|
|
121
|
+
createXHRStreamingFetch
|
|
122
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/patch.ts
|
|
2
|
+
var encoder = new TextEncoder();
|
|
3
|
+
function parseResponseHeaders(headersStr) {
|
|
4
|
+
const headers = {};
|
|
5
|
+
for (const line of headersStr.trim().split("\r\n")) {
|
|
6
|
+
const idx = line.indexOf(": ");
|
|
7
|
+
if (idx > 0) headers[line.slice(0, idx).toLowerCase()] = line.slice(idx + 2);
|
|
8
|
+
}
|
|
9
|
+
return headers;
|
|
10
|
+
}
|
|
11
|
+
function createXHRStreamingFetch() {
|
|
12
|
+
return (input, init) => {
|
|
13
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
14
|
+
const method = init?.method ?? (typeof input !== "string" && !(input instanceof URL) ? input.method : "GET");
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const xhr = new XMLHttpRequest();
|
|
17
|
+
xhr.open(method, url, true);
|
|
18
|
+
xhr.responseType = "text";
|
|
19
|
+
const reqHeaders = init?.headers ? new Headers(init.headers) : new Headers();
|
|
20
|
+
reqHeaders.forEach((value, key) => xhr.setRequestHeader(key, value));
|
|
21
|
+
let resolved = false;
|
|
22
|
+
let streamController = null;
|
|
23
|
+
let processedLength = 0;
|
|
24
|
+
const readableStream = new ReadableStream({
|
|
25
|
+
start(controller) {
|
|
26
|
+
streamController = controller;
|
|
27
|
+
},
|
|
28
|
+
cancel() {
|
|
29
|
+
xhr.abort();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
function pushChunk() {
|
|
33
|
+
const text = xhr.responseText;
|
|
34
|
+
const newText = text.slice(processedLength);
|
|
35
|
+
processedLength = text.length;
|
|
36
|
+
if (newText && streamController) {
|
|
37
|
+
streamController.enqueue(encoder.encode(newText));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function resolveResponse() {
|
|
41
|
+
if (!resolved) {
|
|
42
|
+
resolved = true;
|
|
43
|
+
resolve(
|
|
44
|
+
new Response(readableStream, {
|
|
45
|
+
status: xhr.status,
|
|
46
|
+
statusText: xhr.statusText,
|
|
47
|
+
headers: parseResponseHeaders(xhr.getAllResponseHeaders())
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
xhr.onprogress = () => {
|
|
53
|
+
pushChunk();
|
|
54
|
+
resolveResponse();
|
|
55
|
+
};
|
|
56
|
+
xhr.onload = () => {
|
|
57
|
+
pushChunk();
|
|
58
|
+
streamController?.close();
|
|
59
|
+
resolveResponse();
|
|
60
|
+
};
|
|
61
|
+
xhr.onerror = () => {
|
|
62
|
+
const err = new TypeError("Network request failed");
|
|
63
|
+
streamController?.error(err);
|
|
64
|
+
if (!resolved) reject(err);
|
|
65
|
+
};
|
|
66
|
+
xhr.onabort = () => {
|
|
67
|
+
const err = new DOMException("Request aborted", "AbortError");
|
|
68
|
+
streamController?.error(err);
|
|
69
|
+
if (!resolved) reject(err);
|
|
70
|
+
};
|
|
71
|
+
if (init?.signal) {
|
|
72
|
+
init.signal.addEventListener("abort", () => xhr.abort());
|
|
73
|
+
}
|
|
74
|
+
xhr.send(init?.body ?? null);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
var patched = false;
|
|
79
|
+
function applyStreamingFetchPatch() {
|
|
80
|
+
if (patched) return;
|
|
81
|
+
patched = true;
|
|
82
|
+
const nav = typeof navigator !== "undefined" ? navigator : null;
|
|
83
|
+
const isReactNative = nav?.product === "ReactNative";
|
|
84
|
+
if (isReactNative) {
|
|
85
|
+
globalThis.fetch = createXHRStreamingFetch();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/index.ts
|
|
90
|
+
applyStreamingFetchPatch();
|
|
91
|
+
export {
|
|
92
|
+
applyStreamingFetchPatch,
|
|
93
|
+
createXHRStreamingFetch
|
|
94
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strand-js/react-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"description": "React Native transport adapter for Strand — patches streaming fetch support",
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"module": "./dist/index.mjs",
|
|
@@ -15,8 +16,15 @@
|
|
|
15
16
|
"files": [
|
|
16
17
|
"dist"
|
|
17
18
|
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
21
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"lint": "eslint src"
|
|
25
|
+
},
|
|
18
26
|
"dependencies": {
|
|
19
|
-
"@strand-js/core": "
|
|
27
|
+
"@strand-js/core": "workspace:*"
|
|
20
28
|
},
|
|
21
29
|
"peerDependencies": {
|
|
22
30
|
"react": "^18.0.0",
|
|
@@ -29,12 +37,5 @@
|
|
|
29
37
|
},
|
|
30
38
|
"publishConfig": {
|
|
31
39
|
"access": "public"
|
|
32
|
-
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
35
|
-
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
36
|
-
"typecheck": "tsc --noEmit",
|
|
37
|
-
"test": "vitest run",
|
|
38
|
-
"lint": "eslint src"
|
|
39
40
|
}
|
|
40
|
-
}
|
|
41
|
+
}
|