feat: initial implementation of a flipper plugin

This commit is contained in:
Satyajit Sahoo
2021-03-20 17:36:20 +01:00
parent a7bb7a4afa
commit d6f6f5f94d
32 changed files with 4449 additions and 221 deletions

1
.gitignore vendored
View File

@@ -21,6 +21,7 @@ web-report/
xcuserdata
generated
dist
lib
build

View File

@@ -17,8 +17,8 @@ target 'ReactNavigation' do
#
# Note that if you have use_frameworks! enabled, Flipper will not work.
#
use_flipper!
post_install do |installer|
flipper_post_install(installer)
end
use_flipper!('Flipper' => '0.75.1', 'Flipper-Folly' => '2.5.3', 'Flipper-RSocket' => '1.3.1')
post_install do |installer|
flipper_post_install(installer)
end
end

View File

@@ -1,18 +1,17 @@
PODS:
- boost-for-react-native (1.63.0)
- CocoaAsyncSocket (7.6.4)
- CocoaLibEvent (1.0.0)
- CocoaAsyncSocket (7.6.5)
- DoubleConversion (1.1.6)
- EXApplication (3.1.0):
- EXApplication (3.1.2):
- UMCore
- EXBlur (9.0.3):
- UMCore
- EXConstants (10.1.1):
- EXConstants (10.1.3):
- UMConstantsInterface
- UMCore
- EXErrorRecovery (2.1.0):
- UMCore
- EXFileSystem (11.0.0):
- EXFileSystem (11.0.2):
- UMCore
- UMFileSystemInterface
- EXFont (9.1.0):
@@ -22,7 +21,7 @@ PODS:
- React-Core
- UMCore
- UMImageLoaderInterface
- EXKeepAwake (9.1.1):
- EXKeepAwake (9.1.2):
- UMCore
- EXPermissions (12.0.0):
- UMCore
@@ -32,7 +31,7 @@ PODS:
- UMCore
- EXStructuredHeaders (1.0.1):
- UMCore
- EXUpdates (0.5.3):
- EXUpdates (0.5.4):
- EXStructuredHeaders
- React-Core
- UMCore
@@ -44,50 +43,50 @@ PODS:
- React-Core (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- Flipper (0.54.0):
- Flipper-Folly (~> 2.2)
- Flipper-RSocket (~> 1.1)
- Flipper (0.75.1):
- Flipper-Folly (~> 2.5)
- Flipper-RSocket (~> 1.3)
- Flipper-DoubleConversion (1.1.7)
- Flipper-Folly (2.2.0):
- Flipper-Folly (2.5.3):
- boost-for-react-native
- CocoaLibEvent (~> 1.0)
- Flipper-DoubleConversion
- Flipper-Glog
- OpenSSL-Universal (= 1.0.2.19)
- libevent (~> 2.1.12)
- OpenSSL-Universal (= 1.1.180)
- Flipper-Glog (0.3.6)
- Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.1.0):
- Flipper-Folly (~> 2.2)
- FlipperKit (0.54.0):
- FlipperKit/Core (= 0.54.0)
- FlipperKit/Core (0.54.0):
- Flipper (~> 0.54.0)
- Flipper-RSocket (1.3.1):
- Flipper-Folly (~> 2.5)
- FlipperKit (0.75.1):
- FlipperKit/Core (= 0.75.1)
- FlipperKit/Core (0.75.1):
- Flipper (~> 0.75.1)
- FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines
- FlipperKit/FKPortForwarding
- FlipperKit/CppBridge (0.54.0):
- Flipper (~> 0.54.0)
- FlipperKit/FBCxxFollyDynamicConvert (0.54.0):
- Flipper-Folly (~> 2.2)
- FlipperKit/FBDefines (0.54.0)
- FlipperKit/FKPortForwarding (0.54.0):
- FlipperKit/CppBridge (0.75.1):
- Flipper (~> 0.75.1)
- FlipperKit/FBCxxFollyDynamicConvert (0.75.1):
- Flipper-Folly (~> 2.5)
- FlipperKit/FBDefines (0.75.1)
- FlipperKit/FKPortForwarding (0.75.1):
- CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.54.0)
- FlipperKit/FlipperKitLayoutPlugin (0.54.0):
- FlipperKit/FlipperKitHighlightOverlay (0.75.1)
- FlipperKit/FlipperKitLayoutPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.54.0)
- FlipperKit/FlipperKitNetworkPlugin (0.54.0):
- FlipperKit/FlipperKitLayoutTextSearchable (0.75.1)
- FlipperKit/FlipperKitNetworkPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitReactPlugin (0.54.0):
- FlipperKit/FlipperKitReactPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.54.0):
- FlipperKit/FlipperKitUserDefaultsPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.54.0):
- FlipperKit/SKIOSNetworkPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin
- Folly (2020.01.13.00):
@@ -100,9 +99,8 @@ PODS:
- DoubleConversion
- glog
- glog (0.3.5)
- OpenSSL-Universal (1.0.2.19):
- OpenSSL-Universal/Static (= 1.0.2.19)
- OpenSSL-Universal/Static (1.0.2.19)
- libevent (2.1.12)
- OpenSSL-Universal (1.1.180)
- RCTRequired (0.63.4)
- RCTTypeSafety (0.63.4):
- FBLazyVector (= 0.63.4)
@@ -271,6 +269,8 @@ PODS:
- React-jsinspector (0.63.4)
- react-native-appearance (0.3.4):
- React
- react-native-flipper (0.80.0):
- React-Core
- react-native-safe-area-context (3.2.0):
- React-Core
- react-native-viewpager (5.0.12):
@@ -337,7 +337,7 @@ PODS:
- React-jsi (= 0.63.4)
- RNCAsyncStorage (1.15.1):
- React-Core
- RNCMaskedView (0.2.3):
- RNCMaskedView (0.2.4):
- React-Core
- RNGestureHandler (1.10.3):
- React-Core
@@ -388,7 +388,7 @@ PODS:
- UMImageLoaderInterface (6.1.0)
- UMPermissionsInterface (6.1.0):
- UMCore
- UMReactNativeAdapter (6.2.1):
- UMReactNativeAdapter (6.2.2):
- React-Core
- UMCore
- UMFontInterface
@@ -416,25 +416,25 @@ DEPENDENCIES:
- EXUpdates (from `../../node_modules/expo-updates/ios`)
- FBLazyVector (from `../../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../../node_modules/react-native/Libraries/FBReactNativeSpec`)
- Flipper (~> 0.54.0)
- Flipper (= 0.75.1)
- Flipper-DoubleConversion (= 1.1.7)
- Flipper-Folly (~> 2.2)
- Flipper-Folly (= 2.5.3)
- Flipper-Glog (= 0.3.6)
- Flipper-PeerTalk (~> 0.0.4)
- Flipper-RSocket (~> 1.1)
- FlipperKit (~> 0.54.0)
- FlipperKit/Core (~> 0.54.0)
- FlipperKit/CppBridge (~> 0.54.0)
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.54.0)
- FlipperKit/FBDefines (~> 0.54.0)
- FlipperKit/FKPortForwarding (~> 0.54.0)
- FlipperKit/FlipperKitHighlightOverlay (~> 0.54.0)
- FlipperKit/FlipperKitLayoutPlugin (~> 0.54.0)
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.54.0)
- FlipperKit/FlipperKitNetworkPlugin (~> 0.54.0)
- FlipperKit/FlipperKitReactPlugin (~> 0.54.0)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.54.0)
- FlipperKit/SKIOSNetworkPlugin (~> 0.54.0)
- Flipper-RSocket (= 1.3.1)
- FlipperKit (= 0.75.1)
- FlipperKit/Core (= 0.75.1)
- FlipperKit/CppBridge (= 0.75.1)
- FlipperKit/FBCxxFollyDynamicConvert (= 0.75.1)
- FlipperKit/FBDefines (= 0.75.1)
- FlipperKit/FKPortForwarding (= 0.75.1)
- FlipperKit/FlipperKitHighlightOverlay (= 0.75.1)
- FlipperKit/FlipperKitLayoutPlugin (= 0.75.1)
- FlipperKit/FlipperKitLayoutTextSearchable (= 0.75.1)
- FlipperKit/FlipperKitNetworkPlugin (= 0.75.1)
- FlipperKit/FlipperKitReactPlugin (= 0.75.1)
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.75.1)
- FlipperKit/SKIOSNetworkPlugin (= 0.75.1)
- Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
- RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
@@ -450,6 +450,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-appearance (from `../../node_modules/react-native-appearance`)
- react-native-flipper (from `../../node_modules/react-native-flipper`)
- react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`)
- react-native-viewpager (from `../node_modules/react-native-pager-view`)
- React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`)
@@ -463,7 +464,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../../node_modules/react-native/Libraries/Vibration`)
- ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`)
- "RNCAsyncStorage (from `../../node_modules/@react-native-async-storage/async-storage`)"
- "RNCMaskedView (from `../../node_modules/@react-native-masked-view/masked-view`)"
- "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)"
- RNGestureHandler (from `../../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../../node_modules/react-native-reanimated`)
- RNScreens (from `../../node_modules/react-native-screens`)
@@ -487,7 +488,6 @@ SPEC REPOS:
trunk:
- boost-for-react-native
- CocoaAsyncSocket
- CocoaLibEvent
- Flipper
- Flipper-DoubleConversion
- Flipper-Folly
@@ -495,6 +495,7 @@ SPEC REPOS:
- Flipper-PeerTalk
- Flipper-RSocket
- FlipperKit
- libevent
- OpenSSL-Universal
- YogaKit
@@ -555,6 +556,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native/ReactCommon/jsinspector"
react-native-appearance:
:path: "../../node_modules/react-native-appearance"
react-native-flipper:
:path: "../../node_modules/react-native-flipper"
react-native-safe-area-context:
:path: "../../node_modules/react-native-safe-area-context"
react-native-viewpager:
@@ -582,7 +585,7 @@ EXTERNAL SOURCES:
RNCAsyncStorage:
:path: "../../node_modules/@react-native-async-storage/async-storage"
RNCMaskedView:
:path: "../../node_modules/@react-native-masked-view/masked-view"
:path: "../node_modules/@react-native-masked-view/masked-view"
RNGestureHandler:
:path: "../../node_modules/react-native-gesture-handler"
RNReanimated:
@@ -622,33 +625,33 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: cde416483dac037923206447da6e1454df403714
EXApplication: fe13d11e25ebaca30660ec51ec1112ba4a47fd31
EXApplication: 4797b8b37f0b0470f587fdccf6407f44b50d18b5
EXBlur: 50d490040f3b14898ed8198d5125dc699189f4d9
EXConstants: 7c75e5019483e84cc658c133f48607c951652465
EXConstants: c4dd28acc12039c999612507a5f935556f2c86ce
EXErrorRecovery: 720641265b8cf95e6cdeb1884ac38e794a352488
EXFileSystem: 56e15a0484271771f92ccb8d75a0d77a8982fbc2
EXFileSystem: dcf2273f49431e5037347c733a2dc5d08e0d0a9e
EXFont: d6fb79f9863120f0d0b26b0c2d1453bc9511e9df
EXImageLoader: da941c9399e01ec28f2d5b270bdd21f2c8ca596c
EXKeepAwake: a4938450365c1df5a0dcc4f0be77798ca4fafc3c
EXKeepAwake: d4e4a3ed8c1c4fd940dd62fc5a8be2a190371fd4
EXPermissions: 67ff17d3c12ea06066492dde4242f8047658fd62
EXSplashScreen: 9d79ea338b7bb2ee94df51723870bac70b408d44
EXStructuredHeaders: be834496a4d9fd0069cd12ec1cc57b31c6d3b256
EXUpdates: 79426caf9b4b781f1c2cd53d5b1bcfa3c0292e80
EXUpdates: e191b83e00d3e7ebfd7db3986806ceca09b7b322
FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e
FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e
Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365
Flipper: d3da1aa199aad94455ae725e9f3aa43f3ec17021
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3
Flipper-Folly: 755929a4f851b2fb2c347d533a23f191b008554c
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
FlipperKit: ab353d41aea8aae2ea6daaf813e67496642f3d7d
Flipper-RSocket: 127954abe8b162fcaf68d2134d34dc2bd7076154
FlipperKit: 8a20b5c5fcf9436cac58551dc049867247f64b00
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b
RCTRequired: 082f10cd3f905d6c124597fd1c14f6f2655ff65e
RCTTypeSafety: 8c9c544ecbf20337d069e4ae7fd9a377aadf504b
React: b0a957a2c44da4113b0c4c9853d8387f8e64e615
@@ -660,6 +663,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
react-native-appearance: 0f0e5fc2fcef70e03d48c8fe6b00b9158c2ba8aa
react-native-flipper: 5a9d5959364fca6a8a9658d941343774cb197857
react-native-safe-area-context: f0906bf8bc9835ac9a9d3f97e8bde2a997d8da79
react-native-viewpager: 98a850d1c7ac6263122d82618a77062a5f427073
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
@@ -673,7 +677,7 @@ SPEC CHECKSUMS:
React-RCTVibration: ae4f914cfe8de7d4de95ae1ea6cc8f6315d73d9d
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
RNCAsyncStorage: 08719e311ab90492c2dafd6d6fb7ffb396493638
RNCMaskedView: 3beac403b5178d5df732dc211899ac88593d42ef
RNCMaskedView: fc29d354a40316a990e8fb46391f08aea829c3aa
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNReanimated: 70f662b5232dd5d19ccff581e919a54ea73df51c
RNScreens: e8e8dd0588b5da0ab57dcca76ab9b2d8987757e0
@@ -688,12 +692,12 @@ SPEC CHECKSUMS:
UMFontInterface: 5843cff7db85a42ba629aaac53d33091c35524d3
UMImageLoaderInterface: 9ddffeb644b3f45d4eb0c2f51a2fd95fd5c8d1a4
UMPermissionsInterface: 40b72935a7d12a3f60dc6b7bb99ce47908380cb1
UMReactNativeAdapter: bcb723a9fa9e934edd0be337788cad54b4771b0f
UMReactNativeAdapter: 65ada852a648fcb6674acfbfe72ccb095f2f5b75
UMSensorsInterface: a5e9db661e5d9ae214762033d725989880ae6993
UMTaskManagerInterface: 203c11259d2699b5b3a4eda4adbc466f5cb5c561
Yoga: 4bd86afe9883422a7c4028c00e34790f560923d6
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: b81676504862e17e403cd20d13384c8578f775b2
PODFILE CHECKSUM: 559ae797e06704acfdd4e8aabc341b8c11a0bd5e
COCOAPODS: 1.10.1

View File

@@ -135,6 +135,7 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
65ECC95A68A375172205A7FF /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -227,6 +228,24 @@
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;
};
65ECC95A68A375172205A7FF /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNavigation/Pods-ReactNavigation-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNavigation/Pods-ReactNavigation-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;

View File

@@ -15,7 +15,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "ReactNavigation.app"
BuildableName = "example.app"
BlueprintName = "ReactNavigation"
ReferencedContainer = "container:ReactNavigation.xcodeproj">
</BuildableReference>
@@ -55,7 +55,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "ReactNavigation.app"
BuildableName = "example.app"
BlueprintName = "ReactNavigation"
ReferencedContainer = "container:ReactNavigation.xcodeproj">
</BuildableReference>
@@ -72,7 +72,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "ReactNavigation.app"
BuildableName = "example.app"
BlueprintName = "ReactNavigation"
ReferencedContainer = "container:ReactNavigation.xcodeproj">
</BuildableReference>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -67,6 +67,7 @@
"nodemon": "^2.0.6",
"playwright": "^1.11.0",
"pod-install": "^0.1.19",
"react-native-flipper": "~0.80.0",
"react-test-renderer": "~16.13.1",
"serve": "^11.3.0",
"typescript": "~4.2.3"

View File

@@ -37,7 +37,10 @@ import {
HeaderStyleInterpolators,
StackNavigationProp,
} from '@react-navigation/stack';
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
import {
useReduxDevToolsExtension,
useFlipper,
} from '@react-navigation/devtools';
import { restartApp } from './Restart';
import SettingsItem from './Shared/SettingsItem';
@@ -209,6 +212,7 @@ export default function App() {
const navigationRef = useNavigationContainerRef();
useReduxDevToolsExtension(navigationRef);
useFlipper(navigationRef);
if (!isReady) {
return null;

View File

@@ -5,6 +5,11 @@
"packages": [
"packages/*",
"example"
],
"nohoist": [
"flipper-plugin-react-navigation/flipper",
"flipper-plugin-react-navigation/flipper-pkg",
"flipper-plugin-react-navigation/flipper-pkg/**"
]
},
"publishConfig": {

View File

@@ -36,20 +36,23 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^6.0.0-next.8",
"deep-equal": "^2.0.5"
"deep-equal": "^2.0.5",
"nanoid": "^3.1.22"
},
"devDependencies": {
"@react-navigation/core": "^6.0.0-next.8",
"@testing-library/react-native": "^7.2.0",
"@types/deep-equal": "^1.0.1",
"@types/react": "^16.9.53",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native-builder-bob": "^0.18.1",
"react-native-flipper": "^0.80.0",
"typescript": "^4.2.3"
},
"peerDependencies": {
"react": "*"
"react": "*",
"react-native-flipper": "*"
},
"react-native-builder-bob": {
"source": "src",

View File

@@ -1,9 +1,12 @@
const noop: any = () => {};
export let useReduxDevToolsExtension: typeof import('./useReduxDevToolsExtension').default;
export let useFlipper: typeof import('./useFlipper').default;
if (process.env.NODE_ENV !== 'production') {
useReduxDevToolsExtension = require('./useReduxDevToolsExtension').default;
useFlipper = require('./useFlipper').default;
} else {
useReduxDevToolsExtension = noop;
useFlipper = noop;
}

View File

@@ -0,0 +1,111 @@
import * as React from 'react';
import type {
NavigationContainerRef,
NavigationState,
NavigationAction,
} from '@react-navigation/core';
import deepEqual from 'deep-equal';
export default function useDevToolsBase(
ref: React.RefObject<NavigationContainerRef<any>>,
callback: (
...args:
| [type: 'init', state: NavigationState | undefined]
| [
type: 'action',
action: NavigationAction,
state: NavigationState | undefined
]
) => void
) {
const lastStateRef = React.useRef<NavigationState | undefined>();
const lastActionRef = React.useRef<NavigationAction | undefined>();
const callbackRef = React.useRef(callback);
const lastResetRef = React.useRef<NavigationState | undefined>(undefined);
React.useEffect(() => {
callbackRef.current = callback;
});
React.useEffect(() => {
let timer: number;
let unsubscribeAction: (() => void) | undefined;
let unsubscribeState: (() => void) | undefined;
const initialize = async () => {
if (!ref.current) {
// If the navigation object isn't ready yet, wait for it
await new Promise<void>((resolve) => {
timer = setInterval(() => {
if (ref.current) {
resolve();
clearTimeout(timer);
const state = ref.current.getRootState();
lastStateRef.current = state;
callbackRef.current('init', state);
}
}, 100);
});
}
const navigation = ref.current!;
unsubscribeAction = navigation.addListener('__unsafe_action__', (e) => {
const action = e.data.action;
if (e.data.noop) {
// Even if the state didn't change, it's useful to show the action
callbackRef.current('action', action, lastStateRef.current);
} else {
lastActionRef.current = action;
}
});
unsubscribeState = navigation.addListener('state', (e) => {
// Don't show the action in dev tools if the state is what we sent to reset earlier
if (
lastResetRef.current &&
deepEqual(lastResetRef.current, e.data.state)
) {
lastStateRef.current = undefined;
return;
}
const state = navigation.getRootState();
const lastState = lastStateRef.current;
const action = lastActionRef.current;
lastActionRef.current = undefined;
lastStateRef.current = state;
// If we don't have an action and the state didn't change, then it's probably extraneous
if (action === undefined && deepEqual(state, lastState)) {
return;
}
callbackRef.current('action', action ?? { type: '@@UNKNOWN' }, state);
});
};
initialize();
return () => {
unsubscribeAction?.();
unsubscribeState?.();
clearTimeout(timer);
};
}, [ref]);
const resetRoot = React.useCallback(
(state: NavigationState) => {
if (ref.current) {
lastResetRef.current = state;
ref.current.resetRoot(state);
}
},
[ref]
);
return { resetRoot };
}

View File

@@ -0,0 +1,95 @@
import * as React from 'react';
import { addPlugin, Flipper } from 'react-native-flipper';
import type { NavigationContainerRef } from '@react-navigation/core';
import { nanoid } from 'nanoid/non-secure';
import useDevToolsBase from './useDevToolsBase';
export default function useFlipper(
ref: React.RefObject<NavigationContainerRef<any>>
) {
const connectionRef = React.useRef<Flipper.FlipperConnection>();
const { resetRoot } = useDevToolsBase(ref, (...args) => {
const connection = connectionRef.current;
if (!connection) {
return;
}
switch (args[0]) {
case 'init':
connection.send('init', {
id: nanoid(),
state: args[1],
});
break;
case 'action':
connection.send('action', {
id: nanoid(),
action: args[1],
state: args[2],
});
break;
}
});
React.useEffect(() => {
addPlugin({
getId() {
return 'react-navigation';
},
async onConnect(connection) {
connectionRef.current = connection;
const on = (event: string, listener: (params: any) => Promise<any>) => {
connection.receive(event, (params, responder) => {
try {
const result = listener(params);
// Return null instead of undefined, otherwise flipper doesn't respond
responder.success(result ?? null);
} catch (e) {
responder.error(e);
}
});
};
on('navigation.invoke', ({ method, args = [] }) => {
switch (method) {
case 'resetRoot':
return resetRoot(args[0]);
default:
// @ts-expect-error: we want to call arbitrary methods here
return ref.current?.[method](...args);
}
});
on('linking.invoke', ({ method, args = [] }) => {
// @ts-expect-error: __linking isn't publicly exposed
const linking = ref.current?.__linking;
switch (method) {
case 'getStateFromPath':
case 'getPathFromState':
case 'getActionFromState':
return linking?.[method](
args[0],
args[1]?.trim()
? // eslint-disable-next-line no-eval
eval(`(function() { return ${args[1]}; }())`)
: linking.config
);
default:
return linking?.[method](...args);
}
});
},
onDisconnect() {
connectionRef.current = undefined;
},
runInBackground() {
return false;
},
});
}, [ref, resetRoot]);
}

View File

@@ -1,10 +1,6 @@
import * as React from 'react';
import type {
NavigationContainerRef,
NavigationState,
NavigationAction,
} from '@react-navigation/core';
import deepEqual from 'deep-equal';
import type { NavigationContainerRef } from '@react-navigation/core';
import useDevToolsBase from './useDevToolsBase';
type DevToolsConnection = {
init(value: any): void;
@@ -35,8 +31,22 @@ export default function useReduxDevToolsExtension(
});
}
const lastStateRef = React.useRef<NavigationState | undefined>();
const lastActionRef = React.useRef<NavigationAction | undefined>();
const { resetRoot } = useDevToolsBase(ref, (...args) => {
const devTools = devToolsRef.current;
if (!devTools) {
return;
}
switch (args[0]) {
case 'init':
devTools.init(args[1]);
break;
case 'action':
devTools.send(args[1], args[2]);
break;
}
});
React.useEffect(
() =>
@@ -44,66 +54,9 @@ export default function useReduxDevToolsExtension(
if (message.type === 'DISPATCH' && message.state) {
const state = JSON.parse(message.state);
lastStateRef.current = state;
ref.current?.resetRoot(state);
resetRoot(state);
}
}),
[ref]
[resetRoot]
);
React.useEffect(() => {
const devTools = devToolsRef.current;
const navigation = ref.current;
if (!navigation || !devTools) {
return;
}
if (lastStateRef.current === undefined) {
const state = navigation.getRootState();
devTools.init(state);
lastStateRef.current = state;
}
const unsubscribeAction = navigation.addListener(
'__unsafe_action__',
(e) => {
const action = e.data.action;
if (e.data.noop) {
// Even if the state didn't change, it's useful to show the action
devTools.send(action, lastStateRef.current);
} else {
lastActionRef.current = action;
}
}
);
const unsubscribeState = navigation.addListener('state', (e) => {
// Don't show the action in dev tools if the state is what we sent to reset earlier
if (lastStateRef.current === e.data.state) {
return;
}
const lastState = lastStateRef.current;
const state = navigation.getRootState();
const action = lastActionRef.current;
lastActionRef.current = undefined;
lastStateRef.current = state;
// If we don't have an action and the state didn't change, then it's probably extraneous
if (action === undefined && deepEqual(state, lastState)) {
return;
}
devTools.send(action ?? '@@UNKNOWN', state);
});
return () => {
unsubscribeAction();
unsubscribeState();
};
});
}

View File

@@ -0,0 +1,7 @@
module.exports = {
presets: [
'@babel/preset-typescript',
'@babel/preset-react',
['@babel/preset-env', { targets: { node: '14' } }],
],
};

View File

@@ -0,0 +1,44 @@
{
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "flipper-plugin-react-navigation",
"id": "react-navigation",
"version": "1.0.0",
"pluginType": "client",
"main": "dist/bundle.js",
"flipperBundlerEntry": "src/index.tsx",
"license": "MIT",
"keywords": [
"flipper-plugin"
],
"icon": "directions",
"title": "React Navigation",
"scripts": {
"lint": "flipper-pkg lint",
"prepack": "flipper-pkg lint && flipper-pkg bundle",
"build": "flipper-pkg bundle",
"watch": "flipper-pkg bundle --watch",
"test": "jest --no-watchman"
},
"peerDependencies": {
"antd": "*",
"flipper": "*",
"flipper-plugin": "*"
},
"devDependencies": {
"@ant-design/icons": "^4.6.2",
"@babel/preset-react": "^7.12.13",
"@babel/preset-typescript": "^7.13.0",
"@react-navigation/core": "^6.0.0-next.8",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.2",
"antd": "^4.14.0",
"flipper": "^0.90.2",
"flipper-pkg": "^0.90.2",
"flipper-plugin": "^0.90.2",
"react": "^17.0.1",
"react-dom": "^17.0.1"
},
"dependencies": {
"shortid": "^2.2.16"
}
}

View File

@@ -0,0 +1,129 @@
import * as React from 'react';
import { DetailSidebar, styled } from 'flipper';
import { theme } from 'flipper-plugin';
import { Input } from 'antd';
import type {
getStateFromPath,
getActionFromState,
} from '@react-navigation/core';
import { RouteMap } from './RouteMap';
import { Sidebar } from './Sidebar';
import type { StoreType } from './types';
type Props = StoreType & {
active: boolean;
};
export function LinkingTester({ linking, active }: Props) {
const [rawConfig, setRawConfig] = React.useState('');
const [path, setPath] = React.useState('');
const [state, setState] = React.useState<
ReturnType<typeof getStateFromPath> | undefined
>();
const [action, setAction] = React.useState<
ReturnType<typeof getActionFromState> | undefined
>();
const [error, setError] = React.useState<string | undefined>();
React.useEffect(() => {
(async () => {
try {
const state = await linking(
'getStateFromPath',
path.replace(/(^\w+:|^)\/\//, ''),
rawConfig
);
const action = state
? await linking('getActionFromState', state, rawConfig)
: undefined;
setState(state);
setAction(action);
setError(undefined);
} catch (e) {
setState(undefined);
setAction(undefined);
setError(
e?.message ||
'Failed to parse the path. Make sure that the path matches the patterns specified in the config.'
);
}
})();
}, [linking, path, rawConfig]);
return (
<Container>
<CodeInput
type="text"
value={path}
placeholder="Type a path to display parsed screens, e.g. /users/@vergil"
onChange={(e) => setPath(e.target.value)}
/>
<Details>
<Summary>Custom configuration (Advanced)</Summary>
<CodeEditor
rows={5}
value={rawConfig}
placeholder="Type a custom linking config (leave empty to use the config defined in the app)"
onChange={(e) => setRawConfig(e.target.value)}
/>
</Details>
<Section>
{state ? (
<RouteMap routes={state.routes} />
) : error ? (
<ErrorDescription>Error: {error}</ErrorDescription>
) : null}
{active ? (
<DetailSidebar>
{action && <Sidebar action={action} state={state} />}
</DetailSidebar>
) : null}
</Section>
</Container>
);
}
const Container = styled.div({
height: '100%',
overflow: 'auto',
});
const Summary = styled.summary({
margin: `0 ${theme.space.large}px`,
});
const Section = styled.div({
margin: `${theme.space.large}px 0`,
});
const Details = styled.details({
margin: `${theme.space.large}px 0`,
});
const CodeInput = styled(Input)({
display: 'block',
fontFamily: theme.monospace.fontFamily,
fontSize: theme.monospace.fontSize,
padding: theme.space.medium,
margin: theme.space.large,
width: `calc(100% - ${theme.space.large * 2}px)`,
});
const CodeEditor = styled(Input.TextArea)({
display: 'block',
fontFamily: theme.monospace.fontFamily,
fontSize: theme.monospace.fontSize,
padding: theme.space.medium,
margin: theme.space.large,
width: `calc(100% - ${theme.space.large * 2}px)`,
});
const ErrorDescription = styled.p({
margin: theme.space.huge,
color: theme.errorColor,
});

View File

@@ -0,0 +1,128 @@
import * as React from 'react';
import { CompassOutlined } from '@ant-design/icons';
import { DetailSidebar, VirtualList, styled } from 'flipper';
import { theme } from 'flipper-plugin';
import { Sidebar } from './Sidebar';
import type { StoreType } from './types';
type Props = StoreType & {
active: boolean;
};
export function Logs({ active, logs, index, resetTo }: Props) {
const [selectedID, setSelectedID] = React.useState<string | null>(null);
const selectedItem = selectedID
? logs.find((log) => log.id === selectedID)
: logs[logs.length - 1];
return logs.length ? (
<>
<VirtualList
data={logs}
rowHeight={51}
renderRow={({ id, action }, i) => (
<Row
key={id}
selected={selectedItem?.id === id}
faded={index != null ? index > -1 && i > index : false}
onClick={() => {
if (id === logs[logs.length - 1].id) {
setSelectedID(null);
} else {
setSelectedID(id);
}
}}
>
{action.type}
<JumpButton type="button" onClick={() => resetTo(id)}>
Reset to this
</JumpButton>
</Row>
)}
/>
{active ? (
<DetailSidebar>
{selectedItem && (
<Sidebar action={selectedItem.action} state={selectedItem.state} />
)}
</DetailSidebar>
) : null}
</>
) : (
<Center>
<Faded>
<EmptyIcon />
<BlankslateText>Navigate in the app to see actions</BlankslateText>
</Faded>
</Center>
);
}
const Row = styled.button<{ selected: boolean; faded: boolean }>((props) => ({
'appearance': 'none',
'display': 'flex',
'alignItems': 'center',
'justifyContent': 'space-between',
'fontFamily': theme.monospace.fontFamily,
'fontSize': theme.monospace.fontSize,
'textAlign': 'left',
'padding': `${theme.space.medium}px ${theme.space.large}px`,
'color': props.selected ? '#fff' : '#000',
'backgroundColor': props.selected ? theme.primaryColor : 'transparent',
'opacity': props.faded ? 0.5 : 1,
'border': 0,
'boxShadow': `inset 0 -1px 0 0 ${theme.dividerColor}`,
'width': '100%',
'cursor': 'pointer',
'&:hover': {
backgroundColor: props.selected
? theme.primaryColor
: 'rgba(0, 0, 0, 0.05)',
},
}));
const JumpButton = styled.button({
'appearance': 'none',
'backgroundColor': 'rgba(0, 0, 0, 0.1)',
'border': 0,
'margin': 0,
'padding': `${theme.space.tiny}px ${theme.space.medium}px`,
'color': 'inherit',
'cursor': 'pointer',
'fontSize': theme.fontSize.small,
'borderRadius': theme.borderRadius,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
});
const Center = styled.div({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
});
const Faded = styled.div({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
opacity: 0.3,
});
const EmptyIcon = styled(CompassOutlined)({
display: 'block',
fontSize: 48,
margin: theme.space.large,
opacity: 0.8,
});
const BlankslateText = styled.h5({
color: 'rgba(0, 0, 0, 0.85)',
fontWeight: 600,
fontSize: 16,
lineHeight: 1.5,
});

View File

@@ -0,0 +1,146 @@
import * as React from 'react';
import { styled } from 'flipper';
import { theme } from 'flipper-plugin';
import type { PartialRoute } from './types';
type Props = {
routes: PartialRoute[];
root?: boolean;
};
export function RouteMap({ routes, root = true }: Props) {
return (
<Container
style={{
...(root
? { overflowX: 'auto', padding: `0 ${theme.space.small}px` }
: null),
}}
>
{routes.map((route, i) => (
<Item key={route.name}>
<div>
<Name>
{route.name}
{root ? null : i === 0 ? <ConnectLeft /> : <ConnectUpLeft />}
</Name>
{route.params ? (
<ParamsContainer>
<Params>
<tbody>
{Object.entries(route.params).map(([key, value]) => (
<Row key={key}>
<Key>{key}</Key>
<Separator>:</Separator>
<Value>{JSON.stringify(value)}</Value>
</Row>
))}
</tbody>
</Params>
<ConnectUp />
</ParamsContainer>
) : null}
</div>
{route.state ? (
<RouteMap routes={route.state.routes} root={false} />
) : null}
</Item>
))}
</Container>
);
}
const Container = styled.div({
display: 'flex',
flexDirection: 'column',
});
const Item = styled.div({
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start',
});
const Name = styled.div({
minWidth: 120,
backgroundColor: theme.primaryColor,
color: 'white',
fontSize: theme.fontSize.default,
margin: theme.space.small,
padding: `${theme.space.small}px ${theme.space.large}px`,
borderRadius: theme.borderRadius,
position: 'relative',
textAlign: 'center',
});
const ParamsContainer = styled.div({
position: 'relative',
});
const Params = styled.table({
minWidth: 120,
borderCollapse: 'separate',
border: `1px solid ${theme.primaryColor}`,
fontFamily: theme.monospace.fontFamily,
fontSize: theme.monospace.fontSize,
margin: `${theme.space.large}px ${theme.space.small}px`,
padding: theme.space.small,
borderRadius: theme.borderRadius,
width: 'auto',
overflow: 'visible',
});
const Row = styled.tr({
border: 0,
background: 'none',
});
const Key = styled.td({
color: theme.textColorSecondary,
border: 0,
padding: '0 4px',
textAlign: 'right',
});
const Value = styled.td({
color: theme.primaryColor,
padding: `0 ${theme.space.tiny}px`,
border: 0,
});
const Separator = styled.td({
color: 'inherit',
opacity: 0.3,
border: 0,
padding: 0,
});
const ConnectLeft = styled.div({
position: 'absolute',
width: 16,
height: 1,
backgroundColor: theme.primaryColor,
right: '100%',
top: '50%',
});
const ConnectUpLeft = styled.div({
position: 'absolute',
width: 9,
height: 53,
border: `1px solid ${theme.primaryColor}`,
borderRadius: `0 0 0 ${theme.borderRadius}`,
borderRight: 0,
borderTop: 0,
right: '100%',
bottom: '50%',
});
const ConnectUp = styled.div({
position: 'absolute',
width: 1,
height: 16,
backgroundColor: theme.primaryColor,
right: '50%',
bottom: '100%',
});

View File

@@ -0,0 +1,20 @@
import * as React from 'react';
import { Layout, ManagedDataInspector } from 'flipper';
import { Title4 } from './Typography';
export function Sidebar({
action,
state,
}: {
action: object;
state: object | undefined;
}) {
return (
<Layout.Container gap pad>
<Title4>Action</Title4>
<ManagedDataInspector data={action} expandRoot={false} />
<Title4>State</Title4>
<ManagedDataInspector data={state} expandRoot={false} />
</Layout.Container>
);
}

View File

@@ -0,0 +1,9 @@
import { styled } from 'flipper';
export const Title4 = styled.h4({
fontWeight: 600,
fontSize: 14,
lineHeight: 1.4,
letterSpacing: -0.24,
marginBottom: 0,
});

View File

@@ -0,0 +1,40 @@
import * as React from 'react';
import { styled } from 'flipper';
import { theme } from 'flipper-plugin';
import { Tabs } from 'antd';
import { useStore } from './useStore';
import { Logs } from './Logs';
import { LinkingTester } from './LinkingTester';
const { TabPane } = Tabs;
export function Component() {
const store = useStore();
const [activeKey, setActiveKey] = React.useState('logs');
return (
<Tabs
activeKey={activeKey}
onChange={setActiveKey}
tabBarStyle={{ marginBottom: 0 }}
>
<TabsContent tab={<TabLabel>Logs</TabLabel>} key="logs">
<Logs active={activeKey === 'logs'} {...store} />
</TabsContent>
<TabsContent tab={<TabLabel>Linking</TabLabel>} key="linking">
<LinkingTester active={activeKey === 'linking'} {...store} />
</TabsContent>
</Tabs>
);
}
const TabLabel = styled.span({
padding: `0 ${theme.space.large}px`,
});
const TabsContent = styled(TabPane)({
height: 'calc(100vh - 80px)',
});
export * from './plugin';

View File

@@ -0,0 +1,63 @@
import { PluginClient, createState } from 'flipper-plugin';
import type { Log, NavigationState } from './types';
type Events = {
action: Log;
init: { id: string; state: NavigationState | undefined };
};
type Methods = {
'navigation.invoke': (params: {
method: string;
args: any[];
}) => Promise<any>;
'linking.invoke': (params: { method: string; args: any[] }) => Promise<any>;
};
export function plugin(client: PluginClient<Events, Methods>) {
const logs = createState<Log[]>([], { persist: 'logs' });
const index = createState<number | null>(null, { persist: 'index' });
client.onMessage('init', () => {
logs.set([]);
index.set(null);
});
client.onMessage('action', (action) => {
const indexValue = index.get();
index.set(null);
logs.update((draft) => {
if (indexValue != null) {
draft.splice(indexValue + 1);
}
draft.push(action);
});
});
function navigation(method: string, ...args: any[]) {
return client.send('navigation.invoke', { method, args });
}
function resetTo(id: string) {
const logsValue = logs.get();
const indexValue = logsValue.findIndex((update) => update.id === id)!;
const { state } = logsValue[indexValue];
index.set(indexValue);
return client.send('navigation.invoke', {
method: 'resetRoot',
args: [state],
});
}
function linking(method: string, ...args: any[]) {
return client.send('linking.invoke', { method, args });
}
return {
logs,
index,
resetTo,
navigation,
linking,
};
}

View File

@@ -0,0 +1,41 @@
export type NavigationRoute = {
key: string;
name: string;
params?: object;
state?: NavigationState;
};
export type NavigationState = {
key: string;
index: number;
routes: NavigationRoute[];
};
export type NavigationAction = {
type: string;
payload?: object;
};
export type PartialRoute = {
name: string;
params?: object;
state?: PartialState;
};
export type PartialState = {
routes: PartialRoute[];
};
export type Log = {
id: string;
action: NavigationAction;
state: NavigationState | undefined;
};
export type StoreType = {
logs: Log[];
index: number;
navigation: (method: string, ...args: any[]) => Promise<any>;
linking: (method: string, ...args: any[]) => Promise<any>;
resetTo: (id: string) => Promise<void>;
};

View File

@@ -0,0 +1,17 @@
import { usePlugin, useValue } from 'flipper-plugin';
import type { StoreType } from './types';
import { plugin } from './plugin';
export function useStore(): StoreType {
const instance = usePlugin(plugin);
const logs = useValue(instance.logs);
const index = useValue(instance.index);
return {
logs,
index: index ?? logs.length - 1,
navigation: instance.navigation,
linking: instance.linking,
resetTo: instance.resetTo,
};
}

View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"esModuleInterop": true,
"importsNotUsedAsValues": "error",
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"lib": ["esnext", "dom"],
"module": "esnext",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noImplicitUseStrict": false,
"noStrictGenericChecks": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext"
}
}

View File

@@ -1,6 +1,9 @@
import * as React from 'react';
import {
BaseNavigationContainer,
getPathFromState,
getStateFromPath,
getActionFromState,
NavigationContainerProps,
NavigationContainerRef,
ParamListBase,
@@ -62,6 +65,27 @@ function NavigationContainerInner(
...linking,
});
// Add additional linking related info to the ref
// This will be used by the devtools
React.useEffect(() => {
if (refContainer.current) {
Object.defineProperty(refContainer.current, '__linking', {
get() {
return {
...linking,
enabled: isLinkingEnabled,
prefixes: linking?.prefixes ?? [],
getStateFromPath: linking?.getStateFromPath ?? getStateFromPath,
getPathFromState: linking?.getPathFromState ?? getPathFromState,
getActionFromState:
linking?.getActionFromState ?? getActionFromState,
};
},
enumerable: false,
});
}
});
const [isResolved, initialState] = useThenable(getInitialState);
React.useImperativeHandle(ref, () => refContainer.current);

View File

@@ -1,6 +1,7 @@
import type {
getStateFromPath as getStateFromPathDefault,
getPathFromState as getPathFromStateDefault,
getActionFromState as getActionFromStateDefault,
PathConfigMap,
Route,
} from '@react-navigation/core';
@@ -104,6 +105,10 @@ export type LinkingOptions<ParamList extends {}> = {
* Only applicable on Web.
*/
getPathFromState?: typeof getPathFromStateDefault;
/**
* Custom function to convert the state object to a valid action (advanced).
*/
getActionFromState?: typeof getActionFromStateDefault;
};
export type DocumentTitleOptions = {

View File

@@ -1,8 +1,8 @@
import * as React from 'react';
import { Linking, Platform } from 'react-native';
import {
getActionFromState,
getStateFromPath as getStateFromPathDefault,
getActionFromState as getActionFromStateDefault,
NavigationContainerRef,
ParamListBase,
} from '@react-navigation/core';
@@ -45,6 +45,7 @@ export default function useLinking(
};
},
getStateFromPath = getStateFromPathDefault,
getActionFromState = getActionFromStateDefault,
}: LinkingOptions<ParamListBase>
) {
React.useEffect(() => {
@@ -78,6 +79,7 @@ export default function useLinking(
const configRef = React.useRef(config);
const getInitialURLRef = React.useRef(getInitialURL);
const getStateFromPathRef = React.useRef(getStateFromPath);
const getActionFromStateRef = React.useRef(getActionFromState);
React.useEffect(() => {
enabledRef.current = enabled;
@@ -85,7 +87,8 @@ export default function useLinking(
configRef.current = config;
getInitialURLRef.current = getInitialURL;
getStateFromPathRef.current = getStateFromPath;
}, [config, enabled, prefixes, getInitialURL, getStateFromPath]);
getActionFromStateRef.current = getActionFromState;
});
const getInitialState = React.useCallback(() => {
let state: ResultState | undefined;
@@ -150,7 +153,10 @@ export default function useLinking(
return;
}
const action = getActionFromState(state, configRef.current);
const action = getActionFromStateRef.current(
state,
configRef.current
);
if (action !== undefined) {
try {

View File

@@ -2,9 +2,9 @@ import * as React from 'react';
import {
getStateFromPath as getStateFromPathDefault,
getPathFromState as getPathFromStateDefault,
getActionFromState as getActionFromStateDefault,
NavigationContainerRef,
NavigationState,
getActionFromState,
findFocusedRoute,
ParamListBase,
} from '@react-navigation/core';
@@ -295,6 +295,7 @@ export default function useLinking(
config,
getStateFromPath = getStateFromPathDefault,
getPathFromState = getPathFromStateDefault,
getActionFromState = getActionFromStateDefault,
}: LinkingOptions<ParamListBase>
) {
React.useEffect(() => {
@@ -326,13 +327,15 @@ export default function useLinking(
const configRef = React.useRef(config);
const getStateFromPathRef = React.useRef(getStateFromPath);
const getPathFromStateRef = React.useRef(getPathFromState);
const getActionFromStateRef = React.useRef(getActionFromState);
React.useEffect(() => {
enabledRef.current = enabled;
configRef.current = config;
getStateFromPathRef.current = getStateFromPath;
getPathFromStateRef.current = getPathFromState;
}, [config, enabled, getPathFromState, getStateFromPath]);
getActionFromStateRef.current = getActionFromState;
});
const server = React.useContext(ServerContext);
@@ -413,7 +416,10 @@ export default function useLinking(
}
if (index > previousIndex) {
const action = getActionFromState(state, configRef.current);
const action = getActionFromStateRef.current(
state,
configRef.current
);
if (action !== undefined) {
try {

View File

@@ -24,5 +24,6 @@
"skipLibCheck": true,
"strict": true,
"target": "esnext"
}
},
"exclude": ["packages/flipper-plugin-react-navigation"]
}

3442
yarn.lock

File diff suppressed because it is too large Load Diff