react-native-community / fetch Goto Github PK
View Code? Open in Web Editor NEWThis project forked from jakechampion/fetch
A fetch API polyfill for React Native with text streaming support.
License: MIT License
This project forked from jakechampion/fetch
A fetch API polyfill for React Native with text streaming support.
License: MIT License
I am trying to read() a text/event-stream
. The backend already works in curl and in a Next.js app.
value
returned from reader.read()
is a number
instead of a Uint8Array
, which means the Typescript types are broken.
const fetchMessage = async() => await fetch("http://192.168.1.27:3000/api/chat", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Content-Type": "text/plain;charset=UTF-8",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
},
"body": "{}",
"method": "POST",
"mode": "cors"
});
useEffect(() => {
function createChunkDecoder() {
const decoder = new TextDecoder();
return function(chunk: Uint8Array) {
console.log(typeof chunk)
if (!chunk)
return "";
return decoder.decode(chunk, { stream: true });
};
}
fetchMessage()
.then(response => response.body)
.then(async (stream) => {
if(!stream){
return
}
const reader = stream.getReader();
const decode = createChunkDecoder();
let streamedResponse = ''
while(true) {
const { done, value } = await reader.read();
if (done) {
break;
}
streamedResponse += decode(value);
console.log(streamedResponse)
}
})
},[])
Is there support for Expo in this module?
After installing this library and using react-native-polyfill-globals to polyfill, I'm unable to successfully fetch any data because every request results in Invalid responseType: blob
. I encountered this both on an existing project (which uses react-native v0.72.7
) and on a completely new/clean app that I spun up to test (again using react-native v0.72.7
).
If I add the following to the root index.js
file, it works but I'm not sure why this fixes it (this is some code recommended to get network logs working in the now deprecated React Native Debugger) or what the implications would be:
global.XMLHttpRequest = global.originalXMLHttpRequest || global.XMLHttpRequest;
When performing a "DEL" request to a server which responds with an HTTP "204 No Content" response, I get the following error:
Cannot read property 'blobId' of undefined
This is a JSON dump of the symbolicated stack:
[
{
file: 'my-project-root/node_modules/react-native/Libraries/Blob/BlobManager.js',
methodName: 'createFromOptions',
arguments: [],
lineNumber: 113,
column: 34,
collapse: false
},
{
file: 'my-project-root/node_modules/react-native-fetch-api/src/BlobResponse.js',
methodName: 'constructor',
arguments: [],
lineNumber: 6,
column: 51,
collapse: false
},
{
file: 'my-project-root/node_modules/react-native-fetch-api/src/Fetch.js',
methodName: '__didCompleteNetworkResponse',
arguments: [],
lineNumber: 243,
column: 47,
collapse: false
},
{
file: null,
methodName: 'next',
arguments: [Array],
lineNumber: null,
column: null,
collapse: false
},
{
file: 'my-project-root/node_modules/@babel/runtime/helpers/asyncToGenerator.js',
methodName: 'asyncGeneratorStep',
arguments: [],
lineNumber: 3,
column: 24,
collapse: false
},
{
file: 'my-project-root/node_modules/@babel/runtime/helpers/asyncToGenerator.js',
methodName: '_next',
arguments: [],
lineNumber: 22,
column: 27,
collapse: false
},
{
file: 'my-project-root/node_modules/@babel/runtime/helpers/asyncToGenerator.js',
methodName: 'Promise$argument_0',
arguments: [],
lineNumber: 27,
column: 12,
collapse: false
},
{
file: '/root/react-native/ReactAndroid/hermes-engine/.cxx/Release/5e6w3h5p/arm64-v8a/lib/InternalBytecode/InternalBytecode.js',
methodName: 'tryCallTwo',
arguments: [],
lineNumber: 61,
column: 9,
collapse: false
},
{
file: '/root/react-native/ReactAndroid/hermes-engine/.cxx/Release/5e6w3h5p/arm64-v8a/lib/InternalBytecode/InternalBytecode.js',
methodName: 'doResolve',
arguments: [],
lineNumber: 216,
column: 25,
collapse: false
},
{
file: '/root/react-native/ReactAndroid/hermes-engine/.cxx/Release/5e6w3h5p/arm64-v8a/lib/InternalBytecode/InternalBytecode.js',
methodName: 'Promise',
arguments: [],
lineNumber: 82,
column: 14,
collapse: false
},
{
file: 'my-project-root/node_modules/@babel/runtime/helpers/asyncToGenerator.js',
methodName: 'Promise$argument_0',
arguments: [],
lineNumber: 19,
column: 23,
collapse: false
},
{
file: null,
methodName: 'apply',
arguments: [Array],
lineNumber: null,
column: null,
collapse: false
},
{
file: 'my-project-root/node_modules/react-native-fetch-api/src/Fetch.js',
methodName: 'Fetch',
arguments: [],
lineNumber: 254,
column: 5,
collapse: false
},
{
file: null,
methodName: 'apply',
arguments: [Array],
lineNumber: null,
column: null,
collapse: false
},
{
file: 'my-project-root/node_modules/react-native-fetch-api/src/Fetch.js',
methodName: 'Networking.addListener$argument_1',
arguments: [],
lineNumber: 88,
column: 16,
collapse: false
},
{
file: null,
methodName: 'apply',
arguments: [Array],
lineNumber: null,
column: null,
collapse: false
},
{
file: 'my-project-root/node_modules/react-native/Libraries/vendor/emitter/EventEmitter.js',
methodName: 'emit',
arguments: [],
lineNumber: 105,
column: 36,
collapse: true
},
{
file: null,
methodName: 'apply',
arguments: [Array],
lineNumber: null,
column: null,
collapse: false
},
{
file: 'my-project-root/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js',
methodName: '__callFunction',
arguments: [],
lineNumber: 427,
column: 32,
collapse: true
},
{
file: 'my-project-root/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js',
methodName: '__guard$argument_0',
arguments: [],
lineNumber: 113,
column: 26,
collapse: true
},
{
file: 'my-project-root/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js',
methodName: '__guard',
arguments: [],
lineNumber: 368,
column: 10,
collapse: true
},
{
file: 'my-project-root/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js',
methodName: '__guard$argument_0',
arguments: [],
lineNumber: 112,
column: 17,
collapse: true
}
]
I removed the comments from some methods in Fetch.js
to reveal this:
LOG fetch __didCreateRequest {"requestId": 10}
LOG fetch __didReceiveNetworkResponse {"headers": {"Content-Encoding": "gzip", "Date": "Wed, 26 Apr 2023 21:54:56 GMT", "Strict-Transport-Security": "max-age=31536000; includeSubdomains;", "Vary": "Origin", "X-Appservices-Request-Id": "64499db00f4b7d44b52b7ad5", "X-Frame-Options": "DENY"}, "requestId": 10, "status": 204, "url": "http://10.0.2.2:9090/api/admin/v3.0/groups/643fd5800f4b7d44b53802af/apps/64499dae0f4b7d44b52b79d2"}
LOG fetch __didCompleteNetworkResponse {"didTimeOut": undefined, "errorMessage": null, "requestId": 10}
This shows that contrary to other responses the __didReceiveNetworkData
method is never called, which is why this._nativeResponse
remains undefined
, hence the error.
I found that I can work around the issue by passing the extra reactNative: { textStreaming: true }
options to the fetch function:
await fetch(url, {
method: "DELETE",
reactNative: { textStreaming: true },
});
Hello, I have issue with fetch, it's working extremally slow ~10 seconds for fetch and stream creation :(
I use React Native + Android Studio (virtual device)
UPD: on the real phone it is works fine
Moving this issue JakeChampion#746 here
@cpojer I'm reaching out to follow up on a previous discussion about coming up with a custom Fetch API implementation for React Native at https://github.com/react-native-community/fetch with support for streaming (facebook/react-native#27741). We, MOXY and Protocol Labs, are planning to start this effort shortly and we'd like to know your thoughts on the matter.
In short, what we currently have in mind is as follows:
ReadableStream
is available in RN's environment which can be polyfilled with web-streams-polyfill. If ReadableStream
is expected to only be used in the context of Fetch, we can probably bundle them together.Even if Facebook has no plans to add support for streaming to Fetch for the time being, app developers can easily swap out whatwg-fetch
for @react-native-community/fetch
manually. Even better would be React Native to do this automatically via some sort of extension or plugin API.
Are there any guidelines which React Native Community packages should conform to? e.g. linter and linting config, CI process config, etc.
I'm working on integrating this polyfill to enable text streaming in my app and have encountered a problem that goes like this:
fetch('...').then(response => {
console.log(response.headers); // map object
const clone = response.clone();
console.log(clone.headers); // undefined, and headers isn't optional
clone.then(cloneResponse => {
console.log(cloneResponse.headers); // map object
})
})
It seems like StreamBlobResponse constructor should not return a promise https://github.com/react-native-community/fetch/blob/master/src/StreamBlobResponse.js#L11-L22
I experimented with removing the returns and that does seem to fix it. The implementation treats the respective class properties as optional so everything continues to work. Was this an oversight or is there a deeper reason that it's implemented like this which breaks the Response object?
As previously pointed out in #13 (comment) , ReactNativeFlipper breaks streaming completely.
With Expo SDK50, the nature of these files have changed:
React Native 0.73 changed from Java to Kotlin for Android Main* classes: MainApplication.java/MainActivity.java are now MainApplication.kt/MainActivity.kt. If you depend on any config plugins that use dangerous modifications to change these files, they may need to be updated for SDK 50 support.
In android/app/src/main/java/com/[name]/[app]/MainApplication.kt
, here is the ReactNativeFlipper initialization:
if (BuildConfig.DEBUG) {
ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager)
}
Simply comment it out and streaming will work once more.
if (BuildConfig.DEBUG) {
// hacky fix for: https://github.com/react-native-community/fetch
// ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager)
}
Though the issue is fixed on my end, I am leaving this here for the few people who come across this problem.
I believe React Native's environment does not provide DOMException
, so we need to use a polyfill or develop our own. Check domexpection. If that's the case, for consistency's sake, the polyfill should be delivered via react-native-polyfill-globals.
Check GitHub's implementation for inspiration.
DOMException
should be thrown when:
See: https://developers.google.com/web/updates/2017/09/abortable-fetch
It should be possible to rewrite the code to not rely on both TextEncoder and TextDecoder, just like GitHub as done:
By removing the dependency on both APIs, the app's bundle size can be further reduced. Considering that React Native's environment does not provide these APIs, there would be no need to polyfill them anymore. However, we should, by default, check whether TextEncoder
and TextDecoder
are available and only then fallback.
I'll circle back with a reproduction shortly.
Hey,
I cannot get the reader from the readableStream I get following Error:
Error: [TypeError: This stream has already been locked for exclusive reading by another reader]
I also have added the auto import for the polyfills to the root _layout.tsx as I use filebased routing.
import 'react-native-polyfill-globals/auto';
const res = await fetch("https://www.lotti.ai/api/lotti", {
method: "POST",
headers: {
"Content-Type": "application/json",
Connection: "keep-alive",
},
mode: "cors",
body: JSON.stringify(message),
"reactNative": { textStreaming: true }
})
const reader = res.body?.getReader();
I should also mention that the type for reactNative: {textStreaming: true} seems to not be detected correctly:
Hello Team,
Looks like response.clone()
does not work as expected when trying to re-read the response.
const myRequest = new Request('https://restcountries.com/v3.1/region/europe');
fetch(myRequest)
.then((response) => {
const response2 = response.clone();
response.json().then((myRes) => {
console.log('Response', myRes);
});
response2
.json()
.then((myRes) => {
console.log('Response2 --->', myRes);
})
.catch((e) => {
console.log('@@@@@@');
console.log(e);
console.log('@@@@@@');
});
});
@@@@@@
[TypeError: This stream has already been locked for exclusive reading by another reader]
@@@@@@```
First of all, thank you so much for providing this repo ❤️ . We use streams for our subscribe requests and I was able to integrate with your project in order to get streaming working on iOS. However, I'm still running into an issue where Android is behaving as it did before the web stream polyfill. I can see the subscribe request come through, but it just hangs and no data ever comes through.
I have a working demo here where the stream is fully functional on iOS but not on Android: xmtp/example-chat-react-native#8.
To reproduce, you can run the app & press Generate address
. Then press Send gm
. That should trigger a message to come through the stream. If you have a chance to take a look, I'm so curious what could be causing Android specifically to have trouble here.
JS env: Hermes
RN version: 0.71
All polyfills should be included as well in .polyfills.js
Im using this package & populating polyfills like this:
import 'react-native-polyfill-globals/auto';
my fetch requests (both streaming & non-streaming) work in iOS. It does not work in Android, but I know that is a known problem for development builds.
Im testing it in a production build, and android fails for regular non-streaming HTTP requests. The request is received by my backend which returns a 200. But there is no response or error produced by my app.
"dependencies": {
"@joaosousa/react-native-progress-steps": "^0.4.0",
"@react-native-async-storage/async-storage": "1.18.2",
"@react-native-community/slider": "4.4.2",
"@react-native-google-signin/google-signin": "^10.1.1",
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"@react-navigation/stack": "^6.3.20",
"expo": "~49.0.15",
"expo-constants": "^14.4.2",
"expo-dev-client": "~2.4.12",
"expo-splash-screen": "~0.20.5",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-native": "0.72.6",
"react-native-fetch-api": "^3.0.0",
"react-native-gesture-handler": "~2.12.0",
"react-native-markdown-display": "^7.0.1",
"react-native-paper": "^5.11.3",
"react-native-purchases": "^7.4.0",
"react-native-safe-area-context": "^4.6.3",
"react-native-screens": "~3.22.0",
"react-native-vector-icons": "^10.0.2",
"react-redux": "^9.0.2",
"redux": "^5.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "~18.2.14",
"@types/react-native-vector-icons": "^6.4.18",
"react-native-polyfill-globals": "^3.1.0",
"typescript": "^5.1.3"
},
"private": true
}
Investigate why the iOS job is now failing with the following error:
Invalid runtime: com.apple.CoreSimulator.SimRuntime.iOS-14-2
The runtime in question was available before, so GitHub might have changed something in the latest macOS virtual environment.
See: https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11.0-Readme.md
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.