├── .nvmrc ├── example ├── .watchmanconfig ├── jest.config.js ├── .bundle │ └── config ├── app.json ├── android │ ├── app │ │ ├── debug.keystore │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── values │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ └── drawable │ │ │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── nitrofetch │ │ │ │ │ └── example │ │ │ │ │ └── MainApplication.kt │ │ │ └── debug │ │ │ │ └── AndroidManifest.xml │ │ └── proguard-rules.pro │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── build.gradle │ ├── gradle.properties │ └── gradlew.bat ├── ios │ ├── NitroFetchExample │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── PrivacyInfo.xcprivacy │ │ ├── AppDelegate.swift │ │ ├── Info.plist │ │ └── LaunchScreen.storyboard │ ├── NitroFetchExample.xcworkspace │ │ └── contents.xcworkspacedata │ ├── .xcode.env │ ├── Podfile │ └── NitroFetchExample.xcodeproj │ │ └── xcshareddata │ │ └── xcschemes │ │ └── NitroFetchExample.xcscheme ├── tsconfig.json ├── index.js ├── react-native.config.js ├── babel.config.js ├── Gemfile ├── metro.config.js ├── package.json ├── Gemfile.lock └── README.md ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml └── actions │ └── setup │ └── action.yml ├── package ├── src │ ├── __tests__ │ │ └── index.test.tsx │ ├── NitroInstances.ts │ ├── index.tsx │ └── NitroFetch.nitro.ts ├── nitrogen │ └── generated │ │ ├── .gitattributes │ │ ├── ios │ │ ├── c++ │ │ │ ├── HybridNitroFetchSpecSwift.cpp │ │ │ ├── HybridNativeStorageSpecSwift.cpp │ │ │ ├── HybridNitroFetchClientSpecSwift.cpp │ │ │ ├── HybridNitroFetchSpecSwift.hpp │ │ │ ├── HybridNativeStorageSpecSwift.hpp │ │ │ └── HybridNitroFetchClientSpecSwift.hpp │ │ ├── swift │ │ │ ├── NitroHeader.swift │ │ │ ├── Func_void.swift │ │ │ ├── NitroRequestMethod.swift │ │ │ ├── Func_void_NitroResponse.swift │ │ │ ├── HybridNitroFetchSpec.swift │ │ │ ├── Func_void_std__exception_ptr.swift │ │ │ ├── HybridNativeStorageSpec.swift │ │ │ └── HybridNitroFetchClientSpec.swift │ │ ├── NitroFetchAutolinking.mm │ │ ├── NitroFetch+autolinking.rb │ │ ├── NitroFetchAutolinking.swift │ │ └── NitroFetch-Swift-Cxx-Umbrella.hpp │ │ ├── android │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── margelo │ │ │ │ └── nitro │ │ │ │ └── nitrofetch │ │ │ │ ├── NitroRequestMethod.kt │ │ │ │ ├── NitroHeader.kt │ │ │ │ ├── NitroRequest.kt │ │ │ │ ├── NitroResponse.kt │ │ │ │ ├── nitrofetchOnLoad.kt │ │ │ │ ├── HybridNitroFetchSpec.kt │ │ │ │ ├── HybridNativeStorageSpec.kt │ │ │ │ └── HybridNitroFetchClientSpec.kt │ │ ├── nitrofetch+autolinking.gradle │ │ ├── nitrofetchOnLoad.hpp │ │ ├── c++ │ │ │ ├── JHybridNitroFetchSpec.cpp │ │ │ ├── JNitroHeader.hpp │ │ │ ├── JHybridNitroFetchSpec.hpp │ │ │ ├── JHybridNativeStorageSpec.cpp │ │ │ ├── JHybridNativeStorageSpec.hpp │ │ │ ├── JHybridNitroFetchClientSpec.hpp │ │ │ ├── JNitroRequestMethod.hpp │ │ │ ├── JHybridNitroFetchClientSpec.cpp │ │ │ └── JNitroResponse.hpp │ │ ├── nitrofetchOnLoad.cpp │ │ └── nitrofetch+autolinking.cmake │ │ └── shared │ │ └── c++ │ │ ├── HybridNitroFetchSpec.cpp │ │ ├── HybridNativeStorageSpec.cpp │ │ ├── HybridNitroFetchClientSpec.cpp │ │ ├── HybridNativeStorageSpec.hpp │ │ ├── HybridNitroFetchSpec.hpp │ │ ├── HybridNitroFetchClientSpec.hpp │ │ ├── NitroHeader.hpp │ │ └── NitroRequestMethod.hpp ├── tsconfig.build.json ├── android │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── cpp │ │ │ └── cpp-adapter.cpp │ │ │ └── java │ │ │ └── com │ │ │ └── margelo │ │ │ └── nitro │ │ │ └── nitrofetch │ │ │ ├── NitroFetchPackage.kt │ │ │ ├── FetchCache.kt │ │ │ ├── AutoPrefetcher.kt │ │ │ ├── NitroFetch.kt │ │ │ └── NativeStorage.kt │ ├── gradle.properties │ ├── CMakeLists.txt │ └── build.gradle ├── ios │ ├── NitroFetch.swift │ ├── NitroBootstrap.mm │ ├── FetchCache.swift │ ├── NativeStorage.swift │ └── NitroAutoPrefetcher.swift ├── babel.config.js ├── nitro.json ├── NitroFetch.podspec ├── tsconfig.json └── package.json ├── .gitattributes ├── assets ├── logo.png ├── banner-dark.png └── banner-light.png ├── .editorconfig ├── .yarnrc.yml ├── lefthook.yml ├── docs ├── getting-started.md ├── troubleshooting.md ├── worklets.md ├── ios.md ├── cronet-ios.md ├── android.md ├── api.md ├── cronet-android.md └── prefetch.md ├── turbo.json ├── LICENSE ├── eslint.config.mjs ├── .gitignore ├── package.json └── scripts ├── prepare_cronet_android_maven.sh └── prepare_cronet_android.sh /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.19.0 2 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /package/src/__tests__/index.test.tsx: -------------------------------------------------------------------------------- 1 | it.todo('write a test'); 2 | -------------------------------------------------------------------------------- /package/nitrogen/generated/.gitattributes: -------------------------------------------------------------------------------- 1 | ** linguist-generated=true 2 | -------------------------------------------------------------------------------- /example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/assets/logo.png -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NitroFetchExample", 3 | "displayName": "NitroFetchExample" 4 | } 5 | -------------------------------------------------------------------------------- /package/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "exclude": ["example", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /assets/banner-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/assets/banner-dark.png -------------------------------------------------------------------------------- /assets/banner-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/assets/banner-light.png -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/debug.keystore -------------------------------------------------------------------------------- /package/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NitroFetchExample 3 | 4 | -------------------------------------------------------------------------------- /example/ios/NitroFetchExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@react-native/typescript-config", 3 | "include": ["**/*.ts", "**/*.tsx"], 4 | "exclude": ["**/node_modules", "**/Pods"] 5 | } 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src/App'; 3 | import { name as appName } from './app.json'; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /package/android/gradle.properties: -------------------------------------------------------------------------------- 1 | NitroFetch_kotlinVersion=2.0.21 2 | NitroFetch_minSdkVersion=24 3 | NitroFetch_targetSdkVersion=34 4 | NitroFetch_compileSdkVersion=35 5 | NitroFetch_ndkVersion=27.1.12297006 6 | -------------------------------------------------------------------------------- /package/android/src/main/cpp/cpp-adapter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nitrofetchOnLoad.hpp" 3 | 4 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { 5 | return margelo::nitro::nitrofetch::initialize(vm); 6 | } 7 | -------------------------------------------------------------------------------- /example/react-native.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | project: { 5 | ios: { 6 | automaticPodsInstallation: true, 7 | }, 8 | }, 9 | dependencies: {}, 10 | }; 11 | -------------------------------------------------------------------------------- /package/ios/NitroFetch.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class NitroFetch: HybridNitroFetchSpec { 4 | func createClient() throws -> (any HybridNitroFetchClientSpec) { 5 | return NitroFetchClient() 6 | } 7 | 8 | } 9 | 10 | -------------------------------------------------------------------------------- /package/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | overrides: [ 3 | { 4 | exclude: /\/node_modules\//, 5 | presets: ['module:react-native-builder-bob/babel-preset'], 6 | plugins: ['react-native-worklets-core/plugin'], 7 | }, 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/ios/NitroFetchExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | nmHoistingLimits: workspaces 3 | 4 | plugins: 5 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 6 | spec: "@yarnpkg/plugin-interactive-tools" 7 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 8 | spec: "@yarnpkg/plugin-workspace-tools" 9 | 10 | yarnPath: .yarn/releases/yarn-3.6.1.cjs 11 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'nitrofetch.example' 5 | include ':app' 6 | includeBuild('../../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | lint: 5 | glob: "*.{js,ts,jsx,tsx}" 6 | run: npx eslint --no-warn-ignored {staged_files} 7 | types: 8 | glob: "*.{js,ts, jsx, tsx}" 9 | run: npx tsc --noEmit --project package/tsconfig.json 10 | commit-msg: 11 | parallel: true 12 | commands: 13 | commitlint: 14 | run: npx commitlint --edit 15 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/c++/HybridNitroFetchSpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchSpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridNitroFetchSpecSwift.hpp" 9 | 10 | namespace margelo::nitro::nitrofetch { 11 | } // namespace margelo::nitro::nitrofetch 12 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNativeStorageSpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridNativeStorageSpecSwift.hpp" 9 | 10 | namespace margelo::nitro::nitrofetch { 11 | } // namespace margelo::nitro::nitrofetch 12 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchClientSpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridNitroFetchClientSpecSwift.hpp" 9 | 10 | namespace margelo::nitro::nitrofetch { 11 | } // namespace margelo::nitro::nitrofetch 12 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { getConfig } = require('react-native-builder-bob/babel-config'); 3 | const pkg = require('../package/package.json'); 4 | 5 | const root = path.resolve(__dirname, '..', 'package'); 6 | 7 | module.exports = getConfig( 8 | { 9 | presets: ['module:@react-native/babel-preset'], 10 | plugins: ["react-native-worklets-core/plugin"], 11 | }, 12 | { root, pkg } 13 | ); 14 | -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /package/src/NitroInstances.ts: -------------------------------------------------------------------------------- 1 | import { NitroModules } from 'react-native-nitro-modules'; 2 | import type { 3 | NitroFetch as NitroFetchType, 4 | NativeStorage as NativeStorageType, 5 | } from './NitroFetch.nitro'; 6 | 7 | // Create singletons once per JS runtime 8 | export const NitroFetch: NitroFetchType = 9 | NitroModules.createHybridObject('NitroFetch'); 10 | 11 | export const NativeStorage: NativeStorageType = 12 | NitroModules.createHybridObject('NativeStorage'); 13 | 14 | export const boxedNitroFetch = NitroModules.box(NitroFetch); 15 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | gem 'concurrent-ruby', '< 1.3.4' 11 | 12 | # Ruby 3.4.0 has removed some libraries from the standard library. 13 | gem 'bigdecimal' 14 | gem 'logger' 15 | gem 'benchmark' 16 | gem 'mutex_m' 17 | -------------------------------------------------------------------------------- /package/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { 2 | nitroFetch as fetch, 3 | nitroFetchOnWorklet, 4 | prefetch, 5 | prefetchOnAppStart, 6 | removeFromAutoPrefetch, 7 | removeAllFromAutoprefetch, 8 | } from './fetch'; 9 | export type { NitroRequest, NitroResponse } from './fetch'; 10 | export { NitroFetch } from './NitroInstances'; 11 | import './fetch'; 12 | 13 | // Keep legacy export to avoid breaking any local tests/usages during scaffolding. 14 | // Will be removed once native Cronet path is ready. 15 | export function multiply(a: number, b: number): number { 16 | return a * b; 17 | } 18 | -------------------------------------------------------------------------------- /package/nitro.json: -------------------------------------------------------------------------------- 1 | { 2 | "cxxNamespace": ["nitrofetch"], 3 | "ios": { 4 | "iosModuleName": "NitroFetch" 5 | }, 6 | "android": { 7 | "androidNamespace": ["nitrofetch"], 8 | "androidCxxLibName": "nitrofetch" 9 | }, 10 | "autolinking": { 11 | "NitroFetch": { 12 | "kotlin": "NitroFetch", 13 | "swift": "NitroFetch" 14 | }, 15 | "NitroFetchClient": { 16 | "kotlin": "NitroFetchClient", 17 | "swift": "NitroFetchClient" 18 | }, 19 | "NativeStorage": { 20 | "kotlin": "NativeStorage", 21 | "swift": "NativeStorage" 22 | } 23 | }, 24 | "ignorePaths": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "36.0.0" 4 | minSdkVersion = 24 5 | compileSdkVersion = 36 6 | targetSdkVersion = 36 7 | ndkVersion = "27.1.12297006" 8 | kotlinVersion = "2.1.20" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { getDefaultConfig } = require('@react-native/metro-config'); 3 | 4 | const projectRoot = __dirname; 5 | const monorepoRoot = path.resolve(projectRoot, "../.."); 6 | 7 | /** 8 | * Metro configuration 9 | * https://facebook.github.io/metro/docs/configuration 10 | * 11 | * @type {import('metro-config').MetroConfig} 12 | */ 13 | 14 | const config = getDefaultConfig(projectRoot); 15 | 16 | config.watchFolders = [monorepoRoot]; 17 | config.resolver.nodeModulesPaths = [ 18 | path.resolve(projectRoot, "node_modules"), 19 | path.resolve(monorepoRoot, "node_modules"), 20 | ]; 21 | 22 | module.exports = config; -------------------------------------------------------------------------------- /package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequestMethod.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroRequestMethod.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.nitrofetch 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | 13 | /** 14 | * Represents the JavaScript enum/union "NitroRequestMethod". 15 | */ 16 | @DoNotStrip 17 | @Keep 18 | enum class NitroRequestMethod(@DoNotStrip @Keep val value: Int) { 19 | GET(0), 20 | HEAD(1), 21 | POST(2), 22 | PUT(3), 23 | PATCH(4), 24 | DELETE(5), 25 | OPTIONS(6); 26 | } 27 | -------------------------------------------------------------------------------- /package/nitrogen/generated/shared/c++/HybridNitroFetchSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridNitroFetchSpec.hpp" 9 | 10 | namespace margelo::nitro::nitrofetch { 11 | 12 | void HybridNitroFetchSpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridMethod("createClient", &HybridNitroFetchSpec::createClient); 18 | }); 19 | } 20 | 21 | } // namespace margelo::nitro::nitrofetch 22 | -------------------------------------------------------------------------------- /package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchPackage.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.nitrofetch 2 | 3 | import com.facebook.react.TurboReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.module.model.ReactModuleInfoProvider 7 | 8 | class NitroFetchPackage : TurboReactPackage() { 9 | override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { 10 | return null 11 | } 12 | 13 | override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { 14 | return ReactModuleInfoProvider { HashMap() } 15 | } 16 | 17 | companion object { 18 | init { 19 | System.loadLibrary("nitrofetch") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroHeader.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroHeader.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.nitrofetch 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | import com.margelo.nitro.core.* 13 | 14 | 15 | /** 16 | * Represents the JavaScript object/struct "NitroHeader". 17 | */ 18 | @DoNotStrip 19 | @Keep 20 | data class NitroHeader 21 | @DoNotStrip 22 | @Keep 23 | constructor( 24 | @DoNotStrip 25 | @Keep 26 | val key: String, 27 | @DoNotStrip 28 | @Keep 29 | val value: String 30 | ) { 31 | /* main constructor */ 32 | } 33 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | - Install dependencies: 4 | 5 | ``` 6 | npm install react-native-nitro-fetch react-native-nitro-modules 7 | ``` 8 | 9 | - Rebuild your app so the Nitro module is linked. 10 | 11 | Verify with a simple request: 12 | 13 | ```ts 14 | import { fetch } from 'react-native-nitro-fetch'; 15 | 16 | export async function test() { 17 | const res = await fetch('https://httpbin.org/get'); 18 | console.log('status', res.status); 19 | console.log('headers', Object.fromEntries(res.headers as any)); 20 | console.log('json', await res.json()); 21 | } 22 | ``` 23 | 24 | Notes 25 | 26 | - Android uses Cronet (via `org.chromium.net:cronet-embedded`) which is already included in `android/build.gradle`. 27 | - iOS currently falls back to the built-in fetch path while Cronet integration is in progress. 28 | 29 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/nitrofetch+autolinking.gradle: -------------------------------------------------------------------------------- 1 | /// 2 | /// nitrofetch+autolinking.gradle 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | /// This is a Gradle file that adds all files generated by Nitrogen 9 | /// to the current Gradle project. 10 | /// 11 | /// To use it, add this to your build.gradle: 12 | /// ```gradle 13 | /// apply from: '../nitrogen/generated/android/nitrofetch+autolinking.gradle' 14 | /// ``` 15 | 16 | logger.warn("[NitroModules] 🔥 nitrofetch is boosted by nitro!") 17 | 18 | android { 19 | sourceSets { 20 | main { 21 | java.srcDirs += [ 22 | // Nitrogen files 23 | "${project.projectDir}/../nitrogen/generated/android/kotlin" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/nitrofetchOnLoad.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// nitrofetchOnLoad.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include 9 | #include 10 | 11 | namespace margelo::nitro::nitrofetch { 12 | 13 | /** 14 | * Initializes the native (C++) part of nitrofetch, and autolinks all Hybrid Objects. 15 | * Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`). 16 | * Example: 17 | * ```cpp (cpp-adapter.cpp) 18 | * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { 19 | * return margelo::nitro::nitrofetch::initialize(vm); 20 | * } 21 | * ``` 22 | */ 23 | int initialize(JavaVM* vm); 24 | 25 | } // namespace margelo::nitro::nitrofetch 26 | -------------------------------------------------------------------------------- /package/NitroFetch.podspec: -------------------------------------------------------------------------------- 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 31 | -------------------------------------------------------------------------------- /package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNativeStorageSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridNativeStorageSpec.hpp" 9 | 10 | namespace margelo::nitro::nitrofetch { 11 | 12 | void HybridNativeStorageSpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridMethod("getString", &HybridNativeStorageSpec::getString); 18 | prototype.registerHybridMethod("setString", &HybridNativeStorageSpec::setString); 19 | prototype.registerHybridMethod("removeString", &HybridNativeStorageSpec::removeString); 20 | }); 21 | } 22 | 23 | } // namespace margelo::nitro::nitrofetch 24 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | - No native client available / silent fallback 4 | - On first run or in dev, JS may fall back to the built-in fetch when native isn’t available yet. Rebuild the app after installing dependencies. 5 | 6 | - Prefetch error: "missing prefetchKey" 7 | - Provide `headers: { prefetchKey: '...' }` or `init.prefetchKey` when calling `prefetch()` and when consuming via `fetch()`. 8 | 9 | 10 | - Cronet provider details 11 | - The library logs available Cronet providers and prefers the "Native" provider. Check Android logs for provider name/version during init. 12 | 13 | - Streaming / timeouts / cancellation not working 14 | - Not implemented yet. Current implementation fetches full bodies before resolving. For streaming today, use Expo’s `expo-fetch`. Timeouts/cancellation are planned. 15 | 16 | - WebSockets support 17 | - Not supported. For high‑performance sockets and binary streams, consider `react-native-fast-io`. 18 | -------------------------------------------------------------------------------- /package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchClientSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridNitroFetchClientSpec.hpp" 9 | 10 | namespace margelo::nitro::nitrofetch { 11 | 12 | void HybridNitroFetchClientSpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridMethod("request", &HybridNitroFetchClientSpec::request); 18 | prototype.registerHybridMethod("prefetch", &HybridNitroFetchClientSpec::prefetch); 19 | prototype.registerHybridMethod("requestSync", &HybridNitroFetchClientSpec::requestSync); 20 | }); 21 | } 22 | 23 | } // namespace margelo::nitro::nitrofetch 24 | -------------------------------------------------------------------------------- /package/ios/NitroBootstrap.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #if __has_include() 3 | #import 4 | #endif 5 | 6 | // No need to import the Swift header if you don’t want to. 7 | // Just declare the C entry point: 8 | extern "C" void NitroStartSwift(void); 9 | 10 | @interface NitroFetchBootstrapper : NSObject @end 11 | @implementation NitroFetchBootstrapper 12 | 13 | + (void)load { 14 | #if __has_include() 15 | if (NSClassFromString(@"UIApplication")) { 16 | [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification 17 | object:nil queue:nil 18 | usingBlock:^(__unused NSNotification *note) { 19 | NitroStartSwift(); // <-- call the C symbol 20 | }]; 21 | dispatch_async(dispatch_get_main_queue(), ^{ 22 | NitroStartSwift(); 23 | }); 24 | } 25 | #endif 26 | } 27 | @end 28 | -------------------------------------------------------------------------------- /package/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "paths": { 5 | "react-native-nitro-fetch": [ 6 | "./src/index" 7 | ] 8 | }, 9 | "allowUnreachableCode": false, 10 | "allowUnusedLabels": false, 11 | "customConditions": ["react-native-strict-api"], 12 | "esModuleInterop": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "jsx": "react-native", 15 | "lib": [ 16 | "esnext", 17 | "DOM" 18 | ], 19 | "module": "esnext", 20 | "moduleResolution": "bundler", 21 | "noFallthroughCasesInSwitch": true, 22 | "noImplicitReturns": true, 23 | "noImplicitUseStrict": false, 24 | "noStrictGenericChecks": false, 25 | "noUnusedLocals": true, 26 | "noUnusedParameters": true, 27 | "resolveJsonModule": true, 28 | "skipLibCheck": true, 29 | "strict": true, 30 | "target": "esnext" 31 | }, 32 | "exclude": [ 33 | "example", 34 | "node_modules", 35 | "dist" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /docs/worklets.md: -------------------------------------------------------------------------------- 1 | # Worklets 2 | 3 | `nitroFetchOnWorklet()` lets you run parsing/mapping off the JS thread using `react-native-worklets-core` (Android). On iOS or when worklets are unavailable, it falls back to running the mapper on the JS thread. 4 | 5 | ## Usage 6 | 7 | ```ts 8 | import { nitroFetchOnWorklet } from 'react-native-nitro-fetch'; 9 | 10 | const map = (payload: { bodyString?: string }) => { 11 | 'worklet'; 12 | return JSON.parse(payload.bodyString ?? '{}'); 13 | }; 14 | 15 | const data = await nitroFetchOnWorklet('https://httpbin.org/get', undefined, map, { preferBytes: false }); 16 | ``` 17 | 18 | Options 19 | 20 | - `preferBytes` (default `false`): when `true`, sends `bodyBytes` to the mapper; when `false`, sends `bodyString`. 21 | - `runtimeName`: optional name for the created worklet runtime. 22 | 23 | Notes 24 | 25 | - Ensure `react-native-worklets-core` is installed in your app to get off-thread execution on Android. 26 | - On iOS, the mapper runs on JS but the API surface remains the same. 27 | 28 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build:android": { 5 | "env": ["ORG_GRADLE_PROJECT_newArchEnabled"], 6 | "inputs": [ 7 | "package/package.json", 8 | "package/android", 9 | "!android/build", 10 | "package/src/*.ts", 11 | "package/src/*.tsx", 12 | "example/package.json", 13 | "example/android", 14 | "!example/android/.gradle", 15 | "!example/android/build", 16 | "!example/android/app/build" 17 | ], 18 | "outputs": [] 19 | }, 20 | "build:ios": { 21 | "env": ["RCT_NEW_ARCH_ENABLED"], 22 | "inputs": [ 23 | "package/package.json", 24 | "package/*.podspec", 25 | "package/ios", 26 | "package/src/*.ts", 27 | "package/src/*.tsx", 28 | "example/package.json", 29 | "example/ios", 30 | "!example/ios/build", 31 | "!example/ios/Pods" 32 | ], 33 | "outputs": [] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 21 | -------------------------------------------------------------------------------- /example/ios/NitroFetchExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/ios/NitroFetchExample/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryUserDefaults 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | CA92.1 21 | 22 | 23 | 24 | NSPrivacyAccessedAPIType 25 | NSPrivacyAccessedAPICategorySystemBootTime 26 | NSPrivacyAccessedAPITypeReasons 27 | 28 | 35F9.1 29 | 30 | 31 | 32 | NSPrivacyCollectedDataTypes 33 | 34 | NSPrivacyTracking 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequest.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroRequest.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.nitrofetch 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | import com.margelo.nitro.core.* 13 | 14 | 15 | /** 16 | * Represents the JavaScript object/struct "NitroRequest". 17 | */ 18 | @DoNotStrip 19 | @Keep 20 | data class NitroRequest 21 | @DoNotStrip 22 | @Keep 23 | constructor( 24 | @DoNotStrip 25 | @Keep 26 | val url: String, 27 | @DoNotStrip 28 | @Keep 29 | val method: NitroRequestMethod?, 30 | @DoNotStrip 31 | @Keep 32 | val headers: Array?, 33 | @DoNotStrip 34 | @Keep 35 | val bodyString: String?, 36 | @DoNotStrip 37 | @Keep 38 | val bodyBytes: String?, 39 | @DoNotStrip 40 | @Keep 41 | val timeoutMs: Double?, 42 | @DoNotStrip 43 | @Keep 44 | val followRedirects: Boolean? 45 | ) { 46 | /* main constructor */ 47 | } 48 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/swift/NitroHeader.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroHeader.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | /** 11 | * Represents an instance of `NitroHeader`, backed by a C++ struct. 12 | */ 13 | public typealias NitroHeader = margelo.nitro.nitrofetch.NitroHeader 14 | 15 | public extension NitroHeader { 16 | private typealias bridge = margelo.nitro.nitrofetch.bridge.swift 17 | 18 | /** 19 | * Create a new instance of `NitroHeader`. 20 | */ 21 | init(key: String, value: String) { 22 | self.init(std.string(key), std.string(value)) 23 | } 24 | 25 | var key: String { 26 | @inline(__always) 27 | get { 28 | return String(self.__key) 29 | } 30 | @inline(__always) 31 | set { 32 | self.__key = std.string(newValue) 33 | } 34 | } 35 | 36 | var value: String { 37 | @inline(__always) 38 | get { 39 | return String(self.__value) 40 | } 41 | @inline(__always) 42 | set { 43 | self.__value = std.string(newValue) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { fixupConfigRules } from '@eslint/compat'; 2 | import { FlatCompat } from '@eslint/eslintrc'; 3 | import js from '@eslint/js'; 4 | import prettier from 'eslint-plugin-prettier'; 5 | import { defineConfig } from 'eslint/config'; 6 | import path from 'node:path'; 7 | import { fileURLToPath } from 'node:url'; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all, 15 | }); 16 | 17 | export default defineConfig([ 18 | { 19 | extends: fixupConfigRules(compat.extends('@react-native', 'prettier')), 20 | plugins: { prettier }, 21 | rules: { 22 | 'react/react-in-jsx-scope': 'off', 23 | 'prettier/prettier': 'error', 24 | }, 25 | languageOptions: { 26 | parserOptions: { 27 | requireConfigFile: false, 28 | } 29 | } 30 | }, 31 | { 32 | ignores: [ 33 | 'node_modules/', 34 | 'package/lib/', 35 | '**/metro.config.js', 36 | '**/react-native.config.js', 37 | '**/babel.config.js', 38 | ], 39 | }, 40 | ]); 41 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroResponse.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroResponse.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.nitrofetch 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | import com.margelo.nitro.core.* 13 | 14 | 15 | /** 16 | * Represents the JavaScript object/struct "NitroResponse". 17 | */ 18 | @DoNotStrip 19 | @Keep 20 | data class NitroResponse 21 | @DoNotStrip 22 | @Keep 23 | constructor( 24 | @DoNotStrip 25 | @Keep 26 | val url: String, 27 | @DoNotStrip 28 | @Keep 29 | val status: Double, 30 | @DoNotStrip 31 | @Keep 32 | val statusText: String, 33 | @DoNotStrip 34 | @Keep 35 | val ok: Boolean, 36 | @DoNotStrip 37 | @Keep 38 | val redirected: Boolean, 39 | @DoNotStrip 40 | @Keep 41 | val headers: Array, 42 | @DoNotStrip 43 | @Keep 44 | val bodyString: String?, 45 | @DoNotStrip 46 | @Keep 47 | val bodyBytes: String? 48 | ) { 49 | /* main constructor */ 50 | } 51 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | ENV['RCT_NEW_ARCH_ENABLED'] = '1' 2 | 3 | # Resolve react_native_pods.rb with node to allow for hoisting 4 | require Pod::Executable.execute_command('node', ['-p', 5 | 'require.resolve( 6 | "react-native/scripts/react_native_pods.rb", 7 | {paths: [process.argv[1]]}, 8 | )', __dir__]).strip 9 | 10 | platform :ios, min_ios_version_supported 11 | prepare_react_native_project! 12 | 13 | linkage = ENV['USE_FRAMEWORKS'] 14 | if linkage != nil 15 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 16 | use_frameworks! :linkage => linkage.to_sym 17 | end 18 | 19 | target 'NitroFetchExample' do 20 | config = use_native_modules! 21 | 22 | use_react_native!( 23 | :path => config[:reactNativePath], 24 | # An absolute path to your application root. 25 | :app_path => "#{Pod::Config.instance.installation_root}/.." 26 | ) 27 | 28 | post_install do |installer| 29 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 30 | react_native_post_install( 31 | installer, 32 | config[:reactNativePath], 33 | :mac_catalyst_enabled => false, 34 | # :ccache_enabled => true 35 | ) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/nitrofetchOnLoad.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// nitrofetchOnLoad.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.nitrofetch 9 | 10 | import android.util.Log 11 | 12 | internal class nitrofetchOnLoad { 13 | companion object { 14 | private const val TAG = "nitrofetchOnLoad" 15 | private var didLoad = false 16 | /** 17 | * Initializes the native part of "nitrofetch". 18 | * This method is idempotent and can be called more than once. 19 | */ 20 | @JvmStatic 21 | fun initializeNative() { 22 | if (didLoad) return 23 | try { 24 | Log.i(TAG, "Loading nitrofetch C++ library...") 25 | System.loadLibrary("nitrofetch") 26 | Log.i(TAG, "Successfully loaded nitrofetch C++ library!") 27 | didLoad = true 28 | } catch (e: Error) { 29 | Log.e(TAG, "Failed to load nitrofetch C++ library! Is it properly installed and linked? " + 30 | "Is the name correct? (see `CMakeLists.txt`, at `add_library(...)`)", e) 31 | throw e 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | **/.xcode.env.local 32 | 33 | # Android/IJ 34 | # 35 | .classpath 36 | .cxx 37 | .gradle 38 | .idea 39 | .project 40 | .settings 41 | local.properties 42 | android.iml 43 | 44 | # Cocoapods 45 | # 46 | example/ios/Pods 47 | 48 | # Ruby 49 | example/vendor/ 50 | 51 | # node.js 52 | # 53 | node_modules/ 54 | npm-debug.log 55 | yarn-debug.log 56 | yarn-error.log 57 | 58 | # BUCK 59 | buck-out/ 60 | \.buckd/ 61 | android/app/libs 62 | android/keystores/debug.keystore 63 | 64 | # Yarn 65 | .yarn/* 66 | !.yarn/patches 67 | !.yarn/plugins 68 | !.yarn/releases 69 | !.yarn/sdks 70 | !.yarn/versions 71 | 72 | # Expo 73 | .expo/ 74 | 75 | # Turborepo 76 | .turbo/ 77 | 78 | # generated by bob 79 | lib/ 80 | 81 | # React Native Codegen 82 | ios/generated 83 | android/generated 84 | 85 | # React Native Nitro Modules 86 | #nitrogen/ 87 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Bun and install dependencies 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Bun 8 | uses: oven-sh/setup-bun@v2 9 | with: 10 | bun-version-file: package.json 11 | 12 | - name: Restore dependencies 13 | id: bun-cache 14 | uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 15 | with: 16 | path: | 17 | **/node_modules 18 | ~/.bun/install/cache 19 | key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} 20 | restore-keys: | 21 | ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} 22 | ${{ runner.os }}-bun- 23 | 24 | - name: Install dependencies 25 | if: steps.bun-cache.outputs.cache-hit != 'true' 26 | run: bun install --frozen-lockfile 27 | shell: bash 28 | 29 | - name: Cache dependencies 30 | if: steps.bun-cache.outputs.cache-hit != 'true' 31 | uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 32 | with: 33 | path: | 34 | **/node_modules 35 | ~/.bun/install/cache 36 | key: ${{ steps.bun-cache.outputs.cache-primary-key }} 37 | -------------------------------------------------------------------------------- /docs/ios.md: -------------------------------------------------------------------------------- 1 | # iOS 2 | 3 | - Current status: native `URLSession` client is used for requests and `prefetch()` (with an in‑memory cache for fresh results). Auto‑prefetch on app start is Android‑only. 4 | - Auto‑prefetch on app start is available. Call `NitroAutoPrefetcher.prefetchOnStart()` from `AppDelegate` to trigger it. 5 | - Cronet integration is planned; once available, the iOS client will switch to Cronet for parity with Android. 6 | - `nitroFetchOnWorklet` runs the mapper on the JS thread on iOS (off‑thread mapping requires Android worklets runtime). 7 | 8 | See also: `docs/cronet-ios.md` for high-level Cronet iOS integration notes. 9 | ## Auto‑Prefetch on App Start 10 | 11 | 12 | 13 | 1) Schedule from JS at runtime (same as Android): 14 | 15 | ```ts 16 | import { prefetchOnAppStart } from 'react-native-nitro-fetch'; 17 | await prefetchOnAppStart('https://httpbin.org/uuid', { prefetchKey: 'uuid' }); 18 | ``` 19 | 20 | 2) Trigger native prefetch on app start in `AppDelegate.swift`: 21 | 22 | ```swift 23 | import NitroFetch 24 | 25 | @main 26 | class AppDelegate: UIResponder, UIApplicationDelegate { 27 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 28 | NitroAutoPrefetcher.prefetchOnStart() 29 | return true 30 | } 31 | } 32 | ``` 33 | 34 | Notes 35 | 36 | - Prefetch is best‑effort. 37 | - Responses served shortly after prefetch include header `nitroPrefetched: true`. 38 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/swift/Func_void.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `() -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void { 16 | public typealias bridge = margelo.nitro.nitrofetch.bridge.swift 17 | 18 | private let closure: () -> Void 19 | 20 | public init(_ closure: @escaping () -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call() -> Void { 26 | self.closure() 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-nitro-fetch-example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", 10 | "build:ios": "react-native build-ios --mode Debug" 11 | }, 12 | "dependencies": { 13 | "@react-native/new-app-screen": "0.81.0", 14 | "react": "19.1.0", 15 | "react-native": "0.81.0", 16 | "react-native-nitro-modules": "^0.29.2", 17 | "react-native-safe-area-context": "^5.5.2", 18 | "react-native-worklets-core": "^1.6.2" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.25.2", 22 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", 23 | "@babel/preset-env": "^7.25.3", 24 | "@babel/runtime": "^7.25.0", 25 | "@react-native-community/cli": "20.0.0", 26 | "@react-native-community/cli-platform-android": "20.0.0", 27 | "@react-native-community/cli-platform-ios": "20.0.0", 28 | "@react-native/babel-preset": "0.81.0", 29 | "@react-native/metro-config": "0.81.0", 30 | "@react-native/typescript-config": "0.81.0", 31 | "@types/react": "^19.1.0", 32 | "react-native-builder-bob": "^0.40.13", 33 | "react-native-monorepo-config": "^0.1.9", 34 | "react-native-nitro-fetch": "*" 35 | }, 36 | "engines": { 37 | "node": ">=18" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/cronet-ios.md: -------------------------------------------------------------------------------- 1 | Cronet C API integration (iOS) 2 | 3 | High-level steps 4 | 5 | - Add Cronet framework to the iOS project (via CocoaPods or manual integration). Cronet for iOS ships as a framework with C and Objective‑C APIs. 6 | - Expose headers to Swift by adding a bridging header for C if needed, or use the C API from a small Objective‑C++ (*.mm) wrapper. 7 | - Initialize a shared engine (similar to Android) and hold it for the app lifetime. 8 | - Implement `request(_ req: NitroRequest) async throws -> NitroResponse` in `NitroFetch.swift` by calling into the C/ObjC++ wrapper. 9 | 10 | Linking 11 | 12 | - In the Podspec, add the Cronet pod or vendored framework, e.g.: 13 | 14 | ``` 15 | s.vendored_frameworks = 'path/to/Cronet.framework' 16 | ``` 17 | 18 | Engine + request wrapper (ObjC++) 19 | 20 | ``` 21 | // CronetBridge.h/.mm 22 | #import 23 | 24 | bool CronetInit(void); 25 | void CronetShutdown(void); 26 | bool CronetRequest(const NitroRequest* req, NitroResponse* out); 27 | ``` 28 | 29 | Swift glue 30 | 31 | ``` 32 | final class NitroFetch: HybridNitroFetchSpec { 33 | public func request(req: NitroRequest) async throws -> NitroResponse { 34 | // Call CronetRequest, translate errors into thrown Swift errors 35 | } 36 | } 37 | ``` 38 | 39 | Notes 40 | 41 | - Use async/await in Swift signatures generated by Nitro for Promise returns. 42 | - For streaming, add request handle ids and expose chunk events; use Nitro event emitters. 43 | - Ensure ATS (App Transport Security) allows your test endpoints or use https. 44 | 45 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchSpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchSpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.nitrofetch 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.jni.HybridData 12 | import com.facebook.proguard.annotations.DoNotStrip 13 | import com.margelo.nitro.core.* 14 | 15 | /** 16 | * A Kotlin class representing the NitroFetch HybridObject. 17 | * Implement this abstract class to create Kotlin-based instances of NitroFetch. 18 | */ 19 | @DoNotStrip 20 | @Keep 21 | @Suppress( 22 | "KotlinJniMissingFunction", "unused", 23 | "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", 24 | "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" 25 | ) 26 | abstract class HybridNitroFetchSpec: HybridObject() { 27 | @DoNotStrip 28 | private var mHybridData: HybridData = initHybrid() 29 | 30 | init { 31 | super.updateNative(mHybridData) 32 | } 33 | 34 | override fun updateNative(hybridData: HybridData) { 35 | mHybridData = hybridData 36 | super.updateNative(hybridData) 37 | } 38 | 39 | // Properties 40 | 41 | 42 | // Methods 43 | @DoNotStrip 44 | @Keep 45 | abstract fun createClient(): HybridNitroFetchClientSpec 46 | 47 | private external fun initHybrid(): HybridData 48 | 49 | companion object { 50 | private const val TAG = "HybridNitroFetchSpec" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/android.md: -------------------------------------------------------------------------------- 1 | # Android 2 | 3 | Status 4 | 5 | - Uses Cronet Java API via `org.chromium.net:cronet-embedded` (declared in `android/build.gradle`). No extra setup required for basic usage. 6 | - Cronet engine is initialized once with HTTP/2, QUIC, Brotli, and disk cache under `/nitrofetch_cronet_cache`. 7 | 8 | Prefetch 9 | 10 | - Start a prefetch: 11 | 12 | ```ts 13 | import { prefetch } from 'react-native-nitro-fetch'; 14 | await prefetch('https://httpbin.org/uuid', { headers: { prefetchKey: 'uuid' } }); 15 | ``` 16 | 17 | - Consume later (returns prefetched data when fresh/pending): 18 | 19 | ```ts 20 | import { fetch } from 'react-native-nitro-fetch'; 21 | const res = await fetch('https://httpbin.org/uuid', { headers: { prefetchKey: 'uuid' } }); 22 | console.log(res.headers.get('nitroPrefetched')); // 'true' if prefetched 23 | ``` 24 | 25 | Auto-Prefetch (on next app start) 26 | 27 | - Queue a request in NativeStorage so native can prefetch on next startup: 28 | 29 | ```ts 30 | import { prefetchOnAppStart } from 'react-native-nitro-fetch'; 31 | await prefetchOnAppStart('https://httpbin.org/uuid', { prefetchKey: 'uuid' }); 32 | ``` 33 | 34 | - Clear or remove entries: 35 | 36 | ```ts 37 | import { removeFromAutoPrefetch, removeAllFromAutoprefetch } from 'react-native-nitro-fetch'; 38 | await removeFromAutoPrefetch('uuid'); 39 | await removeAllFromAutoprefetch(); 40 | ``` 41 | 42 | Notes 43 | 44 | - The library prefers the "Native" Cronet provider when available and logs the provider/version during initialization. 45 | - Timeout/cancellation and streaming are not implemented yet. 46 | 47 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/swift/NitroRequestMethod.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroRequestMethod.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | /** 9 | * Represents the JS union `NitroRequestMethod`, backed by a C++ enum. 10 | */ 11 | public typealias NitroRequestMethod = margelo.nitro.nitrofetch.NitroRequestMethod 12 | 13 | public extension NitroRequestMethod { 14 | /** 15 | * Get a NitroRequestMethod for the given String value, or 16 | * return `nil` if the given value was invalid/unknown. 17 | */ 18 | init?(fromString string: String) { 19 | switch string { 20 | case "GET": 21 | self = .get 22 | case "HEAD": 23 | self = .head 24 | case "POST": 25 | self = .post 26 | case "PUT": 27 | self = .put 28 | case "PATCH": 29 | self = .patch 30 | case "DELETE": 31 | self = .delete 32 | case "OPTIONS": 33 | self = .options 34 | default: 35 | return nil 36 | } 37 | } 38 | 39 | /** 40 | * Get the String value this NitroRequestMethod represents. 41 | */ 42 | var stringValue: String { 43 | switch self { 44 | case .get: 45 | return "GET" 46 | case .head: 47 | return "HEAD" 48 | case .post: 49 | return "POST" 50 | case .put: 51 | return "PUT" 52 | case .patch: 53 | return "PATCH" 54 | case .delete: 55 | return "DELETE" 56 | case .options: 57 | return "OPTIONS" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/swift/Func_void_NitroResponse.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_NitroResponse.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `(_ value: NitroResponse) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_NitroResponse { 16 | public typealias bridge = margelo.nitro.nitrofetch.bridge.swift 17 | 18 | private let closure: (_ value: NitroResponse) -> Void 19 | 20 | public init(_ closure: @escaping (_ value: NitroResponse) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(value: NitroResponse) -> Void { 26 | self.closure(value) 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void_NitroResponse`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_NitroResponse { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/swift/HybridNitroFetchSpec.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchSpec.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import Foundation 9 | import NitroModules 10 | 11 | /// See ``HybridNitroFetchSpec`` 12 | public protocol HybridNitroFetchSpec_protocol: HybridObject { 13 | // Properties 14 | 15 | 16 | // Methods 17 | func createClient() throws -> (any HybridNitroFetchClientSpec) 18 | } 19 | 20 | /// See ``HybridNitroFetchSpec`` 21 | open class HybridNitroFetchSpec_base { 22 | private weak var cxxWrapper: HybridNitroFetchSpec_cxx? = nil 23 | public init() { } 24 | public func getCxxWrapper() -> HybridNitroFetchSpec_cxx { 25 | #if DEBUG 26 | guard self is HybridNitroFetchSpec else { 27 | fatalError("`self` is not a `HybridNitroFetchSpec`! Did you accidentally inherit from `HybridNitroFetchSpec_base` instead of `HybridNitroFetchSpec`?") 28 | } 29 | #endif 30 | if let cxxWrapper = self.cxxWrapper { 31 | return cxxWrapper 32 | } else { 33 | let cxxWrapper = HybridNitroFetchSpec_cxx(self as! HybridNitroFetchSpec) 34 | self.cxxWrapper = cxxWrapper 35 | return cxxWrapper 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * A Swift base-protocol representing the NitroFetch HybridObject. 42 | * Implement this protocol to create Swift-based instances of NitroFetch. 43 | * ```swift 44 | * class HybridNitroFetch : HybridNitroFetchSpec { 45 | * // ... 46 | * } 47 | * ``` 48 | */ 49 | public typealias HybridNitroFetchSpec = HybridNitroFetchSpec_protocol & HybridNitroFetchSpec_base 50 | -------------------------------------------------------------------------------- /example/ios/NitroFetchExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import React 3 | import React_RCTAppDelegate 4 | import ReactAppDependencyProvider 5 | //import NitroFetch it requires adding a separate module for it 6 | 7 | @main 8 | class AppDelegate: UIResponder, UIApplicationDelegate { 9 | var window: UIWindow? 10 | 11 | var reactNativeDelegate: ReactNativeDelegate? 12 | var reactNativeFactory: RCTReactNativeFactory? 13 | 14 | func application( 15 | _ application: UIApplication, 16 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil 17 | ) -> Bool { 18 | // Kick off auto-prefetch for queued URLs 19 | //NitroAutoPrefetcher.prefetchOnStart() it requires adding a separate module for it 20 | 21 | let delegate = ReactNativeDelegate() 22 | let factory = RCTReactNativeFactory(delegate: delegate) 23 | delegate.dependencyProvider = RCTAppDependencyProvider() 24 | 25 | reactNativeDelegate = delegate 26 | reactNativeFactory = factory 27 | 28 | window = UIWindow(frame: UIScreen.main.bounds) 29 | 30 | factory.startReactNative( 31 | withModuleName: "NitroFetchExample", 32 | in: window, 33 | launchOptions: launchOptions 34 | ) 35 | 36 | return true 37 | } 38 | } 39 | 40 | class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { 41 | override func sourceURL(for bridge: RCTBridge) -> URL? { 42 | self.bundleURL() 43 | } 44 | 45 | override func bundleURL() -> URL? { 46 | #if DEBUG 47 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") 48 | #else 49 | Bundle.main.url(forResource: "main", withExtension: "jsbundle") 50 | #endif 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_std__exception_ptr.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `(_ error: Error) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_std__exception_ptr { 16 | public typealias bridge = margelo.nitro.nitrofetch.bridge.swift 17 | 18 | private let closure: (_ error: Error) -> Void 19 | 20 | public init(_ closure: @escaping (_ error: Error) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(error: std.exception_ptr) -> Void { 26 | self.closure(RuntimeError.from(cppError: error)) 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void_std__exception_ptr`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__exception_ptr { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/NitroFetchAutolinking.mm: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroFetchAutolinking.mm 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #import 9 | #import 10 | #import "NitroFetch-Swift-Cxx-Umbrella.hpp" 11 | #import 12 | 13 | #include "HybridNitroFetchSpecSwift.hpp" 14 | #include "HybridNitroFetchClientSpecSwift.hpp" 15 | #include "HybridNativeStorageSpecSwift.hpp" 16 | 17 | @interface NitroFetchAutolinking : NSObject 18 | @end 19 | 20 | @implementation NitroFetchAutolinking 21 | 22 | + (void) load { 23 | using namespace margelo::nitro; 24 | using namespace margelo::nitro::nitrofetch; 25 | 26 | HybridObjectRegistry::registerHybridObjectConstructor( 27 | "NitroFetch", 28 | []() -> std::shared_ptr { 29 | std::shared_ptr hybridObject = NitroFetch::NitroFetchAutolinking::createNitroFetch(); 30 | return hybridObject; 31 | } 32 | ); 33 | HybridObjectRegistry::registerHybridObjectConstructor( 34 | "NitroFetchClient", 35 | []() -> std::shared_ptr { 36 | std::shared_ptr hybridObject = NitroFetch::NitroFetchAutolinking::createNitroFetchClient(); 37 | return hybridObject; 38 | } 39 | ); 40 | HybridObjectRegistry::registerHybridObjectConstructor( 41 | "NativeStorage", 42 | []() -> std::shared_ptr { 43 | std::shared_ptr hybridObject = NitroFetch::NitroFetchAutolinking::createNativeStorage(); 44 | return hybridObject; 45 | } 46 | ); 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNativeStorageSpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.nitrofetch 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.jni.HybridData 12 | import com.facebook.proguard.annotations.DoNotStrip 13 | import com.margelo.nitro.core.* 14 | 15 | /** 16 | * A Kotlin class representing the NativeStorage HybridObject. 17 | * Implement this abstract class to create Kotlin-based instances of NativeStorage. 18 | */ 19 | @DoNotStrip 20 | @Keep 21 | @Suppress( 22 | "KotlinJniMissingFunction", "unused", 23 | "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", 24 | "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" 25 | ) 26 | abstract class HybridNativeStorageSpec: HybridObject() { 27 | @DoNotStrip 28 | private var mHybridData: HybridData = initHybrid() 29 | 30 | init { 31 | super.updateNative(mHybridData) 32 | } 33 | 34 | override fun updateNative(hybridData: HybridData) { 35 | mHybridData = hybridData 36 | super.updateNative(hybridData) 37 | } 38 | 39 | // Properties 40 | 41 | 42 | // Methods 43 | @DoNotStrip 44 | @Keep 45 | abstract fun getString(key: String): String 46 | 47 | @DoNotStrip 48 | @Keep 49 | abstract fun setString(key: String, value: String): Unit 50 | 51 | @DoNotStrip 52 | @Keep 53 | abstract fun removeString(key: String): Unit 54 | 55 | private external fun initHybrid(): HybridData 56 | 57 | companion object { 58 | private const val TAG = "HybridNativeStorageSpec" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/nitrofetch/example/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package nitrofetch.example 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative 8 | import com.facebook.react.ReactNativeHost 9 | import com.facebook.react.ReactPackage 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.margelo.nitro.nitrofetch.AutoPrefetcher 13 | 14 | class MainApplication : Application(), ReactApplication { 15 | 16 | override val reactNativeHost: ReactNativeHost = 17 | object : DefaultReactNativeHost(this) { 18 | override fun getPackages(): List = 19 | PackageList(this).packages.apply { 20 | // Packages that cannot be autolinked yet can be added manually here, for example: 21 | // add(MyReactNativePackage()) 22 | } 23 | 24 | override fun getJSMainModuleName(): String = "index" 25 | 26 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 27 | 28 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 29 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 30 | } 31 | 32 | override val reactHost: ReactHost 33 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 34 | 35 | override fun onCreate() { 36 | super.onCreate() 37 | // Best-effort auto prefetch when engine initializes (app start) 38 | try { AutoPrefetcher.prefetchOnStart(this) } catch (_: Throwable) {} 39 | loadReactNative(this) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/ios/NitroFetchExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | NitroFetchExample 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSAllowsLocalNetworking 32 | 33 | 34 | NSLocationWhenInUseUsageDescription 35 | 36 | RCTNewArchEnabled 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIRequiredDeviceCapabilities 41 | 42 | arm64 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchClientSpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.nitrofetch 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.jni.HybridData 12 | import com.facebook.proguard.annotations.DoNotStrip 13 | import com.margelo.nitro.core.* 14 | 15 | /** 16 | * A Kotlin class representing the NitroFetchClient HybridObject. 17 | * Implement this abstract class to create Kotlin-based instances of NitroFetchClient. 18 | */ 19 | @DoNotStrip 20 | @Keep 21 | @Suppress( 22 | "KotlinJniMissingFunction", "unused", 23 | "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", 24 | "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" 25 | ) 26 | abstract class HybridNitroFetchClientSpec: HybridObject() { 27 | @DoNotStrip 28 | private var mHybridData: HybridData = initHybrid() 29 | 30 | init { 31 | super.updateNative(mHybridData) 32 | } 33 | 34 | override fun updateNative(hybridData: HybridData) { 35 | mHybridData = hybridData 36 | super.updateNative(hybridData) 37 | } 38 | 39 | // Properties 40 | 41 | 42 | // Methods 43 | @DoNotStrip 44 | @Keep 45 | abstract fun request(req: NitroRequest): Promise 46 | 47 | @DoNotStrip 48 | @Keep 49 | abstract fun prefetch(req: NitroRequest): Promise 50 | 51 | @DoNotStrip 52 | @Keep 53 | abstract fun requestSync(req: NitroRequest): NitroResponse 54 | 55 | private external fun initHybrid(): HybridData 56 | 57 | companion object { 58 | private const val TAG = "HybridNitroFetchClientSpec" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNativeStorageSpec.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import Foundation 9 | import NitroModules 10 | 11 | /// See ``HybridNativeStorageSpec`` 12 | public protocol HybridNativeStorageSpec_protocol: HybridObject { 13 | // Properties 14 | 15 | 16 | // Methods 17 | func getString(key: String) throws -> String 18 | func setString(key: String, value: String) throws -> Void 19 | func removeString(key: String) throws -> Void 20 | } 21 | 22 | /// See ``HybridNativeStorageSpec`` 23 | open class HybridNativeStorageSpec_base { 24 | private weak var cxxWrapper: HybridNativeStorageSpec_cxx? = nil 25 | public init() { } 26 | public func getCxxWrapper() -> HybridNativeStorageSpec_cxx { 27 | #if DEBUG 28 | guard self is HybridNativeStorageSpec else { 29 | fatalError("`self` is not a `HybridNativeStorageSpec`! Did you accidentally inherit from `HybridNativeStorageSpec_base` instead of `HybridNativeStorageSpec`?") 30 | } 31 | #endif 32 | if let cxxWrapper = self.cxxWrapper { 33 | return cxxWrapper 34 | } else { 35 | let cxxWrapper = HybridNativeStorageSpec_cxx(self as! HybridNativeStorageSpec) 36 | self.cxxWrapper = cxxWrapper 37 | return cxxWrapper 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * A Swift base-protocol representing the NativeStorage HybridObject. 44 | * Implement this protocol to create Swift-based instances of NativeStorage. 45 | * ```swift 46 | * class HybridNativeStorage : HybridNativeStorageSpec { 47 | * // ... 48 | * } 49 | * ``` 50 | */ 51 | public typealias HybridNativeStorageSpec = HybridNativeStorageSpec_protocol & HybridNativeStorageSpec_base 52 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JHybridNitroFetchSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JHybridNitroFetchSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "JHybridNitroFetchSpec.hpp" 9 | 10 | // Forward declaration of `HybridNitroFetchClientSpec` to properly resolve imports. 11 | namespace margelo::nitro::nitrofetch { class HybridNitroFetchClientSpec; } 12 | 13 | #include 14 | #include "HybridNitroFetchClientSpec.hpp" 15 | #include "JHybridNitroFetchClientSpec.hpp" 16 | 17 | namespace margelo::nitro::nitrofetch { 18 | 19 | jni::local_ref JHybridNitroFetchSpec::initHybrid(jni::alias_ref jThis) { 20 | return makeCxxInstance(jThis); 21 | } 22 | 23 | void JHybridNitroFetchSpec::registerNatives() { 24 | registerHybrid({ 25 | makeNativeMethod("initHybrid", JHybridNitroFetchSpec::initHybrid), 26 | }); 27 | } 28 | 29 | size_t JHybridNitroFetchSpec::getExternalMemorySize() noexcept { 30 | static const auto method = javaClassStatic()->getMethod("getMemorySize"); 31 | return method(_javaPart); 32 | } 33 | 34 | void JHybridNitroFetchSpec::dispose() noexcept { 35 | static const auto method = javaClassStatic()->getMethod("dispose"); 36 | method(_javaPart); 37 | } 38 | 39 | // Properties 40 | 41 | 42 | // Methods 43 | std::shared_ptr JHybridNitroFetchSpec::createClient() { 44 | static const auto method = javaClassStatic()->getMethod()>("createClient"); 45 | auto __result = method(_javaPart); 46 | return __result->cthis()->shared_cast(); 47 | } 48 | 49 | } // namespace margelo::nitro::nitrofetch 50 | -------------------------------------------------------------------------------- /package/ios/FetchCache.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class FetchCache { 4 | struct CachedEntry { 5 | let response: NitroResponse 6 | let timestampMs: Int64 7 | } 8 | 9 | private static let queue = DispatchQueue(label: "nitrofetch.cache", attributes: .concurrent) 10 | private static var pending: [String: [(Result) -> Void]] = [:] 11 | private static var results: [String: CachedEntry] = [:] 12 | 13 | static func getPending(_ key: String) -> Bool { 14 | var has = false 15 | queue.sync { has = pending[key] != nil } 16 | return has 17 | } 18 | 19 | static func addPending(_ key: String, completion: @escaping (Result) -> Void) { 20 | queue.async(flags: .barrier) { 21 | var arr = pending[key] ?? [] 22 | arr.append(completion) 23 | pending[key] = arr 24 | } 25 | } 26 | 27 | static func complete(_ key: String, with result: Result) { 28 | var callbacks: [(Result) -> Void] = [] 29 | queue.sync { 30 | callbacks = pending[key] ?? [] 31 | } 32 | queue.async(flags: .barrier) { 33 | pending.removeValue(forKey: key) 34 | if case let .success(resp) = result { 35 | results[key] = CachedEntry(response: resp, timestampMs: Int64(Date().timeIntervalSince1970 * 1000)) 36 | } 37 | } 38 | callbacks.forEach { $0(result) } 39 | } 40 | 41 | static func getResultIfFresh(_ key: String, maxAgeMs: Int64) -> NitroResponse? { 42 | var out: NitroResponse? 43 | queue.sync { 44 | if let entry = results[key] { 45 | let age = Int64(Date().timeIntervalSince1970 * 1000) - entry.timestampMs 46 | if age <= maxAgeMs { 47 | out = entry.response 48 | } else { 49 | results.removeValue(forKey: key) 50 | } 51 | } 52 | } 53 | return out 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchClientSpec.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import Foundation 9 | import NitroModules 10 | 11 | /// See ``HybridNitroFetchClientSpec`` 12 | public protocol HybridNitroFetchClientSpec_protocol: HybridObject { 13 | // Properties 14 | 15 | 16 | // Methods 17 | func request(req: NitroRequest) throws -> Promise 18 | func prefetch(req: NitroRequest) throws -> Promise 19 | func requestSync(req: NitroRequest) throws -> NitroResponse 20 | } 21 | 22 | /// See ``HybridNitroFetchClientSpec`` 23 | open class HybridNitroFetchClientSpec_base { 24 | private weak var cxxWrapper: HybridNitroFetchClientSpec_cxx? = nil 25 | public init() { } 26 | public func getCxxWrapper() -> HybridNitroFetchClientSpec_cxx { 27 | #if DEBUG 28 | guard self is HybridNitroFetchClientSpec else { 29 | fatalError("`self` is not a `HybridNitroFetchClientSpec`! Did you accidentally inherit from `HybridNitroFetchClientSpec_base` instead of `HybridNitroFetchClientSpec`?") 30 | } 31 | #endif 32 | if let cxxWrapper = self.cxxWrapper { 33 | return cxxWrapper 34 | } else { 35 | let cxxWrapper = HybridNitroFetchClientSpec_cxx(self as! HybridNitroFetchClientSpec) 36 | self.cxxWrapper = cxxWrapper 37 | return cxxWrapper 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * A Swift base-protocol representing the NitroFetchClient HybridObject. 44 | * Implement this protocol to create Swift-based instances of NitroFetchClient. 45 | * ```swift 46 | * class HybridNitroFetchClient : HybridNitroFetchClientSpec { 47 | * // ... 48 | * } 49 | * ``` 50 | */ 51 | public typealias HybridNitroFetchClientSpec = HybridNitroFetchClientSpec_protocol & HybridNitroFetchClientSpec_base 52 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JNitroHeader.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JNitroHeader.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include 11 | #include "NitroHeader.hpp" 12 | 13 | #include 14 | 15 | namespace margelo::nitro::nitrofetch { 16 | 17 | using namespace facebook; 18 | 19 | /** 20 | * The C++ JNI bridge between the C++ struct "NitroHeader" and the the Kotlin data class "NitroHeader". 21 | */ 22 | struct JNitroHeader final: public jni::JavaClass { 23 | public: 24 | static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrofetch/NitroHeader;"; 25 | 26 | public: 27 | /** 28 | * Convert this Java/Kotlin-based struct to the C++ struct NitroHeader by copying all values to C++. 29 | */ 30 | [[maybe_unused]] 31 | [[nodiscard]] 32 | NitroHeader toCpp() const { 33 | static const auto clazz = javaClassStatic(); 34 | static const auto fieldKey = clazz->getField("key"); 35 | jni::local_ref key = this->getFieldValue(fieldKey); 36 | static const auto fieldValue = clazz->getField("value"); 37 | jni::local_ref value = this->getFieldValue(fieldValue); 38 | return NitroHeader( 39 | key->toStdString(), 40 | value->toStdString() 41 | ); 42 | } 43 | 44 | public: 45 | /** 46 | * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. 47 | */ 48 | [[maybe_unused]] 49 | static jni::local_ref fromCpp(const NitroHeader& value) { 50 | return newInstance( 51 | jni::make_jstring(value.key), 52 | jni::make_jstring(value.value) 53 | ); 54 | } 55 | }; 56 | 57 | } // namespace margelo::nitro::nitrofetch 58 | -------------------------------------------------------------------------------- /package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNativeStorageSpec.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #if __has_include() 11 | #include 12 | #else 13 | #error NitroModules cannot be found! Are you sure you installed NitroModules properly? 14 | #endif 15 | 16 | 17 | 18 | #include 19 | 20 | namespace margelo::nitro::nitrofetch { 21 | 22 | using namespace margelo::nitro; 23 | 24 | /** 25 | * An abstract base class for `NativeStorage` 26 | * Inherit this class to create instances of `HybridNativeStorageSpec` in C++. 27 | * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. 28 | * @example 29 | * ```cpp 30 | * class HybridNativeStorage: public HybridNativeStorageSpec { 31 | * public: 32 | * HybridNativeStorage(...): HybridObject(TAG) { ... } 33 | * // ... 34 | * }; 35 | * ``` 36 | */ 37 | class HybridNativeStorageSpec: public virtual HybridObject { 38 | public: 39 | // Constructor 40 | explicit HybridNativeStorageSpec(): HybridObject(TAG) { } 41 | 42 | // Destructor 43 | ~HybridNativeStorageSpec() override = default; 44 | 45 | public: 46 | // Properties 47 | 48 | 49 | public: 50 | // Methods 51 | virtual std::string getString(const std::string& key) = 0; 52 | virtual void setString(const std::string& key, const std::string& value) = 0; 53 | virtual void removeString(const std::string& key) = 0; 54 | 55 | protected: 56 | // Hybrid Setup 57 | void loadHybridMethods() override; 58 | 59 | protected: 60 | // Tag for logging 61 | static constexpr auto TAG = "NativeStorage"; 62 | }; 63 | 64 | } // namespace margelo::nitro::nitrofetch 65 | -------------------------------------------------------------------------------- /package/nitrogen/generated/shared/c++/HybridNitroFetchSpec.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchSpec.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #if __has_include() 11 | #include 12 | #else 13 | #error NitroModules cannot be found! Are you sure you installed NitroModules properly? 14 | #endif 15 | 16 | // Forward declaration of `HybridNitroFetchClientSpec` to properly resolve imports. 17 | namespace margelo::nitro::nitrofetch { class HybridNitroFetchClientSpec; } 18 | 19 | #include 20 | #include "HybridNitroFetchClientSpec.hpp" 21 | 22 | namespace margelo::nitro::nitrofetch { 23 | 24 | using namespace margelo::nitro; 25 | 26 | /** 27 | * An abstract base class for `NitroFetch` 28 | * Inherit this class to create instances of `HybridNitroFetchSpec` in C++. 29 | * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. 30 | * @example 31 | * ```cpp 32 | * class HybridNitroFetch: public HybridNitroFetchSpec { 33 | * public: 34 | * HybridNitroFetch(...): HybridObject(TAG) { ... } 35 | * // ... 36 | * }; 37 | * ``` 38 | */ 39 | class HybridNitroFetchSpec: public virtual HybridObject { 40 | public: 41 | // Constructor 42 | explicit HybridNitroFetchSpec(): HybridObject(TAG) { } 43 | 44 | // Destructor 45 | ~HybridNitroFetchSpec() override = default; 46 | 47 | public: 48 | // Properties 49 | 50 | 51 | public: 52 | // Methods 53 | virtual std::shared_ptr createClient() = 0; 54 | 55 | protected: 56 | // Hybrid Setup 57 | void loadHybridMethods() override; 58 | 59 | protected: 60 | // Tag for logging 61 | static constexpr auto TAG = "NitroFetch"; 62 | }; 63 | 64 | } // namespace margelo::nitro::nitrofetch 65 | -------------------------------------------------------------------------------- /package/ios/NativeStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeStorage.swift 3 | // Pods 4 | // 5 | // Created by Ritesh Shukla on 08/11/25. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | final class NativeStorage: HybridNativeStorageSpec { 12 | 13 | private static let suiteName = "nitro_fetch_storage" 14 | 15 | private let userDefaults: UserDefaults 16 | 17 | public override init() { 18 | // Use a named suite for better isolation, fallback to standard if creation fails 19 | if let suite = UserDefaults(suiteName: NativeStorage.suiteName) { 20 | self.userDefaults = suite 21 | } else { 22 | self.userDefaults = UserDefaults.standard 23 | } 24 | super.init() 25 | } 26 | 27 | /// Retrieves a string value for the given key. 28 | /// 29 | /// - Parameter key: The key to look up in storage 30 | /// - Returns: The stored string value, or empty string if key doesn't exist 31 | /// - Throws: RuntimeError if the operation fails 32 | func getString(key: String) throws -> String { 33 | guard let value = userDefaults.string(forKey: key) else { 34 | return "" 35 | } 36 | return value 37 | } 38 | 39 | /// Stores a string value with the given key. 40 | /// 41 | /// - Parameters: 42 | /// - key: The key to store the value under 43 | /// - value: The string value to store 44 | /// - Throws: RuntimeError if the write operation fails 45 | func setString(key: String, value: String) throws { 46 | userDefaults.set(value, forKey: key) 47 | // Synchronize to ensure immediate persistence 48 | userDefaults.synchronize() 49 | } 50 | 51 | /// Deletes the value associated with the given key. 52 | /// If the key doesn't exist, this is a no-op. 53 | /// 54 | /// - Parameter key: The key to delete from storage 55 | /// - Throws: RuntimeError if the delete operation fails 56 | func removeString(key: String) throws { 57 | userDefaults.removeObject(forKey: key) 58 | // Synchronize to ensure immediate persistence 59 | userDefaults.synchronize() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt: -------------------------------------------------------------------------------- 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>() 10 | private val results = ConcurrentHashMap() 11 | 12 | fun getPending(key: String): CompletableFuture? = pending[key] 13 | 14 | fun setPending(key: String, future: CompletableFuture) { 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 | /** 45 | * Check if a fresh result exists WITHOUT consuming it. 46 | * Used to check if we should skip starting a new prefetch. 47 | */ 48 | fun hasFreshResult(key: String, maxAgeMs: Long): Boolean { 49 | val entry = results[key] ?: return false 50 | val age = System.currentTimeMillis() - entry.timestampMs 51 | return age <= maxAgeMs 52 | } 53 | 54 | fun clear() { 55 | pending.clear() 56 | results.clear() 57 | } 58 | } -------------------------------------------------------------------------------- /package/ios/NitroAutoPrefetcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(NitroAutoPrefetcher) 4 | public final class NitroAutoPrefetcher: NSObject { 5 | private static var initialized = false 6 | private static let queueKey = "nitrofetch_autoprefetch_queue" 7 | private static let suiteName = "nitro_fetch_storage" 8 | 9 | @objc 10 | public static func prefetchOnStart() { 11 | if initialized { return } 12 | initialized = true 13 | 14 | // Read from UserDefaults 15 | let userDefaults = UserDefaults(suiteName: suiteName) ?? UserDefaults.standard 16 | guard let raw = userDefaults.string(forKey: queueKey), !raw.isEmpty else { return } 17 | guard let data = raw.data(using: .utf8) else { return } 18 | guard let arr = try? JSONSerialization.jsonObject(with: data, options: []) as? [Any] else { return } 19 | 20 | for item in arr { 21 | guard let obj = item as? [String: Any] else { continue } 22 | guard let url = obj["url"] as? String, !url.isEmpty else { continue } 23 | guard let prefetchKey = obj["prefetchKey"] as? String, !prefetchKey.isEmpty else { continue } 24 | let headersDict = (obj["headers"] as? [String: Any]) ?? [:] 25 | var headers: [NitroHeader] = headersDict.map { (k, v) in NitroHeader(key: String(describing: k), value: String(describing: v)) } 26 | headers.append(NitroHeader(key: "prefetchKey", value: prefetchKey)) 27 | let req = NitroRequest(url: url, 28 | method: nil, 29 | headers: headers, 30 | bodyString: nil, 31 | bodyBytes: nil, 32 | timeoutMs: nil, 33 | followRedirects: true) 34 | Task { 35 | do { try await NitroFetchClient.prefetchStatic(req) } catch { /* ignore – best effort */ } 36 | } 37 | } 38 | } 39 | } 40 | 41 | // Expose a C-ABI symbol the ObjC++ file can call 42 | @_cdecl("NitroStartSwift") 43 | public func NitroStartSwift() { 44 | NitroAutoPrefetcher.prefetchOnStart() 45 | } 46 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JHybridNitroFetchSpec.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchSpec.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include "HybridNitroFetchSpec.hpp" 13 | 14 | 15 | 16 | 17 | namespace margelo::nitro::nitrofetch { 18 | 19 | using namespace facebook; 20 | 21 | class JHybridNitroFetchSpec: public jni::HybridClass, 22 | public virtual HybridNitroFetchSpec { 23 | public: 24 | static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrofetch/HybridNitroFetchSpec;"; 25 | static jni::local_ref initHybrid(jni::alias_ref jThis); 26 | static void registerNatives(); 27 | 28 | protected: 29 | // C++ constructor (called from Java via `initHybrid()`) 30 | explicit JHybridNitroFetchSpec(jni::alias_ref jThis) : 31 | HybridObject(HybridNitroFetchSpec::TAG), 32 | HybridBase(jThis), 33 | _javaPart(jni::make_global(jThis)) {} 34 | 35 | public: 36 | ~JHybridNitroFetchSpec() override { 37 | // Hermes GC can destroy JS objects on a non-JNI Thread. 38 | jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); 39 | } 40 | 41 | public: 42 | size_t getExternalMemorySize() noexcept override; 43 | void dispose() noexcept override; 44 | 45 | public: 46 | inline const jni::global_ref& getJavaPart() const noexcept { 47 | return _javaPart; 48 | } 49 | 50 | public: 51 | // Properties 52 | 53 | 54 | public: 55 | // Methods 56 | std::shared_ptr createClient() override; 57 | 58 | private: 59 | friend HybridBase; 60 | using HybridBase::HybridBase; 61 | jni::global_ref _javaPart; 62 | }; 63 | 64 | } // namespace margelo::nitro::nitrofetch 65 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Use this property to specify which architecture you want to build. 26 | # You can also override it from the CLI using 27 | # ./gradlew -PreactNativeArchitectures=x86_64 28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 29 | 30 | # Use this property to enable support to the new architecture. 31 | # This will allow you to use TurboModules and the Fabric render in 32 | # your application. You should enable this flag either if you want 33 | # to write custom TurboModules/Fabric components OR use libraries that 34 | # are providing them. 35 | newArchEnabled=true 36 | 37 | # Use this property to enable or disable the Hermes JS engine. 38 | # If set to false, you will be using JSC instead. 39 | hermesEnabled=true 40 | 41 | # Use this property to enable edge-to-edge display support. 42 | # This allows your app to draw behind system bars for an immersive UI. 43 | # Note: Only works with ReactActivity and should not be used with custom Activity. 44 | edgeToEdgeEnabled=false 45 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JHybridNativeStorageSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "JHybridNativeStorageSpec.hpp" 9 | 10 | 11 | 12 | #include 13 | 14 | namespace margelo::nitro::nitrofetch { 15 | 16 | jni::local_ref JHybridNativeStorageSpec::initHybrid(jni::alias_ref jThis) { 17 | return makeCxxInstance(jThis); 18 | } 19 | 20 | void JHybridNativeStorageSpec::registerNatives() { 21 | registerHybrid({ 22 | makeNativeMethod("initHybrid", JHybridNativeStorageSpec::initHybrid), 23 | }); 24 | } 25 | 26 | size_t JHybridNativeStorageSpec::getExternalMemorySize() noexcept { 27 | static const auto method = javaClassStatic()->getMethod("getMemorySize"); 28 | return method(_javaPart); 29 | } 30 | 31 | void JHybridNativeStorageSpec::dispose() noexcept { 32 | static const auto method = javaClassStatic()->getMethod("dispose"); 33 | method(_javaPart); 34 | } 35 | 36 | // Properties 37 | 38 | 39 | // Methods 40 | std::string JHybridNativeStorageSpec::getString(const std::string& key) { 41 | static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* key */)>("getString"); 42 | auto __result = method(_javaPart, jni::make_jstring(key)); 43 | return __result->toStdString(); 44 | } 45 | void JHybridNativeStorageSpec::setString(const std::string& key, const std::string& value) { 46 | static const auto method = javaClassStatic()->getMethod /* key */, jni::alias_ref /* value */)>("setString"); 47 | method(_javaPart, jni::make_jstring(key), jni::make_jstring(value)); 48 | } 49 | void JHybridNativeStorageSpec::removeString(const std::string& key) { 50 | static const auto method = javaClassStatic()->getMethod /* key */)>("removeString"); 51 | method(_javaPart, jni::make_jstring(key)); 52 | } 53 | 54 | } // namespace margelo::nitro::nitrofetch 55 | -------------------------------------------------------------------------------- /package/src/NitroFetch.nitro.ts: -------------------------------------------------------------------------------- 1 | import type { HybridObject } from 'react-native-nitro-modules'; 2 | 3 | // Minimal request/response types to model WHATWG fetch without streaming. 4 | export type NitroRequestMethod = 5 | | 'GET' 6 | | 'HEAD' 7 | | 'POST' 8 | | 'PUT' 9 | | 'PATCH' 10 | | 'DELETE' 11 | | 'OPTIONS'; 12 | 13 | export interface NitroHeader { 14 | key: string; 15 | value: string; 16 | } 17 | export interface NitroRequest { 18 | url: string; 19 | method?: NitroRequestMethod; 20 | // Flattened list to keep bridging simple and deterministic 21 | headers?: NitroHeader[]; 22 | // Body as either UTF-8 string or raw bytes. 23 | bodyString?: string; 24 | bodyBytes?: string; //will be ArrayBuffer in future 25 | // Controls 26 | timeoutMs?: number; 27 | followRedirects?: boolean; // default true 28 | } 29 | 30 | export interface NitroResponse { 31 | url: string; // final URL after redirects 32 | status: number; 33 | statusText: string; 34 | ok: boolean; 35 | redirected: boolean; 36 | headers: NitroHeader[]; 37 | // Body as either UTF-8 string or raw bytes (first implementation target) 38 | bodyString?: string; 39 | bodyBytes?: string; //will be ArrayBuffer in future 40 | } 41 | 42 | export interface NitroFetchClient 43 | extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { 44 | // Client-binded request that uses the env configured at creation. 45 | request(req: NitroRequest): Promise; 46 | // Start a prefetch for a given request; expects a header `prefetchKey`. 47 | prefetch(req: NitroRequest): Promise; 48 | 49 | // Synchronous version of request for worklets 50 | requestSync(req: NitroRequest): NitroResponse; 51 | } 52 | 53 | export interface NitroFetch 54 | extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { 55 | // Create a client bound to a given environment (e.g., cache dir). 56 | createClient(): NitroFetchClient; 57 | 58 | // Optional future: global abort/teardown 59 | // shutdown(): void; 60 | } 61 | 62 | export interface NativeStorage 63 | extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { 64 | getString(key: string): string; 65 | setString(key: string, value: string): void; 66 | removeString(key: string): void; 67 | } 68 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNativeStorageSpec.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include "HybridNativeStorageSpec.hpp" 13 | 14 | 15 | 16 | 17 | namespace margelo::nitro::nitrofetch { 18 | 19 | using namespace facebook; 20 | 21 | class JHybridNativeStorageSpec: public jni::HybridClass, 22 | public virtual HybridNativeStorageSpec { 23 | public: 24 | static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrofetch/HybridNativeStorageSpec;"; 25 | static jni::local_ref initHybrid(jni::alias_ref jThis); 26 | static void registerNatives(); 27 | 28 | protected: 29 | // C++ constructor (called from Java via `initHybrid()`) 30 | explicit JHybridNativeStorageSpec(jni::alias_ref jThis) : 31 | HybridObject(HybridNativeStorageSpec::TAG), 32 | HybridBase(jThis), 33 | _javaPart(jni::make_global(jThis)) {} 34 | 35 | public: 36 | ~JHybridNativeStorageSpec() override { 37 | // Hermes GC can destroy JS objects on a non-JNI Thread. 38 | jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); 39 | } 40 | 41 | public: 42 | size_t getExternalMemorySize() noexcept override; 43 | void dispose() noexcept override; 44 | 45 | public: 46 | inline const jni::global_ref& getJavaPart() const noexcept { 47 | return _javaPart; 48 | } 49 | 50 | public: 51 | // Properties 52 | 53 | 54 | public: 55 | // Methods 56 | std::string getString(const std::string& key) override; 57 | void setString(const std::string& key, const std::string& value) override; 58 | void removeString(const std::string& key) override; 59 | 60 | private: 61 | friend HybridBase; 62 | using HybridBase::HybridBase; 63 | jni::global_ref _javaPart; 64 | }; 65 | 66 | } // namespace margelo::nitro::nitrofetch 67 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/NitroFetch+autolinking.rb: -------------------------------------------------------------------------------- 1 | # 2 | # NitroFetch+autolinking.rb 3 | # This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | # https://github.com/mrousavy/nitro 5 | # Copyright © 2025 Marc Rousavy @ Margelo 6 | # 7 | 8 | # This is a Ruby script that adds all files generated by Nitrogen 9 | # to the given podspec. 10 | # 11 | # To use it, add this to your .podspec: 12 | # ```ruby 13 | # Pod::Spec.new do |spec| 14 | # # ... 15 | # 16 | # # Add all files generated by Nitrogen 17 | # load 'nitrogen/generated/ios/NitroFetch+autolinking.rb' 18 | # add_nitrogen_files(spec) 19 | # end 20 | # ``` 21 | 22 | def add_nitrogen_files(spec) 23 | Pod::UI.puts "[NitroModules] 🔥 NitroFetch is boosted by nitro!" 24 | 25 | spec.dependency "NitroModules" 26 | 27 | current_source_files = Array(spec.attributes_hash['source_files']) 28 | spec.source_files = current_source_files + [ 29 | # Generated cross-platform specs 30 | "nitrogen/generated/shared/**/*.{h,hpp,c,cpp,swift}", 31 | # Generated bridges for the cross-platform specs 32 | "nitrogen/generated/ios/**/*.{h,hpp,c,cpp,mm,swift}", 33 | ] 34 | 35 | current_public_header_files = Array(spec.attributes_hash['public_header_files']) 36 | spec.public_header_files = current_public_header_files + [ 37 | # Generated specs 38 | "nitrogen/generated/shared/**/*.{h,hpp}", 39 | # Swift to C++ bridging helpers 40 | "nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp" 41 | ] 42 | 43 | current_private_header_files = Array(spec.attributes_hash['private_header_files']) 44 | spec.private_header_files = current_private_header_files + [ 45 | # iOS specific specs 46 | "nitrogen/generated/ios/c++/**/*.{h,hpp}", 47 | # Views are framework-specific and should be private 48 | "nitrogen/generated/shared/**/views/**/*" 49 | ] 50 | 51 | current_pod_target_xcconfig = spec.attributes_hash['pod_target_xcconfig'] || {} 52 | spec.pod_target_xcconfig = current_pod_target_xcconfig.merge({ 53 | # Use C++ 20 54 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", 55 | # Enables C++ <-> Swift interop (by default it's only C) 56 | "SWIFT_OBJC_INTEROP_MODE" => "objcxx", 57 | # Enables stricter modular headers 58 | "DEFINES_MODULE" => "YES", 59 | }) 60 | end 61 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchClientSpec.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include "HybridNitroFetchClientSpec.hpp" 13 | 14 | 15 | 16 | 17 | namespace margelo::nitro::nitrofetch { 18 | 19 | using namespace facebook; 20 | 21 | class JHybridNitroFetchClientSpec: public jni::HybridClass, 22 | public virtual HybridNitroFetchClientSpec { 23 | public: 24 | static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec;"; 25 | static jni::local_ref initHybrid(jni::alias_ref jThis); 26 | static void registerNatives(); 27 | 28 | protected: 29 | // C++ constructor (called from Java via `initHybrid()`) 30 | explicit JHybridNitroFetchClientSpec(jni::alias_ref jThis) : 31 | HybridObject(HybridNitroFetchClientSpec::TAG), 32 | HybridBase(jThis), 33 | _javaPart(jni::make_global(jThis)) {} 34 | 35 | public: 36 | ~JHybridNitroFetchClientSpec() override { 37 | // Hermes GC can destroy JS objects on a non-JNI Thread. 38 | jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); 39 | } 40 | 41 | public: 42 | size_t getExternalMemorySize() noexcept override; 43 | void dispose() noexcept override; 44 | 45 | public: 46 | inline const jni::global_ref& getJavaPart() const noexcept { 47 | return _javaPart; 48 | } 49 | 50 | public: 51 | // Properties 52 | 53 | 54 | public: 55 | // Methods 56 | std::shared_ptr> request(const NitroRequest& req) override; 57 | std::shared_ptr> prefetch(const NitroRequest& req) override; 58 | NitroResponse requestSync(const NitroRequest& req) override; 59 | 60 | private: 61 | friend HybridBase; 62 | using HybridBase::HybridBase; 63 | jni::global_ref _javaPart; 64 | }; 65 | 66 | } // namespace margelo::nitro::nitrofetch 67 | -------------------------------------------------------------------------------- /package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchClientSpec.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #if __has_include() 11 | #include 12 | #else 13 | #error NitroModules cannot be found! Are you sure you installed NitroModules properly? 14 | #endif 15 | 16 | // Forward declaration of `NitroResponse` to properly resolve imports. 17 | namespace margelo::nitro::nitrofetch { struct NitroResponse; } 18 | // Forward declaration of `NitroRequest` to properly resolve imports. 19 | namespace margelo::nitro::nitrofetch { struct NitroRequest; } 20 | 21 | #include "NitroResponse.hpp" 22 | #include 23 | #include "NitroRequest.hpp" 24 | 25 | namespace margelo::nitro::nitrofetch { 26 | 27 | using namespace margelo::nitro; 28 | 29 | /** 30 | * An abstract base class for `NitroFetchClient` 31 | * Inherit this class to create instances of `HybridNitroFetchClientSpec` in C++. 32 | * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. 33 | * @example 34 | * ```cpp 35 | * class HybridNitroFetchClient: public HybridNitroFetchClientSpec { 36 | * public: 37 | * HybridNitroFetchClient(...): HybridObject(TAG) { ... } 38 | * // ... 39 | * }; 40 | * ``` 41 | */ 42 | class HybridNitroFetchClientSpec: public virtual HybridObject { 43 | public: 44 | // Constructor 45 | explicit HybridNitroFetchClientSpec(): HybridObject(TAG) { } 46 | 47 | // Destructor 48 | ~HybridNitroFetchClientSpec() override = default; 49 | 50 | public: 51 | // Properties 52 | 53 | 54 | public: 55 | // Methods 56 | virtual std::shared_ptr> request(const NitroRequest& req) = 0; 57 | virtual std::shared_ptr> prefetch(const NitroRequest& req) = 0; 58 | virtual NitroResponse requestSync(const NitroRequest& req) = 0; 59 | 60 | protected: 61 | // Hybrid Setup 62 | void loadHybridMethods() override; 63 | 64 | protected: 65 | // Tag for logging 66 | static constexpr auto TAG = "NitroFetchClient"; 67 | }; 68 | 69 | } // namespace margelo::nitro::nitrofetch 70 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## `fetch(input, init)` 4 | 5 | - Drop-in replacement for the global `fetch`. 6 | - Accepts `Headers`, array pairs, or plain object for `init.headers`. 7 | - Body supports: `string`, `URLSearchParams`, `ArrayBuffer`, and typed arrays. 8 | - Returns a `Response` when available; otherwise a minimal object with `arrayBuffer()`, `text()`, `json()`, and `headers`. 9 | 10 | Example 11 | 12 | ```ts 13 | import { fetch } from 'react-native-nitro-fetch'; 14 | const res = await fetch('https://jsonplaceholder.typicode.com/todos/1'); 15 | const data = await res.json(); 16 | ``` 17 | 18 | ## `nitroFetchOnWorklet(input, init, mapWorklet, options?)` 19 | 20 | - Runs the network request and then invokes `mapWorklet` on a worklet runtime (Android) or on JS as a fallback (iOS or when worklets not available). 21 | - `mapWorklet(payload)` receives `{ url, status, statusText, ok, redirected, headers, bodyBytes?, bodyString? }`. 22 | - `options.preferBytes` (default `false`) controls whether `bodyBytes` or `bodyString` is sent to the mapper. 23 | 24 | Example 25 | 26 | ```ts 27 | import { nitroFetchOnWorklet } from 'react-native-nitro-fetch'; 28 | 29 | const map = (payload: { bodyString?: string }) => { 30 | 'worklet'; 31 | return JSON.parse(payload.bodyString ?? '{}'); 32 | }; 33 | 34 | const data = await nitroFetchOnWorklet('https://httpbin.org/get', undefined, map, { preferBytes: false }); 35 | ``` 36 | 37 | ## `prefetch(input, init)` 38 | 39 | - Starts a background request in native when possible. Requires a `prefetchKey` provided either via `headers: { prefetchKey }` or `init.prefetchKey`. 40 | - Later, call `fetch(url, { headers: { prefetchKey } })` to consume a fresh or pending prefetched result. 41 | - On success, response will include header `nitroPrefetched: true`. 42 | 43 | ## `prefetchOnAppStart(input, { prefetchKey })` (Android) 44 | 45 | - Enqueues a request to be prefetched at the next app start by writing into Shared Preferences under `nitrofetch_autoprefetch_queue`. 46 | 47 | ## `removeFromAutoPrefetch(prefetchKey)` / `removeAllFromAutoprefetch()` (Android) 48 | 49 | - Utilities to manage the auto-prefetch queue. 50 | 51 | ## Types 52 | 53 | - `NitroRequest`: `{ url, method?, headers?: { key, value }[], bodyString?, bodyBytes?, timeoutMs?, followRedirects? }`. 54 | - `NitroResponse`: `{ url, status, statusText, ok, redirected, headers, bodyString?, bodyBytes? }`. 55 | 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: Report a reproducible bug or regression in this library. 3 | labels: [bug] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | # Bug report 9 | 10 | 👋 Hi! 11 | 12 | **Please fill the following carefully before opening a new issue ❗** 13 | *(Your issue may be closed if it doesn't provide the required pieces of information)* 14 | - type: checkboxes 15 | attributes: 16 | label: Before submitting a new issue 17 | description: Please perform simple checks first. 18 | options: 19 | - label: I tested using the latest version of the library, as the bug might be already fixed. 20 | required: true 21 | - label: I tested using a [supported version](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md) of react native. 22 | required: true 23 | - label: I checked for possible duplicate issues, with possible answers. 24 | required: true 25 | - type: textarea 26 | id: summary 27 | attributes: 28 | label: Bug summary 29 | description: | 30 | Provide a clear and concise description of what the bug is. 31 | If needed, you can also provide other samples: error messages / stack traces, screenshots, gifs, etc. 32 | validations: 33 | required: true 34 | - type: input 35 | id: library-version 36 | attributes: 37 | label: Library version 38 | description: What version of the library are you using? 39 | placeholder: "x.x.x" 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: react-native-info 44 | attributes: 45 | label: Environment info 46 | description: Run `react-native info` in your terminal and paste the results here. 47 | render: shell 48 | validations: 49 | required: true 50 | - type: textarea 51 | id: steps-to-reproduce 52 | attributes: 53 | label: Steps to reproduce 54 | description: | 55 | You must provide a clear list of steps and code to reproduce the problem. 56 | value: | 57 | 1. … 58 | 2. … 59 | validations: 60 | required: true 61 | - type: input 62 | id: reproducible-example 63 | attributes: 64 | label: Reproducible example repository 65 | description: Please provide a link to a repository on GitHub with a reproducible example. 66 | validations: 67 | required: true 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-nitro-fetch-monorepo", 3 | "packageManager": "bun@1.2.22", 4 | "private": true, 5 | "version": "0.1.4", 6 | "description": "Awesome Fetch :)", 7 | "author": "Szymon Kapala (https://x.com/Turbo_Szymon)", 8 | "workspaces": [ 9 | "example", 10 | "package" 11 | ], 12 | "repository": "https://github.com/margelo/react-native-nitro-fetch.git", 13 | "scripts": { 14 | "example": "bun --cwd example", 15 | "test": "jest", 16 | "typecheck": "bun --cwd package tsc", 17 | "lint": "eslint \"**/*.{js,ts,tsx}\"", 18 | "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", 19 | "prepare": "bun --cwd package bob build", 20 | "nitrogen": "bun --cwd package nitrogen", 21 | "release": "release-it --only-version" 22 | }, 23 | "devDependencies": { 24 | "@commitlint/config-conventional": "^19.6.0", 25 | "@eslint/compat": "^1.2.7", 26 | "@eslint/eslintrc": "^3.3.0", 27 | "@eslint/js": "^9.22.0", 28 | "@evilmartians/lefthook": "^1.5.0", 29 | "@react-native/babel-preset": "0.81.0", 30 | "@react-native/eslint-config": "^0.78.0", 31 | "@release-it/conventional-changelog": "^9.0.2", 32 | "@types/jest": "^29.5.5", 33 | "@types/react": "^19.1.0", 34 | "commitlint": "^19.6.1", 35 | "del-cli": "^5.1.0", 36 | "eslint": "^9.22.0", 37 | "eslint-config-prettier": "^10.1.1", 38 | "eslint-plugin-prettier": "^5.2.3", 39 | "jest": "^29.7.0", 40 | "nitro-codegen": "^0.29.2", 41 | "prettier": "^3.0.3", 42 | "react": "19.1.0", 43 | "react-native": "0.81.0", 44 | "react-native-builder-bob": "^0.40.13", 45 | "react-native-nitro-modules": "^0.29.2", 46 | "release-it": "^17.10.0", 47 | "turbo": "^1.10.7", 48 | "typescript": "^5.8.3" 49 | }, 50 | "commitlint": { 51 | "extends": [ 52 | "@commitlint/config-conventional" 53 | ] 54 | }, 55 | "release-it": { 56 | "git": { 57 | "commitMessage": "chore: release ${version}", 58 | "tagName": "v${version}" 59 | }, 60 | "npm": { 61 | "publish": false 62 | }, 63 | "github": { 64 | "release": true 65 | }, 66 | "plugins": { 67 | "@release-it/conventional-changelog": { 68 | "preset": { 69 | "name": "angular" 70 | } 71 | } 72 | } 73 | }, 74 | "prettier": { 75 | "quoteProps": "consistent", 76 | "singleQuote": true, 77 | "tabWidth": 2, 78 | "trailingComma": "es5", 79 | "useTabs": false 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/NitroFetchAutolinking.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroFetchAutolinking.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | public final class NitroFetchAutolinking { 9 | public typealias bridge = margelo.nitro.nitrofetch.bridge.swift 10 | 11 | /** 12 | * Creates an instance of a Swift class that implements `HybridNitroFetchSpec`, 13 | * and wraps it in a Swift class that can directly interop with C++ (`HybridNitroFetchSpec_cxx`) 14 | * 15 | * This is generated by Nitrogen and will initialize the class specified 16 | * in the `"autolinking"` property of `nitro.json` (in this case, `NitroFetch`). 17 | */ 18 | public static func createNitroFetch() -> bridge.std__shared_ptr_HybridNitroFetchSpec_ { 19 | let hybridObject = NitroFetch() 20 | return { () -> bridge.std__shared_ptr_HybridNitroFetchSpec_ in 21 | let __cxxWrapped = hybridObject.getCxxWrapper() 22 | return __cxxWrapped.getCxxPart() 23 | }() 24 | } 25 | 26 | /** 27 | * Creates an instance of a Swift class that implements `HybridNitroFetchClientSpec`, 28 | * and wraps it in a Swift class that can directly interop with C++ (`HybridNitroFetchClientSpec_cxx`) 29 | * 30 | * This is generated by Nitrogen and will initialize the class specified 31 | * in the `"autolinking"` property of `nitro.json` (in this case, `NitroFetchClient`). 32 | */ 33 | public static func createNitroFetchClient() -> bridge.std__shared_ptr_HybridNitroFetchClientSpec_ { 34 | let hybridObject = NitroFetchClient() 35 | return { () -> bridge.std__shared_ptr_HybridNitroFetchClientSpec_ in 36 | let __cxxWrapped = hybridObject.getCxxWrapper() 37 | return __cxxWrapped.getCxxPart() 38 | }() 39 | } 40 | 41 | /** 42 | * Creates an instance of a Swift class that implements `HybridNativeStorageSpec`, 43 | * and wraps it in a Swift class that can directly interop with C++ (`HybridNativeStorageSpec_cxx`) 44 | * 45 | * This is generated by Nitrogen and will initialize the class specified 46 | * in the `"autolinking"` property of `nitro.json` (in this case, `NativeStorage`). 47 | */ 48 | public static func createNativeStorage() -> bridge.std__shared_ptr_HybridNativeStorageSpec_ { 49 | let hybridObject = NativeStorage() 50 | return { () -> bridge.std__shared_ptr_HybridNativeStorageSpec_ in 51 | let __cxxWrapped = hybridObject.getCxxWrapper() 52 | return __cxxWrapped.getCxxPart() 53 | }() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/c++/HybridNitroFetchSpecSwift.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchSpecSwift.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include "HybridNitroFetchSpec.hpp" 11 | 12 | // Forward declaration of `HybridNitroFetchSpec_cxx` to properly resolve imports. 13 | namespace NitroFetch { class HybridNitroFetchSpec_cxx; } 14 | 15 | // Forward declaration of `HybridNitroFetchClientSpec` to properly resolve imports. 16 | namespace margelo::nitro::nitrofetch { class HybridNitroFetchClientSpec; } 17 | 18 | #include 19 | #include "HybridNitroFetchClientSpec.hpp" 20 | 21 | #include "NitroFetch-Swift-Cxx-Umbrella.hpp" 22 | 23 | namespace margelo::nitro::nitrofetch { 24 | 25 | /** 26 | * The C++ part of HybridNitroFetchSpec_cxx.swift. 27 | * 28 | * HybridNitroFetchSpecSwift (C++) accesses HybridNitroFetchSpec_cxx (Swift), and might 29 | * contain some additional bridging code for C++ <> Swift interop. 30 | * 31 | * Since this obviously introduces an overhead, I hope at some point in 32 | * the future, HybridNitroFetchSpec_cxx can directly inherit from the C++ class HybridNitroFetchSpec 33 | * to simplify the whole structure and memory management. 34 | */ 35 | class HybridNitroFetchSpecSwift: public virtual HybridNitroFetchSpec { 36 | public: 37 | // Constructor from a Swift instance 38 | explicit HybridNitroFetchSpecSwift(const NitroFetch::HybridNitroFetchSpec_cxx& swiftPart): 39 | HybridObject(HybridNitroFetchSpec::TAG), 40 | _swiftPart(swiftPart) { } 41 | 42 | public: 43 | // Get the Swift part 44 | inline NitroFetch::HybridNitroFetchSpec_cxx& getSwiftPart() noexcept { 45 | return _swiftPart; 46 | } 47 | 48 | public: 49 | inline size_t getExternalMemorySize() noexcept override { 50 | return _swiftPart.getMemorySize(); 51 | } 52 | void dispose() noexcept override { 53 | _swiftPart.dispose(); 54 | } 55 | 56 | public: 57 | // Properties 58 | 59 | 60 | public: 61 | // Methods 62 | inline std::shared_ptr createClient() override { 63 | auto __result = _swiftPart.createClient(); 64 | if (__result.hasError()) [[unlikely]] { 65 | std::rethrow_exception(__result.error()); 66 | } 67 | auto __value = std::move(__result.value()); 68 | return __value; 69 | } 70 | 71 | private: 72 | NitroFetch::HybridNitroFetchSpec_cxx _swiftPart; 73 | }; 74 | 75 | } // namespace margelo::nitro::nitrofetch 76 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/nitrofetchOnLoad.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// nitrofetchOnLoad.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #ifndef BUILDING_NITROFETCH_WITH_GENERATED_CMAKE_PROJECT 9 | #error nitrofetchOnLoad.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this? 10 | #endif 11 | 12 | #include "nitrofetchOnLoad.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "JHybridNitroFetchClientSpec.hpp" 19 | #include "JHybridNitroFetchSpec.hpp" 20 | #include "JHybridNativeStorageSpec.hpp" 21 | #include 22 | 23 | namespace margelo::nitro::nitrofetch { 24 | 25 | int initialize(JavaVM* vm) { 26 | using namespace margelo::nitro; 27 | using namespace margelo::nitro::nitrofetch; 28 | using namespace facebook; 29 | 30 | return facebook::jni::initialize(vm, [] { 31 | // Register native JNI methods 32 | margelo::nitro::nitrofetch::JHybridNitroFetchClientSpec::registerNatives(); 33 | margelo::nitro::nitrofetch::JHybridNitroFetchSpec::registerNatives(); 34 | margelo::nitro::nitrofetch::JHybridNativeStorageSpec::registerNatives(); 35 | 36 | // Register Nitro Hybrid Objects 37 | HybridObjectRegistry::registerHybridObjectConstructor( 38 | "NitroFetch", 39 | []() -> std::shared_ptr { 40 | static DefaultConstructableObject object("com/margelo/nitro/nitrofetch/NitroFetch"); 41 | auto instance = object.create(); 42 | return instance->cthis()->shared(); 43 | } 44 | ); 45 | HybridObjectRegistry::registerHybridObjectConstructor( 46 | "NitroFetchClient", 47 | []() -> std::shared_ptr { 48 | static DefaultConstructableObject object("com/margelo/nitro/nitrofetch/NitroFetchClient"); 49 | auto instance = object.create(); 50 | return instance->cthis()->shared(); 51 | } 52 | ); 53 | HybridObjectRegistry::registerHybridObjectConstructor( 54 | "NativeStorage", 55 | []() -> std::shared_ptr { 56 | static DefaultConstructableObject object("com/margelo/nitro/nitrofetch/NativeStorage"); 57 | auto instance = object.create(); 58 | return instance->cthis()->shared(); 59 | } 60 | ); 61 | }); 62 | } 63 | 64 | } // namespace margelo::nitro::nitrofetch 65 | -------------------------------------------------------------------------------- /package/nitrogen/generated/shared/c++/NitroHeader.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroHeader.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #if __has_include() 11 | #include 12 | #else 13 | #error NitroModules cannot be found! Are you sure you installed NitroModules properly? 14 | #endif 15 | #if __has_include() 16 | #include 17 | #else 18 | #error NitroModules cannot be found! Are you sure you installed NitroModules properly? 19 | #endif 20 | 21 | 22 | 23 | #include 24 | 25 | namespace margelo::nitro::nitrofetch { 26 | 27 | /** 28 | * A struct which can be represented as a JavaScript object (NitroHeader). 29 | */ 30 | struct NitroHeader { 31 | public: 32 | std::string key SWIFT_PRIVATE; 33 | std::string value SWIFT_PRIVATE; 34 | 35 | public: 36 | NitroHeader() = default; 37 | explicit NitroHeader(std::string key, std::string value): key(key), value(value) {} 38 | }; 39 | 40 | } // namespace margelo::nitro::nitrofetch 41 | 42 | namespace margelo::nitro { 43 | 44 | // C++ NitroHeader <> JS NitroHeader (object) 45 | template <> 46 | struct JSIConverter final { 47 | static inline margelo::nitro::nitrofetch::NitroHeader fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { 48 | jsi::Object obj = arg.asObject(runtime); 49 | return margelo::nitro::nitrofetch::NitroHeader( 50 | JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "key")), 51 | JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "value")) 52 | ); 53 | } 54 | static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitrofetch::NitroHeader& arg) { 55 | jsi::Object obj(runtime); 56 | obj.setProperty(runtime, "key", JSIConverter::toJSI(runtime, arg.key)); 57 | obj.setProperty(runtime, "value", JSIConverter::toJSI(runtime, arg.value)); 58 | return obj; 59 | } 60 | static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { 61 | if (!value.isObject()) { 62 | return false; 63 | } 64 | jsi::Object obj = value.getObject(runtime); 65 | if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "key"))) return false; 66 | if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "value"))) return false; 67 | return true; 68 | } 69 | }; 70 | 71 | } // namespace margelo::nitro 72 | -------------------------------------------------------------------------------- /package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.nitrofetch 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import org.json.JSONArray 6 | import org.json.JSONObject 7 | import java.util.concurrent.CompletableFuture 8 | 9 | 10 | object AutoPrefetcher { 11 | @Volatile private var initialized = false 12 | private const val KEY_QUEUE = "nitrofetch_autoprefetch_queue" 13 | private const val PREFS_NAME = "nitro_fetch_storage" 14 | 15 | fun prefetchOnStart(app: Application) { 16 | if (initialized) return 17 | initialized = true 18 | try { 19 | val prefs = app.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) 20 | val raw = prefs.getString(KEY_QUEUE, null) ?: "" 21 | if (raw.isEmpty()) return 22 | val arr = JSONArray(raw) 23 | for (i in 0 until arr.length()) { 24 | val o = arr.optJSONObject(i) ?: continue 25 | val url = o.optString("url", null) ?: continue 26 | val prefetchKey = o.optString("prefetchKey", null) ?: continue 27 | val headersObj = o.optJSONObject("headers") ?: JSONObject() 28 | val headersList = mutableListOf>() 29 | headersObj.keys().forEachRemaining { k -> 30 | headersList.add(k to headersObj.optString(k, "")) 31 | } 32 | // Ensure prefetchKey header is present 33 | headersList.add("prefetchKey" to prefetchKey) 34 | 35 | val headerObjs = headersList.map { (k, v) -> NitroHeader(k, v) }.toTypedArray() 36 | val req = NitroRequest( 37 | url = url, 38 | method = null, 39 | headers = headerObjs, 40 | bodyString = null, 41 | bodyBytes = null, 42 | timeoutMs = null, 43 | followRedirects = null 44 | ) 45 | 46 | // If already pending or fresh, skip starting a new one 47 | if (FetchCache.getPending(prefetchKey) != null) continue 48 | if (FetchCache.hasFreshResult(prefetchKey, 5_000L)) continue 49 | 50 | val future = CompletableFuture() 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 | -------------------------------------------------------------------------------- /package/android/CMakeLists.txt: -------------------------------------------------------------------------------- 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() 71 | -------------------------------------------------------------------------------- /docs/cronet-android.md: -------------------------------------------------------------------------------- 1 | Cronet C API integration (Android) 2 | 3 | High-level steps 4 | 5 | - Bring Cronet binaries: add Cronet AAR or native libs/headers to your project. Options: 6 | - Use Google Maven `org.chromium.net:cronet-embedded` (Java wrapper) and extract libcronet. Or 7 | - Use Cronet standalone `.so` and C headers from Chromium release artifacts. 8 | - Update `android/CMakeLists.txt` to link `cronet.aar` or `libcronet.**.so` and include headers. 9 | - Initialize engine once per process from C++ and keep a pointer globally. 10 | - Implement a thin C++ wrapper that: 11 | - Creates `Cronet_UrlRequestParams` from our `NitroRequest`. 12 | - Sets callbacks for redirect, headers received, read completed, finished, error. 13 | - Accumulates bytes to memory (MVP) or streams chunks via Nitro events (v2). 14 | - Resolves a `NitroResponse` with status, headers, final URL, and base64 body. 15 | - Expose a JNI function callable from Kotlin via the Nitro spec. 16 | 17 | CMake (sketch) 18 | 19 | ``` 20 | # After you obtain cronet headers + libs 21 | add_library(cronet SHARED IMPORTED) 22 | set_target_properties(cronet PROPERTIES IMPORTED_LOCATION 23 | ${CMAKE_SOURCE_DIR}/path/to/libcronet.112.0.0.0.so) 24 | target_include_directories(${PACKAGE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/path/to/cronet/include) 25 | target_link_libraries(${PACKAGE_NAME} cronet) 26 | ``` 27 | 28 | Engine init (C++) 29 | 30 | ``` 31 | // cronet_bridge.hpp 32 | #include 33 | 34 | bool cronet_init(); 35 | void cronet_shutdown(); 36 | bool cronet_request(const NitroRequest&, NitroResponse* out); 37 | 38 | // cronet_bridge.cpp 39 | static Cronet_EnginePtr g_engine = nullptr; 40 | bool cronet_init() { 41 | if (g_engine) return true; 42 | Cronet_EngineParamsPtr params = Cronet_EngineParams_Create(); 43 | Cronet_EngineParams_enable_quic_set(params, true); 44 | Cronet_EngineParams_user_agent_set(params, Cronet_String_Create("NitroFetch/0.1")); 45 | g_engine = Cronet_Engine_Create(); 46 | auto rc = Cronet_Engine_StartWithParams(g_engine, params); 47 | Cronet_EngineParams_Destroy(params); 48 | return rc == CRONET_RESULT_SUCCESS; 49 | } 50 | ``` 51 | 52 | Request (C++) 53 | 54 | ``` 55 | // Convert NitroRequest -> Cronet_UrlRequestParams 56 | // Create Cronet_UrlRequest with callbacks 57 | // In on_BytesRead, append to std::vector 58 | // In on_Success/on_Failed, fill NitroResponse fields and return 59 | ``` 60 | 61 | Kotlin hook 62 | 63 | - Implement `override suspend fun request(req: NitroRequest): NitroResponse` in `NitroFetch.kt`. 64 | - Call into JNI function that wraps `cronet_request` and returns a struct matching the generated Nitro type. 65 | 66 | Notes 67 | 68 | - For large bodies, prefer streaming in v2 (expose a request handle and chunk callbacks over Nitro). 69 | - Enable HTTP/2 and QUIC in engine params if needed. 70 | - Handle redirects, timeouts, and cancellation by keeping a map of active requests and calling `Cronet_UrlRequest_Cancel`. 71 | 72 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNativeStorageSpecSwift.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include "HybridNativeStorageSpec.hpp" 11 | 12 | // Forward declaration of `HybridNativeStorageSpec_cxx` to properly resolve imports. 13 | namespace NitroFetch { class HybridNativeStorageSpec_cxx; } 14 | 15 | 16 | 17 | #include 18 | 19 | #include "NitroFetch-Swift-Cxx-Umbrella.hpp" 20 | 21 | namespace margelo::nitro::nitrofetch { 22 | 23 | /** 24 | * The C++ part of HybridNativeStorageSpec_cxx.swift. 25 | * 26 | * HybridNativeStorageSpecSwift (C++) accesses HybridNativeStorageSpec_cxx (Swift), and might 27 | * contain some additional bridging code for C++ <> Swift interop. 28 | * 29 | * Since this obviously introduces an overhead, I hope at some point in 30 | * the future, HybridNativeStorageSpec_cxx can directly inherit from the C++ class HybridNativeStorageSpec 31 | * to simplify the whole structure and memory management. 32 | */ 33 | class HybridNativeStorageSpecSwift: public virtual HybridNativeStorageSpec { 34 | public: 35 | // Constructor from a Swift instance 36 | explicit HybridNativeStorageSpecSwift(const NitroFetch::HybridNativeStorageSpec_cxx& swiftPart): 37 | HybridObject(HybridNativeStorageSpec::TAG), 38 | _swiftPart(swiftPart) { } 39 | 40 | public: 41 | // Get the Swift part 42 | inline NitroFetch::HybridNativeStorageSpec_cxx& getSwiftPart() noexcept { 43 | return _swiftPart; 44 | } 45 | 46 | public: 47 | inline size_t getExternalMemorySize() noexcept override { 48 | return _swiftPart.getMemorySize(); 49 | } 50 | void dispose() noexcept override { 51 | _swiftPart.dispose(); 52 | } 53 | 54 | public: 55 | // Properties 56 | 57 | 58 | public: 59 | // Methods 60 | inline std::string getString(const std::string& key) override { 61 | auto __result = _swiftPart.getString(key); 62 | if (__result.hasError()) [[unlikely]] { 63 | std::rethrow_exception(__result.error()); 64 | } 65 | auto __value = std::move(__result.value()); 66 | return __value; 67 | } 68 | inline void setString(const std::string& key, const std::string& value) override { 69 | auto __result = _swiftPart.setString(key, value); 70 | if (__result.hasError()) [[unlikely]] { 71 | std::rethrow_exception(__result.error()); 72 | } 73 | } 74 | inline void removeString(const std::string& key) override { 75 | auto __result = _swiftPart.removeString(key); 76 | if (__result.hasError()) [[unlikely]] { 77 | std::rethrow_exception(__result.error()); 78 | } 79 | } 80 | 81 | private: 82 | NitroFetch::HybridNativeStorageSpec_cxx _swiftPart; 83 | }; 84 | 85 | } // namespace margelo::nitro::nitrofetch 86 | -------------------------------------------------------------------------------- /scripts/prepare_cronet_android_maven.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # Download prebuilt Cronet from Maven Central and extract native libs. 5 | # Optionally fetch Cronet C headers from Chromium source for the specified version. 6 | # This avoids a full Chromium checkout. 7 | # 8 | # Usage: 9 | # scripts/prepare_cronet_android_maven.sh --version 122.0.6261.69 --abis arm64-v8a,armeabi-v7a 10 | # 11 | # Notes: 12 | # - This script downloads from Maven Central; ensure you have network access. 13 | # - Headers are fetched from Chromium's source tree for convenience. Verify license/compatibility as needed. 14 | 15 | ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" 16 | VERSION="" 17 | ABIS="arm64-v8a" 18 | 19 | while [[ $# -gt 0 ]]; do 20 | case "$1" in 21 | --version) 22 | VERSION="$2"; shift 2 ;; 23 | --abis) 24 | ABIS="$2"; shift 2 ;; 25 | *) echo "Unknown arg: $1" >&2; exit 1 ;; 26 | esac 27 | done 28 | 29 | if [[ -z "$VERSION" ]]; then 30 | echo "--version is required (e.g., 122.0.6261.69)" >&2 31 | exit 1 32 | fi 33 | 34 | TMP_DIR="$(mktemp -d)" 35 | trap 'rm -rf "$TMP_DIR"' EXIT 36 | 37 | echo "Downloading cronet-embedded AAR $VERSION ..." 38 | CRONET_AAR_URL="https://repo1.maven.org/maven2/org/chromium/net/cronet-embedded/$VERSION/cronet-embedded-$VERSION.aar" 39 | curl -fL "$CRONET_AAR_URL" -o "$TMP_DIR/cronet-embedded.aar" 40 | 41 | echo "Extracting native libraries..." 42 | unzip -q -o "$TMP_DIR/cronet-embedded.aar" -d "$TMP_DIR/aar" 43 | 44 | DEST_LIBS_DIR="$ROOT_DIR/android/cronet/libs" 45 | mkdir -p "$DEST_LIBS_DIR" 46 | IFS=',' read -r -a ABI_ARR <<< "$ABIS" 47 | for ABI in "${ABI_ARR[@]}"; do 48 | mkdir -p "$DEST_LIBS_DIR/$ABI" 49 | if [[ -d "$TMP_DIR/aar/jni/$ABI" ]]; then 50 | cp "$TMP_DIR/aar/jni/$ABI"/*.so "$DEST_LIBS_DIR/$ABI/" 51 | echo "Copied libs for $ABI" 52 | else 53 | echo "Warning: ABI $ABI not found in AAR" 54 | fi 55 | done 56 | 57 | echo "Fetching Cronet C headers (cronet_c.h) for version $VERSION ..." 58 | DEST_INCLUDE="$ROOT_DIR/android/cronet/include/cronet" 59 | mkdir -p "$DEST_INCLUDE" 60 | 61 | # Best-effort: Use Chromium source at tag corresponding to major version. 62 | MAJOR="${VERSION%%.*}" 63 | # Try refs for Chromium tags where Cronet lives under components/cronet/native 64 | BASE_RAW="https://raw.githubusercontent.com/chromium/chromium" 65 | PATH_C_H="components/cronet/native/cronet_c.h" 66 | PATH_EXPORT_H="components/cronet/native/cronet_export.h" 67 | 68 | for REF in "$VERSION" "$MAJOR" "main"; do 69 | URL1="$BASE_RAW/$REF/$PATH_C_H" 70 | URL2="$BASE_RAW/$REF/$PATH_EXPORT_H" 71 | if curl -fsL "$URL1" -o "$DEST_INCLUDE/cronet_c.h"; then 72 | echo "Downloaded cronet_c.h from $REF" 73 | curl -fsL "$URL2" -o "$DEST_INCLUDE/cronet_export.h" || true 74 | break 75 | fi 76 | done 77 | 78 | if [[ ! -f "$DEST_INCLUDE/cronet_c.h" ]]; then 79 | echo "Warning: Failed to fetch cronet_c.h automatically. Please place headers under android/cronet/include/cronet/" 80 | fi 81 | 82 | echo "Done. Headers in android/cronet/include (cronet/...), libs in android/cronet/libs/." 83 | 84 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JNitroRequestMethod.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JNitroRequestMethod.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include 11 | #include "NitroRequestMethod.hpp" 12 | 13 | namespace margelo::nitro::nitrofetch { 14 | 15 | using namespace facebook; 16 | 17 | /** 18 | * The C++ JNI bridge between the C++ enum "NitroRequestMethod" and the the Kotlin enum "NitroRequestMethod". 19 | */ 20 | struct JNitroRequestMethod final: public jni::JavaClass { 21 | public: 22 | static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrofetch/NitroRequestMethod;"; 23 | 24 | public: 25 | /** 26 | * Convert this Java/Kotlin-based enum to the C++ enum NitroRequestMethod. 27 | */ 28 | [[maybe_unused]] 29 | [[nodiscard]] 30 | NitroRequestMethod toCpp() const { 31 | static const auto clazz = javaClassStatic(); 32 | static const auto fieldOrdinal = clazz->getField("value"); 33 | int ordinal = this->getFieldValue(fieldOrdinal); 34 | return static_cast(ordinal); 35 | } 36 | 37 | public: 38 | /** 39 | * Create a Java/Kotlin-based enum with the given C++ enum's value. 40 | */ 41 | [[maybe_unused]] 42 | static jni::alias_ref fromCpp(NitroRequestMethod value) { 43 | static const auto clazz = javaClassStatic(); 44 | static const auto fieldGET = clazz->getStaticField("GET"); 45 | static const auto fieldHEAD = clazz->getStaticField("HEAD"); 46 | static const auto fieldPOST = clazz->getStaticField("POST"); 47 | static const auto fieldPUT = clazz->getStaticField("PUT"); 48 | static const auto fieldPATCH = clazz->getStaticField("PATCH"); 49 | static const auto fieldDELETE = clazz->getStaticField("DELETE"); 50 | static const auto fieldOPTIONS = clazz->getStaticField("OPTIONS"); 51 | 52 | switch (value) { 53 | case NitroRequestMethod::GET: 54 | return clazz->getStaticFieldValue(fieldGET); 55 | case NitroRequestMethod::HEAD: 56 | return clazz->getStaticFieldValue(fieldHEAD); 57 | case NitroRequestMethod::POST: 58 | return clazz->getStaticFieldValue(fieldPOST); 59 | case NitroRequestMethod::PUT: 60 | return clazz->getStaticFieldValue(fieldPUT); 61 | case NitroRequestMethod::PATCH: 62 | return clazz->getStaticFieldValue(fieldPATCH); 63 | case NitroRequestMethod::DELETE: 64 | return clazz->getStaticFieldValue(fieldDELETE); 65 | case NitroRequestMethod::OPTIONS: 66 | return clazz->getStaticFieldValue(fieldOPTIONS); 67 | default: 68 | std::string stringValue = std::to_string(static_cast(value)); 69 | throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); 70 | } 71 | } 72 | }; 73 | 74 | } // namespace margelo::nitro::nitrofetch 75 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroFetch-Swift-Cxx-Umbrella.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | // Forward declarations of C++ defined types 11 | // Forward declaration of `HybridNativeStorageSpec` to properly resolve imports. 12 | namespace margelo::nitro::nitrofetch { class HybridNativeStorageSpec; } 13 | // Forward declaration of `HybridNitroFetchClientSpec` to properly resolve imports. 14 | namespace margelo::nitro::nitrofetch { class HybridNitroFetchClientSpec; } 15 | // Forward declaration of `HybridNitroFetchSpec` to properly resolve imports. 16 | namespace margelo::nitro::nitrofetch { class HybridNitroFetchSpec; } 17 | // Forward declaration of `NitroHeader` to properly resolve imports. 18 | namespace margelo::nitro::nitrofetch { struct NitroHeader; } 19 | // Forward declaration of `NitroRequestMethod` to properly resolve imports. 20 | namespace margelo::nitro::nitrofetch { enum class NitroRequestMethod; } 21 | // Forward declaration of `NitroRequest` to properly resolve imports. 22 | namespace margelo::nitro::nitrofetch { struct NitroRequest; } 23 | // Forward declaration of `NitroResponse` to properly resolve imports. 24 | namespace margelo::nitro::nitrofetch { struct NitroResponse; } 25 | 26 | // Include C++ defined types 27 | #include "HybridNativeStorageSpec.hpp" 28 | #include "HybridNitroFetchClientSpec.hpp" 29 | #include "HybridNitroFetchSpec.hpp" 30 | #include "NitroHeader.hpp" 31 | #include "NitroRequest.hpp" 32 | #include "NitroRequestMethod.hpp" 33 | #include "NitroResponse.hpp" 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | // C++ helpers for Swift 43 | #include "NitroFetch-Swift-Cxx-Bridge.hpp" 44 | 45 | // Common C++ types used in Swift 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | // Forward declarations of Swift defined types 52 | // Forward declaration of `HybridNativeStorageSpec_cxx` to properly resolve imports. 53 | namespace NitroFetch { class HybridNativeStorageSpec_cxx; } 54 | // Forward declaration of `HybridNitroFetchClientSpec_cxx` to properly resolve imports. 55 | namespace NitroFetch { class HybridNitroFetchClientSpec_cxx; } 56 | // Forward declaration of `HybridNitroFetchSpec_cxx` to properly resolve imports. 57 | namespace NitroFetch { class HybridNitroFetchSpec_cxx; } 58 | 59 | // Include Swift defined types 60 | #if __has_include("NitroFetch-Swift.h") 61 | // This header is generated by Xcode/Swift on every app build. 62 | // If it cannot be found, make sure the Swift module's name (= podspec name) is actually "NitroFetch". 63 | #include "NitroFetch-Swift.h" 64 | // Same as above, but used when building with frameworks (`use_frameworks`) 65 | #elif __has_include() 66 | #include 67 | #else 68 | #error NitroFetch's autogenerated Swift header cannot be found! Make sure the Swift module's name (= podspec name) is actually "NitroFetch", and try building the app first. 69 | #endif 70 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/nitrofetch+autolinking.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # nitrofetch+autolinking.cmake 3 | # This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | # https://github.com/mrousavy/nitro 5 | # Copyright © 2025 Marc Rousavy @ Margelo 6 | # 7 | 8 | # This is a CMake file that adds all files generated by Nitrogen 9 | # to the current CMake project. 10 | # 11 | # To use it, add this to your CMakeLists.txt: 12 | # ```cmake 13 | # include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/nitrofetch+autolinking.cmake) 14 | # ``` 15 | 16 | # Define a flag to check if we are building properly 17 | add_definitions(-DBUILDING_NITROFETCH_WITH_GENERATED_CMAKE_PROJECT) 18 | 19 | # Enable Raw Props parsing in react-native (for Nitro Views) 20 | add_definitions(-DRN_SERIALIZABLE_STATE) 21 | 22 | # Add all headers that were generated by Nitrogen 23 | include_directories( 24 | "../nitrogen/generated/shared/c++" 25 | "../nitrogen/generated/android/c++" 26 | "../nitrogen/generated/android/" 27 | ) 28 | 29 | # Add all .cpp sources that were generated by Nitrogen 30 | target_sources( 31 | # CMake project name (Android C++ library name) 32 | nitrofetch PRIVATE 33 | # Autolinking Setup 34 | ../nitrogen/generated/android/nitrofetchOnLoad.cpp 35 | # Shared Nitrogen C++ sources 36 | ../nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp 37 | ../nitrogen/generated/shared/c++/HybridNitroFetchSpec.cpp 38 | ../nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp 39 | # Android-specific Nitrogen C++ sources 40 | ../nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp 41 | ../nitrogen/generated/android/c++/JHybridNitroFetchSpec.cpp 42 | ../nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp 43 | ) 44 | 45 | # From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake 46 | # Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake 47 | target_compile_definitions( 48 | nitrofetch PRIVATE 49 | -DFOLLY_NO_CONFIG=1 50 | -DFOLLY_HAVE_CLOCK_GETTIME=1 51 | -DFOLLY_USE_LIBCPP=1 52 | -DFOLLY_CFG_NO_COROUTINES=1 53 | -DFOLLY_MOBILE=1 54 | -DFOLLY_HAVE_RECVMMSG=1 55 | -DFOLLY_HAVE_PTHREAD=1 56 | # Once we target android-23 above, we can comment 57 | # the following line. NDK uses GNU style stderror_r() after API 23. 58 | -DFOLLY_HAVE_XSI_STRERROR_R=1 59 | ) 60 | 61 | # Add all libraries required by the generated specs 62 | find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++ 63 | find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule) 64 | find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library 65 | 66 | # Link all libraries together 67 | target_link_libraries( 68 | nitrofetch 69 | fbjni::fbjni # <-- Facebook C++ JNI helpers 70 | ReactAndroid::jsi # <-- RN: JSI 71 | react-native-nitro-modules::NitroModules # <-- NitroModules Core :) 72 | ) 73 | 74 | # Link react-native (different prefab between RN 0.75 and RN 0.76) 75 | if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76) 76 | target_link_libraries( 77 | nitrofetch 78 | ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab 79 | ) 80 | else() 81 | target_link_libraries( 82 | nitrofetch 83 | ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core 84 | ) 85 | endif() 86 | -------------------------------------------------------------------------------- /example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.7) 5 | base64 6 | nkf 7 | rexml 8 | activesupport (7.2.2.2) 9 | base64 10 | benchmark (>= 0.3) 11 | bigdecimal 12 | concurrent-ruby (~> 1.0, >= 1.3.1) 13 | connection_pool (>= 2.2.5) 14 | drb 15 | i18n (>= 1.6, < 2) 16 | logger (>= 1.4.2) 17 | minitest (>= 5.1) 18 | securerandom (>= 0.3) 19 | tzinfo (~> 2.0, >= 2.0.5) 20 | addressable (2.8.7) 21 | public_suffix (>= 2.0.2, < 7.0) 22 | algoliasearch (1.27.5) 23 | httpclient (~> 2.8, >= 2.8.3) 24 | json (>= 1.5.1) 25 | atomos (0.1.3) 26 | base64 (0.3.0) 27 | benchmark (0.4.1) 28 | bigdecimal (3.2.3) 29 | claide (1.1.0) 30 | cocoapods (1.15.2) 31 | addressable (~> 2.8) 32 | claide (>= 1.0.2, < 2.0) 33 | cocoapods-core (= 1.15.2) 34 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 35 | cocoapods-downloader (>= 2.1, < 3.0) 36 | cocoapods-plugins (>= 1.0.0, < 2.0) 37 | cocoapods-search (>= 1.0.0, < 2.0) 38 | cocoapods-trunk (>= 1.6.0, < 2.0) 39 | cocoapods-try (>= 1.1.0, < 2.0) 40 | colored2 (~> 3.1) 41 | escape (~> 0.0.4) 42 | fourflusher (>= 2.3.0, < 3.0) 43 | gh_inspector (~> 1.0) 44 | molinillo (~> 0.8.0) 45 | nap (~> 1.0) 46 | ruby-macho (>= 2.3.0, < 3.0) 47 | xcodeproj (>= 1.23.0, < 2.0) 48 | cocoapods-core (1.15.2) 49 | activesupport (>= 5.0, < 8) 50 | addressable (~> 2.8) 51 | algoliasearch (~> 1.0) 52 | concurrent-ruby (~> 1.1) 53 | fuzzy_match (~> 2.0.4) 54 | nap (~> 1.0) 55 | netrc (~> 0.11) 56 | public_suffix (~> 4.0) 57 | typhoeus (~> 1.0) 58 | cocoapods-deintegrate (1.0.5) 59 | cocoapods-downloader (2.1) 60 | cocoapods-plugins (1.0.0) 61 | nap 62 | cocoapods-search (1.0.1) 63 | cocoapods-trunk (1.6.0) 64 | nap (>= 0.8, < 2.0) 65 | netrc (~> 0.11) 66 | cocoapods-try (1.2.0) 67 | colored2 (3.1.2) 68 | concurrent-ruby (1.3.3) 69 | connection_pool (2.5.4) 70 | drb (2.2.3) 71 | escape (0.0.4) 72 | ethon (0.15.0) 73 | ffi (>= 1.15.0) 74 | ffi (1.17.2) 75 | fourflusher (2.3.1) 76 | fuzzy_match (2.0.4) 77 | gh_inspector (1.1.3) 78 | httpclient (2.9.0) 79 | mutex_m 80 | i18n (1.14.7) 81 | concurrent-ruby (~> 1.0) 82 | json (2.13.2) 83 | logger (1.7.0) 84 | minitest (5.25.5) 85 | molinillo (0.8.0) 86 | mutex_m (0.3.0) 87 | nanaimo (0.3.0) 88 | nap (1.1.0) 89 | netrc (0.11.0) 90 | nkf (0.2.0) 91 | public_suffix (4.0.7) 92 | rexml (3.4.4) 93 | ruby-macho (2.5.1) 94 | securerandom (0.4.1) 95 | typhoeus (1.5.0) 96 | ethon (>= 0.9.0, < 0.16.0) 97 | tzinfo (2.0.6) 98 | concurrent-ruby (~> 1.0) 99 | xcodeproj (1.25.1) 100 | CFPropertyList (>= 2.3.3, < 4.0) 101 | atomos (~> 0.1.3) 102 | claide (>= 1.0.2, < 2.0) 103 | colored2 (~> 3.1) 104 | nanaimo (~> 0.3.0) 105 | rexml (>= 3.3.6, < 4.0) 106 | 107 | PLATFORMS 108 | ruby 109 | 110 | DEPENDENCIES 111 | activesupport (>= 6.1.7.5, != 7.1.0) 112 | benchmark 113 | bigdecimal 114 | cocoapods (>= 1.13, != 1.15.1, != 1.15.0) 115 | concurrent-ruby (< 1.3.4) 116 | logger 117 | mutex_m 118 | xcodeproj (< 1.26.0) 119 | 120 | RUBY VERSION 121 | ruby 3.3.0p0 122 | 123 | BUNDLED WITH 124 | 2.6.9 125 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @REM Copyright (c) Meta Platforms, Inc. and affiliates. 2 | @REM 3 | @REM This source code is licensed under the MIT license found in the 4 | @REM LICENSE file in the root directory of this source tree. 5 | 6 | @rem 7 | @rem Copyright 2015 the original author or authors. 8 | @rem 9 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 10 | @rem you may not use this file except in compliance with the License. 11 | @rem You may obtain a copy of the License at 12 | @rem 13 | @rem https://www.apache.org/licenses/LICENSE-2.0 14 | @rem 15 | @rem Unless required by applicable law or agreed to in writing, software 16 | @rem distributed under the License is distributed on an "AS IS" BASIS, 17 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | @rem See the License for the specific language governing permissions and 19 | @rem limitations under the License. 20 | @rem 21 | @rem SPDX-License-Identifier: Apache-2.0 22 | @rem 23 | 24 | @if "%DEBUG%"=="" @echo off 25 | @rem ########################################################################## 26 | @rem 27 | @rem Gradle startup script for Windows 28 | @rem 29 | @rem ########################################################################## 30 | 31 | @rem Set local scope for the variables with windows NT shell 32 | if "%OS%"=="Windows_NT" setlocal 33 | 34 | set DIRNAME=%~dp0 35 | if "%DIRNAME%"=="" set DIRNAME=. 36 | @rem This is normally unused 37 | set APP_BASE_NAME=%~n0 38 | set APP_HOME=%DIRNAME% 39 | 40 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 41 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 42 | 43 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 44 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 45 | 46 | @rem Find java.exe 47 | if defined JAVA_HOME goto findJavaFromJavaHome 48 | 49 | set JAVA_EXE=java.exe 50 | %JAVA_EXE% -version >NUL 2>&1 51 | if %ERRORLEVEL% equ 0 goto execute 52 | 53 | echo. 1>&2 54 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 55 | echo. 1>&2 56 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 57 | echo location of your Java installation. 1>&2 58 | 59 | goto fail 60 | 61 | :findJavaFromJavaHome 62 | set JAVA_HOME=%JAVA_HOME:"=% 63 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 64 | 65 | if exist "%JAVA_EXE%" goto execute 66 | 67 | echo. 1>&2 68 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 69 | echo. 1>&2 70 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 71 | echo location of your Java installation. 1>&2 72 | 73 | goto fail 74 | 75 | :execute 76 | @rem Setup the command line 77 | 78 | set CLASSPATH= 79 | 80 | 81 | @rem Execute Gradle 82 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 83 | 84 | :end 85 | @rem End local scope for the variables with windows NT shell 86 | if %ERRORLEVEL% equ 0 goto mainEnd 87 | 88 | :fail 89 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 90 | rem the _cmd.exe /c_ return code! 91 | set EXIT_CODE=%ERRORLEVEL% 92 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 93 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 94 | exit /b %EXIT_CODE% 95 | 96 | :mainEnd 97 | if "%OS%"=="Windows_NT" endlocal 98 | 99 | :omega 100 | -------------------------------------------------------------------------------- /example/ios/NitroFetchExample.xcodeproj/xcshareddata/xcschemes/NitroFetchExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetch.kt: -------------------------------------------------------------------------------- 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 | } 95 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). 2 | 3 | # Getting Started 4 | 5 | > **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. 6 | 7 | ## Step 1: Start Metro 8 | 9 | First, you will need to run **Metro**, the JavaScript build tool for React Native. 10 | 11 | To start the Metro dev server, run the following command from the root of your React Native project: 12 | 13 | ```sh 14 | # Using npm 15 | npm start 16 | 17 | # OR using Yarn 18 | yarn start 19 | ``` 20 | 21 | ## Step 2: Build and run your app 22 | 23 | With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app: 24 | 25 | ### Android 26 | 27 | ```sh 28 | # Using npm 29 | npm run android 30 | 31 | # OR using Yarn 32 | yarn android 33 | ``` 34 | 35 | ### iOS 36 | 37 | For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps). 38 | 39 | The first time you create a new project, run the Ruby bundler to install CocoaPods itself: 40 | 41 | ```sh 42 | bundle install 43 | ``` 44 | 45 | Then, and every time you update your native dependencies, run: 46 | 47 | ```sh 48 | bundle exec pod install 49 | ``` 50 | 51 | For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html). 52 | 53 | ```sh 54 | # Using npm 55 | npm run ios 56 | 57 | # OR using Yarn 58 | yarn ios 59 | ``` 60 | 61 | If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device. 62 | 63 | This is one way to run your app — you can also build it directly from Android Studio or Xcode. 64 | 65 | ## Step 3: Modify your app 66 | 67 | Now that you have successfully run the app, let's make changes! 68 | 69 | Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh). 70 | 71 | When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload: 72 | 73 | - **Android**: Press the R key twice or select **"Reload"** from the **Dev Menu**, accessed via Ctrl + M (Windows/Linux) or Cmd ⌘ + M (macOS). 74 | - **iOS**: Press R in iOS Simulator. 75 | 76 | ## Congratulations! :tada: 77 | 78 | You've successfully run and modified your React Native App. :partying_face: 79 | 80 | ### Now what? 81 | 82 | - If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). 83 | - If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started). 84 | 85 | # Troubleshooting 86 | 87 | If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. 88 | 89 | # Learn More 90 | 91 | To learn more about React Native, take a look at the following resources: 92 | 93 | - [React Native Website](https://reactnative.dev) - learn more about React Native. 94 | - [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. 95 | - [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. 96 | - [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. 97 | - [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. 98 | -------------------------------------------------------------------------------- /scripts/prepare_cronet_android.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # Prepare Chromium Cronet C API build and copy headers/libs into this repo. 5 | # 6 | # Requirements: 7 | # - macOS/Linux host with Python 3, git, Java (for Android tools), Ninja, and Android NDK if building for Android. 8 | # - Sufficient disk space (>30GB) for Chromium checkout. 9 | # 10 | # Usage examples: 11 | # scripts/prepare_cronet_android.sh --checkout /path/to/chromium --arch arm64-v8a 12 | # scripts/prepare_cronet_android.sh --checkout /path/to/chromium --arch armeabi-v7a 13 | # 14 | # Notes: 15 | # - By default builds Android arm64. Use --arch to change. 16 | # - This script will not modify your PATH permanently; it prepends depot_tools for this run. 17 | 18 | ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" 19 | CHROMIUM_DIR="" 20 | ARCH="arm64-v8a" # map to target_cpu 21 | 22 | while [[ $# -gt 0 ]]; do 23 | case "$1" in 24 | --checkout) 25 | CHROMIUM_DIR="$2"; shift 2 ;; 26 | --arch) 27 | ARCH="$2"; shift 2 ;; 28 | *) 29 | echo "Unknown arg: $1" >&2; exit 1 ;; 30 | esac 31 | done 32 | 33 | if [[ -z "$CHROMIUM_DIR" ]]; then 34 | echo "--checkout is required (Chromium source root)" >&2 35 | exit 1 36 | fi 37 | 38 | mkdir -p "$CHROMIUM_DIR" 39 | cd "$CHROMIUM_DIR" 40 | 41 | # 1) depot_tools 42 | if [[ ! -d depot_tools ]]; then 43 | echo "Cloning depot_tools..." 44 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 45 | fi 46 | export PATH="$CHROMIUM_DIR/depot_tools:$PATH" 47 | 48 | # 2) Fetch chromium src 49 | if [[ ! -d src ]]; then 50 | echo "Fetching chromium (this may take a while)..." 51 | fetch --nohooks chromium 52 | fi 53 | cd src 54 | 55 | echo "Running gclient runhooks..." 56 | gclient runhooks 57 | 58 | # 3) Configure GN args 59 | TARGET_CPU="arm64" 60 | case "$ARCH" in 61 | arm64-v8a) TARGET_CPU="arm64" ;; 62 | armeabi-v7a) TARGET_CPU="arm" ;; 63 | x86_64) TARGET_CPU="x64" ;; 64 | x86) TARGET_CPU="x86" ;; 65 | *) echo "Unsupported --arch: $ARCH" >&2; exit 1 ;; 66 | esac 67 | 68 | OUT_DIR="out/cronet_$TARGET_CPU" 69 | ARGS=( 70 | target_os=\"android\" 71 | target_cpu=\"$TARGET_CPU\" 72 | is_debug=false 73 | is_component_build=false 74 | symbol_level=0 75 | ) 76 | 77 | echo "Generating GN files for $TARGET_CPU..." 78 | gn gen "$OUT_DIR" --args="${ARGS[*]}" 79 | 80 | echo "Building cronet_package..." 81 | autoninja -C "$OUT_DIR" cronet_package 82 | 83 | # 4) Find and extract cronet package artifacts 84 | PKG_DIR="$OUT_DIR/cronet" 85 | ZIP_CANDIDATE=$(ls "$OUT_DIR"/cronet_*.zip 2>/dev/null | head -n1 || true) 86 | 87 | if [[ -d "$PKG_DIR" ]]; then 88 | echo "Found cronet package directory: $PKG_DIR" 89 | elif [[ -n "$ZIP_CANDIDATE" ]]; then 90 | echo "Unzipping $ZIP_CANDIDATE..." 91 | unzip -q -o "$ZIP_CANDIDATE" -d "$OUT_DIR" 92 | PKG_DIR="$OUT_DIR/cronet" 93 | else 94 | echo "Could not find cronet package output in $OUT_DIR" >&2 95 | exit 1 96 | fi 97 | 98 | if [[ ! -d "$PKG_DIR/include" ]]; then 99 | echo "Cronet package missing include/ in $PKG_DIR" >&2 100 | exit 1 101 | fi 102 | 103 | # 5) Copy headers + libs into the RN repo under android/cronet 104 | DEST_DIR="$ROOT_DIR/android/cronet" 105 | mkdir -p "$DEST_DIR" 106 | 107 | echo "Copying headers to $DEST_DIR/include ..." 108 | rm -rf "$DEST_DIR/include" 109 | cp -R "$PKG_DIR/include" "$DEST_DIR/" 110 | 111 | echo "Copying libraries for $ARCH ..." 112 | mkdir -p "$DEST_DIR/libs/$ARCH" 113 | # Typical locations under the package 114 | if ls "$PKG_DIR/libs/$ARCH"/*.so >/dev/null 2>&1; then 115 | cp "$PKG_DIR/libs/$ARCH"/*.so "$DEST_DIR/libs/$ARCH/" 116 | else 117 | # Fallback: search any .so files 118 | find "$PKG_DIR" -name "*.so" -exec cp {} "$DEST_DIR/libs/$ARCH/" \; 119 | fi 120 | 121 | echo "Cronet prepared under $DEST_DIR" 122 | echo "CMake will auto-detect headers and libs when building the Android library." 123 | 124 | -------------------------------------------------------------------------------- /package/android/build.gradle: -------------------------------------------------------------------------------- 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 | 127 | configurations { 128 | cronetAar 129 | } 130 | 131 | 132 | // No automatic fetching of Cronet headers/libs; library uses Cronet Java API only. 133 | -------------------------------------------------------------------------------- /package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroFetchClientSpecSwift.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include "HybridNitroFetchClientSpec.hpp" 11 | 12 | // Forward declaration of `HybridNitroFetchClientSpec_cxx` to properly resolve imports. 13 | namespace NitroFetch { class HybridNitroFetchClientSpec_cxx; } 14 | 15 | // Forward declaration of `NitroResponse` to properly resolve imports. 16 | namespace margelo::nitro::nitrofetch { struct NitroResponse; } 17 | // Forward declaration of `NitroHeader` to properly resolve imports. 18 | namespace margelo::nitro::nitrofetch { struct NitroHeader; } 19 | // Forward declaration of `NitroRequest` to properly resolve imports. 20 | namespace margelo::nitro::nitrofetch { struct NitroRequest; } 21 | // Forward declaration of `NitroRequestMethod` to properly resolve imports. 22 | namespace margelo::nitro::nitrofetch { enum class NitroRequestMethod; } 23 | 24 | #include "NitroResponse.hpp" 25 | #include 26 | #include 27 | #include "NitroHeader.hpp" 28 | #include 29 | #include 30 | #include "NitroRequest.hpp" 31 | #include "NitroRequestMethod.hpp" 32 | 33 | #include "NitroFetch-Swift-Cxx-Umbrella.hpp" 34 | 35 | namespace margelo::nitro::nitrofetch { 36 | 37 | /** 38 | * The C++ part of HybridNitroFetchClientSpec_cxx.swift. 39 | * 40 | * HybridNitroFetchClientSpecSwift (C++) accesses HybridNitroFetchClientSpec_cxx (Swift), and might 41 | * contain some additional bridging code for C++ <> Swift interop. 42 | * 43 | * Since this obviously introduces an overhead, I hope at some point in 44 | * the future, HybridNitroFetchClientSpec_cxx can directly inherit from the C++ class HybridNitroFetchClientSpec 45 | * to simplify the whole structure and memory management. 46 | */ 47 | class HybridNitroFetchClientSpecSwift: public virtual HybridNitroFetchClientSpec { 48 | public: 49 | // Constructor from a Swift instance 50 | explicit HybridNitroFetchClientSpecSwift(const NitroFetch::HybridNitroFetchClientSpec_cxx& swiftPart): 51 | HybridObject(HybridNitroFetchClientSpec::TAG), 52 | _swiftPart(swiftPart) { } 53 | 54 | public: 55 | // Get the Swift part 56 | inline NitroFetch::HybridNitroFetchClientSpec_cxx& getSwiftPart() noexcept { 57 | return _swiftPart; 58 | } 59 | 60 | public: 61 | inline size_t getExternalMemorySize() noexcept override { 62 | return _swiftPart.getMemorySize(); 63 | } 64 | void dispose() noexcept override { 65 | _swiftPart.dispose(); 66 | } 67 | 68 | public: 69 | // Properties 70 | 71 | 72 | public: 73 | // Methods 74 | inline std::shared_ptr> request(const NitroRequest& req) override { 75 | auto __result = _swiftPart.request(req); 76 | if (__result.hasError()) [[unlikely]] { 77 | std::rethrow_exception(__result.error()); 78 | } 79 | auto __value = std::move(__result.value()); 80 | return __value; 81 | } 82 | inline std::shared_ptr> prefetch(const NitroRequest& req) override { 83 | auto __result = _swiftPart.prefetch(req); 84 | if (__result.hasError()) [[unlikely]] { 85 | std::rethrow_exception(__result.error()); 86 | } 87 | auto __value = std::move(__result.value()); 88 | return __value; 89 | } 90 | inline NitroResponse requestSync(const NitroRequest& req) override { 91 | auto __result = _swiftPart.requestSync(req); 92 | if (__result.hasError()) [[unlikely]] { 93 | std::rethrow_exception(__result.error()); 94 | } 95 | auto __value = std::move(__result.value()); 96 | return __value; 97 | } 98 | 99 | private: 100 | NitroFetch::HybridNitroFetchClientSpec_cxx _swiftPart; 101 | }; 102 | 103 | } // namespace margelo::nitro::nitrofetch 104 | -------------------------------------------------------------------------------- /package/android/src/main/java/com/margelo/nitro/nitrofetch/NativeStorage.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.nitrofetch 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.content.SharedPreferences 6 | import android.util.Log 7 | import com.facebook.proguard.annotations.DoNotStrip 8 | import com.margelo.nitro.NitroModules 9 | 10 | 11 | 12 | @DoNotStrip 13 | class NativeStorage : HybridNativeStorageSpec() { 14 | 15 | companion object { 16 | private const val TAG = "HybridNativeStorage" 17 | private const val PREFS_NAME = "nitro_fetch_storage" 18 | 19 | 20 | private val sharedPreferences: SharedPreferences by lazy { 21 | val context = NitroModules.applicationContext ?: throw Error("Cannot get Android Context - No Context available!") 22 | context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) 23 | } 24 | 25 | 26 | } 27 | 28 | /** 29 | * Retrieves a string value for the given key. 30 | * 31 | * @param key The key to look up in storage 32 | * @return The stored string value, or empty string if key doesn't exist 33 | * @throws IllegalStateException if SharedPreferences is not available 34 | */ 35 | override fun getString(key: String): String { 36 | return try { 37 | val value = sharedPreferences.getString(key, null) 38 | if (value != null) { 39 | Log.d(TAG, "Retrieved value for key: $key") 40 | value 41 | } else { 42 | Log.d(TAG, "Key not found: $key, returning empty string") 43 | "" 44 | } 45 | } catch (t: Throwable) { 46 | Log.e(TAG, "Error getting string for key: $key", t) 47 | throw RuntimeException("Failed to get string for key: $key", t) 48 | } 49 | } 50 | 51 | /** 52 | * Stores a string value with the given key. 53 | * 54 | * @param key The key to store the value under 55 | * @param value The string value to store 56 | * @throws IllegalStateException if SharedPreferences is not available 57 | * @throws RuntimeException if the write operation fails 58 | */ 59 | override fun setString(key: String, value: String) { 60 | try { 61 | val editor = sharedPreferences.edit() 62 | editor.putString(key, value) 63 | val success = editor.commit() // commit() is synchronous and returns boolean 64 | if (success) { 65 | Log.d(TAG, "Successfully stored value for key: $key") 66 | } else { 67 | Log.e(TAG, "Failed to commit value for key: $key") 68 | throw RuntimeException("Failed to store value for key: $key") 69 | } 70 | } catch (t: Throwable) { 71 | Log.e(TAG, "Error setting string for key: $key", t) 72 | throw RuntimeException("Failed to set string for key: $key", t) 73 | } 74 | } 75 | 76 | /** 77 | * Deletes the value associated with the given key. 78 | * If the key doesn't exist, this is a no-op. 79 | * 80 | * @param key The key to delete from storage 81 | * @throws IllegalStateException if SharedPreferences is not available 82 | * @throws RuntimeException if the delete operation fails 83 | */ 84 | override fun removeString(key: String) { 85 | try { 86 | val editor = sharedPreferences.edit() 87 | editor.remove(key) 88 | val success = editor.commit() // commit() is synchronous and returns boolean 89 | if (success) { 90 | Log.d(TAG, "Successfully deleted key: $key") 91 | } else { 92 | Log.e(TAG, "Failed to commit deletion for key: $key") 93 | throw RuntimeException("Failed to delete key: $key") 94 | } 95 | } catch (t: Throwable) { 96 | Log.e(TAG, "Error deleting key: $key", t) 97 | throw RuntimeException("Failed to delete key: $key", t) 98 | } 99 | } 100 | } 101 | 102 | 103 | -------------------------------------------------------------------------------- /example/ios/NitroFetchExample/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JHybridNitroFetchClientSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "JHybridNitroFetchClientSpec.hpp" 9 | 10 | // Forward declaration of `NitroResponse` to properly resolve imports. 11 | namespace margelo::nitro::nitrofetch { struct NitroResponse; } 12 | // Forward declaration of `NitroHeader` to properly resolve imports. 13 | namespace margelo::nitro::nitrofetch { struct NitroHeader; } 14 | // Forward declaration of `NitroRequest` to properly resolve imports. 15 | namespace margelo::nitro::nitrofetch { struct NitroRequest; } 16 | // Forward declaration of `NitroRequestMethod` to properly resolve imports. 17 | namespace margelo::nitro::nitrofetch { enum class NitroRequestMethod; } 18 | 19 | #include "NitroResponse.hpp" 20 | #include 21 | #include 22 | #include "JNitroResponse.hpp" 23 | #include 24 | #include "NitroHeader.hpp" 25 | #include 26 | #include "JNitroHeader.hpp" 27 | #include 28 | #include "NitroRequest.hpp" 29 | #include "JNitroRequest.hpp" 30 | #include "NitroRequestMethod.hpp" 31 | #include "JNitroRequestMethod.hpp" 32 | 33 | namespace margelo::nitro::nitrofetch { 34 | 35 | jni::local_ref JHybridNitroFetchClientSpec::initHybrid(jni::alias_ref jThis) { 36 | return makeCxxInstance(jThis); 37 | } 38 | 39 | void JHybridNitroFetchClientSpec::registerNatives() { 40 | registerHybrid({ 41 | makeNativeMethod("initHybrid", JHybridNitroFetchClientSpec::initHybrid), 42 | }); 43 | } 44 | 45 | size_t JHybridNitroFetchClientSpec::getExternalMemorySize() noexcept { 46 | static const auto method = javaClassStatic()->getMethod("getMemorySize"); 47 | return method(_javaPart); 48 | } 49 | 50 | void JHybridNitroFetchClientSpec::dispose() noexcept { 51 | static const auto method = javaClassStatic()->getMethod("dispose"); 52 | method(_javaPart); 53 | } 54 | 55 | // Properties 56 | 57 | 58 | // Methods 59 | std::shared_ptr> JHybridNitroFetchClientSpec::request(const NitroRequest& req) { 60 | static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* req */)>("request"); 61 | auto __result = method(_javaPart, JNitroRequest::fromCpp(req)); 62 | return [&]() { 63 | auto __promise = Promise::create(); 64 | __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { 65 | auto __result = jni::static_ref_cast(__boxedResult); 66 | __promise->resolve(__result->toCpp()); 67 | }); 68 | __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { 69 | jni::JniException __jniError(__throwable); 70 | __promise->reject(std::make_exception_ptr(__jniError)); 71 | }); 72 | return __promise; 73 | }(); 74 | } 75 | std::shared_ptr> JHybridNitroFetchClientSpec::prefetch(const NitroRequest& req) { 76 | static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* req */)>("prefetch"); 77 | auto __result = method(_javaPart, JNitroRequest::fromCpp(req)); 78 | return [&]() { 79 | auto __promise = Promise::create(); 80 | __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& /* unit */) { 81 | __promise->resolve(); 82 | }); 83 | __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { 84 | jni::JniException __jniError(__throwable); 85 | __promise->reject(std::make_exception_ptr(__jniError)); 86 | }); 87 | return __promise; 88 | }(); 89 | } 90 | NitroResponse JHybridNitroFetchClientSpec::requestSync(const NitroRequest& req) { 91 | static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* req */)>("requestSync"); 92 | auto __result = method(_javaPart, JNitroRequest::fromCpp(req)); 93 | return __result->toCpp(); 94 | } 95 | 96 | } // namespace margelo::nitro::nitrofetch 97 | -------------------------------------------------------------------------------- /package/nitrogen/generated/android/c++/JNitroResponse.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JNitroResponse.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include 11 | #include "NitroResponse.hpp" 12 | 13 | #include "JNitroHeader.hpp" 14 | #include "NitroHeader.hpp" 15 | #include 16 | #include 17 | #include 18 | 19 | namespace margelo::nitro::nitrofetch { 20 | 21 | using namespace facebook; 22 | 23 | /** 24 | * The C++ JNI bridge between the C++ struct "NitroResponse" and the the Kotlin data class "NitroResponse". 25 | */ 26 | struct JNitroResponse final: public jni::JavaClass { 27 | public: 28 | static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrofetch/NitroResponse;"; 29 | 30 | public: 31 | /** 32 | * Convert this Java/Kotlin-based struct to the C++ struct NitroResponse by copying all values to C++. 33 | */ 34 | [[maybe_unused]] 35 | [[nodiscard]] 36 | NitroResponse toCpp() const { 37 | static const auto clazz = javaClassStatic(); 38 | static const auto fieldUrl = clazz->getField("url"); 39 | jni::local_ref url = this->getFieldValue(fieldUrl); 40 | static const auto fieldStatus = clazz->getField("status"); 41 | double status = this->getFieldValue(fieldStatus); 42 | static const auto fieldStatusText = clazz->getField("statusText"); 43 | jni::local_ref statusText = this->getFieldValue(fieldStatusText); 44 | static const auto fieldOk = clazz->getField("ok"); 45 | jboolean ok = this->getFieldValue(fieldOk); 46 | static const auto fieldRedirected = clazz->getField("redirected"); 47 | jboolean redirected = this->getFieldValue(fieldRedirected); 48 | static const auto fieldHeaders = clazz->getField>("headers"); 49 | jni::local_ref> headers = this->getFieldValue(fieldHeaders); 50 | static const auto fieldBodyString = clazz->getField("bodyString"); 51 | jni::local_ref bodyString = this->getFieldValue(fieldBodyString); 52 | static const auto fieldBodyBytes = clazz->getField("bodyBytes"); 53 | jni::local_ref bodyBytes = this->getFieldValue(fieldBodyBytes); 54 | return NitroResponse( 55 | url->toStdString(), 56 | status, 57 | statusText->toStdString(), 58 | static_cast(ok), 59 | static_cast(redirected), 60 | [&]() { 61 | size_t __size = headers->size(); 62 | std::vector __vector; 63 | __vector.reserve(__size); 64 | for (size_t __i = 0; __i < __size; __i++) { 65 | auto __element = headers->getElement(__i); 66 | __vector.push_back(__element->toCpp()); 67 | } 68 | return __vector; 69 | }(), 70 | bodyString != nullptr ? std::make_optional(bodyString->toStdString()) : std::nullopt, 71 | bodyBytes != nullptr ? std::make_optional(bodyBytes->toStdString()) : std::nullopt 72 | ); 73 | } 74 | 75 | public: 76 | /** 77 | * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. 78 | */ 79 | [[maybe_unused]] 80 | static jni::local_ref fromCpp(const NitroResponse& value) { 81 | return newInstance( 82 | jni::make_jstring(value.url), 83 | value.status, 84 | jni::make_jstring(value.statusText), 85 | value.ok, 86 | value.redirected, 87 | [&]() { 88 | size_t __size = value.headers.size(); 89 | jni::local_ref> __array = jni::JArrayClass::newArray(__size); 90 | for (size_t __i = 0; __i < __size; __i++) { 91 | const auto& __element = value.headers[__i]; 92 | __array->setElement(__i, *JNitroHeader::fromCpp(__element)); 93 | } 94 | return __array; 95 | }(), 96 | value.bodyString.has_value() ? jni::make_jstring(value.bodyString.value()) : nullptr, 97 | value.bodyBytes.has_value() ? jni::make_jstring(value.bodyBytes.value()) : nullptr 98 | ); 99 | } 100 | }; 101 | 102 | } // namespace margelo::nitro::nitrofetch 103 | -------------------------------------------------------------------------------- /docs/prefetch.md: -------------------------------------------------------------------------------- 1 | # Prefetch 2 | 3 | `prefetch()` starts a native request in the background (when available) and lets you consume the result later using the same `prefetchKey`. 4 | 5 | ## Basics 6 | 7 | ```ts 8 | import { fetch, prefetch } from 'react-native-nitro-fetch'; 9 | 10 | // 1) Start prefetch 11 | await prefetch('https://httpbin.org/uuid', { headers: { prefetchKey: 'uuid' } }); 12 | 13 | // 2) Consume later 14 | const res = await fetch('https://httpbin.org/uuid', { headers: { prefetchKey: 'uuid' } }); 15 | console.log('prefetched?', res.headers.get('nitroPrefetched')); 16 | ``` 17 | 18 | Provide the `prefetchKey` either as a header or via `init.prefetchKey`: 19 | 20 | ```ts 21 | await prefetch('https://httpbin.org/uuid', { prefetchKey: 'uuid' } as any); 22 | ``` 23 | 24 | ## Auto-Prefetch on Android 25 | 26 | Use `prefetchOnAppStart()` to enqueue requests in Shared Preferences so they are fetched on next app start: 27 | 28 | ```ts 29 | import { prefetchOnAppStart } from 'react-native-nitro-fetch'; 30 | await prefetchOnAppStart('https://httpbin.org/uuid', { prefetchKey: 'uuid' }); 31 | ``` 32 | 33 | Manage the queue: 34 | 35 | ```ts 36 | import { removeFromAutoPrefetch, removeAllFromAutoprefetch } from 'react-native-nitro-fetch'; 37 | await removeFromAutoPrefetch('uuid'); 38 | await removeAllFromAutoprefetch(); 39 | ``` 40 | 41 | Notes 42 | 43 | - Prefetch is best-effort; if native is unavailable, calls are ignored or fall back to JS fetch. 44 | - Responses served from prefetch add header `nitroPrefetched: true`. 45 | 46 | ## Why Prefetch Is Cool 47 | 48 | - Earlier start at app launch: Auto‑prefetch can kick off network work immediately when the process starts, before React and JS are ready. On mid‑range Android devices (e.g., Samsung A16), we observed the prefetch starting at least ~220 ms earlier than triggering the same request from JS after the app warms up. 49 | - Smoother navigation: Trigger a prefetch when the user initiates navigation, then serve the prefetched result as the destination screen mounts. 50 | 51 | ### Pattern: Prefetch on Navigation Intent + useQuery 52 | 53 | This pattern works well with TanStack Query (react‑query). Start prefetch alongside navigation; when the screen loads, the request is already in flight or finished. 54 | 55 | ```ts 56 | // Somewhere in a list screen 57 | import { prefetch, fetch as nitroFetch } from 'react-native-nitro-fetch'; 58 | import { useNavigation } from '@react-navigation/native'; 59 | 60 | const PREFETCH_KEY = 'user:42'; 61 | const URL = 'https://api.example.com/users/42'; 62 | 63 | function Row() { 64 | const nav = useNavigation(); 65 | return ( 66 |