diff --git a/rn/Makefile b/rn/Makefile index 26b0f3f7..022c3b0f 100644 --- a/rn/Makefile +++ b/rn/Makefile @@ -28,7 +28,6 @@ unlink: node_modules/.fresh: yarn.lock package.json $(MAKE) unlink - rm -fr node_modules yarn $(MAKE) .link touch $@ diff --git a/rn/index.js b/rn/index.js index fdff1828..b7a7ae0b 100644 --- a/rn/index.js +++ b/rn/index.js @@ -4,7 +4,11 @@ import 'text-encoding' import { AppRegistry } from 'react-native' +import Constants from 'expo-constants' + import App from './src/App' import { name as appName } from './app.json' +console.log(Constants.systemFonts ? 'Expo enabled' : 'WARNING: Expo constant not found') + AppRegistry.registerComponent(appName, () => App) diff --git a/rn/ios/BertyLabs.xcodeproj/project.pbxproj b/rn/ios/BertyLabs.xcodeproj/project.pbxproj index 489a7f26..0fa144de 100644 --- a/rn/ios/BertyLabs.xcodeproj/project.pbxproj +++ b/rn/ios/BertyLabs.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 9471DEA1278471C800186ED2 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9471DEA0278471C800186ED2 /* CoreBluetooth.framework */; }; 9471DEA3278471D100186ED2 /* MultipeerConnectivity.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9471DEA2278471D100186ED2 /* MultipeerConnectivity.framework */; }; + ADD449188196630D379B44C4 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC3EB67675AD648E7229FC5 /* ExpoModulesProvider.swift */; }; + FAA4DE4083AB0380F4CBB844 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC222A8922B8F2BEA33D3877 /* ExpoModulesProvider.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -66,7 +68,9 @@ 9471DEA2278471D100186ED2 /* MultipeerConnectivity.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MultipeerConnectivity.framework; path = System/Library/Frameworks/MultipeerConnectivity.framework; sourceTree = SDKROOT; }; ABFE59519B596E51CEFDCCC0 /* libPods-BertyLabs-BertyLabsTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BertyLabs-BertyLabsTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C0A881CF5CF3F2B244570E2A /* Pods-BertyLabs.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BertyLabs.debug.xcconfig"; path = "Target Support Files/Pods-BertyLabs/Pods-BertyLabs.debug.xcconfig"; sourceTree = ""; }; + CC222A8922B8F2BEA33D3877 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-BertyLabs-BertyLabsTests/ExpoModulesProvider.swift"; sourceTree = ""; }; D00AAFFCFCFDA5787532823F /* Pods-BertyLabs.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BertyLabs.release.xcconfig"; path = "Target Support Files/Pods-BertyLabs/Pods-BertyLabs.release.xcconfig"; sourceTree = ""; }; + DBC3EB67675AD648E7229FC5 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-BertyLabs/ExpoModulesProvider.swift"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -109,6 +113,14 @@ name = "Supporting Files"; sourceTree = ""; }; + 05E614BB0DF15543DEE062DD /* BertyLabsTests */ = { + isa = PBXGroup; + children = ( + CC222A8922B8F2BEA33D3877 /* ExpoModulesProvider.swift */, + ); + name = BertyLabsTests; + sourceTree = ""; + }; 13B07FAE1A68108700A75B9A /* BertyLabs */ = { isa = PBXGroup; children = ( @@ -123,6 +135,15 @@ name = BertyLabs; sourceTree = ""; }; + 22F251C12EFEA6644857DA40 /* ExpoModulesProviders */ = { + isa = PBXGroup; + children = ( + B298FCFFA0ADF76A60C883F9 /* BertyLabs */, + 05E614BB0DF15543DEE062DD /* BertyLabsTests */, + ); + name = ExpoModulesProviders; + sourceTree = ""; + }; 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { isa = PBXGroup; children = ( @@ -168,6 +189,7 @@ 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, E233CBF5F47BEE60B243DCF8 /* Pods */, + 22F251C12EFEA6644857DA40 /* ExpoModulesProviders */, ); indentWidth = 2; sourceTree = ""; @@ -183,6 +205,14 @@ name = Products; sourceTree = ""; }; + B298FCFFA0ADF76A60C883F9 /* BertyLabs */ = { + isa = PBXGroup; + children = ( + DBC3EB67675AD648E7229FC5 /* ExpoModulesProvider.swift */, + ); + name = BertyLabs; + sourceTree = ""; + }; E233CBF5F47BEE60B243DCF8 /* Pods */ = { isa = PBXGroup; children = ( @@ -459,6 +489,7 @@ buildActionMask = 2147483647; files = ( 00E356F31AD99517003FC87E /* BertyLabsTests.m in Sources */, + FAA4DE4083AB0380F4CBB844 /* ExpoModulesProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -468,6 +499,7 @@ files = ( 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, + ADD449188196630D379B44C4 /* ExpoModulesProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -492,7 +524,7 @@ "$(inherited)", ); INFOPLIST_FILE = BertyLabsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -516,7 +548,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; INFOPLIST_FILE = BertyLabsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -540,7 +572,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = WMBQ84HN4T; + DEVELOPMENT_TEAM = BD3SV5P3NX; ENABLE_BITCODE = NO; INFOPLIST_FILE = BertyLabs/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -552,7 +584,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = tech.berty.labs.ios.debug; PRODUCT_NAME = BertyLabs; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -620,7 +652,7 @@ COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -636,7 +668,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", @@ -686,7 +718,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -695,7 +727,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", diff --git a/rn/ios/BertyLabs/AppDelegate.h b/rn/ios/BertyLabs/AppDelegate.h index ef1de86a..d3d75b0b 100644 --- a/rn/ios/BertyLabs/AppDelegate.h +++ b/rn/ios/BertyLabs/AppDelegate.h @@ -1,7 +1,8 @@ #import +#import #import -@interface AppDelegate : UIResponder +@interface AppDelegate : EXAppDelegateWrapper @property (nonatomic, strong) UIWindow *window; diff --git a/rn/ios/BertyLabs/AppDelegate.m b/rn/ios/BertyLabs/AppDelegate.m index 1890b3fe..ba33eafe 100644 --- a/rn/ios/BertyLabs/AppDelegate.m +++ b/rn/ios/BertyLabs/AppDelegate.m @@ -31,8 +31,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( InitializeFlipper(application); #endif - RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; - RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge + RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions]; + RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"BertyLabs" initialProperties:nil]; @@ -43,10 +43,11 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( } self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [UIViewController new]; + UIViewController *rootViewController = [self.reactDelegate createRootViewController]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; + [super application:application didFinishLaunchingWithOptions:launchOptions]; return YES; } diff --git a/rn/ios/Podfile b/rn/ios/Podfile index 503890e4..ac386c4a 100644 --- a/rn/ios/Podfile +++ b/rn/ios/Podfile @@ -1,12 +1,14 @@ +require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' -platform :ios, '11.0' +platform :ios, '12.0' target 'BertyLabs' do pod 'Gomobile-IPFS-Bridge', :podspec => "./vendored_pods/Gomobile-IPFS-Bridge.podspec" pod 'Gomobile-IPFS-Core', :podspec => "./vendored_pods/Gomobile-IPFS-Core.podspec" + use_expo_modules! config = use_native_modules! use_react_native!( diff --git a/rn/ios/Podfile.lock b/rn/ios/Podfile.lock index cf782eb6..bbc2d098 100644 --- a/rn/ios/Podfile.lock +++ b/rn/ios/Podfile.lock @@ -2,6 +2,23 @@ PODS: - boost (1.76.0) - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) + - EXApplication (4.0.1): + - ExpoModulesCore + - EXConstants (13.0.0): + - ExpoModulesCore + - EXErrorRecovery (3.0.4): + - ExpoModulesCore + - EXFileSystem (13.2.0): + - ExpoModulesCore + - EXFont (10.0.4): + - ExpoModulesCore + - EXKeepAwake (10.0.1): + - ExpoModulesCore + - Expo (44.0.4): + - ExpoModulesCore + - ExpoModulesCore (0.6.4): + - React-Core + - ReactCommon/turbomodule/core - FBLazyVector (0.66.4) - FBReactNativeSpec (0.66.4): - RCT-Folly (= 2021.06.28.00-v2) @@ -72,9 +89,9 @@ PODS: - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - - Gomobile-IPFS-Bridge (1.1.2): - - Gomobile-IPFS-Core (~> 1.1.2) - - Gomobile-IPFS-Core (1.1.2) + - Gomobile-IPFS-Bridge (1.1.4): + - Gomobile-IPFS-Core (~> 1.1.4) + - Gomobile-IPFS-Core (1.1.4) - libevent (2.1.12) - OpenSSL-Universal (1.1.180) - RCT-Folly (2021.06.28.00-v2): @@ -276,11 +293,11 @@ PODS: - React-logger (0.66.4): - glog - react-native-gomobile-ipfs (0.1.0): - - Gomobile-IPFS-Bridge (~> 1.1.2) + - Gomobile-IPFS-Bridge (~> 1.1.4) - React - react-native-safe-area-context (3.3.2): - React-Core - - react-native-webview (11.15.1): + - react-native-webview (11.16.0): - React-Core - React-perflogger (0.66.4) - React-RCTActionSheet (0.66.4): @@ -359,6 +376,14 @@ PODS: DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EXApplication (from `../node_modules/expo-application/ios`) + - EXConstants (from `../node_modules/expo-constants/ios`) + - EXErrorRecovery (from `../node_modules/expo-error-recovery/ios`) + - EXFileSystem (from `../node_modules/expo-file-system/ios`) + - EXFont (from `../node_modules/expo-font/ios`) + - EXKeepAwake (from `../node_modules/expo-keep-awake/ios`) + - Expo (from `../node_modules/expo/ios`) + - ExpoModulesCore (from `../node_modules/expo-modules-core/ios`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - Flipper (= 0.99.0) @@ -440,6 +465,22 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + EXApplication: + :path: "../node_modules/expo-application/ios" + EXConstants: + :path: "../node_modules/expo-constants/ios" + EXErrorRecovery: + :path: "../node_modules/expo-error-recovery/ios" + EXFileSystem: + :path: "../node_modules/expo-file-system/ios" + EXFont: + :path: "../node_modules/expo-font/ios" + EXKeepAwake: + :path: "../node_modules/expo-keep-awake/ios" + Expo: + :path: "../node_modules/expo/ios" + ExpoModulesCore: + :path: "../node_modules/expo-modules-core/ios" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" FBReactNativeSpec: @@ -515,6 +556,14 @@ SPEC CHECKSUMS: boost: a7c83b31436843459a1961bfd74b96033dc77234 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 + EXApplication: bdc8dc27713235565da1029a34385229f31b8e08 + EXConstants: 5d18484e38e8eacef7695a82214b6ac90133dedf + EXErrorRecovery: deea88a01d38f8b1c1181b4e1d179b0ba0e4bb5b + EXFileSystem: 7bcd3c1428698150d5c8ca140c8183f2ee204048 + EXFont: 1fb13af43dc517c01c0ff21a6e32f9f9bf2ea602 + EXKeepAwake: b571c2ad8323b2fced6e907766e2549f75119471 + Expo: d9b8b5e49c200471fc473a652a84fe30dee73027 + ExpoModulesCore: 64abda7af7cf659d2fa092934e29c6f4ea7e849f FBLazyVector: e5569e42a1c79ca00521846c223173a57aca1fe1 FBReactNativeSpec: fe08c1cd7e2e205718d77ad14b34957cce949b58 Flipper: 30e8eeeed6abdc98edaf32af0cda2f198be4b733 @@ -528,8 +577,8 @@ SPEC CHECKSUMS: FlipperKit: d8d346844eca5d9120c17d441a2f38596e8ed2b9 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gomobile-IPFS-Bridge: 766021f9e07526743f54ecc2908aa9f4611b4fe8 - Gomobile-IPFS-Core: 0efc4c03fbcf8fdcddba8363b0932092e2bdef26 + Gomobile-IPFS-Bridge: 4b3c4fef35106683bdcec5e7f33402d5df14340b + Gomobile-IPFS-Core: fd4c7e55f6722e3ee141460ba1d7462c47852efd libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 @@ -544,9 +593,9 @@ SPEC CHECKSUMS: React-jsiexecutor: 94ce921e1d8ce7023366873ec371f3441383b396 React-jsinspector: d0374f7509d407d2264168b6d0fad0b54e300b85 React-logger: 933f80c97c633ee8965d609876848148e3fef438 - react-native-gomobile-ipfs: c5491024e2386c69def23c4eab59a1ef141ed452 + react-native-gomobile-ipfs: 59eb3c4317d55b87562e93afe4480d38752f293c react-native-safe-area-context: 584dc04881deb49474363f3be89e4ca0e854c057 - react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504 + react-native-webview: 28a8636d97ee641f2ee8f20492d7a6c269c1d703 React-perflogger: 93075d8931c32cd1fce8a98c15d2d5ccc4d891bd React-RCTActionSheet: 7d3041e6761b4f3044a37079ddcb156575fb6d89 React-RCTAnimation: 743e88b55ac62511ae5c2e22803d4f503f2a3a13 @@ -564,6 +613,6 @@ SPEC CHECKSUMS: Yoga: e7dc4e71caba6472ff48ad7d234389b91dadc280 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 85bdcf85b433ff0695ffbef09134d05c97936950 +PODFILE CHECKSUM: 8d0ae7a8e793952adb5423bf9d9ef0ed97cab00d COCOAPODS: 1.11.2 diff --git a/rn/ios/vendored_pods/Gomobile-IPFS-Bridge.podspec b/rn/ios/vendored_pods/Gomobile-IPFS-Bridge.podspec index 66bfc2f5..4fe630c8 100644 --- a/rn/ios/vendored_pods/Gomobile-IPFS-Bridge.podspec +++ b/rn/ios/vendored_pods/Gomobile-IPFS-Bridge.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Gomobile-IPFS-Bridge' - spec.version = '1.1.2' + spec.version = '1.1.4' spec.summary = 'Swift module offering a simple interface to the underlying Gomobile-IPFS-Core objects' spec.homepage = 'https://github.com/ipfs-shipyard/gomobile-ipfs' @@ -11,11 +11,11 @@ http://www.apache.org/licenses/LICENSE-2.0.txt - http://www.opensource.org/licen spec.authors = { 'Antoine Eddi' => 'antoine.e.b@gmail.com', 'Guilhem Fanton' => 'guilhem.fanton@gmail.com' } spec.platform = :ios, '10.0' - spec.source = { :http => 'https://github.com/n0izn0iz/gomobile-ipfs/releases/download/v1.1.2/bridge-1.1.2.zip' } + spec.source = { :http => 'https://github.com/n0izn0iz/gomobile-ipfs/releases/download/v1.1.4/bridge-1.1.4.zip' } spec.swift_version = '5.0' spec.static_framework = true spec.source_files = '*.swift' spec.header_dir = 'GomobileIPFS' - spec.dependency 'Gomobile-IPFS-Core', '~> 1.1.2' + spec.dependency 'Gomobile-IPFS-Core', '~> 1.1.4' end diff --git a/rn/ios/vendored_pods/Gomobile-IPFS-Core.podspec b/rn/ios/vendored_pods/Gomobile-IPFS-Core.podspec index 03295b5c..2f23a126 100644 --- a/rn/ios/vendored_pods/Gomobile-IPFS-Core.podspec +++ b/rn/ios/vendored_pods/Gomobile-IPFS-Core.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Gomobile-IPFS-Core' - spec.version = '1.1.2' + spec.version = '1.1.4' spec.summary = 'iOS Gomobile package exposing go-ipfs methods required by Gomobile-IPFS-Bridge' spec.homepage = 'https://github.com/ipfs-shipyard/gomobile-ipfs' @@ -11,7 +11,7 @@ http://www.apache.org/licenses/LICENSE-2.0.txt - http://www.opensource.org/licen spec.authors = { 'Antoine Eddi' => 'antoine.e.b@gmail.com', 'Guilhem Fanton' => 'guilhem.fanton@gmail.com' } spec.platform = :ios, '10.0' - spec.source = { :http => 'https://github.com/n0izn0iz/gomobile-ipfs/releases/download/v1.1.2/core-1.1.2.zip' } + spec.source = { :http => 'https://github.com/n0izn0iz/gomobile-ipfs/releases/download/v1.1.4/core-1.1.4.zip' } spec.vendored_frameworks = 'Core.xcframework' end diff --git a/rn/package.json b/rn/package.json index 10297d7c..f340bbf2 100644 --- a/rn/package.json +++ b/rn/package.json @@ -12,30 +12,33 @@ "dependencies": { "@react-navigation/native": "^6.0.6", "@react-navigation/native-stack": "^6.2.5", - "@react-navigation/stack": "^6.0.11", + "expo": "^44.0.4", + "expo-file-system": "^13.2.0", "react": "17.0.2", "react-native": "0.66.4", "react-native-gomobile-ipfs": "./react-native-gomobile-ipfs", "react-native-safe-area-context": "^3.3.2", "react-native-screens": "^3.10.1", "react-native-svg": "^12.1.1", - "react-native-webview": "^11.15.1", + "react-native-webview": "^11.16.0", "text-encoding": "^0.7.0" }, "devDependencies": { "@babel/core": "^7.12.9", + "@babel/preset-env": "^7.1.6", "@babel/runtime": "^7.12.5", - "@react-native-community/eslint-config": "^2.0.0", - "@types/jest": "^26.0.23", + "@react-native-community/eslint-config": "^3.0.1", + "@types/jest": "^27.4.0", "@types/react-native": "^0.66.4", "@types/react-test-renderer": "^17.0.1", "@typescript-eslint/eslint-plugin": "^5.7.0", "@typescript-eslint/parser": "^5.7.0", - "babel-jest": "^26.6.3", + "babel-jest": "^27.4.6", "babel-plugin-module-resolver": "^4.1.0", "eslint": "^7.14.0", - "jest": "^26.6.3", + "jest": "^27.4.7", "metro-react-native-babel-preset": "^0.66.2", + "prettier": ">=2", "react-devtools": "^4.22.0", "react-native-svg-transformer": "^1.0.0", "react-test-renderer": "17.0.2", diff --git a/rn/react-native-gomobile-ipfs/ios/Podfile b/rn/react-native-gomobile-ipfs/ios/Podfile index 721dfa20..9947c69d 100644 --- a/rn/react-native-gomobile-ipfs/ios/Podfile +++ b/rn/react-native-gomobile-ipfs/ios/Podfile @@ -5,7 +5,7 @@ platform :ios, '10.0' target 'GomobileIpfs' do - pod 'Gomobile-IPFS-Bridge', '~> 1.1.2' + pod 'Gomobile-IPFS-Bridge', '~> 1.1.4' config = use_native_modules! use_react_native!(:path => config["reactNativePath"]) diff --git a/rn/react-native-gomobile-ipfs/react-native-gomobile-ipfs.podspec b/rn/react-native-gomobile-ipfs/react-native-gomobile-ipfs.podspec index 9c168f96..f44e7a79 100644 --- a/rn/react-native-gomobile-ipfs/react-native-gomobile-ipfs.podspec +++ b/rn/react-native-gomobile-ipfs/react-native-gomobile-ipfs.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency "React" - s.dependency 'Gomobile-IPFS-Bridge', '~> 1.1.2' + s.dependency 'Gomobile-IPFS-Bridge', '~> 1.1.4' end diff --git a/rn/src/components/AppScreenContainer.tsx b/rn/src/components/AppScreenContainer.tsx new file mode 100644 index 00000000..42cd065a --- /dev/null +++ b/rn/src/components/AppScreenContainer.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { SafeAreaView } from 'react-native' + +import { defaultColors } from '@berty-labs/styles' + +export const AppScreenContainer: React.FC = ({ children }) => { + return ( + + {children} + + ) +} diff --git a/rn/src/components/Button.tsx b/rn/src/components/Button.tsx index eeefe12c..09af89c7 100644 --- a/rn/src/components/Button.tsx +++ b/rn/src/components/Button.tsx @@ -5,6 +5,7 @@ import { defaultColors } from '@berty-labs/styles' export type ButtonParams = { title: string + shrink?: boolean onPress?: (event: GestureResponderEvent) => void } & TouchableOpacity['props'] @@ -13,6 +14,7 @@ export const Button: React.FC = ({ onPress, style, disabled, + shrink, ...otherProps }) => { return ( @@ -20,14 +22,13 @@ export const Button: React.FC = ({ = + React.memo(({ onPress, ...otherProps }) => { + return ( + + + + ) + }) + +export const Card: React.FC = React.memo(({ title, children, style, ...otherProps }) => { + return ( + + {typeof title === 'string' && ( + {title} + )} + {children} + + ) +}) diff --git a/rn/src/components/IPFSServicesHealth.tsx b/rn/src/components/IPFSServicesHealth.tsx new file mode 100644 index 00000000..f2e044e1 --- /dev/null +++ b/rn/src/components/IPFSServicesHealth.tsx @@ -0,0 +1,117 @@ +import React from 'react' +import { Text, ViewStyle } from 'react-native' + +import { defaultColors } from '@berty-labs/styles' +import { prettyMilliSeconds, useMountEffect } from '@berty-labs/reactutil' +import { useGomobileIPFS } from '@berty-labs/ipfsutil' +import { PressableCard } from '@berty-labs/components' + +type ServiceStatus = + | { + state: 'init' | 'querying' + deadDetails?: undefined + time?: undefined + } + | { + state: 'up' + deadDetails?: undefined + time: number + } + | { + state: 'dead' + deadDetails?: string + time: number + } + +const textStyle = { color: defaultColors.white, opacity: 0.7 } + +const stateString = (state: ServiceStatus) => { + switch (state.state) { + case 'init': + return 'Tap to ping' + case 'querying': + return '🧐 Querying..' + case 'up': + return '💚 Responded in ' + prettyMilliSeconds(state.time || 0) + case 'dead': + return `☠ Dead after ${prettyMilliSeconds(state.time || 0)}${ + state.deadDetails ? `\n\n${state.deadDetails}` : '' + }` + default: + return `Unknown:\n${JSON.stringify(state, null, 4)}` + } +} + +const PingCard: React.FC<{ style?: ViewStyle; name: string; address?: string }> = ({ + style, + name, + address, +}) => { + const [state, setState] = React.useState({ + state: 'init', + }) + + const refresh = React.useCallback(() => { + const start = async () => { + if (!address) { + return + } + const startDate = Date.now() + setState({ + state: 'querying', + }) + try { + const reply = await fetch(address) + console.log(name, 'ping status:', reply.status) + setState({ + state: 'up', + time: Date.now() - startDate, + }) + } catch (err: unknown) { + console.log('ping error:', err) + setState({ + state: 'dead', + deadDetails: `${err}`, + time: Date.now() - startDate, + }) + } + } + start() + }, [address, name]) + + useMountEffect(refresh) + + return ( + + Address: {address} + {stateString(state)} + + ) +} + +export const IPFSServicesHealth: React.FC<{ style?: ViewStyle }> = ({ style }) => { + const mobileIPFS = useGomobileIPFS() + + const items = [ + { + name: 'Local IPFS Node API', + address: mobileIPFS.apiURL, + }, + { + name: 'Local IPFS Gateway', + address: mobileIPFS.gatewayURL, + }, + /*{ + name: 'Dead example', + address: "http://127.0.0.1:1234", + },*/ + ] + + return ( + <> + {items.map(item => ( + + ))} + + ) +} diff --git a/rn/src/components/Loader.tsx b/rn/src/components/Loader.tsx new file mode 100644 index 00000000..44da17e6 --- /dev/null +++ b/rn/src/components/Loader.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import { View, Text, ActivityIndicator } from 'react-native' + +import { defaultColors } from '@berty-labs/styles' + +import { AppScreenContainer } from './AppScreenContainer' + +export const Loader: React.FC<{ text?: string }> = ({ text }) => ( + + + {text && ( + {text} + )} + + + +) + +export const LoaderScreen: React.FC<{ text?: string }> = ({ text }) => ( + + + +) diff --git a/rn/src/components/index.ts b/rn/src/components/index.ts index 8486fd6d..e7e6be16 100644 --- a/rn/src/components/index.ts +++ b/rn/src/components/index.ts @@ -1 +1,5 @@ export * from './Button' +export * from './AppScreenContainer' +export * from './Card' +export * from './IPFSServicesHealth' +export * from './Loader' diff --git a/rn/src/expoutil/index.ts b/rn/src/expoutil/index.ts new file mode 100644 index 00000000..bae442d3 --- /dev/null +++ b/rn/src/expoutil/index.ts @@ -0,0 +1 @@ +export * from './usePathSize' diff --git a/rn/src/expoutil/usePathSize.ts b/rn/src/expoutil/usePathSize.ts new file mode 100644 index 00000000..564629ad --- /dev/null +++ b/rn/src/expoutil/usePathSize.ts @@ -0,0 +1,31 @@ +import React from 'react' +import * as FileSystem from 'expo-file-system' + +export const usePathSize = (path: string) => { + const [size, setSize] = React.useState() + + const updateSize = React.useCallback( + async (controller: AbortController) => { + const info = await FileSystem.getInfoAsync(path) + if (controller.signal.aborted) { + return + } + setSize(info.size) + }, + [path], + ) + + React.useEffect(() => { + const controller = new AbortController() + updateSize(controller) + const interval = setInterval(() => { + updateSize(controller) + }, 1000) + return () => { + controller.abort() + clearInterval(interval) + } + }, [updateSize]) + + return size +} diff --git a/rn/src/ipfsutil/config.ts b/rn/src/ipfsutil/config.ts new file mode 100644 index 00000000..8a5f39cc --- /dev/null +++ b/rn/src/ipfsutil/config.ts @@ -0,0 +1,46 @@ +export type AnySpec = MountSpec | MeasureSpec | FlatFSSpec | LevelDSSpec + +export type MountSpec = { + mounts?: (AnySpec & { mountpoint: string })[] + type: 'mount' +} + +export type MeasureSpec = { + child: AnySpec + prefix?: string + type: 'measure' +} + +export type FlatFSSpec = { + path?: string + shardFunc?: string + sync?: boolean + type: 'flatfs' +} + +export type LevelDSSpec = { + compression?: string + path?: string + type: 'levelds' +} + +export type IPFSConfig = { + Identity?: { + PeerID?: string + PrivKey?: string + } + Datastore?: { + StorageMax?: string + StorageGCWatermark?: number + GCPeriod?: string + Spec?: AnySpec + HashOnRead?: boolean + BloomFilterSize?: number + } + Discovery?: { + MDNS?: { + Enabled?: boolean + Interval?: number + } + } +} diff --git a/rn/src/ipfsutil/expo-gomobile-ipfs.tsx b/rn/src/ipfsutil/expo-gomobile-ipfs.tsx new file mode 100644 index 00000000..ca028b9f --- /dev/null +++ b/rn/src/ipfsutil/expo-gomobile-ipfs.tsx @@ -0,0 +1,165 @@ +import React, { useContext } from 'react' +import { IPFS } from 'react-native-gomobile-ipfs' +import * as FileSystem from 'expo-file-system' + +import { IPFSConfig } from './config' + +export type GomobileIPFSState = GomobileIPFSInternalState & { + switchToNode: (name: string) => Promise +} + +export type GomobileIPFSInternalState = + | { + state: 'loading' + ipfs?: undefined + gatewayURL?: undefined + apiURL?: undefined + name?: undefined + } + | { + state: 'up' + ipfs: IPFS + gatewayURL: string + apiURL: string + name: string + } + | { + state: 'unloading' + ipfs?: undefined + gatewayURL?: undefined + apiURL?: undefined + name?: undefined + } + +export type GomobileIPFSOptions = { + gatewayMaddr?: string +} + +const loadingState: GomobileIPFSState = { state: 'loading', switchToNode: async () => {} } + +const GomobileIPFSContext = React.createContext(loadingState) + +// let nodeState: GomobileIPFSState | undefined + +export const useGomobileIPFSNode = ( + name = 'default-0', + opts?: GomobileIPFSOptions, +): GomobileIPFSState => { + const [state, setState] = React.useState(loadingState) + const [nextNode, setNextNode] = React.useState(name) + const gatewayMaddr = opts?.gatewayMaddr || '/ip4/127.0.0.1/tcp/4242' + + const switchToNode = React.useCallback( + async (newName: string) => { + if (state.state !== 'up') { + return + } + setState({ ...state, state: 'unloading' } as any) + if (!state.ipfs) { + return + } + console.log('stoping ipfs node', state.name) + await state.ipfs.stop() + console.log('freeing ipfs node', state.name) + await state.ipfs.free() + setNextNode(newName) + }, + [state], + ) + + React.useEffect(() => { + /*if (nodeState) { + setState(nodeState) + return + }*/ + let canceled = false + let clean = async () => {} + const start = async () => { + // Create instance of IPFS. Use one instance per app. + console.log('creating ipfs node', nextNode) + const ipfs = await IPFS.create('ipfs-repos/' + nextNode) + if (canceled) { + console.log('cancel 0') + await ipfs.free() + return + } + console.log(ipfs) + + // Start IPFS instance. It will connect to the network. + console.log('starting ipfs node', nextNode) + await ipfs.start() + clean = async () => { + /*console.log('stoping ipfs node') + await ipfs.stop() + console.log('freeing ipfs node') + await ipfs.free() + console.log('ipfs node free')*/ + } + if (canceled) { + await clean() + console.log('cancel 1') + return + } + console.log('started ipfs node', nextNode) + + // Serve API + const finalAPIMaddr = await ipfs.serveAPI('5001') + console.log('ipfs api:', finalAPIMaddr) + if (canceled) { + console.log('cancel 2') + return + } + + // Serve gateway + const finalGatewayMaddr = await ipfs.serveGateway(gatewayMaddr) + console.log('ipfs gateway:', finalGatewayMaddr) + if (canceled) { + console.log('cancel 2') + return + } + + // Update state + // FIXME: really parse maddr + const gaParts = finalGatewayMaddr.split('/') + const gatewayURL = `http://${gaParts[2]}:${gaParts[4]}` + const aaParts = finalAPIMaddr.split('/') + const apiURL = `http://${aaParts[2]}:${aaParts[4]}` + const st: GomobileIPFSInternalState = { + gatewayURL, + apiURL, + ipfs, + state: 'up', + name: nextNode, + } + //nodeState = st + setState(st) + } + start() + return () => { + canceled = true + clean() + } + }, [nextNode, gatewayMaddr]) + + return React.useMemo(() => ({ ...state, switchToNode }), [state, switchToNode]) +} + +export const GomobileIPFSProvider: React.FC = ({ children }) => { + const state = useGomobileIPFSNode() + return {children} +} + +export const useGomobileIPFS = () => { + return useContext(GomobileIPFSContext) +} + +export const ipfsRepoPath = (name: string) => FileSystem.documentDirectory + 'ipfs-repos/' + name + +export const ipfsConfigPath = (name: string) => + FileSystem.documentDirectory + 'ipfs-repos/' + name + '/config' + +export const getIPFSConfigOffline = async (name: string): Promise => { + const path = ipfsConfigPath(name) + const configString = await FileSystem.readAsStringAsync(path) + return JSON.parse(configString) +} diff --git a/rn/src/ipfsutil/index.tsx b/rn/src/ipfsutil/index.tsx index b67d5527..760d3dec 100644 --- a/rn/src/ipfsutil/index.tsx +++ b/rn/src/ipfsutil/index.tsx @@ -1,115 +1,2 @@ -import React, { useContext } from 'react' -import { IPFS } from 'react-native-gomobile-ipfs' - -import { useMountEffect } from '@berty-labs/reactutil' - -export type GomobileIPFSState = - | { - state: 'loading' - ipfs?: undefined - gatewayURL?: undefined - apiURL?: undefined - } - | { - state: 'up' - ipfs: IPFS - gatewayURL: string - apiURL: string - } - -export type GomobileIPFSOptions = { - gatewayMaddr?: string -} - -const loadingState: GomobileIPFSState = { state: 'loading' } - -const GomobileIPFSContext = React.createContext(loadingState) - -let nodeState: GomobileIPFSState | undefined - -export const useGomobileIPFSNode = ( - path = 'ipfs-repos/default-0', - opts?: GomobileIPFSOptions, -): GomobileIPFSState => { - const [state, setState] = React.useState(loadingState) - const gatewayMaddr = opts?.gatewayMaddr || '/ip4/127.0.0.1/tcp/4242' - - useMountEffect(() => { - if (nodeState) { - setState(nodeState) - return - } - let canceled = false - let clean = async () => {} - const start = async () => { - // Create instance of IPFS. Use one instance per app. - console.log('creating ipfs node') - const ipfs = await IPFS.create(path) - if (canceled) { - console.log('cancel 0') - await ipfs.free() - return - } - console.log(ipfs) - - // Start IPFS instance. It will connect to the network. - console.log('starting ipfs node') - await ipfs.start() - clean = async () => { - /*console.log('stoping ipfs node') - await ipfs.stop() - console.log('freeing ipfs node') - await ipfs.free() - console.log('ipfs node free')*/ - } - if (canceled) { - await clean() - console.log('cancel 1') - return - } - console.log('started ipfs node') - - // Serve API - const finalAPIMaddr = await ipfs.serveAPI('5001') - console.log('ipfs api:', finalAPIMaddr) - if (canceled) { - console.log('cancel 2') - return - } - - // Serve gateway - const finalGatewayMaddr = await ipfs.serveGateway(gatewayMaddr) - console.log('ipfs gateway:', finalGatewayMaddr) - if (canceled) { - console.log('cancel 2') - return - } - - // Update state - // FIXME: really parse maddr - const gaParts = finalGatewayMaddr.split('/') - const gatewayURL = `http://${gaParts[2]}:${gaParts[4]}` - const aaParts = finalAPIMaddr.split('/') - const apiURL = `http://${aaParts[2]}:${aaParts[4]}` - const st: GomobileIPFSState = { gatewayURL, apiURL, ipfs, state: 'up' } - nodeState = st - setState(st) - } - start() - return () => { - canceled = true - clean() - } - }) - - return state -} - -export const GomobileIPFSProvider: React.FC = ({ children }) => { - const state = useGomobileIPFSNode() - return {children} -} - -export const useGomobileIPFS = () => { - return useContext(GomobileIPFSContext) -} +export * from './expo-gomobile-ipfs' +export * from './config' diff --git a/rn/src/navigation/index.tsx b/rn/src/navigation/index.tsx index ec589a54..46f44a42 100644 --- a/rn/src/navigation/index.tsx +++ b/rn/src/navigation/index.tsx @@ -9,17 +9,25 @@ import { NavigationProp, } from '@react-navigation/native' -import { ServicesHealth, GatewaysRace, NftCollection, OnBoarding, Home } from '@berty-labs/screens' +import { + ServicesHealth, + GatewaysRace, + NftCollection, + OnBoarding, + Home, + NodeManager, + IPFSWebUI, + NodeConfig, +} from '@berty-labs/screens' import { defaultColors } from '@berty-labs/styles' import { ScreensParams } from './types' -import { IPFSWebUI } from '@berty-labs/screens/IPFSWebUI' export type { ScreensParams, ScreenProps, ScreenFC } from './types' export const isReadyRef: React.MutableRefObject = React.createRef() export const navigationRef = React.createRef>() -export const useNavigation = () => useReactNavigation>() +export const useAppNavigation = () => useReactNavigation>() const screenOptions: NativeStackNavigationOptions = { headerStyle: { @@ -65,6 +73,16 @@ export const Navigation: React.FC = React.memo(() => { component={IPFSWebUI} options={{ ...screenOptions, title: 'IPFS WebUI' }} /> + + ) }) diff --git a/rn/src/navigation/types.ts b/rn/src/navigation/types.ts index 980be48b..9db955da 100644 --- a/rn/src/navigation/types.ts +++ b/rn/src/navigation/types.ts @@ -1,6 +1,6 @@ import React from 'react' -import { StackScreenProps } from '@react-navigation/stack' +import { NativeStackScreenProps } from '@react-navigation/native-stack' export type ScreensParams = { OnBoarding: undefined @@ -9,8 +9,10 @@ export type ScreensParams = { ServicesHealth: undefined GatewaysRace: undefined NftCollection: undefined + NodeManager: undefined + NodeConfig: { name: string } } -export type ScreenProps = StackScreenProps +export type ScreenProps = NativeStackScreenProps export type ScreenFC = React.FC> diff --git a/rn/src/reactutil/index.ts b/rn/src/reactutil/index.ts index 068a6289..da09f264 100644 --- a/rn/src/reactutil/index.ts +++ b/rn/src/reactutil/index.ts @@ -1,4 +1,39 @@ import React from 'react' +export * from './randomName' + // eslint-disable-next-line react-hooks/exhaustive-deps export const useMountEffect = (effect: React.EffectCallback) => React.useEffect(effect, []) + +export const useUnmountRef = () => { + const unmountRef = React.useRef(false) + useMountEffect(() => { + return () => { + unmountRef.current = true + } + }) + return unmountRef +} + +export const prettyMilliSeconds = (ms: number) => { + if (ms >= 60000) { + return `${(ms / 60000).toFixed(0)}min ${((ms % 60000) / 1000).toFixed(0)}sec` + } + if (ms >= 1000) { + return `${(ms / 1000).toFixed(2)}sec` + } + return `${ms}ms` +} + +export const prettyBytes = (rb: number | null | undefined) => { + const b = rb || 0 + if (b < 1000) { + return `${b.toFixed(2)} B` + } + if (b < 1000000) { + return `${(b / 1000).toFixed(2)} kB` + } + if (b < 1000000000) { + return `${(b / 1000000).toFixed(2)} MB` + } +} diff --git a/rn/src/reactutil/randomName.ts b/rn/src/reactutil/randomName.ts new file mode 100644 index 00000000..2d5b9321 --- /dev/null +++ b/rn/src/reactutil/randomName.ts @@ -0,0 +1,1208 @@ +export const animalNames = [ + 'Aardvark', + 'Albatross', + 'Alligator', + 'Alpaca', + 'Ant', + 'Anteater', + 'Antelope', + 'Ape', + 'Armadillo', + 'Donkey', + 'Baboon', + 'Badger', + 'Barracuda', + 'Bat', + 'Bear', + 'Beaver', + 'Bee', + 'Bison', + 'Boar', + 'Buffalo', + 'Butterfly', + 'Camel', + 'Capybara', + 'Caribou', + 'Cassowary', + 'Cat', + 'Caterpillar', + 'Cattle', + 'Chamois', + 'Cheetah', + 'Chicken', + 'Chimpanzee', + 'Chinchilla', + 'Chough', + 'Clam', + 'Cobra', + 'Cockroach', + 'Cod', + 'Cormorant', + 'Coyote', + 'Crab', + 'Crane', + 'Crocodile', + 'Crow', + 'Curlew', + 'Deer', + 'Dinosaur', + 'Dog', + 'Dogfish', + 'Dolphin', + 'Dotterel', + 'Dove', + 'Dragonfly', + 'Duck', + 'Dugong', + 'Dunlin', + 'Eagle', + 'Echidna', + 'Eel', + 'Eland', + 'Elephant', + 'Elk', + 'Emu', + 'Falcon', + 'Ferret', + 'Finch', + 'Fish', + 'Flamingo', + 'Fly', + 'Fox', + 'Frog', + 'Gaur', + 'Gazelle', + 'Gerbil', + 'Giraffe', + 'Gnat', + 'Gnu', + 'Goat', + 'Goldfinch', + 'Goldfish', + 'Goose', + 'Gorilla', + 'Goshawk', + 'Grasshopper', + 'Grouse', + 'Guanaco', + 'Gull', + 'Hamster', + 'Hare', + 'Hawk', + 'Hedgehog', + 'Heron', + 'Herring', + 'Hippopotamus', + 'Hornet', + 'Horse', + 'Human', + 'Hummingbird', + 'Hyena', + 'Ibex', + 'Ibis', + 'Jackal', + 'Jaguar', + 'Jay', + 'Jellyfish', + 'Kangaroo', + 'Kingfisher', + 'Koala', + 'Kookabura', + 'Kouprey', + 'Kudu', + 'Lapwing', + 'Lark', + 'Lemur', + 'Leopard', + 'Lion', + 'Llama', + 'Lobster', + 'Locust', + 'Loris', + 'Louse', + 'Lyrebird', + 'Magpie', + 'Mallard', + 'Manatee', + 'Mandrill', + 'Mantis', + 'Marten', + 'Meerkat', + 'Mink', + 'Mole', + 'Mongoose', + 'Monkey', + 'Moose', + 'Mosquito', + 'Mouse', + 'Mule', + 'Narwhal', + 'Newt', + 'Nightingale', + 'Octopus', + 'Okapi', + 'Opossum', + 'Oryx', + 'Ostrich', + 'Otter', + 'Owl', + 'Oyster', + 'Panther', + 'Parrot', + 'Partridge', + 'Peafowl', + 'Pelican', + 'Penguin', + 'Pheasant', + 'Pig', + 'Pigeon', + 'Pony', + 'Porcupine', + 'Porpoise', + 'Quail', + 'Quelea', + 'Quetzal', + 'Rabbit', + 'Raccoon', + 'Rail', + 'Ram', + 'Rat', + 'Raven', + 'Red deer', + 'Red panda', + 'Reindeer', + 'Rhinoceros', + 'Rook', + 'Salamander', + 'Salmon', + 'Sand Dollar', + 'Sandpiper', + 'Sardine', + 'Scorpion', + 'Seahorse', + 'Seal', + 'Shark', + 'Sheep', + 'Shrew', + 'Skunk', + 'Snail', + 'Snake', + 'Sparrow', + 'Spider', + 'Spoonbill', + 'Squid', + 'Squirrel', + 'Starling', + 'Stingray', + 'Stinkbug', + 'Stork', + 'Swallow', + 'Swan', + 'Tapir', + 'Tarsier', + 'Termite', + 'Tiger', + 'Toad', + 'Trout', + 'Turkey', + 'Turtle', + 'Viper', + 'Vulture', + 'Wallaby', + 'Walrus', + 'Wasp', + 'Weasel', + 'Whale', + 'Wildcat', + 'Wolf', + 'Wolverine', + 'Wombat', + 'Woodcock', + 'Woodpecker', + 'Worm', + 'Wren', + 'Yak', + 'Zebra', +] + +export const adjectives = [ + 'Aristotelian', + 'Arthurian', + 'Bohemian', + 'Brethren', + 'Mosaic', + 'Oceanic', + 'Proctor', + 'Terran', + 'Tudor', + 'abroad', + 'absorbing', + 'abstract', + 'academic', + 'accelerated', + 'accented', + 'accountant', + 'acquainted', + 'acute', + 'addicting', + 'addictive', + 'adjustable', + 'admired', + 'adult', + 'adverse', + 'advised', + 'aerosol', + 'afraid', + 'aggravated', + 'aggressive', + 'agreeable', + 'alienate', + 'aligned', + 'all-round', + 'alleged', + 'almond', + 'alright', + 'altruistic', + 'ambient', + 'ambivalent', + 'amiable', + 'amino', + 'amorphous', + 'amused', + 'anatomical', + 'ancestral', + 'angelic', + 'angrier', + 'answerable', + 'antiquarian', + 'antiretroviral', + 'appellate', + 'applicable', + 'apportioned', + 'approachable', + 'appropriated', + 'archer', + 'aroused', + 'arrested', + 'assertive', + 'assigned', + 'athletic', + 'atrocious', + 'attained', + 'authoritarian', + 'autobiographical', + 'avaricious', + 'avocado', + 'awake', + 'awsome', + 'backstage', + 'backwoods', + 'balding', + 'bandaged', + 'banded', + 'banned', + 'barreled', + 'battle', + 'beaten', + 'begotten', + 'beguiled', + 'bellied', + 'belted', + 'beneficent', + 'besieged', + 'betting', + 'big-money', + 'biggest', + 'biochemical', + 'bipolar', + 'blackened', + 'blame', + 'blessed', + 'blindfolded', + 'bloat', + 'blocked', + 'blooded', + 'blue-collar', + 'blushing', + 'boastful', + 'bolder', + 'bolstered', + 'bonnie', + 'bored', + 'boundary', + 'bounded', + 'bounding', + 'branched', + 'brawling', + 'brazen', + 'breeding', + 'bridged', + 'brimming', + 'brimstone', + 'broadest', + 'broiled', + 'broker', + 'bronze', + 'bruising', + 'buffy', + 'bullied', + 'bungling', + 'burial', + 'buttery', + 'candied', + 'canonical', + 'cantankerous', + 'cardinal', + 'carefree', + 'caretaker', + 'casual', + 'cathartic', + 'causal', + 'chapel', + 'characterized', + 'charcoal', + 'cheeky', + 'cherished', + 'chipotle', + 'chirping', + 'chivalrous', + 'circumstantial', + 'civic', + 'civil', + 'civilised', + 'clanking', + 'clapping', + 'claptrap', + 'classless', + 'cleansed', + 'cleric', + 'cloistered', + 'codified', + 'colloquial', + 'colour', + 'combat', + 'combined', + 'comely', + 'commissioned', + 'commonplace', + 'commuter', + 'commuting', + 'comparable', + 'complementary', + 'compromising', + 'conceding', + 'concentrated', + 'conceptual', + 'conditioned', + 'confederate', + 'confident', + 'confidential', + 'confining', + 'confuse', + 'congressional', + 'consequential', + 'conservative', + 'constituent', + 'contaminated', + 'contemporaneous', + 'contraceptive', + 'convertible', + 'convex', + 'cooked', + 'coronary', + 'corporatist', + 'correlated', + 'corroborated', + 'cosmic', + 'cover', + 'crash', + 'crypto', + 'culminate', + 'cushioned', + 'dandy', + 'dashing', + 'dazzled', + 'decreased', + 'decrepit', + 'dedicated', + 'defaced', + 'defective', + 'defenseless', + 'deluded', + 'deodorant', + 'departed', + 'depress', + 'designing', + 'despairing', + 'destitute', + 'detective', + 'determined', + 'devastating', + 'deviant', + 'devilish', + 'devoted', + 'diagonal', + 'dictated', + 'didactic', + 'differentiated', + 'diffused', + 'dirtier', + 'disabling', + 'disconnected', + 'discovered', + 'disdainful', + 'diseased', + 'disfigured', + 'disheartened', + 'disheveled', + 'disillusioned', + 'disparate', + 'dissident', + 'doable', + 'doctrinal', + 'doing', + 'dotted', + 'double-blind', + 'downbeat', + 'dozen', + 'draining', + 'draught', + 'dread', + 'dried', + 'dropped', + 'dulled', + 'duplicate', + 'eaten', + 'echoing', + 'economical', + 'elaborated', + 'elastic', + 'elective', + 'electoral', + 'elven', + 'embryo', + 'emerald', + 'emergency', + 'emissary', + 'emotional', + 'employed', + 'enamel', + 'encased', + 'encrusted', + 'endangered', + 'engraved', + 'engrossing', + 'enlarged', + 'enlisted', + 'enlivened', + 'ensconced', + 'entangled', + 'enthralling', + 'entire', + 'envious', + 'eradicated', + 'eroded', + 'esoteric', + 'essential', + 'evaporated', + 'ever-present', + 'evergreen', + 'everlasting', + 'exacting', + 'exasperated', + 'excess', + 'exciting', + 'executable', + 'existent', + 'exonerated', + 'exorbitant', + 'exponential', + 'export', + 'extraordinary', + 'exultant', + 'exulting', + 'facsimile', + 'fading', + 'fainter', + 'faith-based', + 'fallacious', + 'faltering', + 'famous', + 'fancier', + 'fast-growing', + 'fated', + 'favourable', + 'fearless', + 'feathered', + 'fellow', + 'fermented', + 'ferocious', + 'fiddling', + 'filling', + 'firmer', + 'fitted', + 'flammable', + 'flawed', + 'fledgling', + 'fleshy', + 'flexible', + 'flickering', + 'floral', + 'flowering', + 'flowing', + 'foggy', + 'folic', + 'foolhardy', + 'foolish', + 'footy', + 'forehand', + 'forked', + 'formative', + 'formulaic', + 'foul-mouthed', + 'fractional', + 'fragrant', + 'fraudulent', + 'freakish', + 'freckled', + 'freelance', + 'freight', + 'fresh', + 'fretted', + 'frugal', + 'fulfilling', + 'fuming', + 'funded', + 'funny', + 'garbled', + 'gathered', + 'geologic', + 'geometric', + 'gibberish', + 'gilded', + 'ginger', + 'glare', + 'glaring', + 'gleaming', + 'glorified', + 'glorious', + 'goalless', + 'gold-plated', + 'goody', + 'grammatical', + 'grande', + 'grateful', + 'gratuitous', + 'graven', + 'greener', + 'grinding', + 'grizzly', + 'groaning', + 'grudging', + 'guaranteed', + 'gusty', + 'half-breed', + 'hand-held', + 'handheld', + 'hands-off', + 'hard-pressed', + 'harlot', + 'healing', + 'healthier', + 'healthiest', + 'heart', + 'heart-shaped', + 'heathen', + 'hedonistic', + 'heralded', + 'herbal', + 'high-density', + 'high-performance', + 'high-res', + 'high-yield', + 'hissy', + 'hitless', + 'holiness', + 'homesick', + 'honorable', + 'hooded', + 'hopeless', + 'horrendous', + 'horrible', + 'hot-button', + 'huddled', + 'human', + 'humbling', + 'humid', + 'humiliating', + 'hypnotized', + 'idealistic', + 'idiosyncratic', + 'ignited', + 'illustrated', + 'illustrative', + 'imitated', + 'immense', + 'immersive', + 'immigrant', + 'immoral', + 'impassive', + 'impressionable', + 'improbable', + 'impulsive', + 'in-between', + 'in-flight', + 'inattentive', + 'inbound', + 'inbounds', + 'incalculable', + 'incomprehensible', + 'indefatigable', + 'indigo', + 'indiscriminate', + 'indomitable', + 'inert', + 'inflate', + 'inform', + 'inheriting', + 'injured', + 'injurious', + 'inking', + 'inoffensive', + 'insane', + 'insensible', + 'insidious', + 'insincere', + 'insistent', + 'insolent', + 'insufferable', + 'intemperate', + 'interdependent', + 'interesting', + 'interfering', + 'intern', + 'interpreted', + 'intersecting', + 'intolerable', + 'intolerant', + 'intuitive', + 'irresolute', + 'irritate', + 'jealous', + 'jerking', + 'joining', + 'joint', + 'journalistic', + 'joyful', + 'keyed', + 'knowing', + 'lacklustre', + 'laden', + 'lagging', + 'lamented', + 'laughable', + 'layered', + 'leather', + 'leathern', + 'leery', + 'left-footed', + 'legible', + 'leisure', + 'lessening', + 'liberating', + 'life-size', + 'lifted', + 'lightest', + 'limitless', + 'listening', + 'literary', + 'liver', + 'livid', + 'lobster', + 'locked', + 'long-held', + 'long-lasting', + 'long-running', + 'long-suffering', + 'loudest', + 'loveliest', + 'low-budget', + 'low-carb', + 'lowering', + 'lucid', + 'luckless', + 'lusty', + 'luxurious', + 'magazine', + 'maniac', + 'manmade', + 'maroon', + 'mastered', + 'mated', + 'material', + 'materialistic', + 'meaningful', + 'measuring', + 'mediaeval', + 'medical', + 'meditated', + 'medley', + 'melodic', + 'memorable', + 'memorial', + 'metabolic', + 'metallic', + 'metallurgical', + 'metering', + 'midair', + 'midterm', + 'midway', + 'mighty', + 'migrating', + 'mind-blowing', + 'mind-boggling', + 'minor', + 'mirrored', + 'misguided', + 'misshapen', + 'mitigated', + 'mixed', + 'modernized', + 'molecular', + 'monarch', + 'monastic', + 'morbid', + 'motley', + 'motorized', + 'mounted', + 'multi-million', + 'multidisciplinary', + 'muscled', + 'muscular', + 'muted', + 'mysterious', + 'mythic', + 'nail-biting', + 'natural', + 'nauseous', + 'negative', + 'networked', + 'neurological', + 'neutered', + 'newest', + 'night', + 'nitrous', + 'no-fly', + 'noncommercial', + 'nonsense', + 'north', + 'nuanced', + 'occurring', + 'offensive', + 'oldest', + 'oncoming', + 'one-eyed', + 'one-year', + 'onstage', + 'onward', + 'opaque', + 'open-ended', + 'operating', + 'opportunist', + 'opposing', + 'opt-in', + 'ordinate', + 'outdone', + 'outlaw', + 'outsized', + 'overboard', + 'overheated', + 'oversize', + 'overworked', + 'oyster', + 'paced', + 'panting', + 'paralyzed', + 'paramount', + 'parental', + 'parted', + 'partisan', + 'passive', + 'pastel', + 'patriot', + 'peacekeeping', + 'pedestrian', + 'peevish', + 'penal', + 'penned', + 'pensive', + 'perceptual', + 'perky', + 'permissible', + 'pernicious', + 'perpetuate', + 'perplexed', + 'pervasive', + 'petrochemical', + 'philosophical', + 'picturesque', + 'pillaged', + 'piped', + 'piquant', + 'pitching', + 'plausible', + 'pliable', + 'plumb', + 'politician', + 'polygamous', + 'poorest', + 'portmanteau', + 'posed', + 'positive', + 'possible', + 'postpartum', + 'prank', + 'pre-emptive', + 'precocious', + 'predicted', + 'premium', + 'preparatory', + 'prerequisite', + 'prescient', + 'preserved', + 'presidential', + 'pressed', + 'pressurized', + 'presumed', + 'prewar', + 'priced', + 'pricier', + 'primal', + 'primer', + 'primetime', + 'printed', + 'private', + 'problem', + 'procedural', + 'process', + 'prodigious', + 'professional', + 'programmed', + 'progressive', + 'prolific', + 'promising', + 'promulgated', + 'pronged', + 'proportionate', + 'protracted', + 'pulled', + 'pulsed', + 'purgatory', + 'quick', + 'rapid-fire', + 'raunchy', + 'razed', + 'reactive', + 'readable', + 'realizing', + 'recognised', + 'recovering', + 'recurrent', + 'recycled', + 'redeemable', + 'reflecting', + 'regal', + 'registering', + 'reliable', + 'reminiscent', + 'remorseless', + 'removable', + 'renewable', + 'repeating', + 'repellent', + 'reserve', + 'resigned', + 'respectful', + 'rested', + 'restrict', + 'resultant', + 'retaliatory', + 'retiring', + 'revelatory', + 'reverend', + 'reversing', + 'revolving', + 'ridiculous', + 'right-hand', + 'ringed', + 'risque', + 'robust', + 'roomful', + 'rotating', + 'roused', + 'rubber', + 'run-down', + 'running', + 'runtime', + 'rustling', + 'safest', + 'salient', + 'sanctioned', + 'saute', + 'saved', + 'scandalized', + 'scarlet', + 'scattering', + 'sceptical', + 'scheming', + 'scoundrel', + 'scratched', + 'scratchy', + 'scrolled', + 'seated', + 'second-best', + 'segregated', + 'self-taught', + 'semiautomatic', + 'senior', + 'sensed', + 'sentient', + 'sexier', + 'shadowy', + 'shaken', + 'shaker', + 'shameless', + 'shaped', + 'shiny', + 'shipped', + 'shivering', + 'shoestring', + 'short', + 'short-lived', + 'signed', + 'simplest', + 'simplistic', + 'sizable', + 'skeleton', + 'skinny', + 'skirting', + 'skyrocketed', + 'slamming', + 'slanting', + 'slapstick', + 'sleek', + 'sleepless', + 'sleepy', + 'slender', + 'slimmer', + 'smacking', + 'smokeless', + 'smothered', + 'smouldering', + 'snuff', + 'socialized', + 'solid-state', + 'sometime', + 'sought', + 'spanking', + 'sparing', + 'spattered', + 'specialized', + 'specific', + 'speedy', + 'spherical', + 'spiky', + 'spineless', + 'sprung', + 'squint', + 'stainless', + 'standing', + 'starlight', + 'startled', + 'stately', + 'statewide', + 'stereoscopic', + 'sticky', + 'stimulant', + 'stinky', + 'stoked', + 'stolen', + 'storied', + 'strained', + 'strapping', + 'strengthened', + 'stubborn', + 'stylized', + 'suave', + 'subjective', + 'subjugated', + 'subordinate', + 'succeeding', + 'suffering', + 'summary', + 'sunset', + 'sunshine', + 'supernatural', + 'supervisory', + 'supply-side', + 'surrogate', + 'suspended', + 'suspenseful', + 'swarthy', + 'sweating', + 'sweeping', + 'swinging', + 'swooning', + 'sympathize', + 'synchronized', + 'synonymous', + 'synthetic', + 'tailed', + 'tallest', + 'tangible', + 'tanked', + 'tarry', + 'technical', + 'tectonic', + 'telepathic', + 'tenderest', + 'territorial', + 'testimonial', + 'theistic', + 'thicker', + 'threatening', + 'tight-lipped', + 'timed', + 'timely', + 'timid', + 'torrent', + 'totalled', + 'tougher', + 'traditional', + 'transformed', + 'trapped', + 'traveled', + 'traverse', + 'treated', + 'trial', + 'trunk', + 'trusting', + 'trying', + 'twisted', + 'two-lane', + 'tyrannical', + 'unaided', + 'unassisted', + 'unassuming', + 'unattractive', + 'uncapped', + 'uncomfortable', + 'uncontrolled', + 'uncooked', + 'uncooperative', + 'underground', + 'undersea', + 'undisturbed', + 'unearthly', + 'uneasy', + 'unequal', + 'unfazed', + 'unfinished', + 'unforeseen', + 'unforgivable', + 'unidentified', + 'unimaginative', + 'uninspired', + 'unintended', + 'uninvited', + 'universal', + 'unmasked', + 'unorthodox', + 'unparalleled', + 'unpleasant', + 'unprincipled', + 'unread', + 'unreasonable', + 'unregulated', + 'unreliable', + 'unremitting', + 'unsafe', + 'unsanitary', + 'unsealed', + 'unsuccessful', + 'unsupervised', + 'untimely', + 'unwary', + 'unwrapped', + 'uppity', + 'upstart', + 'useless', + 'utter', + 'valiant', + 'valid', + 'valued', + 'vanilla', + 'vaulting', + 'vaunted', + 'veering', + 'vegetative', + 'vented', + 'verbal', + 'verifying', + 'veritable', + 'versed', + 'vinyl', + 'virgin', + 'visceral', + 'visual', + 'voluptuous', + 'walk-on', + 'wanton', + 'warlike', + 'washed', + 'waterproof', + 'waved', + 'weakest', + 'well-bred', + 'well-chosen', + 'well-informed', + 'wetting', + 'wheeled', + 'whirlwind', + 'widen', + 'widening', + 'willful', + 'willing', + 'winnable', + 'winningest', + 'wireless', + 'wistful', + 'woeful', + 'wooded', + 'woodland', + 'wordless', + 'workable', + 'worldly', + 'worldwide', + 'worst-case', + 'worsted', + 'worthless', +] + +export const randomAnimalName = () => { + return animalNames[Math.floor(animalNames.length * Math.random())] +} + +export const randomAdjective = () => { + return adjectives[Math.floor(adjectives.length * Math.random())] +} + +export const capitalize = (s: string) => (s.length ? s[0].toUpperCase() + s.substring(1) : s) + +export const randomSpacedName = () => { + return `${capitalize(randomAdjective().toLowerCase())} ${randomAnimalName().toLowerCase()}` +} + +export const randomCarretName = () => { + return `${randomAdjective().toLowerCase()}-${randomAnimalName().toLowerCase().replace(' ', '-')}` +} diff --git a/rn/src/screens/GatewaysRace.tsx b/rn/src/screens/GatewaysRace.tsx index d509af43..61dd82f5 100644 --- a/rn/src/screens/GatewaysRace.tsx +++ b/rn/src/screens/GatewaysRace.tsx @@ -1,7 +1,6 @@ import React from 'react' import { ImageStyle, - SafeAreaView, ScrollView, ViewStyle, View, @@ -13,6 +12,8 @@ import { import { ScreenFC } from '@berty-labs/navigation' import { defaultColors } from '@berty-labs/styles' import { useGomobileIPFS } from '@berty-labs/ipfsutil' +import { AppScreenContainer } from '@berty-labs/components' +import { prettyMilliSeconds } from '@berty-labs/reactutil' // const superlativeApesRoot = "/ipfs/QmbYCLEdnez33AnjigGAhM3ouNj8LMTXBwUqVLaLnUvBbU" const superlativeApe = '/ipfs/QmQVS8VrY9FFpUWKitGhFzzx3xGixAd4frf1Czr7AQxeTc/1.png' @@ -25,16 +26,6 @@ const style: ViewStyle & ImageStyle = { justifyContent: 'center', } -const prettyMilliSeconds = (ms: number) => { - if (ms >= 60000) { - return `${(ms / 60000).toFixed(0)}min ${((ms % 60000) / 1000).toFixed(0)}sec` - } - if (ms >= 1000) { - return `${(ms / 1000).toFixed(2)}sec` - } - return `${ms}ms` -} - const textStyle = { color: defaultColors.white, fontSize: 18, @@ -43,27 +34,26 @@ const textStyle = { export const GatewaysRace: ScreenFC<'GatewaysRace'> = () => { const init = React.useRef(Date.now()) + const mobileIPFS = useGomobileIPFS() const [loadedLocal, setLoadedLocal] = React.useState() const [localError, setLocalError] = React.useState() + const [imageURI, setImageURI] = React.useState() const [loadedExternal, setLoadedExternal] = React.useState() const [externalError, setExternalError] = React.useState() - const mobileIPFS = useGomobileIPFS() - const [imageURI, setImageURI] = React.useState() React.useEffect(() => { if (!mobileIPFS.gatewayURL) { return } - let canceled = false + const controller = new AbortController() const start = async () => { const url = mobileIPFS.gatewayURL + superlativeApe try { console.log('pre-fetching:', url) - const reply = await fetch(url) - if (canceled) { - console.log('pre-fetch canceled:', url) - return + const reply = await fetch(url, { signal: controller.signal }) + if (controller.signal.aborted) { + throw new Error('abort') } if (!(reply.status === 200)) { setLoadedLocal(Date.now()) @@ -74,6 +64,10 @@ export const GatewaysRace: ScreenFC<'GatewaysRace'> = () => { console.log('pre-fetched:', url) setImageURI(url) } catch (err: unknown) { + if (controller.signal.aborted) { + console.log('pre-fetch abort:', url) + return + } console.warn(`pre-fetch: ${err}: ${url}`) setLoadedLocal(Date.now()) setLocalError(err instanceof Error ? err : new Error(`${err}`)) @@ -81,7 +75,7 @@ export const GatewaysRace: ScreenFC<'GatewaysRace'> = () => { } start() return () => { - canceled = true + controller.abort() } }, [mobileIPFS.gatewayURL]) @@ -90,7 +84,7 @@ export const GatewaysRace: ScreenFC<'GatewaysRace'> = () => { } return ( - + @@ -149,6 +143,6 @@ export const GatewaysRace: ScreenFC<'GatewaysRace'> = () => { - + ) } diff --git a/rn/src/screens/IPFSWebUI.tsx b/rn/src/screens/IPFSWebUI.tsx index 8aa9fce1..c353d678 100644 --- a/rn/src/screens/IPFSWebUI.tsx +++ b/rn/src/screens/IPFSWebUI.tsx @@ -1,24 +1,74 @@ import React from 'react' -import { ActivityIndicator, SafeAreaView } from 'react-native' +import { Text } from 'react-native' import { WebView } from 'react-native-webview' import { useGomobileIPFS } from '@berty-labs/ipfsutil' import { ScreenFC } from '@berty-labs/navigation' import { defaultColors } from '@berty-labs/styles' +import { AppScreenContainer, LoaderScreen } from '@berty-labs/components' export const IPFSWebUI: ScreenFC<'IPFSWebUI'> = () => { const mobileIPFS = useGomobileIPFS() - if (!mobileIPFS.gatewayURL) { - return + const [loadedLocal, setLoadedLocal] = React.useState() + const [localError, setLocalError] = React.useState() + const [imageURI, setImageURI] = React.useState() + React.useEffect(() => { + if (!mobileIPFS.gatewayURL) { + return + } + const controller = new AbortController() + const start = async () => { + const url = `${mobileIPFS.gatewayURL}/ipfs/bafybeihcyruaeza7uyjd6ugicbcrqumejf6uf353e5etdkhotqffwtguva` + try { + console.log('pre-fetching:', url) + const reply = await fetch(url, { signal: controller.signal }) + if (!(reply.status === 200)) { + setLoadedLocal(Date.now()) + setLocalError(new Error(`bad reply status: ${reply.status}`)) + console.warn(`pre-fetch: bad reply status: ${reply.status}: ${url}`) + return + } + console.log('pre-fetched:', url) + setImageURI(url) + setLoadedLocal(Date.now()) + } catch (err: unknown) { + if (controller.signal.aborted) { + console.log('pre-fetch abort:', url) + return + } + console.warn(`pre-fetch: ${err}: ${url}`) + setLoadedLocal(Date.now()) + setLocalError(err instanceof Error ? err : new Error(`${err}`)) + } + } + start() + return () => { + controller.abort() + } + }, [mobileIPFS.gatewayURL]) + if (!loadedLocal) { + return + } + if (localError) { + return ( + + {`${localError}`} + + ) } return ( - + { + setLoadedLocal(Date.now()) + setLocalError(err instanceof Error ? err : new Error(`${err}`)) }} /> - + ) } diff --git a/rn/src/screens/NftCollection.tsx b/rn/src/screens/NftCollection.tsx index 20b0988f..18f2b892 100644 --- a/rn/src/screens/NftCollection.tsx +++ b/rn/src/screens/NftCollection.tsx @@ -4,7 +4,6 @@ import { Text, Image, ActivityIndicator, - SafeAreaView, FlatList, Dimensions, useWindowDimensions, @@ -13,15 +12,25 @@ import { import { defaultColors } from '@berty-labs/styles' import { useGomobileIPFS } from '@berty-labs/ipfsutil' import { ScreenFC } from '@berty-labs/navigation' +import { AppScreenContainer, Loader } from '@berty-labs/components' const superlativeRoot = '/ipfs/QmZdUpu5sC2YPKBdocZqYtLyy2DSBD6WLK7STKFQoDgbYW/metadata' // const externalGateway = 'https://gateway.pinata.cloud' +const fetchURL = async (url: string, controller: AbortController) => { + console.log('fetching:', url) + let reply = await fetch(url, { signal: controller.signal }) + //console.log('fetch:', reply.status) + if (!(reply.status === 200)) { + throw new Error(`bad http status: ${reply.status}`) + } + return reply +} + const NFT: React.FC<{ index: number item: string - fetchURL: (url: string) => Promise | undefined>> -}> = ({ index, item, fetchURL }) => { +}> = ({ index, item }) => { const mobileIPFS = useGomobileIPFS() const [nft, setNft] = React.useState<{ uri: string; name: string; desc: string }>() @@ -32,34 +41,45 @@ const NFT: React.FC<{ if (!mobileIPFS.gatewayURL) { return } - let canceled = false + const controller = new AbortController() const start = async () => { - // fetch ipfs json file - const fileReply = await fetchURL(mobileIPFS.gatewayURL + item) - if (!fileReply || canceled) { - return - } - const usableReply = await fileReply.text() - const parsedUsableReply = JSON.parse(usableReply) - const regex = /\/ipfs\/.+?\/image\/[0-9]+.png/g - // recup nft image - const uriFinal = parsedUsableReply.image.match(regex) - // fetch nft image - const nftReply = await fetchURL(mobileIPFS.gatewayURL + uriFinal) - if (!nftReply || canceled) { - return + try { + // fetch ipfs json file + const fileReply = await fetchURL(mobileIPFS.gatewayURL + item, controller) + if (controller.signal.aborted) { + throw new Error('abort') + } + const usableReply = await fileReply.text() + if (controller.signal.aborted) { + throw new Error('abort') + } + const parsedUsableReply = JSON.parse(usableReply) + const regex = /\/ipfs\/.+?\/image\/[0-9]+.png/g + // recup nft image + const uriFinal = parsedUsableReply.image.match(regex) + // fetch nft image + const nftReply = await fetchURL(mobileIPFS.gatewayURL + uriFinal, controller) + if (controller.signal.aborted) { + throw new Error('abort') + } + setNft({ + uri: nftReply.url, + name: parsedUsableReply.name, + desc: parsedUsableReply.description, + }) + } catch (err: unknown) { + if (controller.signal.aborted) { + console.log('fetch-nft abort:', item) + return + } + console.warn('fetch-nft error:', err) } - setNft({ - uri: nftReply.url, - name: parsedUsableReply.name, - desc: parsedUsableReply.description, - }) } start() return () => { - canceled = true + controller.abort() } - }, [fetchURL, item, mobileIPFS.gatewayURL]) + }, [item, mobileIPFS.gatewayURL]) return ( ) : ( - + + + )} ) @@ -117,48 +139,45 @@ export const NftCollection: ScreenFC<'NftCollection'> = () => { const [ipfsFiles, setIpfsFiles] = React.useState([]) const winsz = useWindowDimensions() - const fetchURL = React.useCallback(async (url: string) => { - console.log('fetching:', url) - let reply = await fetch(url) - //console.log('fetch:', reply.status) - if (!(reply.status === 200)) { - console.warn('fetch fail') - return - } - return reply - }, []) - React.useEffect(() => { if (!mobileIPFS.gatewayURL) { return } - let canceled = false + const controller = new AbortController() const start = async () => { - // fetch ipfs directory - const ipfsDirReply = await fetchURL(mobileIPFS.gatewayURL + superlativeRoot) - if (!ipfsDirReply || canceled) { - return - } - const usableReply = await ipfsDirReply.text() - const regex = /\/ipfs\/.+?\/metadata\/[0-9]+/g - // recup ipfs json files - const files = usableReply.match(regex) - if (!files) { - return + try { + // fetch ipfs directory + const ipfsDirReply = await fetchURL(mobileIPFS.gatewayURL + superlativeRoot, controller) + if (controller.signal.aborted) { + throw new Error('abort') + } + const usableReply = await ipfsDirReply.text() + if (controller.signal.aborted) { + throw new Error('abort') + } + const regex = /\/ipfs\/.+?\/metadata\/[0-9]+/g + // recup ipfs json files + const files = usableReply.match(regex) + if (!files) { + return + } + setIpfsFiles(files) + } catch (err: unknown) { + if (controller.signal.aborted) { + console.log('fetch-nft-list abort') + return + } + console.warn('fetch-nft-list error:', err) } - if (canceled) { - return - } - setIpfsFiles(files) } start() return () => { - canceled = true + controller.abort() } - }, [fetchURL, mobileIPFS.gatewayURL]) + }, [mobileIPFS.gatewayURL]) return ( - + = () => { REKT - - - {!ipfsFiles.length && ( - <> - - Loading NFT list from IPFS... - - - - )} - + {!ipfsFiles.length && } )} contentContainerStyle={{ alignItems: 'flex-start' }} data={ipfsFiles} - renderItem={({ item, index }) => } + renderItem={({ item, index }) => } /> - + ) } diff --git a/rn/src/screens/NodeConfig.tsx b/rn/src/screens/NodeConfig.tsx new file mode 100644 index 00000000..6dd7ab01 --- /dev/null +++ b/rn/src/screens/NodeConfig.tsx @@ -0,0 +1,250 @@ +import React from 'react' +import { ScrollView, Share, Text, TouchableOpacity, View, ViewStyle } from 'react-native' +import * as FileSystem from 'expo-file-system' + +import { ScreenFC } from '@berty-labs/navigation' +import { + AnySpec, + getIPFSConfigOffline, + IPFSConfig, + ipfsConfigPath, + ipfsRepoPath, + useGomobileIPFS, +} from '@berty-labs/ipfsutil' +import { AppScreenContainer, Button, Card, IPFSServicesHealth } from '@berty-labs/components' +import { defaultColors } from '@berty-labs/styles' +import { prettyBytes, useMountEffect } from '@berty-labs/reactutil' +import { usePathSize } from '@berty-labs/expoutil' + +const textStyle = { color: defaultColors.white, opacity: 0.7 } + +const space = 15 + +const prettyDatastoreType = (name: string) => { + switch (name) { + case 'levelds': + return 'LevelDB' + case 'flatfs': + return 'FlatFS' + default: + return name + } +} + +const MDNSCard: React.FC<{ config?: IPFSConfig; style?: ViewStyle }> = ({ config, style }) => { + const enabled = config?.Discovery?.MDNS?.Enabled + return ( + + + {typeof enabled === 'boolean' ? (enabled ? 'Enabled' : 'Disabled') : 'Unknown state'} + + {typeof config?.Discovery?.MDNS?.Interval === 'number' && ( + {config?.Discovery?.MDNS?.Interval} seconds interval + )} + + ) +} + +export const NodeConfig: ScreenFC<'NodeConfig'> = ({ + route: { + params: { name }, + }, + navigation: { goBack }, +}) => { + const mobileIPFS = useGomobileIPFS() + const [rootSpec, setRootSpec] = React.useState() + const [blocksSpec, setBlocksSpec] = React.useState() + const [config, setConfig] = React.useState() + const [configString, setConfigString] = React.useState() + const [showRawConfig, setShowRawConfig] = React.useState(false) + const [isBareRepo, setIsBareRepo] = React.useState(false) + const size = usePathSize(ipfsRepoPath(name)) + + const refresh = React.useCallback( + async (controller: AbortController) => { + const info = await FileSystem.getInfoAsync(ipfsConfigPath(name)) + if (controller.signal.aborted) { + return + } + if (!info.exists) { + setIsBareRepo(true) + return + } + setIsBareRepo(false) + const config = await getIPFSConfigOffline(name) + if (controller.signal.aborted) { + return + } + if (config.Identity?.PrivKey) { + delete config.Identity.PrivKey + } + setConfig(config) + const prettyConfigString = JSON.stringify(config, null, 4) + setConfigString(prettyConfigString) + //console.log(prettyConfigString) + // FIXME: make this recursive + const spec = config.Datastore?.Spec + switch (spec?.type) { + case 'mount': + const mounts = spec?.mounts || [] + for (const mount of mounts) { + if (mount.mountpoint === '/') { + let spec: AnySpec = mount + if (mount.type === 'measure') { + spec = mount.child + } + setRootSpec(spec) + console.log('root', mount) + } + if (mount.mountpoint === '/blocks') { + let spec: AnySpec = mount + if (mount.type === 'measure') { + spec = mount.child + } + setBlocksSpec(spec) + console.log('blocks', mount) + } + } + break + } + }, + [name], + ) + + useMountEffect(() => { + const controller = new AbortController() + refresh(controller) + const interval = setInterval(() => { + refresh(controller) + }, 4000) + return () => { + controller.abort() + clearInterval(interval) + } + }) + + const isInUse = mobileIPFS.name === name + + const useNodeButton = ( +