expo-dev-launcher 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/android/build.gradle +1 -1
- package/android/src/debug/assets/expo_dev_launcher_android.bundle +22 -21
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/expo/modules/devlauncher/helpers/DevLauncherReactUtils.kt +3 -1
- package/android/src/main/java/expo/modules/devlauncher/launcher/errors/DevLauncherUncaughtExceptionHandler.kt +6 -9
- package/android/src/main/java/expo/modules/devlauncher/logs/DevLauncherRemoteLog.kt +13 -37
- package/android/src/main/java/expo/modules/devlauncher/logs/DevLauncherRemoteLogManager.kt +27 -31
- package/android/src/react-native-64/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/android/src/react-native-65/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/android/src/react-native-66/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/android/src/react-native-67/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/android/src/react-native-69/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/bundle/components/AppHeader.tsx +7 -1
- package/bundle/components/ScreenContainer.tsx +8 -0
- package/bundle/native-modules/DevLauncherAuth.ts +21 -10
- package/bundle/screens/BranchesScreen.tsx +18 -15
- package/bundle/screens/ExtensionsScreen.tsx +37 -38
- package/bundle/screens/ExtensionsStack.tsx +1 -0
- package/bundle/screens/HomeScreen.tsx +9 -2
- package/bundle/screens/SettingsScreen.tsx +113 -110
- package/bundle/screens/UpdatesScreen.tsx +44 -39
- package/ios/EXDevLauncherController.m +3 -0
- package/ios/EXDevLauncherURLHelper.swift +30 -19
- package/ios/Errors/EXDevLauncherUncaughtExceptionHandler.swift +14 -10
- package/ios/Logs/EXDevLauncherRemoteLogsManager.swift +39 -34
- package/ios/ReactDelegateHandler/ExpoDevLauncherReactDelegateHandler.swift +13 -1
- package/ios/main.jsbundle +22 -21
- package/package.json +4 -4
|
@@ -16,6 +16,7 @@ import * as React from 'react';
|
|
|
16
16
|
import { ScrollView, Switch } from 'react-native';
|
|
17
17
|
import { useQueryClient } from 'react-query';
|
|
18
18
|
import { SafeAreaTop } from '../components/SafeAreaTop';
|
|
19
|
+
import { ScreenContainer } from '../components/ScreenContainer';
|
|
19
20
|
|
|
20
21
|
import { Toasts } from '../components/Toasts';
|
|
21
22
|
import { copyToClipboardAsync, updatesConfig } from '../native-modules/DevLauncherInternal';
|
|
@@ -83,135 +84,137 @@ export function SettingsScreen() {
|
|
|
83
84
|
|
|
84
85
|
return (
|
|
85
86
|
<ScrollView testID="DevLauncherSettingsScreen" showsVerticalScrollIndicator={false}>
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<View px="medium">
|
|
90
|
-
<Heading size="large">Settings</Heading>
|
|
91
|
-
</View>
|
|
92
|
-
|
|
93
|
-
<View py="large" px="medium">
|
|
94
|
-
<View bg="default" rounded="large">
|
|
95
|
-
<Row px="medium" py="small" align="center">
|
|
96
|
-
<ShowMenuIcon />
|
|
97
|
-
<Spacer.Horizontal size="small" />
|
|
98
|
-
<Text size="large">Show menu at launch</Text>
|
|
99
|
-
<Spacer.Horizontal />
|
|
100
|
-
<Switch
|
|
101
|
-
accessibilityRole="switch"
|
|
102
|
-
accessibilityLabel="Toggle showing menu at launch"
|
|
103
|
-
value={showsAtLaunch}
|
|
104
|
-
onValueChange={() => setShowsAtLaunch(!showsAtLaunch)}
|
|
105
|
-
/>
|
|
106
|
-
</Row>
|
|
107
|
-
</View>
|
|
108
|
-
|
|
109
|
-
<Spacer.Vertical size="large" />
|
|
87
|
+
<ScreenContainer>
|
|
88
|
+
<SafeAreaTop />
|
|
89
|
+
<Spacer.Vertical size="medium" />
|
|
110
90
|
|
|
111
|
-
<View
|
|
112
|
-
<Heading
|
|
91
|
+
<View px="medium">
|
|
92
|
+
<Heading size="large">Settings</Heading>
|
|
113
93
|
</View>
|
|
114
94
|
|
|
115
|
-
<View>
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
roundedBottom="none"
|
|
120
|
-
onPress={() => setMotionGestureEnabled(!motionGestureEnabled)}
|
|
121
|
-
accessibilityState={{ checked: motionGestureEnabled }}>
|
|
122
|
-
<Row px="medium" py="small" align="center" bg="default">
|
|
123
|
-
<ShakeDeviceIcon />
|
|
124
|
-
<Spacer.Horizontal size="small" />
|
|
125
|
-
<Text size="large" color="default">
|
|
126
|
-
Shake device
|
|
127
|
-
</Text>
|
|
128
|
-
<Spacer.Horizontal />
|
|
129
|
-
{motionGestureEnabled && <CheckIcon />}
|
|
130
|
-
</Row>
|
|
131
|
-
</Button.ScaleOnPressContainer>
|
|
132
|
-
|
|
133
|
-
<Divider />
|
|
134
|
-
|
|
135
|
-
<Button.ScaleOnPressContainer
|
|
136
|
-
bg="default"
|
|
137
|
-
roundedBottom="large"
|
|
138
|
-
roundedTop="none"
|
|
139
|
-
onPress={() => setTouchGestureEnabled(!touchGestureEnabled)}
|
|
140
|
-
accessibilityState={{ checked: touchGestureEnabled }}>
|
|
141
|
-
<Row px="medium" py="small" bg="default">
|
|
142
|
-
<ThreeFingerPressIcon />
|
|
95
|
+
<View py="large" px="medium">
|
|
96
|
+
<View bg="default" rounded="large">
|
|
97
|
+
<Row px="medium" py="small" align="center">
|
|
98
|
+
<ShowMenuIcon />
|
|
143
99
|
<Spacer.Horizontal size="small" />
|
|
144
|
-
<Text size="large"
|
|
145
|
-
Three-finger long-press
|
|
146
|
-
</Text>
|
|
100
|
+
<Text size="large">Show menu at launch</Text>
|
|
147
101
|
<Spacer.Horizontal />
|
|
148
|
-
|
|
102
|
+
<Switch
|
|
103
|
+
accessibilityRole="switch"
|
|
104
|
+
accessibilityLabel="Toggle showing menu at launch"
|
|
105
|
+
value={showsAtLaunch}
|
|
106
|
+
onValueChange={() => setShowsAtLaunch(!showsAtLaunch)}
|
|
107
|
+
/>
|
|
149
108
|
</Row>
|
|
150
|
-
</
|
|
151
|
-
</View>
|
|
152
|
-
|
|
153
|
-
<View padding="small">
|
|
154
|
-
<Text color="secondary" size="small" leading="large">
|
|
155
|
-
Selected gestures will toggle the developer menu while inside a preview. The menu allows
|
|
156
|
-
you to reload or return to home and exposes developer tools.
|
|
157
|
-
</Text>
|
|
158
|
-
</View>
|
|
109
|
+
</View>
|
|
159
110
|
|
|
160
|
-
|
|
111
|
+
<Spacer.Vertical size="large" />
|
|
161
112
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
<Spacer.Horizontal />
|
|
166
|
-
<Text>{buildInfo?.appVersion}</Text>
|
|
167
|
-
</Row>
|
|
113
|
+
<View padding="medium">
|
|
114
|
+
<Heading color="secondary">Menu gestures</Heading>
|
|
115
|
+
</View>
|
|
168
116
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
117
|
+
<View>
|
|
118
|
+
<Button.ScaleOnPressContainer
|
|
119
|
+
bg="default"
|
|
120
|
+
roundedTop="large"
|
|
121
|
+
roundedBottom="none"
|
|
122
|
+
onPress={() => setMotionGestureEnabled(!motionGestureEnabled)}
|
|
123
|
+
accessibilityState={{ checked: motionGestureEnabled }}>
|
|
172
124
|
<Row px="medium" py="small" align="center" bg="default">
|
|
173
|
-
<
|
|
125
|
+
<ShakeDeviceIcon />
|
|
126
|
+
<Spacer.Horizontal size="small" />
|
|
127
|
+
<Text size="large" color="default">
|
|
128
|
+
Shake device
|
|
129
|
+
</Text>
|
|
174
130
|
<Spacer.Horizontal />
|
|
175
|
-
<
|
|
131
|
+
{motionGestureEnabled && <CheckIcon />}
|
|
176
132
|
</Row>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
133
|
+
</Button.ScaleOnPressContainer>
|
|
134
|
+
|
|
135
|
+
<Divider />
|
|
136
|
+
|
|
137
|
+
<Button.ScaleOnPressContainer
|
|
138
|
+
bg="default"
|
|
139
|
+
roundedBottom="large"
|
|
140
|
+
roundedTop="none"
|
|
141
|
+
onPress={() => setTouchGestureEnabled(!touchGestureEnabled)}
|
|
142
|
+
accessibilityState={{ checked: touchGestureEnabled }}>
|
|
143
|
+
<Row px="medium" py="small" bg="default">
|
|
144
|
+
<ThreeFingerPressIcon />
|
|
145
|
+
<Spacer.Horizontal size="small" />
|
|
146
|
+
<Text size="large" color="default">
|
|
147
|
+
Three-finger long-press
|
|
148
|
+
</Text>
|
|
185
149
|
<Spacer.Horizontal />
|
|
186
|
-
<
|
|
150
|
+
{touchGestureEnabled && <CheckIcon />}
|
|
187
151
|
</Row>
|
|
188
|
-
|
|
189
|
-
|
|
152
|
+
</Button.ScaleOnPressContainer>
|
|
153
|
+
</View>
|
|
154
|
+
|
|
155
|
+
<View padding="small">
|
|
156
|
+
<Text color="secondary" size="small" leading="large">
|
|
157
|
+
Selected gestures will toggle the developer menu while inside a preview. The menu
|
|
158
|
+
allows you to reload or return to home and exposes developer tools.
|
|
159
|
+
</Text>
|
|
160
|
+
</View>
|
|
190
161
|
|
|
191
|
-
<
|
|
162
|
+
<Spacer.Vertical size="medium" />
|
|
192
163
|
|
|
193
|
-
<
|
|
194
|
-
onPress={onCopyPress}
|
|
195
|
-
disabled={hasCopiedContent}
|
|
196
|
-
bg="default"
|
|
197
|
-
roundedTop="none"
|
|
198
|
-
roundedBottom="large">
|
|
164
|
+
<View rounded="large" overflow="hidden">
|
|
199
165
|
<Row px="medium" py="small" align="center" bg="default">
|
|
200
|
-
<Text
|
|
201
|
-
|
|
202
|
-
</Text>
|
|
166
|
+
<Text>Version</Text>
|
|
167
|
+
<Spacer.Horizontal />
|
|
168
|
+
<Text>{buildInfo?.appVersion}</Text>
|
|
203
169
|
</Row>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
170
|
+
|
|
171
|
+
{Boolean(buildInfo.runtimeVersion) && (
|
|
172
|
+
<>
|
|
173
|
+
<Divider />
|
|
174
|
+
<Row px="medium" py="small" align="center" bg="default">
|
|
175
|
+
<Text>Runtime Version</Text>
|
|
176
|
+
<Spacer.Horizontal />
|
|
177
|
+
<Text>{buildInfo.runtimeVersion}</Text>
|
|
178
|
+
</Row>
|
|
179
|
+
</>
|
|
180
|
+
)}
|
|
181
|
+
|
|
182
|
+
{Boolean(buildInfo.sdkVersion) && !buildInfo.runtimeVersion && (
|
|
183
|
+
<>
|
|
184
|
+
<Divider />
|
|
185
|
+
<Row px="medium" py="small" align="center" bg="default">
|
|
186
|
+
<Text>SDK Version</Text>
|
|
187
|
+
<Spacer.Horizontal />
|
|
188
|
+
<Text>{buildInfo.sdkVersion}</Text>
|
|
189
|
+
</Row>
|
|
190
|
+
</>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
<Divider />
|
|
194
|
+
|
|
195
|
+
<Button.ScaleOnPressContainer
|
|
196
|
+
onPress={onCopyPress}
|
|
197
|
+
disabled={hasCopiedContent}
|
|
198
|
+
bg="default"
|
|
199
|
+
roundedTop="none"
|
|
200
|
+
roundedBottom="large">
|
|
201
|
+
<Row px="medium" py="small" align="center" bg="default">
|
|
202
|
+
<Text color="link" size="medium">
|
|
203
|
+
{hasCopiedContent ? 'Copied to clipboard!' : 'Tap to Copy All'}
|
|
204
|
+
</Text>
|
|
205
|
+
</Row>
|
|
206
|
+
</Button.ScaleOnPressContainer>
|
|
207
|
+
{userData?.isExpoAdmin && (
|
|
208
|
+
<>
|
|
209
|
+
<Spacer.Vertical size="medium" />
|
|
210
|
+
<DebugSettings />
|
|
211
|
+
<Spacer.Vertical size="medium" />
|
|
212
|
+
<UpdatesDebugSettings />
|
|
213
|
+
</>
|
|
214
|
+
)}
|
|
215
|
+
</View>
|
|
213
216
|
</View>
|
|
214
|
-
</
|
|
217
|
+
</ScreenContainer>
|
|
215
218
|
</ScrollView>
|
|
216
219
|
);
|
|
217
220
|
}
|
|
@@ -19,6 +19,7 @@ import { ScrollView } from 'react-native-gesture-handler';
|
|
|
19
19
|
import { BasicButton } from '../components/BasicButton';
|
|
20
20
|
import { EASUpdateRow } from '../components/EASUpdatesRows';
|
|
21
21
|
import { FlatList } from '../components/FlatList';
|
|
22
|
+
import { ScreenContainer } from '../components/ScreenContainer';
|
|
22
23
|
import { useOnUpdatePress } from '../hooks/useOnUpdatePress';
|
|
23
24
|
import { useUpdatesConfig } from '../providers/UpdatesConfigProvider';
|
|
24
25
|
import { useChannelsForApp } from '../queries/useChannelsForApp';
|
|
@@ -89,29 +90,31 @@ export function UpdatesScreen({ route }: UpdatesScreenProps) {
|
|
|
89
90
|
|
|
90
91
|
function EmptyList() {
|
|
91
92
|
return (
|
|
92
|
-
<
|
|
93
|
-
<View>
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
<ScreenContainer>
|
|
94
|
+
<View mt="large" mx="medium" bg="default" rounded="large" padding="medium">
|
|
95
|
+
<View>
|
|
96
|
+
<Heading>There are no updates available for this branch.</Heading>
|
|
97
|
+
<Spacer.Vertical size="small" />
|
|
98
|
+
<Text color="secondary" size="small">
|
|
99
|
+
Updates allow you to deliver code directly to your users.
|
|
100
|
+
</Text>
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
<View py="medium" align="centered">
|
|
103
|
+
<Button.ScaleOnPressContainer
|
|
104
|
+
bg="tertiary"
|
|
105
|
+
onPress={() => {
|
|
106
|
+
Linking.openURL(`https://docs.expo.dev/eas-update/how-eas-update-works/`);
|
|
107
|
+
}}>
|
|
108
|
+
<View px="2.5" py="2">
|
|
109
|
+
<Button.Text weight="medium" color="tertiary">
|
|
110
|
+
Publish an update
|
|
111
|
+
</Button.Text>
|
|
112
|
+
</View>
|
|
113
|
+
</Button.ScaleOnPressContainer>
|
|
114
|
+
</View>
|
|
112
115
|
</View>
|
|
113
116
|
</View>
|
|
114
|
-
</
|
|
117
|
+
</ScreenContainer>
|
|
115
118
|
);
|
|
116
119
|
}
|
|
117
120
|
|
|
@@ -137,25 +140,27 @@ export function UpdatesScreen({ route }: UpdatesScreenProps) {
|
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
return (
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
143
|
+
<ScreenContainer>
|
|
144
|
+
<View flex="1">
|
|
145
|
+
<BranchDetailsHeader
|
|
146
|
+
branchName={branchName}
|
|
147
|
+
updates={updates}
|
|
148
|
+
onOpenPress={() => onUpdatePress(updates[0])}
|
|
149
|
+
/>
|
|
150
|
+
<FlatList
|
|
151
|
+
isLoading={isLoading}
|
|
152
|
+
isRefreshing={isRefreshing}
|
|
153
|
+
onRefresh={() => refetch()}
|
|
154
|
+
ListHeaderComponent={Header}
|
|
155
|
+
data={updates}
|
|
156
|
+
extraData={{ length: updates.length, loadingUpdateId }}
|
|
157
|
+
renderItem={renderUpdate}
|
|
158
|
+
keyExtractor={(item) => item.id}
|
|
159
|
+
ListFooterComponent={Footer}
|
|
160
|
+
ListEmptyComponent={EmptyList}
|
|
161
|
+
/>
|
|
162
|
+
</View>
|
|
163
|
+
</ScreenContainer>
|
|
159
164
|
);
|
|
160
165
|
}
|
|
161
166
|
|
|
@@ -404,6 +404,9 @@
|
|
|
404
404
|
projectUrl = expoUrl;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
// Disable onboarding popup if "&disableOnboarding=1" is a param
|
|
408
|
+
[EXDevLauncherURLHelper disableOnboardingPopupIfNeeded:expoUrl];
|
|
409
|
+
|
|
407
410
|
NSString *installationID = [_installationIDHelper getOrCreateInstallationID];
|
|
408
411
|
|
|
409
412
|
NSDictionary *updatesConfiguration = [EXDevLauncherUpdatesHelper createUpdatesConfigurationWithURL:expoUrl
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
2
|
|
|
3
3
|
import Foundation
|
|
4
|
+
import EXDevMenu
|
|
4
5
|
|
|
5
6
|
@objc
|
|
6
7
|
public class EXDevLauncherUrl: NSObject {
|
|
7
8
|
@objc
|
|
8
9
|
public var url: URL
|
|
9
|
-
|
|
10
|
+
|
|
10
11
|
@objc
|
|
11
12
|
public var queryParams: [String: String]
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
@objc
|
|
14
15
|
public init(_ url: URL) {
|
|
15
16
|
self.queryParams = EXDevLauncherURLHelper.getQueryParamsForUrl(url)
|
|
16
17
|
self.url = url
|
|
17
|
-
|
|
18
|
-
if
|
|
18
|
+
|
|
19
|
+
if EXDevLauncherURLHelper.isDevLauncherURL(url) {
|
|
19
20
|
if let urlParam = self.queryParams["url"] {
|
|
20
21
|
if let urlFromParam = URL.init(string: urlParam) {
|
|
21
22
|
self.url = EXDevLauncherURLHelper.replaceEXPScheme(urlFromParam, to: "http")
|
|
@@ -24,7 +25,7 @@ public class EXDevLauncherUrl: NSObject {
|
|
|
24
25
|
} else {
|
|
25
26
|
self.url = EXDevLauncherURLHelper.replaceEXPScheme(self.url, to: "http")
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
+
|
|
28
29
|
super.init()
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -35,41 +36,51 @@ public class EXDevLauncherURLHelper: NSObject {
|
|
|
35
36
|
public static func isDevLauncherURL(_ url: URL?) -> Bool {
|
|
36
37
|
return url?.host == "expo-development-client"
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
@objc
|
|
40
41
|
public static func hasUrlQueryParam(_ url: URL) -> Bool {
|
|
41
42
|
var hasUrlQueryParam = false
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
let components = URLComponents.init(url: url, resolvingAgainstBaseURL: false)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
45
|
+
|
|
46
|
+
if ((components?.queryItems?.contains(where: {
|
|
47
|
+
$0.name == "url" && $0.value != nil
|
|
48
|
+
})) ?? false) {
|
|
49
|
+
hasUrlQueryParam = true
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
return hasUrlQueryParam
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
@objc
|
|
56
|
+
public static func disableOnboardingPopupIfNeeded(_ url: URL) {
|
|
57
|
+
let components = URLComponents.init(url: url, resolvingAgainstBaseURL: false)
|
|
58
|
+
|
|
59
|
+
if ((components?.queryItems?.contains(where: {
|
|
60
|
+
$0.name == "disableOnboarding" && ($0.value ?? "") == "1"
|
|
61
|
+
})) ?? false) {
|
|
62
|
+
DevMenuPreferences.isOnboardingFinished = true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
@objc
|
|
56
67
|
public static func replaceEXPScheme(_ url: URL, to scheme: String) -> URL {
|
|
57
68
|
var components = URLComponents.init(url: url, resolvingAgainstBaseURL: false)!
|
|
58
|
-
if
|
|
69
|
+
if components.scheme == "exp" {
|
|
59
70
|
components.scheme = scheme
|
|
60
71
|
}
|
|
61
72
|
return components.url!
|
|
62
73
|
}
|
|
63
|
-
|
|
74
|
+
|
|
64
75
|
@objc
|
|
65
76
|
public static func getQueryParamsForUrl(_ url: URL) -> [String: String] {
|
|
66
77
|
let components = URLComponents.init(url: url, resolvingAgainstBaseURL: false)
|
|
67
78
|
var dict: [String: String] = [:]
|
|
68
|
-
|
|
79
|
+
|
|
69
80
|
for parameter in components?.queryItems ?? [] {
|
|
70
81
|
dict[parameter.name] = parameter.value?.removingPercentEncoding ?? ""
|
|
71
82
|
}
|
|
72
|
-
|
|
83
|
+
|
|
73
84
|
return dict
|
|
74
85
|
}
|
|
75
86
|
}
|
|
@@ -40,7 +40,7 @@ public class EXDevLauncherUncaughtExceptionHandler: NSObject {
|
|
|
40
40
|
static func tryToSendExceptionToBundler(_ exception: NSException) {
|
|
41
41
|
let controller = EXDevLauncherController.sharedInstance()
|
|
42
42
|
if (controller.isAppRunning()) {
|
|
43
|
-
guard let url =
|
|
43
|
+
guard let url = getWebSocketUrl(controller) else {
|
|
44
44
|
return
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -50,18 +50,22 @@ public class EXDevLauncherUncaughtExceptionHandler: NSObject {
|
|
|
50
50
|
logsManager.sendSync()
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
static func
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
53
|
+
|
|
54
|
+
static func getWebSocketUrl(_ controller: EXDevLauncherController) -> URL? {
|
|
55
|
+
// URL structure replicates
|
|
56
|
+
// https://github.com/facebook/react-native/blob/0.69-stable/Libraries/Utilities/HMRClient.js#L164
|
|
57
|
+
// but URLSessionWebSocketTask will crash if the scheme is not `ws` or `wss`
|
|
60
58
|
guard let appUrl = controller.appBridge?.bundleURL else {
|
|
61
59
|
return nil
|
|
62
60
|
}
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
guard let socketUrl = URL.init(string: "hot", relativeTo: appUrl) else {
|
|
62
|
+
return nil
|
|
63
|
+
}
|
|
64
|
+
guard var components = URLComponents(url: socketUrl, resolvingAgainstBaseURL: true) else {
|
|
65
|
+
return nil
|
|
66
|
+
}
|
|
67
|
+
components.scheme = "ws"
|
|
68
|
+
return components.url
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
static func tryToSaveException(_ exception: NSException) {
|
|
@@ -1,52 +1,57 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
class EXDevLauncherRemoteLogsManager {
|
|
4
|
-
private var batch: [
|
|
4
|
+
private var batch: [String] = []
|
|
5
5
|
private let url: URL
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
init(withUrl url: URL) {
|
|
8
8
|
self.url = url
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
func deferError(exception: NSException) {
|
|
12
|
-
batch.append(
|
|
13
|
-
|
|
14
|
-
"body": [
|
|
15
|
-
"message": exception.description,
|
|
16
|
-
"stack": exception.callStackSymbols.joined(separator: "\n")
|
|
17
|
-
],
|
|
18
|
-
"includesStack": true
|
|
19
|
-
])
|
|
12
|
+
batch.append("\(exception.name.rawValue): \(exception.reason ?? exception.description)")
|
|
13
|
+
batch.append(" \(exception.callStackSymbols.joined(separator: "\n "))")
|
|
20
14
|
}
|
|
21
|
-
|
|
15
|
+
|
|
22
16
|
func deferError(message: String) {
|
|
23
|
-
batch.append(
|
|
24
|
-
"level": "error",
|
|
25
|
-
"body": message,
|
|
26
|
-
"includesStack": false
|
|
27
|
-
])
|
|
17
|
+
batch.append(message)
|
|
28
18
|
}
|
|
29
|
-
|
|
19
|
+
|
|
30
20
|
func sendSync() {
|
|
31
|
-
|
|
21
|
+
// message format comes from
|
|
22
|
+
// https://github.com/facebook/react-native/blob/0.69-stable/Libraries/Utilities/HMRClient.js#L119-L134
|
|
23
|
+
let messageJson = [
|
|
24
|
+
"type": "log",
|
|
25
|
+
"level": "error",
|
|
26
|
+
"mode": "BRIDGE",
|
|
27
|
+
// `data` is an array whose members are simply concatenated with a space before printing to
|
|
28
|
+
// the console, so we join messages with a newline and send an array consisting of just a
|
|
29
|
+
// single item.
|
|
30
|
+
"data": [batch.joined(separator: "\n")]
|
|
31
|
+
] as [String: Any]
|
|
32
|
+
guard let data = try? JSONSerialization.data(withJSONObject: messageJson, options: []) else {
|
|
32
33
|
batch.removeAll()
|
|
33
34
|
return
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
|
|
36
37
|
batch.removeAll()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
|
|
39
|
+
if #available(iOS 13.0, *) {
|
|
40
|
+
let group = DispatchGroup()
|
|
41
|
+
group.enter()
|
|
42
|
+
|
|
43
|
+
let task = URLSession.shared.webSocketTask(with: self.url)
|
|
44
|
+
task.resume()
|
|
45
|
+
|
|
46
|
+
guard let dataString = String(data: data, encoding: .utf8) else {
|
|
47
|
+
group.leave()
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
let message = URLSessionWebSocketTask.Message.string(dataString)
|
|
51
|
+
task.send(message) { _ in
|
|
52
|
+
group.leave()
|
|
53
|
+
}
|
|
54
|
+
_ = group.wait(timeout: DispatchTime.now() + .seconds(2))
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -73,7 +73,19 @@ public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, RCTB
|
|
|
73
73
|
// MARK: EXDevelopmentClientControllerDelegate implementations
|
|
74
74
|
|
|
75
75
|
public func devLauncherController(_ developmentClientController: EXDevLauncherController, didStartWithSuccess success: Bool) {
|
|
76
|
-
|
|
76
|
+
var launchOptions: [AnyHashable: Any] = [:]
|
|
77
|
+
|
|
78
|
+
if let initialLaunchOptions = self.launchOptions {
|
|
79
|
+
for (key, value) in initialLaunchOptions {
|
|
80
|
+
launchOptions[key] = value
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (key, value) in developmentClientController.getLaunchOptions() {
|
|
85
|
+
launchOptions[key] = value
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let bridge = RCTBridge(delegate: self.bridgeDelegate, launchOptions: launchOptions)
|
|
77
89
|
developmentClientController.appBridge = bridge
|
|
78
90
|
|
|
79
91
|
let rootView = RCTRootView(bridge: bridge!, moduleName: self.rootViewModuleName!, initialProperties: self.rootViewInitialProperties)
|