expo-dev-launcher 0.9.0 → 0.9.1

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.
Files changed (23) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/expo/modules/devlauncher/helpers/DevLauncherOkHttpExtension.kt +3 -2
  4. package/android/src/main/java/expo/modules/devlauncher/launcher/manifest/DevLauncherManifestParser.kt +7 -2
  5. package/ios/Manifest/EXDevLauncherManifestParser.m +1 -0
  6. package/ios/Tests/EXDevLauncherManifestParserTests.m +23 -0
  7. package/ios/assets/__node_modules/css-select/node_modules/dom-serializer/foreignNames.json +102 -0
  8. package/ios/assets/node_modules/@react-navigation/elements/src/assets/back-icon-mask.png +0 -0
  9. package/ios/assets/node_modules/@react-navigation/elements/src/assets/back-icon.png +0 -0
  10. package/ios/assets/node_modules/@react-navigation/elements/src/assets/back-icon@2x.png +0 -0
  11. package/ios/assets/node_modules/@react-navigation/elements/src/assets/back-icon@3x.png +0 -0
  12. package/ios/assets/node_modules/@react-navigation/stack/src/views/assets/back-icon-mask.png +0 -0
  13. package/ios/assets/node_modules/@react-navigation/stack/src/views/assets/back-icon.png +0 -0
  14. package/ios/assets/node_modules/@react-navigation/stack/src/views/assets/back-icon@2x.png +0 -0
  15. package/ios/assets/node_modules/@react-navigation/stack/src/views/assets/back-icon@3x.png +0 -0
  16. package/package.json +3 -3
  17. package/plugin/build/resolveExpoUpdatesVersion.js +8 -1
  18. package/plugin/build/withDevLauncher.d.ts +1 -0
  19. package/plugin/build/withDevLauncher.js +64 -24
  20. package/plugin/build/withDevLauncherAppDelegate.js +41 -6
  21. package/plugin/src/resolveExpoUpdatesVersion.ts +7 -1
  22. package/plugin/src/withDevLauncher.ts +83 -30
  23. package/plugin/src/withDevLauncherAppDelegate.ts +48 -6
package/CHANGELOG.md CHANGED
@@ -10,6 +10,15 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.9.1 — 2021-12-15
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Fix plugin when `MainActivity.onNewIntent` exists. ([#15459](https://github.com/expo/expo/pull/15459) by [@janicduplessis](https://github.com/janicduplessis))
18
+ - Fix plugin when `expo-updates` is not present. ([#15541](https://github.com/expo/expo/pull/15541) by [@esamelson](https://github.com/esamelson))
19
+ - Include expo-platform header in manifest requests. ([#15563](https://github.com/expo/expo/pull/15563) by [@esamelson](https://github.com/esamelson))
20
+ - Fix plugin compatibility with SDK 44. ([#15562](https://github.com/expo/expo/pull/15562) & [#15570](https://github.com/expo/expo/pull/15570) by [@lukmccall](https://github.com/lukmccall) & [@esamelson](https://github.com/esamelson))
21
+
13
22
  ## 0.9.0 — 2021-12-03
14
23
 
15
24
  ### 🎉 New features
@@ -20,7 +29,6 @@
20
29
 
21
30
  - Fix `No native splash screen registered for given view controller` error happening when project is using both `expo-dev-client` and `expo-splash-screen` packages. ([#14745](https://github.com/expo/expo/pull/14745) by [@kudo](https://github.com/kudo))
22
31
  - Fix cannot load url that starts with exp. (by [@lukmccall](https://github.com/lukmccall))
23
- - [plugin] Fix config plugin compatibility with expo-screen-orientation. ([#14752](https://github.com/expo/expo/pull/14752) by [@esamelson](https://github.com/esamelson))
24
32
 
25
33
  ## 0.8.4 — 2021-10-21
26
34
 
@@ -25,7 +25,7 @@ android {
25
25
  minSdkVersion safeExtGet('minSdkVersion', 21)
26
26
  targetSdkVersion safeExtGet('targetSdkVersion', 30)
27
27
  versionCode 9
28
- versionName "0.9.0"
28
+ versionName "0.9.1"
29
29
  }
30
30
 
31
31
  lintOptions {
@@ -4,6 +4,7 @@ import android.net.Uri
4
4
  import kotlinx.coroutines.suspendCancellableCoroutine
5
5
  import okhttp3.Call
6
6
  import okhttp3.Callback
7
+ import okhttp3.Headers
7
8
  import okhttp3.OkHttpClient
8
9
  import okhttp3.Request
9
10
  import okhttp3.Response
@@ -31,5 +32,5 @@ suspend inline fun Request.await(okHttpClient: OkHttpClient): Response {
31
32
  }
32
33
  }
33
34
 
34
- fun fetch(url: Uri, method: String) =
35
- Request.Builder().method(method, null).url(url.toString()).build()
35
+ fun fetch(url: Uri, method: String, headers: Headers) =
36
+ Request.Builder().method(method, null).url(url.toString()).headers(headers).build()
@@ -4,6 +4,7 @@ import android.net.Uri
4
4
  import expo.modules.devlauncher.helpers.await
5
5
  import expo.modules.devlauncher.helpers.fetch
6
6
  import expo.modules.manifests.core.Manifest
7
+ import okhttp3.Headers
7
8
  import okhttp3.OkHttpClient
8
9
  import org.json.JSONObject
9
10
  import java.io.Reader
@@ -13,7 +14,7 @@ class DevLauncherManifestParser(
13
14
  private val url: Uri
14
15
  ) {
15
16
  suspend fun isManifestUrl(): Boolean {
16
- val response = fetch(url, "HEAD").await(httpClient)
17
+ val response = fetch(url, "HEAD", getHeaders()).await(httpClient)
17
18
  val contentType = response.header("Content-Type")
18
19
  // published projects may respond unsuccessfully to HEAD requests sent with no headers
19
20
  return !response.isSuccessful
@@ -22,7 +23,7 @@ class DevLauncherManifestParser(
22
23
  }
23
24
 
24
25
  private suspend fun downloadManifest(): Reader {
25
- val response = fetch(url, "GET").await(httpClient)
26
+ val response = fetch(url, "GET", getHeaders()).await(httpClient)
26
27
  if (!response.isSuccessful) {
27
28
  throw Exception("Failed to open app.\n\nIf you are trying to load the app from a development server, check your network connectivity and make sure you can access the server from your device.\n\nIf you are trying to open a published project, install a compatible version of expo-updates and follow all setup and integration steps.")
28
29
  }
@@ -35,4 +36,8 @@ class DevLauncherManifestParser(
35
36
  return Manifest.fromManifestJson(JSONObject(it.readText()))
36
37
  }
37
38
  }
39
+
40
+ private fun getHeaders(): Headers {
41
+ return Headers.of(mapOf("expo-platform" to "android"))
42
+ }
38
43
  }
@@ -91,6 +91,7 @@ typedef void (^CompletionHandler)(NSData *data, NSURLResponse *response);
91
91
  {
92
92
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url];
93
93
  [request setHTTPMethod:method];
94
+ [request setValue:@"ios" forHTTPHeaderField:@"expo-platform"];
94
95
  NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
95
96
  if (error) {
96
97
  onError(error);
@@ -117,6 +117,29 @@
117
117
  [self waitForExpectationsWithTimeout:5 handler:nil];
118
118
  }
119
119
 
120
+ - (void)testIsManifestURL_RequestIncludesPlatformHeader
121
+ {
122
+ [HTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
123
+ return [request.URL.host isEqualToString:@"ohhttpstubs"] && [request.URL.path isEqualToString:@"/platform"];
124
+ } withStubResponse:^HTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
125
+ XCTAssertEqualObjects(@"ios", request.allHTTPHeaderFields[@"expo-platform"]);
126
+ return [HTTPStubsResponse responseWithData:[NSData new] statusCode:200 headers:nil];
127
+ }];
128
+
129
+ EXDevLauncherManifestParser *parser = [[EXDevLauncherManifestParser alloc] initWithURL:[NSURL URLWithString:@"http://ohhttpstubs/platform"] session:NSURLSession.sharedSession];
130
+
131
+ XCTestExpectation *expectation = [self expectationWithDescription:@"request should include expo-platform header"];
132
+
133
+ [parser isManifestURLWithCompletion:^(BOOL isManifestURL) {
134
+ [expectation fulfill];
135
+ } onError:^(NSError * _Nonnull error) {
136
+ XCTFail(@"Response should have been successful");
137
+ [expectation fulfill];
138
+ }];
139
+
140
+ [self waitForExpectationsWithTimeout:5 handler:nil];
141
+ }
142
+
120
143
  - (void)testParseManifest
121
144
  {
122
145
  [HTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
@@ -0,0 +1,102 @@
1
+ {
2
+ "elementNames" : {
3
+ "altglyph" : "altGlyph",
4
+ "altglyphdef" : "altGlyphDef",
5
+ "altglyphitem" : "altGlyphItem",
6
+ "animatecolor" : "animateColor",
7
+ "animatemotion" : "animateMotion",
8
+ "animatetransform" : "animateTransform",
9
+ "clippath" : "clipPath",
10
+ "feblend" : "feBlend",
11
+ "fecolormatrix" : "feColorMatrix",
12
+ "fecomponenttransfer" : "feComponentTransfer",
13
+ "fecomposite" : "feComposite",
14
+ "feconvolvematrix" : "feConvolveMatrix",
15
+ "fediffuselighting" : "feDiffuseLighting",
16
+ "fedisplacementmap" : "feDisplacementMap",
17
+ "fedistantlight" : "feDistantLight",
18
+ "fedropshadow" : "feDropShadow",
19
+ "feflood" : "feFlood",
20
+ "fefunca" : "feFuncA",
21
+ "fefuncb" : "feFuncB",
22
+ "fefuncg" : "feFuncG",
23
+ "fefuncr" : "feFuncR",
24
+ "fegaussianblur" : "feGaussianBlur",
25
+ "feimage" : "feImage",
26
+ "femerge" : "feMerge",
27
+ "femergenode" : "feMergeNode",
28
+ "femorphology" : "feMorphology",
29
+ "feoffset" : "feOffset",
30
+ "fepointlight" : "fePointLight",
31
+ "fespecularlighting" : "feSpecularLighting",
32
+ "fespotlight" : "feSpotLight",
33
+ "fetile" : "feTile",
34
+ "feturbulence" : "feTurbulence",
35
+ "foreignobject" : "foreignObject",
36
+ "glyphref" : "glyphRef",
37
+ "lineargradient" : "linearGradient",
38
+ "radialgradient" : "radialGradient",
39
+ "textpath" : "textPath"
40
+ },
41
+ "attributeNames" : {
42
+ "definitionurl" : "definitionURL",
43
+ "attributename" : "attributeName",
44
+ "attributetype" : "attributeType",
45
+ "basefrequency" : "baseFrequency",
46
+ "baseprofile" : "baseProfile",
47
+ "calcmode" : "calcMode",
48
+ "clippathunits" : "clipPathUnits",
49
+ "diffuseconstant" : "diffuseConstant",
50
+ "edgemode" : "edgeMode",
51
+ "filterunits" : "filterUnits",
52
+ "glyphref" : "glyphRef",
53
+ "gradienttransform" : "gradientTransform",
54
+ "gradientunits" : "gradientUnits",
55
+ "kernelmatrix" : "kernelMatrix",
56
+ "kernelunitlength" : "kernelUnitLength",
57
+ "keypoints" : "keyPoints",
58
+ "keysplines" : "keySplines",
59
+ "keytimes" : "keyTimes",
60
+ "lengthadjust" : "lengthAdjust",
61
+ "limitingconeangle" : "limitingConeAngle",
62
+ "markerheight" : "markerHeight",
63
+ "markerunits" : "markerUnits",
64
+ "markerwidth" : "markerWidth",
65
+ "maskcontentunits" : "maskContentUnits",
66
+ "maskunits" : "maskUnits",
67
+ "numoctaves" : "numOctaves",
68
+ "pathlength" : "pathLength",
69
+ "patterncontentunits" : "patternContentUnits",
70
+ "patterntransform" : "patternTransform",
71
+ "patternunits" : "patternUnits",
72
+ "pointsatx" : "pointsAtX",
73
+ "pointsaty" : "pointsAtY",
74
+ "pointsatz" : "pointsAtZ",
75
+ "preservealpha" : "preserveAlpha",
76
+ "preserveaspectratio" : "preserveAspectRatio",
77
+ "primitiveunits" : "primitiveUnits",
78
+ "refx" : "refX",
79
+ "refy" : "refY",
80
+ "repeatcount" : "repeatCount",
81
+ "repeatdur" : "repeatDur",
82
+ "requiredextensions" : "requiredExtensions",
83
+ "requiredfeatures" : "requiredFeatures",
84
+ "specularconstant" : "specularConstant",
85
+ "specularexponent" : "specularExponent",
86
+ "spreadmethod" : "spreadMethod",
87
+ "startoffset" : "startOffset",
88
+ "stddeviation" : "stdDeviation",
89
+ "stitchtiles" : "stitchTiles",
90
+ "surfacescale" : "surfaceScale",
91
+ "systemlanguage" : "systemLanguage",
92
+ "tablevalues" : "tableValues",
93
+ "targetx" : "targetX",
94
+ "targety" : "targetY",
95
+ "textlength" : "textLength",
96
+ "viewbox" : "viewBox",
97
+ "viewtarget" : "viewTarget",
98
+ "xchannelselector" : "xChannelSelector",
99
+ "ychannelselector" : "yChannelSelector",
100
+ "zoomandpan" : "zoomAndPan"
101
+ }
102
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-dev-launcher",
3
3
  "title": "Expo Development Launcher",
4
- "version": "0.9.0",
4
+ "version": "0.9.1",
5
5
  "description": "Pre-release version of the Expo development launcher package for testing.",
6
6
  "main": "build/DevLauncher.js",
7
7
  "types": "build/DevLauncher.d.ts",
@@ -33,10 +33,10 @@
33
33
  "semver": "^7.3.5"
34
34
  },
35
35
  "devDependencies": {
36
- "babel-preset-expo": "~8.5.1",
36
+ "babel-preset-expo": "~9.0.0",
37
37
  "expo-module-scripts": "^2.0.0",
38
38
  "react": "17.0.1",
39
39
  "react-native": "0.64.3"
40
40
  },
41
- "gitHead": "33c06f161723dc5b6099b9d7eded686bd92c7bbf"
41
+ "gitHead": "58a32c58b6532ec0e14df10b772fa33f7657fbcc"
42
42
  }
@@ -8,7 +8,14 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const resolve_from_1 = __importDefault(require("resolve-from"));
10
10
  function resolveExpoUpdatesVersion(projectRoot) {
11
- const expoUpdatesBuildPath = (0, resolve_from_1.default)(projectRoot, 'expo-updates');
11
+ let expoUpdatesBuildPath;
12
+ try {
13
+ expoUpdatesBuildPath = (0, resolve_from_1.default)(projectRoot, 'expo-updates');
14
+ }
15
+ catch (e) {
16
+ // this is expected in projects that don't have expo-updates installed
17
+ return null;
18
+ }
12
19
  if (!expoUpdatesBuildPath) {
13
20
  return null;
14
21
  }
@@ -1,3 +1,4 @@
1
1
  import { ConfigPlugin } from '@expo/config-plugins';
2
+ export declare function modifyJavaMainActivity(content: string): string;
2
3
  declare const _default: ConfigPlugin<unknown>;
3
4
  export default _default;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.modifyJavaMainActivity = void 0;
6
7
  const config_plugins_1 = require("@expo/config-plugins");
7
8
  const fs_1 = __importDefault(require("fs"));
8
9
  const path_1 = __importDefault(require("path"));
@@ -14,16 +15,20 @@ const withDevLauncherAppDelegate_1 = require("./withDevLauncherAppDelegate");
14
15
  const pkg = require('expo-dev-launcher/package.json');
15
16
  const DEV_LAUNCHER_ANDROID_IMPORT = 'expo.modules.devlauncher.DevLauncherController';
16
17
  const DEV_LAUNCHER_UPDATES_ANDROID_IMPORT = 'expo.modules.updates.UpdatesDevLauncherController';
17
- const DEV_LAUNCHER_ON_NEW_INTENT = `
18
- @Override
19
- public void onNewIntent(Intent intent) {
20
- if (DevLauncherController.tryToHandleIntent(this, intent)) {
21
- return;
22
- }
23
- super.onNewIntent(intent);
24
- }
25
- `;
26
- const DEV_LAUNCHER_WRAPPED_ACTIVITY_DELEGATE = `DevLauncherController.wrapReactActivityDelegate(this, () -> $1);`;
18
+ const DEV_LAUNCHER_ON_NEW_INTENT = [
19
+ '',
20
+ ' @Override',
21
+ ' public void onNewIntent(Intent intent) {',
22
+ ' super.onNewIntent(intent);',
23
+ ' }',
24
+ '',
25
+ ].join('\n');
26
+ const DEV_LAUNCHER_HANDLE_INTENT = [
27
+ ' if (DevLauncherController.tryToHandleIntent(this, intent)) {',
28
+ ' return;',
29
+ ' }',
30
+ ].join('\n');
31
+ const DEV_LAUNCHER_WRAPPED_ACTIVITY_DELEGATE = (activityDelegateDeclaration) => `DevLauncherController.wrapReactActivityDelegate(this, () -> ${activityDelegateDeclaration})`;
27
32
  const DEV_LAUNCHER_ANDROID_INIT = 'DevLauncherController.initialize(this, getReactNativeHost());';
28
33
  const DEV_LAUNCHER_UPDATES_ANDROID_INIT = `if (BuildConfig.DEBUG) {
29
34
  DevLauncherController.getInstance().setUpdatesInterface(UpdatesDevLauncherController.initialize(this));
@@ -37,6 +42,26 @@ async function readFileAsync(path) {
37
42
  async function saveFileAsync(path, content) {
38
43
  return fs_1.default.promises.writeFile(path, content, 'utf8');
39
44
  }
45
+ function findClosingBracketMatchIndex(str, pos) {
46
+ if (str[pos] !== '(') {
47
+ throw new Error("No '(' at index " + pos);
48
+ }
49
+ let depth = 1;
50
+ for (let i = pos + 1; i < str.length; i++) {
51
+ switch (str[i]) {
52
+ case '(':
53
+ depth++;
54
+ break;
55
+ case ')':
56
+ if (--depth === 0) {
57
+ return i;
58
+ }
59
+ break;
60
+ }
61
+ }
62
+ return -1; // No matching closing parenthesis
63
+ }
64
+ const replaceBetween = (origin, startIndex, endIndex, insertion) => `${origin.substring(0, startIndex)}${insertion}${origin.substring(endIndex)}`;
40
65
  function addJavaImports(javaSource, javaImports) {
41
66
  const lines = javaSource.split('\n');
42
67
  const lineIndexWithPackageDeclaration = lines.findIndex((line) => line.match(/^package .*;$/));
@@ -110,23 +135,38 @@ const withDevLauncherApplication = (config) => {
110
135
  },
111
136
  ]);
112
137
  };
138
+ function modifyJavaMainActivity(content) {
139
+ content = addJavaImports(content, [DEV_LAUNCHER_ANDROID_IMPORT, 'android.content.Intent']);
140
+ if (!content.includes('onNewIntent')) {
141
+ const lines = content.split('\n');
142
+ const onCreateIndex = lines.findIndex((line) => line.includes('public class MainActivity'));
143
+ lines.splice(onCreateIndex + 1, 0, DEV_LAUNCHER_ON_NEW_INTENT);
144
+ content = lines.join('\n');
145
+ }
146
+ if (!content.includes(DEV_LAUNCHER_HANDLE_INTENT)) {
147
+ content = (0, utils_1.addLines)(content, /super\.onNewIntent\(intent\)/, 0, [DEV_LAUNCHER_HANDLE_INTENT]);
148
+ }
149
+ if (!content.includes('DevLauncherController.wrapReactActivityDelegate')) {
150
+ const activityDelegateMatches = Array.from(content.matchAll(/new ReactActivityDelegate(Wrapper)/g));
151
+ if (activityDelegateMatches.length !== 1) {
152
+ config_plugins_1.WarningAggregator.addWarningAndroid('expo-dev-launcher', `Failed to wrap 'ReactActivityDelegate'
153
+ See the expo-dev-client installation instructions to modify your MainActivity.java manually: ${constants_1.InstallationPage}`);
154
+ return content;
155
+ }
156
+ const activityDelegateMatch = activityDelegateMatches[0];
157
+ const matchIndex = activityDelegateMatch.index;
158
+ const openingBracketIndex = matchIndex + activityDelegateMatch[0].length; // next character after `new ReactActivityDelegateWrapper`
159
+ const closingBracketIndex = findClosingBracketMatchIndex(content, openingBracketIndex);
160
+ const reactActivityDelegateDeclaration = content.substring(matchIndex, closingBracketIndex + 1);
161
+ content = replaceBetween(content, matchIndex, closingBracketIndex + 1, DEV_LAUNCHER_WRAPPED_ACTIVITY_DELEGATE(reactActivityDelegateDeclaration));
162
+ }
163
+ return content;
164
+ }
165
+ exports.modifyJavaMainActivity = modifyJavaMainActivity;
113
166
  const withDevLauncherActivity = (config) => {
114
167
  return (0, config_plugins_1.withMainActivity)(config, (config) => {
115
168
  if (config.modResults.language === 'java') {
116
- let content = addJavaImports(config.modResults.contents, [
117
- DEV_LAUNCHER_ANDROID_IMPORT,
118
- 'android.content.Intent',
119
- ]);
120
- if (!content.includes(DEV_LAUNCHER_ON_NEW_INTENT)) {
121
- const lines = content.split('\n');
122
- const onCreateIndex = lines.findIndex((line) => line.includes('public class MainActivity'));
123
- lines.splice(onCreateIndex + 1, 0, DEV_LAUNCHER_ON_NEW_INTENT);
124
- content = lines.join('\n');
125
- }
126
- if (!content.includes('DevLauncherController.wrapReactActivityDelegate')) {
127
- content = content.replace(/(new ReactActivityDelegate(Wrapper)?(.|\s)*\}\)?);$/mu, DEV_LAUNCHER_WRAPPED_ACTIVITY_DELEGATE);
128
- }
129
- config.modResults.contents = content;
169
+ config.modResults.contents = modifyJavaMainActivity(config.modResults.contents);
130
170
  }
131
171
  else {
132
172
  config_plugins_1.WarningAggregator.addWarningAndroid('expo-dev-launcher', `Cannot automatically configure MainActivity if it's not java.
@@ -91,6 +91,16 @@ const DEV_LAUNCHER_INIT_TO_REMOVE = new RegExp(escapeRegExpCharacters(`RCTBridge
91
91
  rootView.backgroundColor = [UIColor whiteColor];
92
92
  }
93
93
 
94
+ self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
95
+ UIViewController *rootViewController = `) +
96
+ `([^;]+)` +
97
+ escapeRegExpCharacters(`;
98
+ rootViewController.view = rootView;
99
+ self.window.rootViewController = rootViewController;
100
+ [self.window makeKeyAndVisible];`), 'm');
101
+ const DEV_LAUNCHER_INIT_TO_REMOVE_SDK_44 = new RegExp(escapeRegExpCharacters(`RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions];
102
+ RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
103
+ rootView.backgroundColor = [UIColor whiteColor];
94
104
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
95
105
  UIViewController *rootViewController = `) +
96
106
  `([^;]+)` +
@@ -147,6 +157,20 @@ const DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION = (viewContro
147
157
  return bridge;
148
158
  }
149
159
  `;
160
+ const DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_SDK_44 = `
161
+ - (RCTBridge *)initializeReactNativeApp:(NSDictionary *)launchOptions
162
+ {
163
+ RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions];
164
+ RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
165
+ rootView.backgroundColor = [UIColor whiteColor];
166
+ UIViewController *rootViewController = [self.reactDelegate createRootViewController];
167
+ rootViewController.view = rootView;
168
+ self.window.rootViewController = rootViewController;
169
+ [self.window makeKeyAndVisible];
170
+
171
+ return bridge;
172
+ }
173
+ `;
150
174
  function addImports(appDelegate, shouldAddUpdatesIntegration) {
151
175
  if (!appDelegate.includes(DEV_LAUNCHER_APP_DELEGATE_IOS_IMPORT) &&
152
176
  !appDelegate.includes(DEV_LAUNCHER_UPDATES_APP_DELEGATE_IOS_IMPORT)) {
@@ -202,18 +226,29 @@ function modifyLegacyAppDelegate(appDelegate, expoUpdatesVersion = null) {
202
226
  exports.modifyLegacyAppDelegate = modifyLegacyAppDelegate;
203
227
  function modifyAppDelegate(appDelegate, expoUpdatesVersion = null) {
204
228
  const shouldAddUpdatesIntegration = expoUpdatesVersion != null && semver_1.default.gt(expoUpdatesVersion, '0.6.0');
205
- if (!DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_REGEX.test(appDelegate)) {
206
- if (DEV_LAUNCHER_INIT_TO_REMOVE.test(appDelegate)) {
229
+ if (!DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_REGEX.test(appDelegate) &&
230
+ !appDelegate.includes(DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_SDK_44)) {
231
+ let initToRemove;
232
+ let shouldAddSDK44Init = false;
233
+ if (DEV_LAUNCHER_INIT_TO_REMOVE_SDK_44.test(appDelegate)) {
234
+ initToRemove = DEV_LAUNCHER_INIT_TO_REMOVE_SDK_44;
235
+ shouldAddSDK44Init = true;
236
+ }
237
+ else if (DEV_LAUNCHER_INIT_TO_REMOVE.test(appDelegate)) {
238
+ initToRemove = DEV_LAUNCHER_INIT_TO_REMOVE;
239
+ }
240
+ if (initToRemove) {
207
241
  // UIViewController can be initialized differently depending on whether expo-screen-orientation is installed,
208
242
  // so we need to preserve whatever is there already.
209
243
  let viewControllerInit;
210
- appDelegate = appDelegate.replace(DEV_LAUNCHER_INIT_TO_REMOVE, (match, p1) => {
244
+ appDelegate = appDelegate.replace(initToRemove, (match, p1) => {
211
245
  viewControllerInit = p1;
212
246
  return DEV_LAUNCHER_NEW_INIT;
213
247
  });
214
- appDelegate = (0, utils_1.addLines)(appDelegate, '@implementation AppDelegate', 1, [
215
- DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION(viewControllerInit),
216
- ]);
248
+ const initToAdd = shouldAddSDK44Init
249
+ ? DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_SDK_44
250
+ : DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION(viewControllerInit);
251
+ appDelegate = (0, utils_1.addLines)(appDelegate, '@implementation AppDelegate', 1, [initToAdd]);
217
252
  }
218
253
  else {
219
254
  config_plugins_1.WarningAggregator.addWarningIOS('expo-dev-launcher', `Failed to modify AppDelegate init function.
@@ -3,7 +3,13 @@ import path from 'path';
3
3
  import resolveFrom from 'resolve-from';
4
4
 
5
5
  export function resolveExpoUpdatesVersion(projectRoot: string): string | null {
6
- const expoUpdatesBuildPath = resolveFrom(projectRoot, 'expo-updates');
6
+ let expoUpdatesBuildPath;
7
+ try {
8
+ expoUpdatesBuildPath = resolveFrom(projectRoot, 'expo-updates');
9
+ } catch (e) {
10
+ // this is expected in projects that don't have expo-updates installed
11
+ return null;
12
+ }
7
13
  if (!expoUpdatesBuildPath) {
8
14
  return null;
9
15
  }
@@ -20,16 +20,21 @@ const pkg = require('expo-dev-launcher/package.json');
20
20
 
21
21
  const DEV_LAUNCHER_ANDROID_IMPORT = 'expo.modules.devlauncher.DevLauncherController';
22
22
  const DEV_LAUNCHER_UPDATES_ANDROID_IMPORT = 'expo.modules.updates.UpdatesDevLauncherController';
23
- const DEV_LAUNCHER_ON_NEW_INTENT = `
24
- @Override
25
- public void onNewIntent(Intent intent) {
26
- if (DevLauncherController.tryToHandleIntent(this, intent)) {
27
- return;
28
- }
29
- super.onNewIntent(intent);
30
- }
31
- `;
32
- const DEV_LAUNCHER_WRAPPED_ACTIVITY_DELEGATE = `DevLauncherController.wrapReactActivityDelegate(this, () -> $1);`;
23
+ const DEV_LAUNCHER_ON_NEW_INTENT = [
24
+ '',
25
+ ' @Override',
26
+ ' public void onNewIntent(Intent intent) {',
27
+ ' super.onNewIntent(intent);',
28
+ ' }',
29
+ '',
30
+ ].join('\n');
31
+ const DEV_LAUNCHER_HANDLE_INTENT = [
32
+ ' if (DevLauncherController.tryToHandleIntent(this, intent)) {',
33
+ ' return;',
34
+ ' }',
35
+ ].join('\n');
36
+ const DEV_LAUNCHER_WRAPPED_ACTIVITY_DELEGATE = (activityDelegateDeclaration: string) =>
37
+ `DevLauncherController.wrapReactActivityDelegate(this, () -> ${activityDelegateDeclaration})`;
33
38
  const DEV_LAUNCHER_ANDROID_INIT = 'DevLauncherController.initialize(this, getReactNativeHost());';
34
39
  const DEV_LAUNCHER_UPDATES_ANDROID_INIT = `if (BuildConfig.DEBUG) {
35
40
  DevLauncherController.getInstance().setUpdatesInterface(UpdatesDevLauncherController.initialize(this));
@@ -48,6 +53,29 @@ async function saveFileAsync(path: string, content: string): Promise<void> {
48
53
  return fs.promises.writeFile(path, content, 'utf8');
49
54
  }
50
55
 
56
+ function findClosingBracketMatchIndex(str: string, pos: number) {
57
+ if (str[pos] !== '(') {
58
+ throw new Error("No '(' at index " + pos);
59
+ }
60
+ let depth = 1;
61
+ for (let i = pos + 1; i < str.length; i++) {
62
+ switch (str[i]) {
63
+ case '(':
64
+ depth++;
65
+ break;
66
+ case ')':
67
+ if (--depth === 0) {
68
+ return i;
69
+ }
70
+ break;
71
+ }
72
+ }
73
+ return -1; // No matching closing parenthesis
74
+ }
75
+
76
+ const replaceBetween = (origin: string, startIndex: number, endIndex: number, insertion: string) =>
77
+ `${origin.substring(0, startIndex)}${insertion}${origin.substring(endIndex)}`;
78
+
51
79
  function addJavaImports(javaSource: string, javaImports: string[]): string {
52
80
  const lines = javaSource.split('\n');
53
81
  const lineIndexWithPackageDeclaration = lines.findIndex((line) => line.match(/^package .*;$/));
@@ -153,31 +181,56 @@ const withDevLauncherApplication: ConfigPlugin = (config) => {
153
181
  ]);
154
182
  };
155
183
 
156
- const withDevLauncherActivity: ConfigPlugin = (config) => {
157
- return withMainActivity(config, (config) => {
158
- if (config.modResults.language === 'java') {
159
- let content = addJavaImports(config.modResults.contents, [
160
- DEV_LAUNCHER_ANDROID_IMPORT,
161
- 'android.content.Intent',
162
- ]);
184
+ export function modifyJavaMainActivity(content: string): string {
185
+ content = addJavaImports(content, [DEV_LAUNCHER_ANDROID_IMPORT, 'android.content.Intent']);
163
186
 
164
- if (!content.includes(DEV_LAUNCHER_ON_NEW_INTENT)) {
165
- const lines = content.split('\n');
166
- const onCreateIndex = lines.findIndex((line) => line.includes('public class MainActivity'));
187
+ if (!content.includes('onNewIntent')) {
188
+ const lines = content.split('\n');
189
+ const onCreateIndex = lines.findIndex((line) => line.includes('public class MainActivity'));
167
190
 
168
- lines.splice(onCreateIndex + 1, 0, DEV_LAUNCHER_ON_NEW_INTENT);
191
+ lines.splice(onCreateIndex + 1, 0, DEV_LAUNCHER_ON_NEW_INTENT);
169
192
 
170
- content = lines.join('\n');
171
- }
193
+ content = lines.join('\n');
194
+ }
195
+ if (!content.includes(DEV_LAUNCHER_HANDLE_INTENT)) {
196
+ content = addLines(content, /super\.onNewIntent\(intent\)/, 0, [DEV_LAUNCHER_HANDLE_INTENT]);
197
+ }
172
198
 
173
- if (!content.includes('DevLauncherController.wrapReactActivityDelegate')) {
174
- content = content.replace(
175
- /(new ReactActivityDelegate(Wrapper)?(.|\s)*\}\)?);$/mu,
176
- DEV_LAUNCHER_WRAPPED_ACTIVITY_DELEGATE
177
- );
178
- }
199
+ if (!content.includes('DevLauncherController.wrapReactActivityDelegate')) {
200
+ const activityDelegateMatches = Array.from(
201
+ content.matchAll(/new ReactActivityDelegate(Wrapper)/g)
202
+ );
203
+
204
+ if (activityDelegateMatches.length !== 1) {
205
+ WarningAggregator.addWarningAndroid(
206
+ 'expo-dev-launcher',
207
+ `Failed to wrap 'ReactActivityDelegate'
208
+ See the expo-dev-client installation instructions to modify your MainActivity.java manually: ${InstallationPage}`
209
+ );
210
+ return content;
211
+ }
212
+
213
+ const activityDelegateMatch = activityDelegateMatches[0];
214
+ const matchIndex = activityDelegateMatch.index!;
215
+ const openingBracketIndex = matchIndex + activityDelegateMatch[0].length; // next character after `new ReactActivityDelegateWrapper`
216
+
217
+ const closingBracketIndex = findClosingBracketMatchIndex(content, openingBracketIndex);
218
+ const reactActivityDelegateDeclaration = content.substring(matchIndex, closingBracketIndex + 1);
179
219
 
180
- config.modResults.contents = content;
220
+ content = replaceBetween(
221
+ content,
222
+ matchIndex,
223
+ closingBracketIndex + 1,
224
+ DEV_LAUNCHER_WRAPPED_ACTIVITY_DELEGATE(reactActivityDelegateDeclaration)
225
+ );
226
+ }
227
+ return content;
228
+ }
229
+
230
+ const withDevLauncherActivity: ConfigPlugin = (config) => {
231
+ return withMainActivity(config, (config) => {
232
+ if (config.modResults.language === 'java') {
233
+ config.modResults.contents = modifyJavaMainActivity(config.modResults.contents);
181
234
  } else {
182
235
  WarningAggregator.addWarningAndroid(
183
236
  'expo-dev-launcher',
@@ -105,6 +105,20 @@ const DEV_LAUNCHER_INIT_TO_REMOVE = new RegExp(
105
105
  'm'
106
106
  );
107
107
 
108
+ const DEV_LAUNCHER_INIT_TO_REMOVE_SDK_44 = new RegExp(
109
+ escapeRegExpCharacters(`RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions];
110
+ RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
111
+ rootView.backgroundColor = [UIColor whiteColor];
112
+ self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
113
+ UIViewController *rootViewController = `) +
114
+ `([^;]+)` +
115
+ escapeRegExpCharacters(`;
116
+ rootViewController.view = rootView;
117
+ self.window.rootViewController = rootViewController;
118
+ [self.window makeKeyAndVisible];`),
119
+ 'm'
120
+ );
121
+
108
122
  const DEV_LAUNCHER_NEW_INIT = `self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
109
123
  #if defined(EX_DEV_LAUNCHER_ENABLED)
110
124
  EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
@@ -162,6 +176,21 @@ const DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION = (
162
176
  }
163
177
  `;
164
178
 
179
+ const DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_SDK_44 = `
180
+ - (RCTBridge *)initializeReactNativeApp:(NSDictionary *)launchOptions
181
+ {
182
+ RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions];
183
+ RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
184
+ rootView.backgroundColor = [UIColor whiteColor];
185
+ UIViewController *rootViewController = [self.reactDelegate createRootViewController];
186
+ rootViewController.view = rootView;
187
+ self.window.rootViewController = rootViewController;
188
+ [self.window makeKeyAndVisible];
189
+
190
+ return bridge;
191
+ }
192
+ `;
193
+
165
194
  function addImports(appDelegate: string, shouldAddUpdatesIntegration: boolean): string {
166
195
  if (
167
196
  !appDelegate.includes(DEV_LAUNCHER_APP_DELEGATE_IOS_IMPORT) &&
@@ -259,18 +288,31 @@ export function modifyAppDelegate(appDelegate: string, expoUpdatesVersion: strin
259
288
  const shouldAddUpdatesIntegration =
260
289
  expoUpdatesVersion != null && semver.gt(expoUpdatesVersion, '0.6.0');
261
290
 
262
- if (!DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_REGEX.test(appDelegate)) {
263
- if (DEV_LAUNCHER_INIT_TO_REMOVE.test(appDelegate)) {
291
+ if (
292
+ !DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_REGEX.test(appDelegate) &&
293
+ !appDelegate.includes(DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_SDK_44)
294
+ ) {
295
+ let initToRemove;
296
+ let shouldAddSDK44Init = false;
297
+ if (DEV_LAUNCHER_INIT_TO_REMOVE_SDK_44.test(appDelegate)) {
298
+ initToRemove = DEV_LAUNCHER_INIT_TO_REMOVE_SDK_44;
299
+ shouldAddSDK44Init = true;
300
+ } else if (DEV_LAUNCHER_INIT_TO_REMOVE.test(appDelegate)) {
301
+ initToRemove = DEV_LAUNCHER_INIT_TO_REMOVE;
302
+ }
303
+
304
+ if (initToRemove) {
264
305
  // UIViewController can be initialized differently depending on whether expo-screen-orientation is installed,
265
306
  // so we need to preserve whatever is there already.
266
307
  let viewControllerInit;
267
- appDelegate = appDelegate.replace(DEV_LAUNCHER_INIT_TO_REMOVE, (match, p1) => {
308
+ appDelegate = appDelegate.replace(initToRemove, (match, p1) => {
268
309
  viewControllerInit = p1;
269
310
  return DEV_LAUNCHER_NEW_INIT;
270
311
  });
271
- appDelegate = addLines(appDelegate, '@implementation AppDelegate', 1, [
272
- DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION(viewControllerInit),
273
- ]);
312
+ const initToAdd = shouldAddSDK44Init
313
+ ? DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION_SDK_44
314
+ : DEV_LAUNCHER_INITIALIZE_REACT_NATIVE_APP_FUNCTION_DEFINITION(viewControllerInit);
315
+ appDelegate = addLines(appDelegate, '@implementation AppDelegate', 1, [initToAdd]);
274
316
  } else {
275
317
  WarningAggregator.addWarningIOS(
276
318
  'expo-dev-launcher',