├── .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 |