Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions app/native/IRFileStorage/IRFileStorage.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// IRFileStorage.mm
// Reactotron-macOS
//

#import <Foundation/Foundation.h>
#import <AppSpec/AppSpec.h>

@interface IRFileStorage : NativeIRFileStorageSpecBase <NativeIRFileStorageSpec>
@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<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeIRFileStorageSpecJSI>(params);
}

@end


55 changes: 55 additions & 0 deletions app/native/IRFileStorage/IRFileStorage.windows.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include "pch.h"
#include "IRFileStorage.windows.h"
#include <filesystem>
#include <fstream>

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<size_t>(file.tellg()));
file.seekg(0, std::ios::beg);
file.read(contents.data(), static_cast<std::streamsize>(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<std::streamsize>(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);
}

}


25 changes: 25 additions & 0 deletions app/native/IRFileStorage/IRFileStorage.windows.h
Original file line number Diff line number Diff line change
@@ -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;
};
}


11 changes: 11 additions & 0 deletions app/native/IRFileStorage/NativeIRFileStorage.ts
Original file line number Diff line number Diff line change
@@ -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<Spec>("IRFileStorage")
23 changes: 11 additions & 12 deletions app/state/useGlobal.ts
Original file line number Diff line number Diff line change
@@ -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/"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: on windows, this would be better put temp in C:\Users\Username\AppData\Local\Temp. But for now this is okay.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to utilize std::filesystem::path temp = std::filesystem::temp_directory_path(); That'll get the path Josh is talking about (and also should be good on macOS also)

https://en.cppreference.com/w/cpp/filesystem/temp_directory_path.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this evaluates to /var/folders/09/c8x8x89n75n02qyqvvybl54h0000gp/T/ on mac, are we alright with that? Seems fine to me, because i don't think people are really going to care about it, but figured i'd bring that up before i commit changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also @joshuayoes i believe the client information that you're seeing persisted is coming from the standalone-server

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)
}
Expand All @@ -24,7 +23,7 @@ const _componentsToRerender: Record<string, Dispatch<SetStateAction<never[]>>[]>

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
}
Expand Down Expand Up @@ -113,15 +112,15 @@ function buildSetValue<T>(id: string, persist: boolean) {
}

export function deleteGlobal(id: string): void {
delete globals[id]
delete _globals[id]
}

/**
* Clear all globals and reset the storage entirely.
* 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) => {
Expand Down
2 changes: 1 addition & 1 deletion macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1836,7 +1836,7 @@ SPEC CHECKSUMS:
React-timing: 756815d960e39ff0d09ff9c7cb7159299edf0169
React-utils: 0ed154e9b14a89a2d5c962ec00cdbad9f02ae0b8
ReactAppDependencyProvider: 3a460ab76f1149df99ed324bb1c764b76de7a730
ReactCodegen: 4aab124636ad46be1546ce579d11e864dd1a8bc5
ReactCodegen: e1ed669e0a6adcd5e6a20194922009bd4f4cf58d
ReactCommon: 15c98590279f1b5ab3436f4bf14c9f0a123df289
SocketRocket: 03f7111df1a343b162bf5b06ead333be808e1e0a
Yoga: 187db0b2a37012c01dac36afb886a29b92bfd943
Expand Down
74 changes: 37 additions & 37 deletions macos/Reactotron.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
4F3799E74F7D2A242D38CF99 /* ProcessUtils.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; name = ProcessUtils.c; path = ../../app/native/ProcessUtils/ProcessUtils.c; sourceTree = "<group>"; };
5030E1330EE236E4CA991AFD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
514201492437B4B30078DB4F /* Reactotron.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reactotron.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand All @@ -34,8 +33,9 @@
514201562437B4B40078DB4F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
514201572437B4B40078DB4F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
514201592437B4B40078DB4F /* Reactotron.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Reactotron.entitlements; sourceTree = "<group>"; };
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 = "<group>"; };
8B174B7FD8393A730D51E178 /* ProcessUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = ProcessUtils.h; path = ../../app/native/ProcessUtils/ProcessUtils.h; sourceTree = "<group>"; };
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 */
Expand All @@ -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;
};
Expand All @@ -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 = "<group>";
Expand All @@ -65,7 +65,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
AF5D9503835923B7F2C9630F /* libPods-Reactotron-macOS.a */,
6EE9DDD5A3D9662ECA30ABA8 /* libPods-Reactotron-macOS.a */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 = (
);
Expand Down Expand Up @@ -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;
Expand All @@ -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 = (
Expand All @@ -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 = (
Expand All @@ -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 */
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down