diff --git a/app/native/IRFileStorage/IRFileStorage.mm b/app/native/IRFileStorage/IRFileStorage.mm new file mode 100644 index 0000000..0dbc19d --- /dev/null +++ b/app/native/IRFileStorage/IRFileStorage.mm @@ -0,0 +1,64 @@ +// +// IRFileStorage.mm +// Reactotron-macOS +// + +#import +#import + +@interface IRFileStorage : NativeIRFileStorageSpecBase +@end + +@implementation IRFileStorage + +RCT_EXPORT_MODULE() + +static BOOL ensureDirectoryExists(NSString *path) { + if (path.length == 0) return NO; + BOOL isDir = NO; + NSFileManager *fm = [NSFileManager defaultManager]; + if ([fm fileExistsAtPath:path isDirectory:&isDir]) { + return isDir; + } + NSError *error = nil; + BOOL ok = [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]; + return ok && error == nil; +} + +// Spec methods (sync) +- (NSString *)read:(NSString *)path { + if (path == nil || path.length == 0) return @""; + NSData *data = [NSData dataWithContentsOfFile:path]; + if (data == nil) return @""; + NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return str ?: @""; +} + +- (void)write:(NSString *)path data:(NSString *)data { + if (path == nil || data == nil) return; + NSString *dir = [path stringByDeletingLastPathComponent]; + ensureDirectoryExists(dir); + NSError *err = nil; + [data writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&err]; +} + +- (void)remove:(NSString *)path { + if (path == nil) return; + NSFileManager *fm = [NSFileManager defaultManager]; + if (![fm fileExistsAtPath:path]) return; + NSError *err = nil; + [fm removeItemAtPath:path error:&err]; +} + +- (void)ensureDir:(NSString *)path { + if (path == nil) return; + ensureDirectoryExists(path); +} + +- (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); +} + +@end + + diff --git a/app/native/IRFileStorage/IRFileStorage.windows.cpp b/app/native/IRFileStorage/IRFileStorage.windows.cpp new file mode 100644 index 0000000..26d9d2e --- /dev/null +++ b/app/native/IRFileStorage/IRFileStorage.windows.cpp @@ -0,0 +1,55 @@ +#include "pch.h" +#include "IRFileStorage.windows.h" +#include +#include + +namespace fs = std::filesystem; + +namespace winrt::reactotron::implementation { + + static void ensure_dir_exists(const std::string &path) { + if (path.empty()) return; + std::error_code ec; + fs::create_directories(fs::path(path), ec); + } + + std::string IRFileStorage::read(std::string path) noexcept { + try { + std::ifstream file(path, std::ios::in | std::ios::binary); + if (!file.is_open()) return std::string(); + std::string contents; + file.seekg(0, std::ios::end); + contents.resize(static_cast(file.tellg())); + file.seekg(0, std::ios::beg); + file.read(contents.data(), static_cast(contents.size())); + return contents; + } catch (...) { + return std::string(); + } + } + + void IRFileStorage::write(std::string path, std::string data) noexcept { + try { + ensure_dir_exists(fs::path(path).parent_path().string()); + std::ofstream file(path, std::ios::out | std::ios::binary | std::ios::trunc); + if (!file.is_open()) return; + file.write(data.data(), static_cast(data.size())); + } catch (...) { + } + } + + void IRFileStorage::remove(std::string path) noexcept { + try { + std::error_code ec; + fs::remove(fs::path(path), ec); + } catch (...) { + } + } + + void IRFileStorage::ensureDir(std::string path) noexcept { + ensure_dir_exists(path); + } + +} + + diff --git a/app/native/IRFileStorage/IRFileStorage.windows.h b/app/native/IRFileStorage/IRFileStorage.windows.h new file mode 100644 index 0000000..1e9aa87 --- /dev/null +++ b/app/native/IRFileStorage/IRFileStorage.windows.h @@ -0,0 +1,25 @@ +#pragma once +#include "NativeModules.h" + +namespace winrt::reactotron::implementation +{ + REACT_MODULE(IRFileStorage) + struct IRFileStorage + { + IRFileStorage() noexcept = default; + + REACT_SYNC_METHOD(read) + std::string read(std::string path) noexcept; + + REACT_METHOD(write) + void write(std::string path, std::string data) noexcept; + + REACT_METHOD(remove) + void remove(std::string path) noexcept; + + REACT_METHOD(ensureDir) + void ensureDir(std::string path) noexcept; + }; +} + + diff --git a/app/native/IRFileStorage/NativeIRFileStorage.ts b/app/native/IRFileStorage/NativeIRFileStorage.ts new file mode 100644 index 0000000..050d8cf --- /dev/null +++ b/app/native/IRFileStorage/NativeIRFileStorage.ts @@ -0,0 +1,11 @@ +import type { TurboModule } from "react-native" +import { TurboModuleRegistry } from "react-native" + +export interface Spec extends TurboModule { + read(path: string): string + write(path: string, data: string): void + remove(path: string): void + ensureDir(path: string): void +} + +export default TurboModuleRegistry.getEnforcing("IRFileStorage") diff --git a/app/state/useGlobal.ts b/app/state/useGlobal.ts index c2b20e4..61198a1 100644 --- a/app/state/useGlobal.ts +++ b/app/state/useGlobal.ts @@ -1,19 +1,18 @@ import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react" -import { MMKV } from "react-native-mmkv" +import IRFileStorage from "../native/IRFileStorage/NativeIRFileStorage" type UseGlobalOptions = { persist?: boolean } -const PERSISTED_KEY = "global-state" -export const storage = new MMKV({ - // TODO: figure out if we can access "~/Library/Application Support/Reactotron/mmkv"? - path: "/tmp/reactotron/mmkv/", - id: "reactotron", -}) +const PERSISTED_KEY = "global-state.json" +const STORAGE_DIR = "/tmp/reactotron/filestorage/" +const STORAGE_PATH = `${STORAGE_DIR}${PERSISTED_KEY}` +IRFileStorage.ensureDir(STORAGE_DIR) -// Load the globals from MMKV. +// Load the globals from file storage. let _loadGlobals: any = {} try { - _loadGlobals = JSON.parse(storage.getString(PERSISTED_KEY) || "{}") + const contents = IRFileStorage.read(STORAGE_PATH) + _loadGlobals = JSON.parse(contents || "{}") } catch (e) { console.error("Error loading globals", e) } @@ -24,7 +23,7 @@ const _componentsToRerender: Record>[]> let _saveInitiatedAt: number = 0 function saveGlobals() { - storage.set(PERSISTED_KEY, JSON.stringify(_persistedGlobals)) + IRFileStorage.write(STORAGE_PATH, JSON.stringify(_persistedGlobals)) console.tron.log("saved globals", _persistedGlobals) _saveInitiatedAt = 0 } @@ -113,7 +112,7 @@ function buildSetValue(id: string, persist: boolean) { } export function deleteGlobal(id: string): void { - delete globals[id] + delete _globals[id] } /** @@ -121,7 +120,7 @@ export function deleteGlobal(id: string): void { * Optionally rerender all components that use useGlobal. */ export function clearGlobals(rerender: boolean = true): void { - storage.delete(PERSISTED_KEY) + IRFileStorage.remove(STORAGE_PATH) Object.keys(_globals).forEach((key) => delete _globals[key]) if (rerender) { Object.keys(_componentsToRerender).forEach((key) => { diff --git a/macos/Podfile.lock b/macos/Podfile.lock index cac1465..0776d5d 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1836,7 +1836,7 @@ SPEC CHECKSUMS: React-timing: 756815d960e39ff0d09ff9c7cb7159299edf0169 React-utils: 0ed154e9b14a89a2d5c962ec00cdbad9f02ae0b8 ReactAppDependencyProvider: 3a460ab76f1149df99ed324bb1c764b76de7a730 - ReactCodegen: 4aab124636ad46be1546ce579d11e864dd1a8bc5 + ReactCodegen: e1ed669e0a6adcd5e6a20194922009bd4f4cf58d ReactCommon: 15c98590279f1b5ab3436f4bf14c9f0a123df289 SocketRocket: 03f7111df1a343b162bf5b06ead333be808e1e0a Yoga: 187db0b2a37012c01dac36afb886a29b92bfd943 diff --git a/macos/Reactotron.xcodeproj/project.pbxproj b/macos/Reactotron.xcodeproj/project.pbxproj index 9e207d0..68156bc 100644 --- a/macos/Reactotron.xcodeproj/project.pbxproj +++ b/macos/Reactotron.xcodeproj/project.pbxproj @@ -14,16 +14,15 @@ 514201522437B4B40078DB4F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 514201512437B4B40078DB4F /* Assets.xcassets */; }; 514201552437B4B40078DB4F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 514201532437B4B40078DB4F /* Main.storyboard */; }; 514201582437B4B40078DB4F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 514201572437B4B40078DB4F /* main.m */; }; - 781C0A510276619A3FA0F1B5 /* libPods-Reactotron-macOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF5D9503835923B7F2C9630F /* libPods-Reactotron-macOS.a */; }; 831983FBC55C0EF9C65DB4A6 /* ProcessUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = 4F3799E74F7D2A242D38CF99 /* ProcessUtils.c */; }; + A4131919A3A609B53ED7F87E /* libPods-Reactotron-macOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EE9DDD5A3D9662ECA30ABA8 /* libPods-Reactotron-macOS.a */; }; E94E8A1F2DA73754008B52A6 /* SpaceGrotesk.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E94E8A1E2DA73754008B52A6 /* SpaceGrotesk.ttf */; }; EBA23295D3FDE3328174ADF1 /* BuildFile in Headers */ = {isa = PBXBuildFile; }; FF4C4719434907CDFF48ECE7 /* ProcessUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B174B7FD8393A730D51E178 /* ProcessUtils.h */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 2AC32FEAB6C823D7FAA18BFE /* Pods-Reactotron-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Reactotron-macOS.release.xcconfig"; path = "Target Support Files/Pods-Reactotron-macOS/Pods-Reactotron-macOS.release.xcconfig"; sourceTree = ""; }; - 417D988DC189149043CB9C6B /* Pods-Reactotron-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Reactotron-macOS.debug.xcconfig"; path = "Target Support Files/Pods-Reactotron-macOS/Pods-Reactotron-macOS.debug.xcconfig"; sourceTree = ""; }; + 3A2896657CE74FD74F9EC2AC /* Pods-Reactotron-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Reactotron-macOS.release.xcconfig"; path = "Target Support Files/Pods-Reactotron-macOS/Pods-Reactotron-macOS.release.xcconfig"; sourceTree = ""; }; 4F3799E74F7D2A242D38CF99 /* ProcessUtils.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; name = ProcessUtils.c; path = ../../app/native/ProcessUtils/ProcessUtils.c; sourceTree = ""; }; 5030E1330EE236E4CA991AFD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 514201492437B4B30078DB4F /* Reactotron.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reactotron.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -34,8 +33,9 @@ 514201562437B4B40078DB4F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 514201572437B4B40078DB4F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 514201592437B4B40078DB4F /* Reactotron.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Reactotron.entitlements; sourceTree = ""; }; + 6EE9DDD5A3D9662ECA30ABA8 /* libPods-Reactotron-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Reactotron-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6FFFCA6750A2ADAB0517B860 /* Pods-Reactotron-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Reactotron-macOS.debug.xcconfig"; path = "Target Support Files/Pods-Reactotron-macOS/Pods-Reactotron-macOS.debug.xcconfig"; sourceTree = ""; }; 8B174B7FD8393A730D51E178 /* ProcessUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = ProcessUtils.h; path = ../../app/native/ProcessUtils/ProcessUtils.h; sourceTree = ""; }; - AF5D9503835923B7F2C9630F /* libPods-Reactotron-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Reactotron-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E94E8A1E2DA73754008B52A6 /* SpaceGrotesk.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = SpaceGrotesk.ttf; path = ../assets/fonts/SpaceGrotesk.ttf; sourceTree = SOURCE_ROOT; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -45,7 +45,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 781C0A510276619A3FA0F1B5 /* libPods-Reactotron-macOS.a in Frameworks */, + A4131919A3A609B53ED7F87E /* libPods-Reactotron-macOS.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -55,8 +55,8 @@ 01D19913288DA74FEB538384 /* Pods */ = { isa = PBXGroup; children = ( - 417D988DC189149043CB9C6B /* Pods-Reactotron-macOS.debug.xcconfig */, - 2AC32FEAB6C823D7FAA18BFE /* Pods-Reactotron-macOS.release.xcconfig */, + 6FFFCA6750A2ADAB0517B860 /* Pods-Reactotron-macOS.debug.xcconfig */, + 3A2896657CE74FD74F9EC2AC /* Pods-Reactotron-macOS.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -65,7 +65,7 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - AF5D9503835923B7F2C9630F /* libPods-Reactotron-macOS.a */, + 6EE9DDD5A3D9662ECA30ABA8 /* libPods-Reactotron-macOS.a */, ); name = Frameworks; sourceTree = ""; @@ -144,14 +144,14 @@ isa = PBXNativeTarget; buildConfigurationList = 5142015A2437B4B40078DB4F /* Build configuration list for PBXNativeTarget "Reactotron-macOS" */; buildPhases = ( - D379D3EECB71C1F9751C49E8 /* [CP] Check Pods Manifest.lock */, + 168704560B171A19261EFC9C /* [CP] Check Pods Manifest.lock */, 514201452437B4B30078DB4F /* Sources */, 514201462437B4B30078DB4F /* Frameworks */, 514201472437B4B30078DB4F /* Resources */, 381D8A6E24576A4E00465D17 /* Bundle React Native code and images */, 614A44CF57166CA8B6AD60B6 /* Headers */, - 83FF77371CEFDD8B5ECB691C /* [CP] Embed Pods Frameworks */, - A107699AF4EC552473C5FA0B /* [CP] Copy Pods Resources */, + 623FB969C09A98A62AEE2747 /* [CP] Embed Pods Frameworks */, + C279393ADDA420B1412B4FB8 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -210,6 +210,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 168704560B171A19261EFC9C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Reactotron-macOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 381D8A6E24576A4E00465D17 /* Bundle React Native code and images */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -228,7 +250,7 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native-macos/scripts/react-native-xcode.sh\n"; }; - 83FF77371CEFDD8B5ECB691C /* [CP] Embed Pods Frameworks */ = { + 623FB969C09A98A62AEE2747 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -246,7 +268,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Reactotron-macOS/Pods-Reactotron-macOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - A107699AF4EC552473C5FA0B /* [CP] Copy Pods Resources */ = { + C279393ADDA420B1412B4FB8 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -272,28 +294,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Reactotron-macOS/Pods-Reactotron-macOS-resources.sh\"\n"; showEnvVarsInLog = 0; }; - D379D3EECB71C1F9751C49E8 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Reactotron-macOS-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -325,7 +325,7 @@ /* Begin XCBuildConfiguration section */ 5142015B2437B4B40078DB4F /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 417D988DC189149043CB9C6B /* Pods-Reactotron-macOS.debug.xcconfig */; + baseConfigurationReference = 6FFFCA6750A2ADAB0517B860 /* Pods-Reactotron-macOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -352,7 +352,7 @@ }; 5142015C2437B4B40078DB4F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2AC32FEAB6C823D7FAA18BFE /* Pods-Reactotron-macOS.release.xcconfig */; + baseConfigurationReference = 3A2896657CE74FD74F9EC2AC /* Pods-Reactotron-macOS.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES;