react-native-nitro-fetch 0.1.1
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/LICENSE +20 -0
- package/NitroFetch.podspec +30 -0
- package/README.md +134 -0
- package/android/CMakeLists.txt +70 -0
- package/android/build.gradle +131 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +91 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt +48 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetch.kt +94 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +256 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchPackage.kt +22 -0
- package/ios/FetchCache.swift +56 -0
- package/ios/NitroAutoPrefetcher.swift +81 -0
- package/ios/NitroBootstrap.mm +27 -0
- package/ios/NitroFetch.swift +9 -0
- package/ios/NitroFetchClient.swift +205 -0
- package/lib/module/NitroFetch.nitro.js +7 -0
- package/lib/module/NitroFetch.nitro.js.map +1 -0
- package/lib/module/NitroInstances.js +6 -0
- package/lib/module/NitroInstances.js.map +1 -0
- package/lib/module/fetch.js +441 -0
- package/lib/module/fetch.js.map +1 -0
- package/lib/module/index.js +12 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NitroFetch.nitro.d.ts +39 -0
- package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -0
- package/lib/typescript/src/NitroInstances.d.ts +3 -0
- package/lib/typescript/src/NitroInstances.d.ts.map +1 -0
- package/lib/typescript/src/fetch.d.ts +26 -0
- package/lib/typescript/src/fetch.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/nitro.json +21 -0
- package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp +96 -0
- package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp +65 -0
- package/nitrogen/generated/android/c++/JHybridNitroFetchSpec.cpp +49 -0
- package/nitrogen/generated/android/c++/JHybridNitroFetchSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JNitroHeader.hpp +57 -0
- package/nitrogen/generated/android/c++/JNitroRequest.hpp +103 -0
- package/nitrogen/generated/android/c++/JNitroRequestMethod.hpp +74 -0
- package/nitrogen/generated/android/c++/JNitroResponse.hpp +105 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchSpec.kt +52 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroHeader.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequest.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequestMethod.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroResponse.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/nitrofetchOnLoad.kt +35 -0
- package/nitrogen/generated/android/nitrofetch+autolinking.cmake +80 -0
- package/nitrogen/generated/android/nitrofetch+autolinking.gradle +27 -0
- package/nitrogen/generated/android/nitrofetchOnLoad.cpp +54 -0
- package/nitrogen/generated/android/nitrofetchOnLoad.hpp +25 -0
- package/nitrogen/generated/ios/NitroFetch+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp +73 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +298 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp +67 -0
- package/nitrogen/generated/ios/NitroFetchAutolinking.mm +41 -0
- package/nitrogen/generated/ios/NitroFetchAutolinking.swift +40 -0
- package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp +101 -0
- package/nitrogen/generated/ios/c++/HybridNitroFetchSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNitroFetchSpecSwift.hpp +75 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_NitroResponse.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift +50 -0
- package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift +149 -0
- package/nitrogen/generated/ios/swift/HybridNitroFetchSpec.swift +49 -0
- package/nitrogen/generated/ios/swift/HybridNitroFetchSpec_cxx.swift +126 -0
- package/nitrogen/generated/ios/swift/NitroHeader.swift +46 -0
- package/nitrogen/generated/ios/swift/NitroRequest.swift +199 -0
- package/nitrogen/generated/ios/swift/NitroRequestMethod.swift +60 -0
- package/nitrogen/generated/ios/swift/NitroResponse.swift +155 -0
- package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/HybridNitroFetchSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridNitroFetchSpec.hpp +64 -0
- package/nitrogen/generated/shared/c++/NitroHeader.hpp +71 -0
- package/nitrogen/generated/shared/c++/NitroRequest.hpp +101 -0
- package/nitrogen/generated/shared/c++/NitroRequestMethod.hpp +96 -0
- package/nitrogen/generated/shared/c++/NitroResponse.hpp +102 -0
- package/package.json +177 -0
- package/src/NitroFetch.nitro.ts +57 -0
- package/src/NitroInstances.ts +8 -0
- package/src/fetch.ts +426 -0
- package/src/index.tsx +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Szymon Kapala
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "NitroFetch"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "http://google.com.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
s.source_files = [
|
|
18
|
+
"ios/**/*.{swift}",
|
|
19
|
+
"ios/**/*.{m,mm}",
|
|
20
|
+
"cpp/**/*.{hpp,cpp}",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
s.dependency 'React-jsi'
|
|
24
|
+
s.dependency 'React-callinvoker'
|
|
25
|
+
|
|
26
|
+
load 'nitrogen/generated/ios/NitroFetch+autolinking.rb'
|
|
27
|
+
add_nitrogen_files(s)
|
|
28
|
+
|
|
29
|
+
install_modules_dependencies(s)
|
|
30
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/logo.png" alt="Nitro Fetch Logo" width="200" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# react-native-nitro-fetch
|
|
6
|
+
|
|
7
|
+
Nitro-powered fetch for React Native. Android uses Chromium Cronet (via `org.chromium.net:cronet-embedded`); iOS currently falls back to the built-in fetch. Includes helpers for background prefetching and off-thread parsing with worklets.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install react-native-nitro-fetch react-native-nitro-modules
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- `react-native-nitro-modules` is required as this library relies on Nitro Modules. Rebuild your app after installing.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { fetch } from 'react-native-nitro-fetch';
|
|
21
|
+
|
|
22
|
+
const res = await fetch('https://httpbin.org/get');
|
|
23
|
+
const json = await res.json();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Nitro-backed `fetch`: drop-in replacement for global fetch.
|
|
29
|
+
- Android Cronet: fast HTTP stack via `org.chromium.net:cronet-embedded` (already wired in `android/build.gradle`).
|
|
30
|
+
- Prefetch: start a background request tied to a `prefetchKey` and serve it later.
|
|
31
|
+
- Android auto-prefetch: enqueue requests to MMKV so they warm up on next app start.
|
|
32
|
+
- Worklets helper: run mapping/parsing off the JS thread with `react-native-worklets-core`.
|
|
33
|
+
|
|
34
|
+
## Why Prefetch
|
|
35
|
+
|
|
36
|
+
- Faster first paint of data: Prefetching lets you move network I/O earlier on the critical path so the screen can render with data sooner.
|
|
37
|
+
- App start wins: With auto‑prefetch + MMKV, we can begin fetching immediately at process start. In our measurements on mid‑range Android devices (e.g., Samsung A16), this starts at least ~220 ms earlier than initiating the same request from JS after React is up.
|
|
38
|
+
- UX hooks: Kick off prefetch on navigation intent (button press) and serve the result when the destination screen mounts.
|
|
39
|
+
|
|
40
|
+
See `docs/prefetch.md` for patterns and examples.
|
|
41
|
+
|
|
42
|
+
## Why Cronet
|
|
43
|
+
|
|
44
|
+
- Performance: Enables HTTP/2 multiplexing and QUIC/HTTP/3, reducing latency and avoiding head‑of‑line blocking.
|
|
45
|
+
- Efficiency: Advanced connection management, TLS/ALPN, Brotli, and robust on‑disk caching.
|
|
46
|
+
- Battle‑tested: Built on Chromium’s networking stack (the same tech behind Chrome) and widely adopted across the ecosystem, including the Flutter community.
|
|
47
|
+
|
|
48
|
+
## Philosophy
|
|
49
|
+
|
|
50
|
+
- Nitro Fetch often outperforms built‑in fetch thanks to Cronet’s optimizations, but raw speed is not the primary goal.
|
|
51
|
+
- The main goals are:
|
|
52
|
+
- High‑quality prefetching (including auto‑prefetch on app start)
|
|
53
|
+
- Enabling a multi‑threaded React Native architecture (e.g., off‑thread mapping with worklets)
|
|
54
|
+
- Performance is a nice side‑effect.
|
|
55
|
+
|
|
56
|
+
## Usage Examples
|
|
57
|
+
|
|
58
|
+
- Basic fetch (drop-in replacement):
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { fetch } from 'react-native-nitro-fetch';
|
|
62
|
+
const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
|
|
63
|
+
console.log(await res.json());
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- Prefetch and consume (Android or JS fallback):
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { fetch, prefetch } from 'react-native-nitro-fetch';
|
|
70
|
+
|
|
71
|
+
await prefetch('https://httpbin.org/uuid', { headers: { prefetchKey: 'uuid' } });
|
|
72
|
+
const res = await fetch('https://httpbin.org/uuid', { headers: { prefetchKey: 'uuid' } });
|
|
73
|
+
console.log('prefetched header:', res.headers.get('nitroPrefetched'));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- Schedule auto-prefetch on Android (requires `react-native-mmkv` in your app):
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { prefetchOnAppStart } from 'react-native-nitro-fetch';
|
|
80
|
+
await prefetchOnAppStart('https://httpbin.org/uuid', { prefetchKey: 'uuid' });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- Off-thread parsing with worklets:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { nitroFetchOnWorklet } from 'react-native-nitro-fetch';
|
|
87
|
+
|
|
88
|
+
const map = (payload: { bodyString?: string }) => {
|
|
89
|
+
'worklet';
|
|
90
|
+
return JSON.parse(payload.bodyString ?? '{}');
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const data = await nitroFetchOnWorklet('https://httpbin.org/get', undefined, map, { preferBytes: false });
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Platform Notes
|
|
97
|
+
|
|
98
|
+
- Android: Uses Cronet Java API; no extra setup needed beyond install and rebuild. Cronet engine is initialized once and enables HTTP/2, QUIC, Brotli, and disk cache.
|
|
99
|
+
- iOS: Uses a native `URLSession` client for requests and prefetch (in‑memory cache). Cronet integration is still planned for future releases.
|
|
100
|
+
|
|
101
|
+
## Limitations & Alternatives
|
|
102
|
+
|
|
103
|
+
- HTTP streaming: Not supported yet. For streaming responses today, use Expo’s `expo-fetch`. Streaming is on the roadmap.
|
|
104
|
+
- WebSockets: Not supported. For high‑performance sockets and binary streams, consider `react-native-fast-io`.
|
|
105
|
+
|
|
106
|
+
## Documentation
|
|
107
|
+
|
|
108
|
+
- Getting Started: `docs/getting-started.md`
|
|
109
|
+
- API Reference: `docs/api.md`
|
|
110
|
+
- Android Details: `docs/android.md`
|
|
111
|
+
- iOS Details: `docs/ios.md`
|
|
112
|
+
- Prefetch & Auto-Prefetch: `docs/prefetch.md`
|
|
113
|
+
- Worklets: `docs/worklets.md`
|
|
114
|
+
- Troubleshooting: `docs/troubleshooting.md`
|
|
115
|
+
- Cronet (Android) notes: `docs/cronet-android.md`
|
|
116
|
+
- Cronet (iOS) notes: `docs/cronet-ios.md`
|
|
117
|
+
|
|
118
|
+
## Work With Margelo
|
|
119
|
+
|
|
120
|
+
Need top‑notch React Native help or custom networking solutions? Reach out to Margelo: hello@margelo.com
|
|
121
|
+
|
|
122
|
+
## Contributing
|
|
123
|
+
|
|
124
|
+
- Development workflow: `CONTRIBUTING.md#development-workflow`
|
|
125
|
+
- Sending a pull request: `CONTRIBUTING.md#sending-a-pull-request`
|
|
126
|
+
- Code of conduct: `CODE_OF_CONDUCT.md`
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
project(nitrofetch)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME nitrofetch)
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Define C++ library and add all sources
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED
|
|
10
|
+
src/main/cpp/cpp-adapter.cpp
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Add Nitrogen specs :)
|
|
14
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/nitrofetch+autolinking.cmake)
|
|
15
|
+
|
|
16
|
+
# Set up local includes
|
|
17
|
+
include_directories("src/main/cpp" "../cpp")
|
|
18
|
+
|
|
19
|
+
# Allow Gradle to pass a CRONET_ROOT pointing to extracted headers/libs (see build.gradle prepareCronet task)
|
|
20
|
+
if (DEFINED CRONET_ROOT)
|
|
21
|
+
set(CRONET_ROOT_DIR ${CRONET_ROOT})
|
|
22
|
+
else()
|
|
23
|
+
set(CRONET_ROOT_DIR ${CMAKE_SOURCE_DIR}/cronet)
|
|
24
|
+
endif()
|
|
25
|
+
|
|
26
|
+
# Optional: include Cronet headers if present (placed by script under android/cronet/include)
|
|
27
|
+
if (EXISTS ${CRONET_ROOT_DIR}/include)
|
|
28
|
+
message(STATUS "Cronet headers base: ${CRONET_ROOT_DIR}/include")
|
|
29
|
+
include_directories(${CRONET_ROOT_DIR}/include)
|
|
30
|
+
# Some Cronet packages nest headers under include/cronet
|
|
31
|
+
if (EXISTS ${CRONET_ROOT_DIR}/include/cronet)
|
|
32
|
+
include_directories(${CRONET_ROOT_DIR}/include/cronet)
|
|
33
|
+
if (EXISTS ${CRONET_ROOT_DIR}/include/cronet/cronet_c.h)
|
|
34
|
+
message(STATUS "Found cronet_c.h: ${CRONET_ROOT_DIR}/include/cronet/cronet_c.h")
|
|
35
|
+
else()
|
|
36
|
+
message(WARNING "cronet_c.h not found under ${CRONET_ROOT_DIR}/include/cronet")
|
|
37
|
+
endif()
|
|
38
|
+
if (EXISTS ${CRONET_ROOT_DIR}/include/cronet/cronet.idl_c.h)
|
|
39
|
+
message(STATUS "Found cronet.idl_c.h: ${CRONET_ROOT_DIR}/include/cronet/cronet.idl_c.h")
|
|
40
|
+
else()
|
|
41
|
+
message(WARNING "cronet.idl_c.h not found under ${CRONET_ROOT_DIR}/include/cronet")
|
|
42
|
+
endif()
|
|
43
|
+
else()
|
|
44
|
+
message(WARNING "Cronet nested include dir not found: ${CRONET_ROOT_DIR}/include/cronet")
|
|
45
|
+
endif()
|
|
46
|
+
endif()
|
|
47
|
+
|
|
48
|
+
find_library(LOG_LIB log)
|
|
49
|
+
|
|
50
|
+
# Link all libraries together
|
|
51
|
+
target_link_libraries(
|
|
52
|
+
${PACKAGE_NAME}
|
|
53
|
+
${LOG_LIB}
|
|
54
|
+
android # <-- Android core
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
## Kotlin-based implementation only; no custom C++ headers forced.
|
|
58
|
+
|
|
59
|
+
# Optional: link Cronet if library file is present (drop-in via prepare script or Gradle task)
|
|
60
|
+
set(CRONET_LIB_DIR ${CRONET_ROOT_DIR}/libs/${ANDROID_ABI})
|
|
61
|
+
if (EXISTS ${CRONET_LIB_DIR})
|
|
62
|
+
file(GLOB CRONET_LIBS "${CRONET_LIB_DIR}/*cronet*.so")
|
|
63
|
+
if (CRONET_LIBS)
|
|
64
|
+
message(STATUS "Linking Cronet from ${CRONET_LIB_DIR}")
|
|
65
|
+
target_link_libraries(${PACKAGE_NAME} ${CRONET_LIBS})
|
|
66
|
+
target_compile_definitions(${PACKAGE_NAME} PRIVATE NITROFETCH_LINKS_CRONET=1)
|
|
67
|
+
else()
|
|
68
|
+
message(WARNING "Cronet libs not found in ${CRONET_LIB_DIR}")
|
|
69
|
+
endif()
|
|
70
|
+
endif()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['NitroFetch_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def reactNativeArchitectures() {
|
|
19
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
20
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
apply plugin: "com.android.library"
|
|
24
|
+
apply plugin: "kotlin-android"
|
|
25
|
+
apply from: '../nitrogen/generated/android/nitrofetch+autolinking.gradle'
|
|
26
|
+
|
|
27
|
+
// Do not apply React Gradle plugin in library to avoid RN codegen duplicating app classes
|
|
28
|
+
|
|
29
|
+
def getExtOrIntegerDefault(name) {
|
|
30
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroFetch_" + name]).toInteger()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
android {
|
|
34
|
+
namespace "com.margelo.nitro.nitrofetch"
|
|
35
|
+
|
|
36
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
37
|
+
|
|
38
|
+
defaultConfig {
|
|
39
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
40
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
41
|
+
|
|
42
|
+
externalNativeBuild {
|
|
43
|
+
cmake {
|
|
44
|
+
cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
|
|
45
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
46
|
+
abiFilters (*reactNativeArchitectures())
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
externalNativeBuild {
|
|
52
|
+
cmake {
|
|
53
|
+
path "CMakeLists.txt"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
packagingOptions {
|
|
58
|
+
excludes = [
|
|
59
|
+
"**/libc++_shared.so",
|
|
60
|
+
"**/libfbjni.so",
|
|
61
|
+
"**/libjsi.so",
|
|
62
|
+
"**/libfolly_json.so",
|
|
63
|
+
"**/libfolly_runtime.so",
|
|
64
|
+
"**/libglog.so",
|
|
65
|
+
"**/libhermes.so",
|
|
66
|
+
"**/libhermes-executor-debug.so",
|
|
67
|
+
"**/libhermes_executor.so",
|
|
68
|
+
"**/libreactnative.so",
|
|
69
|
+
"**/libreactnativejni.so",
|
|
70
|
+
"**/libturbomodulejsijni.so",
|
|
71
|
+
"**/libreact_nativemodule_core.so",
|
|
72
|
+
"**/libjscexecutor.so"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
buildFeatures {
|
|
77
|
+
buildConfig true
|
|
78
|
+
prefab true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
buildTypes {
|
|
82
|
+
release {
|
|
83
|
+
minifyEnabled false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
lintOptions {
|
|
88
|
+
disable "GradleCompatible"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
compileOptions {
|
|
92
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
93
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
sourceSets {
|
|
97
|
+
main {
|
|
98
|
+
java.srcDirs += [
|
|
99
|
+
"generated/java",
|
|
100
|
+
"generated/jni"
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
repositories {
|
|
107
|
+
mavenCentral()
|
|
108
|
+
google()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
112
|
+
// ---------- Cronet (Java API only) ----------
|
|
113
|
+
def cronetVersion = (getExtOrDefault("cronetVersion") ?: "119.6045.31")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
dependencies {
|
|
117
|
+
implementation "com.facebook.react:react-android"
|
|
118
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
119
|
+
implementation project(":react-native-nitro-modules")
|
|
120
|
+
// Provide org.chromium.net Java API for CronetEngine in Kotlin
|
|
121
|
+
// Cronet
|
|
122
|
+
api "org.chromium.net:cronet-embedded:${cronetVersion}"
|
|
123
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
configurations {
|
|
127
|
+
cronetAar
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// No automatic fetching of Cronet headers/libs; library uses Cronet Java API only.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrofetch
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import org.json.JSONArray
|
|
5
|
+
import org.json.JSONObject
|
|
6
|
+
import java.util.concurrent.CompletableFuture
|
|
7
|
+
|
|
8
|
+
object AutoPrefetcher {
|
|
9
|
+
@Volatile private var initialized = false
|
|
10
|
+
|
|
11
|
+
fun prefetchOnStart(app: Application) {
|
|
12
|
+
if (initialized) return
|
|
13
|
+
initialized = true
|
|
14
|
+
try {
|
|
15
|
+
val mmkv = getMMKV(app) ?: return
|
|
16
|
+
val raw = invokeMMKVDecodeString(mmkv, KEY_QUEUE) ?: return
|
|
17
|
+
if (raw.isEmpty()) return
|
|
18
|
+
val arr = JSONArray(raw)
|
|
19
|
+
for (i in 0 until arr.length()) {
|
|
20
|
+
val o = arr.optJSONObject(i) ?: continue
|
|
21
|
+
val url = o.optString("url", null) ?: continue
|
|
22
|
+
val prefetchKey = o.optString("prefetchKey", null) ?: continue
|
|
23
|
+
val headersObj = o.optJSONObject("headers") ?: JSONObject()
|
|
24
|
+
val headersList = mutableListOf<Pair<String, String>>()
|
|
25
|
+
headersObj.keys().forEachRemaining { k ->
|
|
26
|
+
headersList.add(k to headersObj.optString(k, ""))
|
|
27
|
+
}
|
|
28
|
+
// Ensure prefetchKey header is present
|
|
29
|
+
headersList.add("prefetchKey" to prefetchKey)
|
|
30
|
+
|
|
31
|
+
val headerObjs = headersList.map { (k, v) -> NitroHeader(k, v) }.toTypedArray()
|
|
32
|
+
val req = NitroRequest(
|
|
33
|
+
url = url,
|
|
34
|
+
method = null,
|
|
35
|
+
headers = headerObjs,
|
|
36
|
+
bodyString = null,
|
|
37
|
+
bodyBytes = null,
|
|
38
|
+
timeoutMs = null,
|
|
39
|
+
followRedirects = null
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// If already pending or fresh, skip starting a new one
|
|
43
|
+
if (FetchCache.getPending(prefetchKey) != null) {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
if (FetchCache.getResultIfFresh(prefetchKey, 5_000L) != null) {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
val future = CompletableFuture<NitroResponse>()
|
|
51
|
+
FetchCache.setPending(prefetchKey, future)
|
|
52
|
+
NitroFetchClient.fetch(req,
|
|
53
|
+
onSuccess = { res ->
|
|
54
|
+
try {
|
|
55
|
+
FetchCache.complete(prefetchKey, res)
|
|
56
|
+
future.complete(res)
|
|
57
|
+
} catch (t: Throwable) {
|
|
58
|
+
FetchCache.completeExceptionally(prefetchKey, t)
|
|
59
|
+
future.completeExceptionally(t)
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
onFail = { err ->
|
|
63
|
+
FetchCache.completeExceptionally(prefetchKey, err)
|
|
64
|
+
future.completeExceptionally(err)
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
} catch (_: Throwable) {
|
|
69
|
+
// ignore – prefetch-on-start is best-effort
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private const val KEY_QUEUE = "nitrofetch_autoprefetch_queue"
|
|
74
|
+
|
|
75
|
+
private fun getMMKV(app: Application): Any? {
|
|
76
|
+
return try {
|
|
77
|
+
val cls = Class.forName("com.tencent.mmkv.MMKV")
|
|
78
|
+
// Initialize if available
|
|
79
|
+
try { cls.getMethod("initialize", Application::class.java).invoke(null, app) } catch (_: Throwable) {}
|
|
80
|
+
// Get default instance
|
|
81
|
+
cls.getMethod("defaultMMKV").invoke(null)
|
|
82
|
+
} catch (_: Throwable) { null }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private fun invokeMMKVDecodeString(mmkv: Any, key: String): String? {
|
|
86
|
+
return try {
|
|
87
|
+
val m = mmkv.javaClass.getMethod("decodeString", String::class.java, String::class.java)
|
|
88
|
+
m.invoke(mmkv, key, null) as? String
|
|
89
|
+
} catch (_: Throwable) { null }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrofetch
|
|
2
|
+
|
|
3
|
+
import java.util.concurrent.CompletableFuture
|
|
4
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
5
|
+
|
|
6
|
+
data class CachedEntry(val response: NitroResponse, val timestampMs: Long)
|
|
7
|
+
|
|
8
|
+
object FetchCache {
|
|
9
|
+
private val pending = ConcurrentHashMap<String, CompletableFuture<NitroResponse>>()
|
|
10
|
+
private val results = ConcurrentHashMap<String, CachedEntry>()
|
|
11
|
+
|
|
12
|
+
fun getPending(key: String): CompletableFuture<NitroResponse>? = pending[key]
|
|
13
|
+
|
|
14
|
+
fun setPending(key: String, future: CompletableFuture<NitroResponse>) {
|
|
15
|
+
pending[key] = future
|
|
16
|
+
// Cleanup: remove pending entry when completed
|
|
17
|
+
future.whenComplete { _, _ ->
|
|
18
|
+
pending.remove(key)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fun complete(key: String, value: NitroResponse) {
|
|
23
|
+
results[key] = CachedEntry(value, System.currentTimeMillis())
|
|
24
|
+
pending[key]?.complete(value)
|
|
25
|
+
pending.remove(key)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fun completeExceptionally(key: String, t: Throwable) {
|
|
29
|
+
pending[key]?.completeExceptionally(t)
|
|
30
|
+
pending.remove(key)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fun getResult(key: String): NitroResponse? {
|
|
34
|
+
val entry = results.remove(key) ?: return null
|
|
35
|
+
return entry.response
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fun getResultIfFresh(key: String, maxAgeMs: Long): NitroResponse? {
|
|
39
|
+
val entry = results.remove(key) ?: return null
|
|
40
|
+
val age = System.currentTimeMillis() - entry.timestampMs
|
|
41
|
+
return if (age <= maxAgeMs) entry.response else null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fun clear() {
|
|
45
|
+
pending.clear()
|
|
46
|
+
results.clear()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrofetch
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
6
|
+
import org.chromium.net.CronetEngine
|
|
7
|
+
import org.chromium.net.CronetProvider
|
|
8
|
+
import java.io.File
|
|
9
|
+
import java.util.concurrent.Executor
|
|
10
|
+
import java.util.concurrent.Executors
|
|
11
|
+
|
|
12
|
+
@DoNotStrip
|
|
13
|
+
class NitroFetch : HybridNitroFetchSpec() {
|
|
14
|
+
// Generated base may expect env-less createClient.
|
|
15
|
+
override fun createClient(): NitroFetchClient {
|
|
16
|
+
return NitroFetchClient(getEngine(), ioExecutor)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
companion object {
|
|
20
|
+
@Volatile private var engineRef: CronetEngine? = null
|
|
21
|
+
|
|
22
|
+
// Simpler & safer for callbacks than a pool (avoid reentrancy races in glue code).
|
|
23
|
+
val ioExecutor: Executor by lazy {
|
|
24
|
+
val cores = Runtime.getRuntime().availableProcessors().coerceAtLeast(2)
|
|
25
|
+
Executors.newFixedThreadPool(cores) { r ->
|
|
26
|
+
Thread(r, "NitroCronet-io").apply {
|
|
27
|
+
isDaemon = true
|
|
28
|
+
priority = Thread.NORM_PRIORITY
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fun getEngine(): CronetEngine {
|
|
34
|
+
engineRef?.let { return it }
|
|
35
|
+
synchronized(this) {
|
|
36
|
+
engineRef?.let { return it }
|
|
37
|
+
|
|
38
|
+
val app = currentApplication() ?: initialApplication()
|
|
39
|
+
?: throw IllegalStateException("NitroFetch: Application not available")
|
|
40
|
+
|
|
41
|
+
// Log available providers and prefer the Native one (avoids Play-Services DNS quirks)
|
|
42
|
+
val providers = CronetProvider.getAllProviders(app)
|
|
43
|
+
providers.forEach { Log.i("NitroFetch", "Cronet provider: ${it.name} v=${it.version}") }
|
|
44
|
+
val nativeProvider = providers.firstOrNull { it.name.contains("Native", ignoreCase = true) }
|
|
45
|
+
|
|
46
|
+
val cacheDir = File(app.cacheDir, "nitrofetch_cronet_cache").apply { mkdirs() }
|
|
47
|
+
val builder = (nativeProvider?.createBuilder() ?: CronetEngine.Builder(app))
|
|
48
|
+
.enableHttp2(true)
|
|
49
|
+
.enableQuic(true)
|
|
50
|
+
.enableBrotli(true)
|
|
51
|
+
.setStoragePath(cacheDir.absolutePath)
|
|
52
|
+
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, 50 * 1024 * 1024)
|
|
53
|
+
.setUserAgent("NitroFetch/0.1")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// --- Optional debugging knobs (uncomment temporarily) ---
|
|
57
|
+
// Enable NetLog-like tracing in NetworkService:
|
|
58
|
+
// builder.setExperimentalOptions("""{"NetworkService":{"enable_network_logging":true}}""")
|
|
59
|
+
//
|
|
60
|
+
// Prove DNS issues by mapping a host (TESTING ONLY, remove in prod):
|
|
61
|
+
// builder.setExperimentalOptions("""{"HostResolverRules":{"host_resolver_rules":"MAP httpbin.org 54.167.17.38"}}""")
|
|
62
|
+
|
|
63
|
+
val engine = builder.build()
|
|
64
|
+
Log.i("NitroFetch", "CronetEngine initialized. Provider=${nativeProvider?.name ?: "Default"} Cache=${cacheDir.absolutePath}")
|
|
65
|
+
engineRef = engine
|
|
66
|
+
return engine
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun shutdown() {
|
|
71
|
+
synchronized(this) {
|
|
72
|
+
try {
|
|
73
|
+
engineRef?.shutdown()
|
|
74
|
+
} catch (_: Throwable) {
|
|
75
|
+
// ignore – shutdown is best-effort
|
|
76
|
+
} finally {
|
|
77
|
+
engineRef = null
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private fun currentApplication(): Application? = try {
|
|
83
|
+
val cls = Class.forName("android.app.ActivityThread")
|
|
84
|
+
val m = cls.getMethod("currentApplication")
|
|
85
|
+
m.invoke(null) as? Application
|
|
86
|
+
} catch (_: Throwable) { null }
|
|
87
|
+
|
|
88
|
+
private fun initialApplication(): Application? = try {
|
|
89
|
+
val cls = Class.forName("android.app.AppGlobals")
|
|
90
|
+
val m = cls.getMethod("getInitialApplication")
|
|
91
|
+
m.invoke(null) as? Application
|
|
92
|
+
} catch (_: Throwable) { null }
|
|
93
|
+
}
|
|
94
|
+
}
|