From 462ac73d76504dc05d0bb524efda990c554aa198 Mon Sep 17 00:00:00 2001 From: riteshshukla04 Date: Sat, 8 Nov 2025 03:09:48 +0530 Subject: [PATCH 1/4] feat: android-done --- bun.lock | 3 + example/package.json | 1 + example/src/App.tsx | 11 +- .../nitro/nitrofetch/AutoPrefetcher.kt | 35 +---- .../margelo/nitro/nitrofetch/FetchCache.kt | 12 +- .../margelo/nitro/nitrofetch/NativeStorage.kt | 102 ++++++++++++ .../nitro/nitrofetch/NitroFetchClient.kt | 4 +- package/nitro.json | 4 + .../android/c++/JHybridNativeStorageSpec.cpp | 54 +++++++ .../android/c++/JHybridNativeStorageSpec.hpp | 66 ++++++++ .../nitrofetch/HybridNativeStorageSpec.kt | 60 ++++++++ .../android/nitrofetch+autolinking.cmake | 2 + .../generated/android/nitrofetchOnLoad.cpp | 10 ++ .../ios/NitroFetch-Swift-Cxx-Bridge.cpp | 17 ++ .../ios/NitroFetch-Swift-Cxx-Bridge.hpp | 35 +++++ .../ios/NitroFetch-Swift-Cxx-Umbrella.hpp | 5 + .../generated/ios/NitroFetchAutolinking.mm | 8 + .../generated/ios/NitroFetchAutolinking.swift | 15 ++ .../ios/c++/HybridNativeStorageSpecSwift.cpp | 11 ++ .../ios/c++/HybridNativeStorageSpecSwift.hpp | 85 ++++++++++ .../ios/swift/HybridNativeStorageSpec.swift | 51 ++++++ .../swift/HybridNativeStorageSpec_cxx.swift | 145 ++++++++++++++++++ .../shared/c++/HybridNativeStorageSpec.cpp | 23 +++ .../shared/c++/HybridNativeStorageSpec.hpp | 64 ++++++++ package/src/NitroFetch.nitro.ts | 10 ++ package/src/NitroInstances.ts | 6 +- package/src/fetch.ts | 39 ++--- 27 files changed, 812 insertions(+), 66 deletions(-) create mode 100644 package/android/src/main/java/com/margelo/nitro/nitrofetch/NativeStorage.kt create mode 100644 package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp create mode 100644 package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp create mode 100644 package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt create mode 100644 package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp create mode 100644 package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp create mode 100644 package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift create mode 100644 package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift create mode 100644 package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp create mode 100644 package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp diff --git a/bun.lock b/bun.lock index 48814d8..5ad6dd7 100644 --- a/bun.lock +++ b/bun.lock @@ -38,6 +38,7 @@ "@react-native/new-app-screen": "0.81.0", "react": "19.1.0", "react-native": "0.81.0", + "react-native-mmkv": "^4.0.0", "react-native-nitro-modules": "^0.29.2", "react-native-safe-area-context": "^5.5.2", "react-native-worklets-core": "^1.6.0", @@ -1726,6 +1727,8 @@ "react-native-builder-bob": ["react-native-builder-bob@0.40.13", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-transform-flow-strip-types": "^7.26.5", "@babel/plugin-transform-strict-mode": "^7.24.7", "@babel/preset-env": "^7.25.2", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "arktype": "^2.1.15", "babel-plugin-syntax-hermes-parser": "^0.28.0", "browserslist": "^4.20.4", "cross-spawn": "^7.0.3", "dedent": "^0.7.0", "del": "^6.1.1", "escape-string-regexp": "^4.0.0", "fs-extra": "^10.1.0", "glob": "^8.0.3", "is-git-dirty": "^2.0.1", "json5": "^2.2.1", "kleur": "^4.1.4", "prompts": "^2.4.2", "react-native-monorepo-config": "^0.1.8", "which": "^2.0.2", "yargs": "^17.5.1" }, "bin": { "bob": "bin/bob" } }, "sha512-CtucAJ5PMLH3GPNlg3TB5rb3UPot6VjkD9T8Uhz/AAWit/DmWll0zG33ZZeka69E2569saAjShDz3IKAoYGFtA=="], + "react-native-mmkv": ["react-native-mmkv@4.0.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-Osoy8as2ZLzO1TTsKxc4tX14Qk19qRVMWnS4ZVBwxie9Re5cjt7rqlpDkJczK3H/y3z70EQ6rmKI/cNMCLGAYQ=="], + "react-native-monorepo-config": ["react-native-monorepo-config@0.1.10", "", { "dependencies": { "escape-string-regexp": "^5.0.0", "fast-glob": "^3.3.3" } }, "sha512-v0rlaLZiCUg95Mpw6xNRQce5k9yio0qscKjNQaPtFYMNL75YugS2UPUItIPLIRbZubK+s2/LRzBjX+mdyUgh4g=="], "react-native-nitro-fetch": ["react-native-nitro-fetch@workspace:package"], diff --git a/example/package.json b/example/package.json index 8f1b218..05718c3 100644 --- a/example/package.json +++ b/example/package.json @@ -13,6 +13,7 @@ "@react-native/new-app-screen": "0.81.0", "react": "19.1.0", "react-native": "0.81.0", + "react-native-mmkv": "^4.0.0", "react-native-nitro-modules": "^0.29.2", "react-native-safe-area-context": "^5.5.2", "react-native-worklets-core": "^1.6.0" diff --git a/example/src/App.tsx b/example/src/App.tsx index a4f3320..59a2e68 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -181,7 +181,7 @@ export default function App() { Array<{ id: string; usd: number }> >([]); const [prefetchInfo, setPrefetchInfo] = React.useState(''); - const PREFETCH_URL = 'https://httpbin.org/uuid'; + const PREFETCH_URL = 'https://pokeapi.co/api/v2/pokemon/ditto'; const PREFETCH_KEY = 'uuid'; const loadPrices = React.useCallback(async () => { @@ -265,9 +265,9 @@ export default function App() { } }, [running]); - React.useEffect(() => { - run(); - }, [run]); + // React.useEffect(() => { + // run(); + // }, [run]); return ( @@ -309,8 +309,9 @@ export default function App() { const res = await nitroFetch(PREFETCH_URL, { headers: { prefetchKey: PREFETCH_KEY }, }); + console.log('res', res); const text = await res.text(); - const pref = res.headers.get('nitroPrefetched'); + const pref = res.headers?.nitroPrefetched; setPrefetchInfo( `Fetched. nitroPrefetched=${pref ?? 'null'} len=${text.length}` ); diff --git a/package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt b/package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt index e9b7a7e..b3d554e 100644 --- a/package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +++ b/package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt @@ -1,21 +1,23 @@ package com.margelo.nitro.nitrofetch import android.app.Application +import android.content.Context import org.json.JSONArray import org.json.JSONObject import java.util.concurrent.CompletableFuture -import com.tencent.mmkv.MMKV; object AutoPrefetcher { @Volatile private var initialized = false + private const val KEY_QUEUE = "nitrofetch_autoprefetch_queue" + private const val PREFS_NAME = "nitro_fetch_storage" fun prefetchOnStart(app: Application) { if (initialized) return initialized = true try { - val mmkv = getMMKV(app) ?: return - val raw = invokeMMKVDecodeString(mmkv, KEY_QUEUE) ?: return + val prefs = app.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val raw = prefs.getString(KEY_QUEUE, null) ?: "" if (raw.isEmpty()) return val arr = JSONArray(raw) for (i in 0 until arr.length()) { @@ -42,12 +44,8 @@ object AutoPrefetcher { ) // If already pending or fresh, skip starting a new one - if (FetchCache.getPending(prefetchKey) != null) { - continue - } - if (FetchCache.getResultIfFresh(prefetchKey, 5_000L) != null) { - continue - } + if (FetchCache.getPending(prefetchKey) != null) continue + if (FetchCache.hasFreshResult(prefetchKey, 5_000L)) continue val future = CompletableFuture() FetchCache.setPending(prefetchKey, future) @@ -71,23 +69,4 @@ object AutoPrefetcher { // ignore – prefetch-on-start is best-effort } } - - private const val KEY_QUEUE = "nitrofetch_autoprefetch_queue" - - private fun getMMKV(app: Application): Any? { - return try { - MMKV.initialize(app); - - return MMKV.defaultMMKV() - } catch (_: Throwable) { - null - } - } - - private fun invokeMMKVDecodeString(mmkv: Any, key: String): String? { - return try { - val m = mmkv.javaClass.getMethod("decodeString", String::class.java, String::class.java) - m.invoke(mmkv, key, null) as? String - } catch (_: Throwable) { null } - } } diff --git a/package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt b/package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt index bdb2984..7072d9f 100644 --- a/package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt +++ b/package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt @@ -41,8 +41,18 @@ object FetchCache { return if (age <= maxAgeMs) entry.response else null } + /** + * Check if a fresh result exists WITHOUT consuming it. + * Used to check if we should skip starting a new prefetch. + */ + fun hasFreshResult(key: String, maxAgeMs: Long): Boolean { + val entry = results[key] ?: return false + val age = System.currentTimeMillis() - entry.timestampMs + return age <= maxAgeMs + } + fun clear() { pending.clear() results.clear() } -} +} \ No newline at end of file diff --git a/package/android/src/main/java/com/margelo/nitro/nitrofetch/NativeStorage.kt b/package/android/src/main/java/com/margelo/nitro/nitrofetch/NativeStorage.kt new file mode 100644 index 0000000..9257983 --- /dev/null +++ b/package/android/src/main/java/com/margelo/nitro/nitrofetch/NativeStorage.kt @@ -0,0 +1,102 @@ +package com.margelo.nitro.nitrofetch + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.util.Log +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.NitroModules + + + +@DoNotStrip +class NativeStorage : HybridNativeStorageSpec() { + + companion object { + private const val TAG = "HybridNativeStorage" + private const val PREFS_NAME = "nitro_fetch_storage" + + + private val sharedPreferences: SharedPreferences by lazy { + val context = NitroModules.applicationContext ?: throw Error("Cannot get Android Context - No Context available!") + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + } + + + } + + /** + * Retrieves a string value for the given key. + * + * @param key The key to look up in storage + * @return The stored string value, or empty string if key doesn't exist + * @throws IllegalStateException if SharedPreferences is not available + */ + override fun getString(key: String): String { + return try { + val value = sharedPreferences.getString(key, null) + if (value != null) { + Log.d(TAG, "Retrieved value for key: $key") + value + } else { + Log.d(TAG, "Key not found: $key, returning empty string") + "" + } + } catch (t: Throwable) { + Log.e(TAG, "Error getting string for key: $key", t) + throw RuntimeException("Failed to get string for key: $key", t) + } + } + + /** + * Stores a string value with the given key. + * + * @param key The key to store the value under + * @param value The string value to store + * @throws IllegalStateException if SharedPreferences is not available + * @throws RuntimeException if the write operation fails + */ + override fun setString(key: String, value: String) { + try { + val editor = sharedPreferences.edit() + editor.putString(key, value) + val success = editor.commit() // commit() is synchronous and returns boolean + if (success) { + Log.d(TAG, "Successfully stored value for key: $key") + } else { + Log.e(TAG, "Failed to commit value for key: $key") + throw RuntimeException("Failed to store value for key: $key") + } + } catch (t: Throwable) { + Log.e(TAG, "Error setting string for key: $key", t) + throw RuntimeException("Failed to set string for key: $key", t) + } + } + + /** + * Deletes the value associated with the given key. + * If the key doesn't exist, this is a no-op. + * + * @param key The key to delete from storage + * @throws IllegalStateException if SharedPreferences is not available + * @throws RuntimeException if the delete operation fails + */ + override fun removeString(key: String) { + try { + val editor = sharedPreferences.edit() + editor.remove(key) + val success = editor.commit() // commit() is synchronous and returns boolean + if (success) { + Log.d(TAG, "Successfully deleted key: $key") + } else { + Log.e(TAG, "Failed to commit deletion for key: $key") + throw RuntimeException("Failed to delete key: $key") + } + } catch (t: Throwable) { + Log.e(TAG, "Error deleting key: $key", t) + throw RuntimeException("Failed to delete key: $key", t) + } + } +} + + diff --git a/package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt b/package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt index 9f3d63a..bbff569 100644 --- a/package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +++ b/package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt @@ -220,8 +220,8 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E promise.reject(IllegalArgumentException("prefetch: missing 'prefetchKey' header")) return promise } - // If already have a fresh result, resolve immediately - FetchCache.getResultIfFresh(key, 5_000L)?.let { + // If already have a fresh result, resolve immediately (NON-DESTRUCTIVE CHECK) + if (FetchCache.hasFreshResult(key, 5_000L)) { promise.resolve(Unit) return promise } diff --git a/package/nitro.json b/package/nitro.json index 671ce73..ef718ac 100644 --- a/package/nitro.json +++ b/package/nitro.json @@ -15,6 +15,10 @@ "NitroFetchClient": { "kotlin": "NitroFetchClient", "swift": "NitroFetchClient" + }, + "NativeStorage": { + "kotlin": "NativeStorage", + "swift": "NativeStorage" } }, "ignorePaths": ["node_modules"] diff --git a/package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp b/package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp new file mode 100644 index 0000000..5895794 --- /dev/null +++ b/package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp @@ -0,0 +1,54 @@ +/// +/// JHybridNativeStorageSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "JHybridNativeStorageSpec.hpp" + + + +#include + +namespace margelo::nitro::nitrofetch { + + jni::local_ref JHybridNativeStorageSpec::initHybrid(jni::alias_ref jThis) { + return makeCxxInstance(jThis); + } + + void JHybridNativeStorageSpec::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", JHybridNativeStorageSpec::initHybrid), + }); + } + + size_t JHybridNativeStorageSpec::getExternalMemorySize() noexcept { + static const auto method = javaClassStatic()->getMethod("getMemorySize"); + return method(_javaPart); + } + + void JHybridNativeStorageSpec::dispose() noexcept { + static const auto method = javaClassStatic()->getMethod("dispose"); + method(_javaPart); + } + + // Properties + + + // Methods + std::string JHybridNativeStorageSpec::getString(const std::string& key) { + static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* key */)>("getString"); + auto __result = method(_javaPart, jni::make_jstring(key)); + return __result->toStdString(); + } + void JHybridNativeStorageSpec::setString(const std::string& key, const std::string& value) { + static const auto method = javaClassStatic()->getMethod /* key */, jni::alias_ref /* value */)>("setString"); + method(_javaPart, jni::make_jstring(key), jni::make_jstring(value)); + } + void JHybridNativeStorageSpec::removeString(const std::string& key) { + static const auto method = javaClassStatic()->getMethod /* key */)>("removeString"); + method(_javaPart, jni::make_jstring(key)); + } + +} // namespace margelo::nitro::nitrofetch diff --git a/package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp b/package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp new file mode 100644 index 0000000..183808e --- /dev/null +++ b/package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp @@ -0,0 +1,66 @@ +/// +/// HybridNativeStorageSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include "HybridNativeStorageSpec.hpp" + + + + +namespace margelo::nitro::nitrofetch { + + using namespace facebook; + + class JHybridNativeStorageSpec: public jni::HybridClass, + public virtual HybridNativeStorageSpec { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrofetch/HybridNativeStorageSpec;"; + static jni::local_ref initHybrid(jni::alias_ref jThis); + static void registerNatives(); + + protected: + // C++ constructor (called from Java via `initHybrid()`) + explicit JHybridNativeStorageSpec(jni::alias_ref jThis) : + HybridObject(HybridNativeStorageSpec::TAG), + HybridBase(jThis), + _javaPart(jni::make_global(jThis)) {} + + public: + ~JHybridNativeStorageSpec() override { + // Hermes GC can destroy JS objects on a non-JNI Thread. + jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); + } + + public: + size_t getExternalMemorySize() noexcept override; + void dispose() noexcept override; + + public: + inline const jni::global_ref& getJavaPart() const noexcept { + return _javaPart; + } + + public: + // Properties + + + public: + // Methods + std::string getString(const std::string& key) override; + void setString(const std::string& key, const std::string& value) override; + void removeString(const std::string& key) override; + + private: + friend HybridBase; + using HybridBase::HybridBase; + jni::global_ref _javaPart; + }; + +} // namespace margelo::nitro::nitrofetch diff --git a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt new file mode 100644 index 0000000..20fac64 --- /dev/null +++ b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt @@ -0,0 +1,60 @@ +/// +/// HybridNativeStorageSpec.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrofetch + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + +/** + * A Kotlin class representing the NativeStorage HybridObject. + * Implement this abstract class to create Kotlin-based instances of NativeStorage. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", + "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" +) +abstract class HybridNativeStorageSpec: HybridObject() { + @DoNotStrip + private var mHybridData: HybridData = initHybrid() + + init { + super.updateNative(mHybridData) + } + + override fun updateNative(hybridData: HybridData) { + mHybridData = hybridData + super.updateNative(hybridData) + } + + // Properties + + + // Methods + @DoNotStrip + @Keep + abstract fun getString(key: String): String + + @DoNotStrip + @Keep + abstract fun setString(key: String, value: String): Unit + + @DoNotStrip + @Keep + abstract fun removeString(key: String): Unit + + private external fun initHybrid(): HybridData + + companion object { + private const val TAG = "HybridNativeStorageSpec" + } +} diff --git a/package/nitrogen/generated/android/nitrofetch+autolinking.cmake b/package/nitrogen/generated/android/nitrofetch+autolinking.cmake index 1512532..a405e46 100644 --- a/package/nitrogen/generated/android/nitrofetch+autolinking.cmake +++ b/package/nitrogen/generated/android/nitrofetch+autolinking.cmake @@ -35,9 +35,11 @@ target_sources( # Shared Nitrogen C++ sources ../nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp ../nitrogen/generated/shared/c++/HybridNitroFetchSpec.cpp + ../nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp # Android-specific Nitrogen C++ sources ../nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp ../nitrogen/generated/android/c++/JHybridNitroFetchSpec.cpp + ../nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp ) # From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake diff --git a/package/nitrogen/generated/android/nitrofetchOnLoad.cpp b/package/nitrogen/generated/android/nitrofetchOnLoad.cpp index cbd1f8a..dc20e42 100644 --- a/package/nitrogen/generated/android/nitrofetchOnLoad.cpp +++ b/package/nitrogen/generated/android/nitrofetchOnLoad.cpp @@ -17,6 +17,7 @@ #include "JHybridNitroFetchClientSpec.hpp" #include "JHybridNitroFetchSpec.hpp" +#include "JHybridNativeStorageSpec.hpp" #include namespace margelo::nitro::nitrofetch { @@ -30,6 +31,7 @@ int initialize(JavaVM* vm) { // Register native JNI methods margelo::nitro::nitrofetch::JHybridNitroFetchClientSpec::registerNatives(); margelo::nitro::nitrofetch::JHybridNitroFetchSpec::registerNatives(); + margelo::nitro::nitrofetch::JHybridNativeStorageSpec::registerNatives(); // Register Nitro Hybrid Objects HybridObjectRegistry::registerHybridObjectConstructor( @@ -48,6 +50,14 @@ int initialize(JavaVM* vm) { return instance->cthis()->shared(); } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "NativeStorage", + []() -> std::shared_ptr { + static DefaultConstructableObject object("com/margelo/nitro/nitrofetch/NativeStorage"); + auto instance = object.create(); + return instance->cthis()->shared(); + } + ); }); } diff --git a/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp b/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp index 3b39dd5..0fdb88f 100644 --- a/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp +++ b/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp @@ -8,6 +8,7 @@ #include "NitroFetch-Swift-Cxx-Bridge.hpp" // Include C++ implementation defined types +#include "HybridNativeStorageSpecSwift.hpp" #include "HybridNitroFetchClientSpecSwift.hpp" #include "HybridNitroFetchSpecSwift.hpp" #include "NitroFetch-Swift-Cxx-Umbrella.hpp" @@ -69,5 +70,21 @@ namespace margelo::nitro::nitrofetch::bridge::swift { NitroFetch::HybridNitroFetchSpec_cxx& swiftPart = swiftWrapper->getSwiftPart(); return swiftPart.toUnsafe(); } + + // pragma MARK: std::shared_ptr + std::shared_ptr create_std__shared_ptr_HybridNativeStorageSpec_(void* _Nonnull swiftUnsafePointer) noexcept { + NitroFetch::HybridNativeStorageSpec_cxx swiftPart = NitroFetch::HybridNativeStorageSpec_cxx::fromUnsafe(swiftUnsafePointer); + return std::make_shared(swiftPart); + } + void* _Nonnull get_std__shared_ptr_HybridNativeStorageSpec_(std__shared_ptr_HybridNativeStorageSpec_ cppType) noexcept { + std::shared_ptr swiftWrapper = std::dynamic_pointer_cast(cppType); + #ifdef NITRO_DEBUG + if (swiftWrapper == nullptr) [[unlikely]] { + throw std::runtime_error("Class \"HybridNativeStorageSpec\" is not implemented in Swift!"); + } + #endif + NitroFetch::HybridNativeStorageSpec_cxx& swiftPart = swiftWrapper->getSwiftPart(); + return swiftPart.toUnsafe(); + } } // namespace margelo::nitro::nitrofetch::bridge::swift diff --git a/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp b/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp index cc4a79d..69c358a 100644 --- a/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +++ b/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp @@ -8,6 +8,8 @@ #pragma once // Forward declarations of C++ defined types +// Forward declaration of `HybridNativeStorageSpec` to properly resolve imports. +namespace margelo::nitro::nitrofetch { class HybridNativeStorageSpec; } // Forward declaration of `HybridNitroFetchClientSpec` to properly resolve imports. namespace margelo::nitro::nitrofetch { class HybridNitroFetchClientSpec; } // Forward declaration of `HybridNitroFetchSpec` to properly resolve imports. @@ -20,12 +22,15 @@ namespace margelo::nitro::nitrofetch { enum class NitroRequestMethod; } namespace margelo::nitro::nitrofetch { struct NitroResponse; } // Forward declarations of Swift defined types +// Forward declaration of `HybridNativeStorageSpec_cxx` to properly resolve imports. +namespace NitroFetch { class HybridNativeStorageSpec_cxx; } // Forward declaration of `HybridNitroFetchClientSpec_cxx` to properly resolve imports. namespace NitroFetch { class HybridNitroFetchClientSpec_cxx; } // Forward declaration of `HybridNitroFetchSpec_cxx` to properly resolve imports. namespace NitroFetch { class HybridNitroFetchSpec_cxx; } // Include C++ defined types +#include "HybridNativeStorageSpec.hpp" #include "HybridNitroFetchClientSpec.hpp" #include "HybridNitroFetchSpec.hpp" #include "NitroHeader.hpp" @@ -273,5 +278,35 @@ namespace margelo::nitro::nitrofetch::bridge::swift { inline Result_std__shared_ptr_HybridNitroFetchClientSpec__ create_Result_std__shared_ptr_HybridNitroFetchClientSpec__(const std::exception_ptr& error) noexcept { return Result>::withError(error); } + + // pragma MARK: std::shared_ptr + /** + * Specialized version of `std::shared_ptr`. + */ + using std__shared_ptr_HybridNativeStorageSpec_ = std::shared_ptr; + std::shared_ptr create_std__shared_ptr_HybridNativeStorageSpec_(void* _Nonnull swiftUnsafePointer) noexcept; + void* _Nonnull get_std__shared_ptr_HybridNativeStorageSpec_(std__shared_ptr_HybridNativeStorageSpec_ cppType) noexcept; + + // pragma MARK: std::weak_ptr + using std__weak_ptr_HybridNativeStorageSpec_ = std::weak_ptr; + inline std__weak_ptr_HybridNativeStorageSpec_ weakify_std__shared_ptr_HybridNativeStorageSpec_(const std::shared_ptr& strong) noexcept { return strong; } + + // pragma MARK: Result + using Result_std__string_ = Result; + inline Result_std__string_ create_Result_std__string_(const std::string& value) noexcept { + return Result::withValue(value); + } + inline Result_std__string_ create_Result_std__string_(const std::exception_ptr& error) noexcept { + return Result::withError(error); + } + + // pragma MARK: Result + using Result_void_ = Result; + inline Result_void_ create_Result_void_() noexcept { + return Result::withValue(); + } + inline Result_void_ create_Result_void_(const std::exception_ptr& error) noexcept { + return Result::withError(error); + } } // namespace margelo::nitro::nitrofetch::bridge::swift diff --git a/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp b/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp index cccdc32..441f71e 100644 --- a/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp +++ b/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp @@ -8,6 +8,8 @@ #pragma once // Forward declarations of C++ defined types +// Forward declaration of `HybridNativeStorageSpec` to properly resolve imports. +namespace margelo::nitro::nitrofetch { class HybridNativeStorageSpec; } // Forward declaration of `HybridNitroFetchClientSpec` to properly resolve imports. namespace margelo::nitro::nitrofetch { class HybridNitroFetchClientSpec; } // Forward declaration of `HybridNitroFetchSpec` to properly resolve imports. @@ -22,6 +24,7 @@ namespace margelo::nitro::nitrofetch { struct NitroRequest; } namespace margelo::nitro::nitrofetch { struct NitroResponse; } // Include C++ defined types +#include "HybridNativeStorageSpec.hpp" #include "HybridNitroFetchClientSpec.hpp" #include "HybridNitroFetchSpec.hpp" #include "NitroHeader.hpp" @@ -46,6 +49,8 @@ namespace margelo::nitro::nitrofetch { struct NitroResponse; } #include // Forward declarations of Swift defined types +// Forward declaration of `HybridNativeStorageSpec_cxx` to properly resolve imports. +namespace NitroFetch { class HybridNativeStorageSpec_cxx; } // Forward declaration of `HybridNitroFetchClientSpec_cxx` to properly resolve imports. namespace NitroFetch { class HybridNitroFetchClientSpec_cxx; } // Forward declaration of `HybridNitroFetchSpec_cxx` to properly resolve imports. diff --git a/package/nitrogen/generated/ios/NitroFetchAutolinking.mm b/package/nitrogen/generated/ios/NitroFetchAutolinking.mm index f218901..f718b04 100644 --- a/package/nitrogen/generated/ios/NitroFetchAutolinking.mm +++ b/package/nitrogen/generated/ios/NitroFetchAutolinking.mm @@ -12,6 +12,7 @@ #include "HybridNitroFetchSpecSwift.hpp" #include "HybridNitroFetchClientSpecSwift.hpp" +#include "HybridNativeStorageSpecSwift.hpp" @interface NitroFetchAutolinking : NSObject @end @@ -36,6 +37,13 @@ + (void) load { return hybridObject; } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "NativeStorage", + []() -> std::shared_ptr { + std::shared_ptr hybridObject = NitroFetch::NitroFetchAutolinking::createNativeStorage(); + return hybridObject; + } + ); } @end diff --git a/package/nitrogen/generated/ios/NitroFetchAutolinking.swift b/package/nitrogen/generated/ios/NitroFetchAutolinking.swift index ce57305..e25d673 100644 --- a/package/nitrogen/generated/ios/NitroFetchAutolinking.swift +++ b/package/nitrogen/generated/ios/NitroFetchAutolinking.swift @@ -37,4 +37,19 @@ public final class NitroFetchAutolinking { return __cxxWrapped.getCxxPart() }() } + + /** + * Creates an instance of a Swift class that implements `HybridNativeStorageSpec`, + * and wraps it in a Swift class that can directly interop with C++ (`HybridNativeStorageSpec_cxx`) + * + * This is generated by Nitrogen and will initialize the class specified + * in the `"autolinking"` property of `nitro.json` (in this case, `NativeStorage`). + */ + public static func createNativeStorage() -> bridge.std__shared_ptr_HybridNativeStorageSpec_ { + let hybridObject = NativeStorage() + return { () -> bridge.std__shared_ptr_HybridNativeStorageSpec_ in + let __cxxWrapped = hybridObject.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }() + } } diff --git a/package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp b/package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp new file mode 100644 index 0000000..49a0709 --- /dev/null +++ b/package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp @@ -0,0 +1,11 @@ +/// +/// HybridNativeStorageSpecSwift.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "HybridNativeStorageSpecSwift.hpp" + +namespace margelo::nitro::nitrofetch { +} // namespace margelo::nitro::nitrofetch diff --git a/package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp b/package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp new file mode 100644 index 0000000..6814e62 --- /dev/null +++ b/package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp @@ -0,0 +1,85 @@ +/// +/// HybridNativeStorageSpecSwift.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include "HybridNativeStorageSpec.hpp" + +// Forward declaration of `HybridNativeStorageSpec_cxx` to properly resolve imports. +namespace NitroFetch { class HybridNativeStorageSpec_cxx; } + + + +#include + +#include "NitroFetch-Swift-Cxx-Umbrella.hpp" + +namespace margelo::nitro::nitrofetch { + + /** + * The C++ part of HybridNativeStorageSpec_cxx.swift. + * + * HybridNativeStorageSpecSwift (C++) accesses HybridNativeStorageSpec_cxx (Swift), and might + * contain some additional bridging code for C++ <> Swift interop. + * + * Since this obviously introduces an overhead, I hope at some point in + * the future, HybridNativeStorageSpec_cxx can directly inherit from the C++ class HybridNativeStorageSpec + * to simplify the whole structure and memory management. + */ + class HybridNativeStorageSpecSwift: public virtual HybridNativeStorageSpec { + public: + // Constructor from a Swift instance + explicit HybridNativeStorageSpecSwift(const NitroFetch::HybridNativeStorageSpec_cxx& swiftPart): + HybridObject(HybridNativeStorageSpec::TAG), + _swiftPart(swiftPart) { } + + public: + // Get the Swift part + inline NitroFetch::HybridNativeStorageSpec_cxx& getSwiftPart() noexcept { + return _swiftPart; + } + + public: + inline size_t getExternalMemorySize() noexcept override { + return _swiftPart.getMemorySize(); + } + void dispose() noexcept override { + _swiftPart.dispose(); + } + + public: + // Properties + + + public: + // Methods + inline std::string getString(const std::string& key) override { + auto __result = _swiftPart.getString(key); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } + inline void setString(const std::string& key, const std::string& value) override { + auto __result = _swiftPart.setString(key, value); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + } + inline void removeString(const std::string& key) override { + auto __result = _swiftPart.removeString(key); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + } + + private: + NitroFetch::HybridNativeStorageSpec_cxx _swiftPart; + }; + +} // namespace margelo::nitro::nitrofetch diff --git a/package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift b/package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift new file mode 100644 index 0000000..fc5bab2 --- /dev/null +++ b/package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift @@ -0,0 +1,51 @@ +/// +/// HybridNativeStorageSpec.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import Foundation +import NitroModules + +/// See ``HybridNativeStorageSpec`` +public protocol HybridNativeStorageSpec_protocol: HybridObject { + // Properties + + + // Methods + func getString(key: String) throws -> String + func setString(key: String, value: String) throws -> Void + func removeString(key: String) throws -> Void +} + +/// See ``HybridNativeStorageSpec`` +open class HybridNativeStorageSpec_base { + private weak var cxxWrapper: HybridNativeStorageSpec_cxx? = nil + public init() { } + public func getCxxWrapper() -> HybridNativeStorageSpec_cxx { + #if DEBUG + guard self is HybridNativeStorageSpec else { + fatalError("`self` is not a `HybridNativeStorageSpec`! Did you accidentally inherit from `HybridNativeStorageSpec_base` instead of `HybridNativeStorageSpec`?") + } + #endif + if let cxxWrapper = self.cxxWrapper { + return cxxWrapper + } else { + let cxxWrapper = HybridNativeStorageSpec_cxx(self as! HybridNativeStorageSpec) + self.cxxWrapper = cxxWrapper + return cxxWrapper + } + } +} + +/** + * A Swift base-protocol representing the NativeStorage HybridObject. + * Implement this protocol to create Swift-based instances of NativeStorage. + * ```swift + * class HybridNativeStorage : HybridNativeStorageSpec { + * // ... + * } + * ``` + */ +public typealias HybridNativeStorageSpec = HybridNativeStorageSpec_protocol & HybridNativeStorageSpec_base diff --git a/package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift b/package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift new file mode 100644 index 0000000..e94a3c6 --- /dev/null +++ b/package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift @@ -0,0 +1,145 @@ +/// +/// HybridNativeStorageSpec_cxx.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import Foundation +import NitroModules + +/** + * A class implementation that bridges HybridNativeStorageSpec over to C++. + * In C++, we cannot use Swift protocols - so we need to wrap it in a class to make it strongly defined. + * + * Also, some Swift types need to be bridged with special handling: + * - Enums need to be wrapped in Structs, otherwise they cannot be accessed bi-directionally (Swift bug: https://github.com/swiftlang/swift/issues/75330) + * - Other HybridObjects need to be wrapped/unwrapped from the Swift TCxx wrapper + * - Throwing methods need to be wrapped with a Result type, as exceptions cannot be propagated to C++ + */ +open class HybridNativeStorageSpec_cxx { + /** + * The Swift <> C++ bridge's namespace (`margelo::nitro::nitrofetch::bridge::swift`) + * from `NitroFetch-Swift-Cxx-Bridge.hpp`. + * This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift. + */ + public typealias bridge = margelo.nitro.nitrofetch.bridge.swift + + /** + * Holds an instance of the `HybridNativeStorageSpec` Swift protocol. + */ + private var __implementation: any HybridNativeStorageSpec + + /** + * Holds a weak pointer to the C++ class that wraps the Swift class. + */ + private var __cxxPart: bridge.std__weak_ptr_HybridNativeStorageSpec_ + + /** + * Create a new `HybridNativeStorageSpec_cxx` that wraps the given `HybridNativeStorageSpec`. + * All properties and methods bridge to C++ types. + */ + public init(_ implementation: any HybridNativeStorageSpec) { + self.__implementation = implementation + self.__cxxPart = .init() + /* no base class */ + } + + /** + * Get the actual `HybridNativeStorageSpec` instance this class wraps. + */ + @inline(__always) + public func getHybridNativeStorageSpec() -> any HybridNativeStorageSpec { + return __implementation + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `HybridNativeStorageSpec_cxx`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + public class func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> HybridNativeStorageSpec_cxx { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } + + /** + * Gets (or creates) the C++ part of this Hybrid Object. + * The C++ part is a `std::shared_ptr`. + */ + public func getCxxPart() -> bridge.std__shared_ptr_HybridNativeStorageSpec_ { + let cachedCxxPart = self.__cxxPart.lock() + if cachedCxxPart.__convertToBool() { + return cachedCxxPart + } else { + let newCxxPart = bridge.create_std__shared_ptr_HybridNativeStorageSpec_(self.toUnsafe()) + __cxxPart = bridge.weakify_std__shared_ptr_HybridNativeStorageSpec_(newCxxPart) + return newCxxPart + } + } + + + + /** + * Get the memory size of the Swift class (plus size of any other allocations) + * so the JS VM can properly track it and garbage-collect the JS object if needed. + */ + @inline(__always) + public var memorySize: Int { + return MemoryHelper.getSizeOf(self.__implementation) + self.__implementation.memorySize + } + + /** + * Call dispose() on the Swift class. + * This _may_ be called manually from JS. + */ + @inline(__always) + public func dispose() { + self.__implementation.dispose() + } + + // Properties + + + // Methods + @inline(__always) + public final func getString(key: std.string) -> bridge.Result_std__string_ { + do { + let __result = try self.__implementation.getString(key: String(key)) + let __resultCpp = std.string(__result) + return bridge.create_Result_std__string_(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_std__string_(__exceptionPtr) + } + } + + @inline(__always) + public final func setString(key: std.string, value: std.string) -> bridge.Result_void_ { + do { + try self.__implementation.setString(key: String(key), value: String(value)) + return bridge.create_Result_void_() + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_void_(__exceptionPtr) + } + } + + @inline(__always) + public final func removeString(key: std.string) -> bridge.Result_void_ { + do { + try self.__implementation.removeString(key: String(key)) + return bridge.create_Result_void_() + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_void_(__exceptionPtr) + } + } +} diff --git a/package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp b/package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp new file mode 100644 index 0000000..8e04174 --- /dev/null +++ b/package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp @@ -0,0 +1,23 @@ +/// +/// HybridNativeStorageSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "HybridNativeStorageSpec.hpp" + +namespace margelo::nitro::nitrofetch { + + void HybridNativeStorageSpec::loadHybridMethods() { + // load base methods/properties + HybridObject::loadHybridMethods(); + // load custom methods/properties + registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridMethod("getString", &HybridNativeStorageSpec::getString); + prototype.registerHybridMethod("setString", &HybridNativeStorageSpec::setString); + prototype.registerHybridMethod("removeString", &HybridNativeStorageSpec::removeString); + }); + } + +} // namespace margelo::nitro::nitrofetch diff --git a/package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp b/package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp new file mode 100644 index 0000000..b196cd3 --- /dev/null +++ b/package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp @@ -0,0 +1,64 @@ +/// +/// HybridNativeStorageSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + + + +#include + +namespace margelo::nitro::nitrofetch { + + using namespace margelo::nitro; + + /** + * An abstract base class for `NativeStorage` + * Inherit this class to create instances of `HybridNativeStorageSpec` in C++. + * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. + * @example + * ```cpp + * class HybridNativeStorage: public HybridNativeStorageSpec { + * public: + * HybridNativeStorage(...): HybridObject(TAG) { ... } + * // ... + * }; + * ``` + */ + class HybridNativeStorageSpec: public virtual HybridObject { + public: + // Constructor + explicit HybridNativeStorageSpec(): HybridObject(TAG) { } + + // Destructor + ~HybridNativeStorageSpec() override = default; + + public: + // Properties + + + public: + // Methods + virtual std::string getString(const std::string& key) = 0; + virtual void setString(const std::string& key, const std::string& value) = 0; + virtual void removeString(const std::string& key) = 0; + + protected: + // Hybrid Setup + void loadHybridMethods() override; + + protected: + // Tag for logging + static constexpr auto TAG = "NativeStorage"; + }; + +} // namespace margelo::nitro::nitrofetch diff --git a/package/src/NitroFetch.nitro.ts b/package/src/NitroFetch.nitro.ts index 09373e6..43818e8 100644 --- a/package/src/NitroFetch.nitro.ts +++ b/package/src/NitroFetch.nitro.ts @@ -55,3 +55,13 @@ export interface NitroFetch // Optional future: global abort/teardown // shutdown(): void; } + + + + + +export interface NativeStorage extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { + getString(key: string): string; + setString(key: string, value: string): void; + removeString(key: string): void; +} \ No newline at end of file diff --git a/package/src/NitroInstances.ts b/package/src/NitroInstances.ts index dafda47..262b686 100644 --- a/package/src/NitroInstances.ts +++ b/package/src/NitroInstances.ts @@ -1,6 +1,10 @@ import { NitroModules } from 'react-native-nitro-modules'; -import type { NitroFetch as NitroFetchType } from './NitroFetch.nitro'; +import type { NitroFetch as NitroFetchType, NativeStorage as NativeStorageType } from './NitroFetch.nitro'; // Create singletons once per JS runtime export const NitroFetch: NitroFetchType = NitroModules.createHybridObject('NitroFetch'); + + +export const NativeStorage: NativeStorageType = + NitroModules.createHybridObject('NativeStorage'); \ No newline at end of file diff --git a/package/src/fetch.ts b/package/src/fetch.ts index f86e208..17ee55b 100644 --- a/package/src/fetch.ts +++ b/package/src/fetch.ts @@ -5,6 +5,7 @@ import type { NitroResponse, } from './NitroFetch.nitro'; import { NitroFetch as NitroFetchSingleton } from './NitroInstances'; +import { NativeStorage as NativeStorageSingleton } from './NitroInstances'; // No base64: pass strings/ArrayBuffers directly @@ -259,13 +260,11 @@ export async function prefetchOnAppStart( // Write or append to MMKV queue try { // Dynamically require to keep it optional for consumers - - const { MMKV } = require('react-native-mmkv'); - const storage = new MMKV(); // default instance matches Android's defaultMMKV + const KEY = 'nitrofetch_autoprefetch_queue'; let arr: any[] = []; try { - const raw = storage.getString(KEY); + const raw = NativeStorageSingleton.getString('nitrofetch_autoprefetch_queue'); if (raw) arr = JSON.parse(raw); if (!Array.isArray(arr)) arr = []; } catch { @@ -275,10 +274,11 @@ export async function prefetchOnAppStart( arr = arr.filter((e) => e && e.prefetchKey !== prefetchKey); } arr.push(entry); - storage.set(KEY, JSON.stringify(arr)); + NativeStorageSingleton.setString(KEY, JSON.stringify(arr)); + console.log("done") } catch (e) { console.warn( - 'react-native-mmkv not available; prefetchOnAppStart is a no-op', + 'react-native-mmkv not ffff; prefetchOnAppStart is a no-op', e ); } @@ -290,12 +290,10 @@ export async function removeFromAutoPrefetch( ): Promise { // No-op on iOS try { - const { MMKV } = require('react-native-mmkv'); - const storage = new MMKV(); const KEY = 'nitrofetch_autoprefetch_queue'; let arr: any[] = []; try { - const raw = storage.getString(KEY); + const raw = NativeStorageSingleton.getString('nitrofetch_autoprefetch_queue'); if (raw) arr = JSON.parse(raw); if (!Array.isArray(arr)) arr = []; } catch { @@ -303,13 +301,9 @@ export async function removeFromAutoPrefetch( } const next = arr.filter((e) => e && e.prefetchKey !== prefetchKey); if (next.length === 0) { - if (typeof (storage as any).delete === 'function') { - (storage as any).delete(KEY); - } else { - storage.set(KEY, JSON.stringify([])); - } + NativeStorageSingleton.removeString(KEY); } else if (next.length !== arr.length) { - storage.set(KEY, JSON.stringify(next)); + NativeStorageSingleton.setString(KEY, JSON.stringify(next)); } } catch (e) { console.warn( @@ -321,21 +315,8 @@ export async function removeFromAutoPrefetch( // Remove all entries from the auto-prefetch queue in MMKV. export async function removeAllFromAutoprefetch(): Promise { - try { - const { MMKV } = require('react-native-mmkv'); - const storage = new MMKV(); const KEY = 'nitrofetch_autoprefetch_queue'; - if (typeof (storage as any).delete === 'function') { - (storage as any).delete(KEY); - } else { - storage.set(KEY, JSON.stringify([])); - } - } catch (e) { - console.warn( - 'react-native-mmkv not available; removeAllFromAutoprefetch is a no-op', - e - ); - } + NativeStorageSingleton.setString(KEY, JSON.stringify([])); } // Optional off-thread processing using react-native-worklets-core From 667a4a714600ad70971b9d36f481b5114fc3d240 Mon Sep 17 00:00:00 2001 From: riteshshukla04 Date: Sat, 8 Nov 2025 04:01:01 +0530 Subject: [PATCH 2/4] chore: remove-mmkv --- bun.lock | 3 - docs/android.md | 2 +- docs/api.md | 5 +- docs/ios.md | 6 +- docs/prefetch.md | 8 +-- docs/troubleshooting.md | 2 - .../ios/NitroFetchExample/AppDelegate.swift | 2 +- example/ios/Podfile.lock | 2 +- example/package.json | 1 - example/src/App.tsx | 10 +-- package/android/build.gradle | 1 - package/ios/NativeStorage.swift | 61 +++++++++++++++++++ package/ios/NitroAutoPrefetcher.swift | 46 ++------------ package/src/NitroFetch.nitro.ts | 9 +-- package/src/NitroInstances.ts | 8 ++- package/src/fetch.ts | 35 +++++------ 16 files changed, 105 insertions(+), 96 deletions(-) create mode 100644 package/ios/NativeStorage.swift diff --git a/bun.lock b/bun.lock index 5ad6dd7..48814d8 100644 --- a/bun.lock +++ b/bun.lock @@ -38,7 +38,6 @@ "@react-native/new-app-screen": "0.81.0", "react": "19.1.0", "react-native": "0.81.0", - "react-native-mmkv": "^4.0.0", "react-native-nitro-modules": "^0.29.2", "react-native-safe-area-context": "^5.5.2", "react-native-worklets-core": "^1.6.0", @@ -1727,8 +1726,6 @@ "react-native-builder-bob": ["react-native-builder-bob@0.40.13", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-transform-flow-strip-types": "^7.26.5", "@babel/plugin-transform-strict-mode": "^7.24.7", "@babel/preset-env": "^7.25.2", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "arktype": "^2.1.15", "babel-plugin-syntax-hermes-parser": "^0.28.0", "browserslist": "^4.20.4", "cross-spawn": "^7.0.3", "dedent": "^0.7.0", "del": "^6.1.1", "escape-string-regexp": "^4.0.0", "fs-extra": "^10.1.0", "glob": "^8.0.3", "is-git-dirty": "^2.0.1", "json5": "^2.2.1", "kleur": "^4.1.4", "prompts": "^2.4.2", "react-native-monorepo-config": "^0.1.8", "which": "^2.0.2", "yargs": "^17.5.1" }, "bin": { "bob": "bin/bob" } }, "sha512-CtucAJ5PMLH3GPNlg3TB5rb3UPot6VjkD9T8Uhz/AAWit/DmWll0zG33ZZeka69E2569saAjShDz3IKAoYGFtA=="], - "react-native-mmkv": ["react-native-mmkv@4.0.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-Osoy8as2ZLzO1TTsKxc4tX14Qk19qRVMWnS4ZVBwxie9Re5cjt7rqlpDkJczK3H/y3z70EQ6rmKI/cNMCLGAYQ=="], - "react-native-monorepo-config": ["react-native-monorepo-config@0.1.10", "", { "dependencies": { "escape-string-regexp": "^5.0.0", "fast-glob": "^3.3.3" } }, "sha512-v0rlaLZiCUg95Mpw6xNRQce5k9yio0qscKjNQaPtFYMNL75YugS2UPUItIPLIRbZubK+s2/LRzBjX+mdyUgh4g=="], "react-native-nitro-fetch": ["react-native-nitro-fetch@workspace:package"], diff --git a/docs/android.md b/docs/android.md index 8af8b08..f8a5dcd 100644 --- a/docs/android.md +++ b/docs/android.md @@ -24,7 +24,7 @@ console.log(res.headers.get('nitroPrefetched')); // 'true' if prefetched Auto-Prefetch (on next app start) -- Queue a request in MMKV so native can prefetch on next startup: +- Queue a request in NativeStorage so native can prefetch on next startup: ```ts import { prefetchOnAppStart } from 'react-native-nitro-fetch'; diff --git a/docs/api.md b/docs/api.md index 36ba233..09f8546 100644 --- a/docs/api.md +++ b/docs/api.md @@ -42,12 +42,11 @@ const data = await nitroFetchOnWorklet('https://httpbin.org/get', undefined, map ## `prefetchOnAppStart(input, { prefetchKey })` (Android) -- Enqueues a request to be prefetched at the next app start by writing into MMKV under `nitrofetch_autoprefetch_queue`. -- Requires `react-native-mmkv` in the app. No-op on iOS. +- Enqueues a request to be prefetched at the next app start by writing into Shared Preferences under `nitrofetch_autoprefetch_queue`. ## `removeFromAutoPrefetch(prefetchKey)` / `removeAllFromAutoprefetch()` (Android) -- Utilities to manage the MMKV auto-prefetch queue. +- Utilities to manage the auto-prefetch queue. ## Types diff --git a/docs/ios.md b/docs/ios.md index 7f73905..68e80e3 100644 --- a/docs/ios.md +++ b/docs/ios.md @@ -1,14 +1,14 @@ # iOS - 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. -- Auto‑prefetch on app start is available if your app includes MMKV. Call `NitroAutoPrefetcher.prefetchOnStart()` from `AppDelegate` to trigger it. +- Auto‑prefetch on app start is available. Call `NitroAutoPrefetcher.prefetchOnStart()` from `AppDelegate` to trigger it. - Cronet integration is planned; once available, the iOS client will switch to Cronet for parity with Android. - `nitroFetchOnWorklet` runs the mapper on the JS thread on iOS (off‑thread mapping requires Android worklets runtime). See also: `docs/cronet-ios.md` for high-level Cronet iOS integration notes. ## Auto‑Prefetch on App Start -If your app includes `react-native-mmkv` (which links MMKV on iOS), you can prefetch queued URLs at startup. + 1) Schedule from JS at runtime (same as Android): @@ -33,5 +33,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Notes -- Prefetch is best‑effort. If MMKV is not present, the call is a no‑op. +- Prefetch is best‑effort. - Responses served shortly after prefetch include header `nitroPrefetched: true`. diff --git a/docs/prefetch.md b/docs/prefetch.md index 885b128..208e312 100644 --- a/docs/prefetch.md +++ b/docs/prefetch.md @@ -23,7 +23,7 @@ await prefetch('https://httpbin.org/uuid', { prefetchKey: 'uuid' } as any); ## Auto-Prefetch on Android -Use `prefetchOnAppStart()` to enqueue requests in MMKV so they are fetched on next app start: +Use `prefetchOnAppStart()` to enqueue requests in Shared Preferences so they are fetched on next app start: ```ts import { prefetchOnAppStart } from 'react-native-nitro-fetch'; @@ -45,7 +45,7 @@ Notes ## Why Prefetch Is Cool -- Earlier start at app launch: Auto‑prefetch with MMKV 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. +- 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. - Smoother navigation: Trigger a prefetch when the user initiates navigation, then serve the prefetched result as the destination screen mounts. ### Pattern: Prefetch on Navigation Intent + useQuery @@ -102,9 +102,9 @@ export function UserDetails() { } ``` -## Auto-Prefetch on iOS (with MMKV) +## Auto-Prefetch on iOS (with User Defaults) -If your app links MMKV (e.g., via `react-native-mmkv`), you can prefetch queued URLs at app startup by calling the native bootstrap in your `AppDelegate`: +You can prefetch queued URLs at app startup by calling the native bootstrap in your `AppDelegate`: ```swift import NitroFetch diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f732e4f..8aa97c0 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -6,8 +6,6 @@ - Prefetch error: "missing prefetchKey" - Provide `headers: { prefetchKey: '...' }` or `init.prefetchKey` when calling `prefetch()` and when consuming via `fetch()`. -- MMKV warnings for auto-prefetch - - Install `react-native-mmkv` if you want Android auto-prefetch. Without it, `prefetchOnAppStart()` becomes a no-op. - Cronet provider details - The library logs available Cronet providers and prefers the "Native" provider. Check Android logs for provider name/version during init. diff --git a/example/ios/NitroFetchExample/AppDelegate.swift b/example/ios/NitroFetchExample/AppDelegate.swift index 660c5d3..82a83b4 100644 --- a/example/ios/NitroFetchExample/AppDelegate.swift +++ b/example/ios/NitroFetchExample/AppDelegate.swift @@ -15,7 +15,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { - // Kick off auto-prefetch for queued URLs (if MMKV is linked) + // Kick off auto-prefetch for queued URLs //NitroAutoPrefetcher.prefetchOnStart() it requires adding a separate module for it let delegate = ReactNativeDelegate() diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 08d2faa..5b995c7 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2716,4 +2716,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 709af4b3fd9758044c5ffe392d6f84807c66572a -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/example/package.json b/example/package.json index 05718c3..8f1b218 100644 --- a/example/package.json +++ b/example/package.json @@ -13,7 +13,6 @@ "@react-native/new-app-screen": "0.81.0", "react": "19.1.0", "react-native": "0.81.0", - "react-native-mmkv": "^4.0.0", "react-native-nitro-modules": "^0.29.2", "react-native-safe-area-context": "^5.5.2", "react-native-worklets-core": "^1.6.0" diff --git a/example/src/App.tsx b/example/src/App.tsx index 59a2e68..046896a 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -265,9 +265,9 @@ export default function App() { } }, [running]); - // React.useEffect(() => { - // run(); - // }, [run]); + React.useEffect(() => { + run(); + }, [run]); return ( @@ -323,13 +323,13 @@ export default function App() {