expo-web-browser 10.1.1 → 11.0.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/README.md +3 -3
  3. package/android/build.gradle +30 -10
  4. package/android/src/main/java/expo/modules/webbrowser/CustomTabsActivitiesHelper.kt +123 -0
  5. package/android/src/main/java/expo/modules/webbrowser/CustomTabsConnectionHelper.kt +97 -0
  6. package/android/src/main/java/expo/modules/webbrowser/DeferredClientActionsQueue.kt +45 -0
  7. package/android/src/main/java/expo/modules/webbrowser/WebBrowserExceptions.kt +13 -0
  8. package/android/src/main/java/expo/modules/webbrowser/WebBrowserModule.kt +170 -0
  9. package/android/src/main/java/expo/modules/webbrowser/WebBrowserOptions.kt +15 -0
  10. package/build/ExpoWebBrowser.d.ts +1 -0
  11. package/build/ExpoWebBrowser.d.ts.map +1 -0
  12. package/build/ExpoWebBrowser.web.d.ts +1 -0
  13. package/build/ExpoWebBrowser.web.d.ts.map +1 -0
  14. package/build/ExpoWebBrowser.web.js +11 -11
  15. package/build/ExpoWebBrowser.web.js.map +1 -1
  16. package/build/WebBrowser.d.ts +7 -5
  17. package/build/WebBrowser.d.ts.map +1 -0
  18. package/build/WebBrowser.js +21 -19
  19. package/build/WebBrowser.js.map +1 -1
  20. package/build/WebBrowser.types.d.ts +65 -0
  21. package/build/WebBrowser.types.d.ts.map +1 -0
  22. package/build/WebBrowser.types.js +44 -0
  23. package/build/WebBrowser.types.js.map +1 -1
  24. package/expo-module.config.json +9 -0
  25. package/ios/{EXWebBrowser.podspec → ExpoWebBrowser.podspec} +10 -3
  26. package/ios/WebAuthSession.swift +59 -0
  27. package/ios/WebBrowserExceptions.swift +9 -0
  28. package/ios/WebBrowserModule.swift +53 -0
  29. package/ios/WebBrowserOptions.swift +86 -0
  30. package/ios/WebBrowserSession.swift +63 -0
  31. package/package.json +2 -2
  32. package/src/ExpoWebBrowser.web.ts +14 -14
  33. package/src/WebBrowser.ts +29 -21
  34. package/src/WebBrowser.types.ts +67 -0
  35. package/tsconfig.json +1 -1
  36. package/android/src/main/java/expo/modules/webbrowser/CustomTabsActivitiesHelper.java +0 -33
  37. package/android/src/main/java/expo/modules/webbrowser/CustomTabsConnectionHelper.java +0 -13
  38. package/android/src/main/java/expo/modules/webbrowser/DeferredClientActionsQueue.java +0 -50
  39. package/android/src/main/java/expo/modules/webbrowser/InternalCustomTabsActivitiesHelper.java +0 -127
  40. package/android/src/main/java/expo/modules/webbrowser/InternalCustomTabsConnectionHelper.java +0 -126
  41. package/android/src/main/java/expo/modules/webbrowser/WebBrowserModule.java +0 -218
  42. package/android/src/main/java/expo/modules/webbrowser/WebBrowserPackage.java +0 -27
  43. package/android/src/main/java/expo/modules/webbrowser/error/NoPreferredPackageFound.java +0 -15
  44. package/android/src/main/java/expo/modules/webbrowser/error/PackageManagerNotFoundException.java +0 -10
  45. package/ios/EXWebBrowser/EXWebBrowser.h +0 -9
  46. package/ios/EXWebBrowser/EXWebBrowser.m +0 -280
  47. package/unimodule.json +0 -4
package/CHANGELOG.md CHANGED
@@ -10,7 +10,43 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
- ## 10.1.1 — 2022-02-01
13
+ ## 11.0.0 — 2022-07-07
14
+
15
+ ### 🎉 New features
16
+
17
+ - Native module on Android is now written in Kotlin using the new API. ([#17454](https://github.com/expo/expo/pull/17454) by [@barthap](https://github.com/barthap))
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - Fixed `removeListener(): Method has been deprecated` warning. ([#17645](https://github.com/expo/expo/pull/17645) by [@barthap](https://github.com/barthap))
22
+ - Fixed `service not registered` exception on Android. ([#17855](https://github.com/expo/expo/pull/17855) by [@lukmccall](https://github.com/lukmccall))
23
+ - Fixed `redirectUrl` auth session argument to be optional and thus match documentation. ([#17953](https://github.com/expo/expo/pull/17953) by [@barthap](https://github.com/barthap))
24
+ - Fixed `windowFeatures` property not being properly recognized on web. ([#18106](https://github.com/expo/expo/pull/18106) by [@barthap](https://github.com/barthap))
25
+
26
+ ### 💡 Others
27
+
28
+ - Migrated Expo modules definitions to the new naming convention. ([#17193](https://github.com/expo/expo/pull/17193) by [@tsapeta](https://github.com/tsapeta))
29
+ - Rewritten Android code to Kotlin. ([#17195](https://github.com/expo/expo/pull/17195) by [@barthap](https://github.com/barthap))
30
+
31
+ ## 10.2.1 — 2022-05-24
32
+
33
+ ### 🐛 Bug fixes
34
+
35
+ - On Web fix popup being blocked by Safari. ([#17222](https://github.com/expo/expo/pull/17222) by [@sreuter](https://github.com/sreuter))
36
+
37
+ ## 10.2.0 — 2022-04-18
38
+
39
+ ### 🎉 New features
40
+
41
+ - Native module on iOS is now written in Swift using the new API. ([#16201](https://github.com/expo/expo/pull/16201) by [@tsapeta](https://github.com/tsapeta))
42
+ - Add `presentationStyle` option to customize browser window appearance on iOS. ([#16919](https://github.com/expo/expo/pull/16919) by [@barthap](https://github.com/barthap))
43
+ - Add `preferEphemeralSession` option to `openAuthSessionAsync` to ask for a private auth session on iOS. ([#16926](https://github.com/expo/expo/pull/16926) by [@barthap](https://github.com/barthap))
44
+
45
+ ### ⚠️ Notices
46
+
47
+ - On Android bump `compileSdkVersion` to `31`, `targetSdkVersion` to `31` and `Java` version to `11`. ([#16941](https://github.com/expo/expo/pull/16941) by [@bbarthec](https://github.com/bbarthec))
48
+
49
+ ## 10.1.1 - 2022-02-01
14
50
 
15
51
  ### 🐛 Bug fixes
16
52
 
package/README.md CHANGED
@@ -4,12 +4,12 @@ Provides access to the system's web browser and supports handling redirects. On
4
4
 
5
5
  # API documentation
6
6
 
7
- - [Documentation for the master branch](https://github.com/expo/expo/blob/master/docs/pages/versions/unversioned/sdk/webbrowser.md)
8
- - [Documentation for the latest stable release](https://docs.expo.io/versions/latest/sdk/webbrowser/)
7
+ - [Documentation for the main branch](https://github.com/expo/expo/blob/main/docs/pages/versions/unversioned/sdk/webbrowser.md)
8
+ - [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/webbrowser/)
9
9
 
10
10
  # Installation in managed Expo projects
11
11
 
12
- For managed [managed](https://docs.expo.io/versions/latest/introduction/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.io/versions/latest/sdk/webbrowser/).
12
+ For [managed](https://docs.expo.dev/versions/latest/introduction/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/webbrowser/).
13
13
 
14
14
  # Installation in bare React Native projects
15
15
 
@@ -3,21 +3,36 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven-publish'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '10.1.1'
6
+ version = '11.0.0'
7
7
 
8
8
 
9
9
  buildscript {
10
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
11
+ if (expoModulesCorePlugin.exists()) {
12
+ apply from: expoModulesCorePlugin
13
+ applyKotlinExpoModulesCorePlugin()
14
+ }
15
+
10
16
  // Simple helper that allows the root project to override versions declared by this library.
11
17
  ext.safeExtGet = { prop, fallback ->
12
18
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
13
19
  }
14
20
 
21
+ // Ensures backward compatibility
22
+ ext.getKotlinVersion = {
23
+ if (ext.has("kotlinVersion")) {
24
+ ext.kotlinVersion()
25
+ } else {
26
+ ext.safeExtGet("kotlinVersion", "1.6.10")
27
+ }
28
+ }
29
+
15
30
  repositories {
16
31
  mavenCentral()
17
32
  }
18
33
 
19
34
  dependencies {
20
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.4.21')}")
35
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
21
36
  }
22
37
  }
23
38
 
@@ -45,18 +60,22 @@ afterEvaluate {
45
60
  }
46
61
 
47
62
  android {
48
- compileSdkVersion safeExtGet("compileSdkVersion", 30)
63
+ compileSdkVersion safeExtGet("compileSdkVersion", 31)
49
64
 
50
65
  compileOptions {
51
- sourceCompatibility JavaVersion.VERSION_1_8
52
- targetCompatibility JavaVersion.VERSION_1_8
66
+ sourceCompatibility JavaVersion.VERSION_11
67
+ targetCompatibility JavaVersion.VERSION_11
68
+ }
69
+
70
+ kotlinOptions {
71
+ jvmTarget = JavaVersion.VERSION_11.majorVersion
53
72
  }
54
73
 
55
74
  defaultConfig {
56
75
  minSdkVersion safeExtGet("minSdkVersion", 21)
57
- targetSdkVersion safeExtGet("targetSdkVersion", 30)
76
+ targetSdkVersion safeExtGet("targetSdkVersion", 31)
58
77
  versionCode 18
59
- versionName '10.1.1'
78
+ versionName '11.0.0'
60
79
  }
61
80
  lintOptions {
62
81
  abortOnError false
@@ -68,10 +87,11 @@ dependencies {
68
87
 
69
88
  api "androidx.browser:browser:1.2.0"
70
89
 
71
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.4.21')}"
90
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
91
+ implementation "androidx.core:core-ktx:1.7.0"
72
92
 
73
- if (project.findProject(':unimodules-test-core')) {
74
- testImplementation project(':unimodules-test-core')
93
+ if (project.findProject(':expo-modules-test-core')) {
94
+ testImplementation project(':expo-modules-test-core')
75
95
  }
76
96
  testImplementation "org.robolectric:robolectric:4.3.1"
77
97
  }
@@ -0,0 +1,123 @@
1
+ package expo.modules.webbrowser
2
+
3
+ import android.app.Activity
4
+ import expo.modules.core.errors.CurrentActivityNotFoundException
5
+ import android.content.pm.ResolveInfo
6
+ import android.content.Intent
7
+ import android.content.pm.PackageManager
8
+ import android.net.Uri
9
+ import androidx.browser.customtabs.CustomTabsClient
10
+ import androidx.browser.customtabs.CustomTabsIntent
11
+ import androidx.browser.customtabs.CustomTabsService
12
+ import expo.modules.core.interfaces.ActivityProvider
13
+ import java.util.ArrayList
14
+ import java.util.LinkedHashSet
15
+
16
+ private const val DUMMY_URL = "https://expo.dev"
17
+
18
+ internal class CustomTabsActivitiesHelper(
19
+ private val activityProvider: ActivityProvider?
20
+ ) {
21
+
22
+ // region Actual custom tabs activities helper methods
23
+
24
+ /**
25
+ * @throws CurrentActivityNotFoundException
26
+ * @throws PackageManagerNotFoundException
27
+ */
28
+ fun canResolveIntent(intent: Intent): Boolean = getResolvingActivities(intent).isNotEmpty()
29
+
30
+ /**
31
+ * @throws PackageManagerNotFoundException
32
+ * @throws CurrentActivityNotFoundException
33
+ */
34
+ val customTabsResolvingActivities: ArrayList<String>
35
+ get() = getResolvingActivities(createDefaultCustomTabsIntent())
36
+ .mapToDistinctArrayList { resolveInfo: ResolveInfo ->
37
+ resolveInfo.activityInfo.packageName
38
+ }
39
+
40
+ /**
41
+ * @throws PackageManagerNotFoundException
42
+ * @throws CurrentActivityNotFoundException
43
+ */
44
+ val customTabsResolvingServices: ArrayList<String>
45
+ get() = packageManager.queryIntentServices(createDefaultCustomTabsServiceIntent(), 0)
46
+ .mapToDistinctArrayList { resolveInfo: ResolveInfo ->
47
+ resolveInfo.serviceInfo.packageName
48
+ }
49
+
50
+ /**
51
+ * @throws PackageManagerNotFoundException
52
+ * @throws CurrentActivityNotFoundException
53
+ */
54
+ fun getPreferredCustomTabsResolvingActivity(packages: List<String?>?): String? {
55
+ val resolvedPackages = packages ?: customTabsResolvingActivities
56
+ return CustomTabsClient.getPackageName(currentActivity, resolvedPackages)
57
+ }
58
+
59
+ /**
60
+ * @throws PackageManagerNotFoundException
61
+ * @throws CurrentActivityNotFoundException
62
+ */
63
+ val defaultCustomTabsResolvingActivity: String?
64
+ get() {
65
+ val info = packageManager.resolveActivity(createDefaultCustomTabsIntent(), 0)
66
+ return info?.activityInfo?.packageName
67
+ }
68
+
69
+ /**
70
+ * @throws CurrentActivityNotFoundException
71
+ */
72
+ fun startCustomTabs(intent: Intent) {
73
+ currentActivity.startActivity(intent)
74
+ }
75
+
76
+ // endregion
77
+
78
+ // region Private helpers
79
+
80
+ /**
81
+ * @throws CurrentActivityNotFoundException
82
+ * @throws PackageManagerNotFoundException
83
+ */
84
+ private fun getResolvingActivities(intent: Intent): List<ResolveInfo> {
85
+ return packageManager.queryIntentActivities(intent, 0)
86
+ }
87
+
88
+ /**
89
+ * @throws CurrentActivityNotFoundException
90
+ * @throws PackageManagerNotFoundException
91
+ */
92
+ private val packageManager: PackageManager
93
+ get() = currentActivity.packageManager ?: throw PackageManagerNotFoundException()
94
+
95
+ /**
96
+ * @throws CurrentActivityNotFoundException
97
+ */
98
+ private val currentActivity: Activity
99
+ get() {
100
+ return activityProvider?.currentActivity ?: throw CurrentActivityNotFoundException()
101
+ }
102
+
103
+ // endregion
104
+ }
105
+
106
+ private inline fun <T, R> Collection<T>.mapToDistinctArrayList(mapper: (T) -> R): ArrayList<R> {
107
+ val resultSet = LinkedHashSet<R>()
108
+ for (element in this) {
109
+ resultSet.add(mapper.invoke(element))
110
+ }
111
+ return ArrayList(resultSet)
112
+ }
113
+
114
+ private fun createDefaultCustomTabsIntent(): Intent {
115
+ val customTabsIntent = CustomTabsIntent.Builder().build()
116
+ return customTabsIntent.intent.apply {
117
+ data = Uri.parse(DUMMY_URL)
118
+ }
119
+ }
120
+
121
+ private fun createDefaultCustomTabsServiceIntent() = Intent().apply {
122
+ action = CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION
123
+ }
@@ -0,0 +1,97 @@
1
+ package expo.modules.webbrowser
2
+
3
+ import android.content.ComponentName
4
+ import android.content.Context
5
+ import android.net.Uri
6
+ import androidx.browser.customtabs.CustomTabsClient
7
+ import androidx.browser.customtabs.CustomTabsServiceConnection
8
+ import androidx.browser.customtabs.CustomTabsSession
9
+
10
+ internal class CustomTabsConnectionHelper(
11
+ private val context: Context
12
+ ) : CustomTabsServiceConnection() {
13
+ private var currentPackageName: String? = null
14
+ private val clientActions = DeferredClientActionsQueue<CustomTabsClient>()
15
+ private val sessionActions = DeferredClientActionsQueue<CustomTabsSession?>()
16
+
17
+ // region lifecycle methods
18
+ fun destroy() = clearConnection()
19
+ // endregion
20
+
21
+ // region Actual connection helper methods
22
+ fun warmUp(packageName: String) {
23
+ clientActions.executeOrQueueAction { client: CustomTabsClient -> client.warmup(0) }
24
+ ensureConnection(packageName)
25
+ }
26
+
27
+ fun mayInitWithUrl(packageName: String, uri: Uri) {
28
+ sessionActions.executeOrQueueAction { session: CustomTabsSession? ->
29
+ session?.mayLaunchUrl(uri, null, null)
30
+ }
31
+ ensureConnection(packageName)
32
+ ensureSession()
33
+ }
34
+
35
+ fun coolDown(packageName: String): Boolean {
36
+ if (isConnectionStarted(packageName)) {
37
+ clearConnection()
38
+ return true
39
+ }
40
+ return false
41
+ }
42
+ // endregion
43
+
44
+ // region CustomTabsServiceConnection implementation
45
+ override fun onBindingDied(componentName: ComponentName) {
46
+ if (isConnectionStarted(componentName.packageName)) {
47
+ clearConnection()
48
+ }
49
+ }
50
+
51
+ override fun onCustomTabsServiceConnected(componentName: ComponentName, client: CustomTabsClient) {
52
+ if (isConnectionStarted(componentName.packageName)) {
53
+ clientActions.setClient(client)
54
+ }
55
+ }
56
+
57
+ override fun onServiceDisconnected(componentName: ComponentName) {
58
+ if (isConnectionStarted(componentName.packageName)) {
59
+ clearConnection()
60
+ }
61
+ }
62
+ // endregion
63
+
64
+ private fun ensureSession() {
65
+ if (sessionActions.hasClient()) {
66
+ return
67
+ }
68
+
69
+ clientActions.executeOrQueueAction { client: CustomTabsClient ->
70
+ sessionActions.setClient(client.newSession(null))
71
+ }
72
+ }
73
+
74
+ private fun ensureConnection(packageName: String) {
75
+ if (currentPackageName != null && currentPackageName != packageName) {
76
+ clearConnection()
77
+ }
78
+ if (!isConnectionStarted(packageName)) {
79
+ CustomTabsClient.bindCustomTabsService(context, packageName, this)
80
+ currentPackageName = packageName
81
+ }
82
+ }
83
+
84
+ private fun isConnectionStarted(packageName: String): Boolean {
85
+ return packageName == currentPackageName
86
+ }
87
+
88
+ private fun clearConnection() {
89
+ if (currentPackageName != null) {
90
+ context.unbindService(this)
91
+ }
92
+
93
+ currentPackageName = null
94
+ clientActions.clear()
95
+ sessionActions.clear()
96
+ }
97
+ }
@@ -0,0 +1,45 @@
1
+ package expo.modules.webbrowser
2
+
3
+ import expo.modules.core.interfaces.Consumer
4
+ import java.util.*
5
+
6
+ class DeferredClientActionsQueue<T> {
7
+ private val actions: Queue<Consumer<T>> = LinkedList()
8
+ private var client: T? = null
9
+
10
+ fun setClient(client: T) {
11
+ this.client = client
12
+ executeQueuedActions()
13
+ }
14
+
15
+ fun hasClient(): Boolean = client != null
16
+
17
+ fun executeOrQueueAction(action: Consumer<T>) {
18
+ if (client != null) {
19
+ action.apply(client)
20
+ } else {
21
+ addActionToQueue(action)
22
+ }
23
+ }
24
+
25
+ fun clear() {
26
+ client = null
27
+ actions.clear()
28
+ }
29
+
30
+ private fun executeQueuedActions() {
31
+ if (client == null) {
32
+ return
33
+ }
34
+
35
+ var action = actions.poll()
36
+ while (action != null) {
37
+ action.apply(client)
38
+ action = actions.poll()
39
+ }
40
+ }
41
+
42
+ private fun addActionToQueue(consumer: Consumer<T>) {
43
+ actions.add(consumer)
44
+ }
45
+ }
@@ -0,0 +1,13 @@
1
+ package expo.modules.webbrowser
2
+
3
+ import expo.modules.kotlin.exception.CodedException
4
+
5
+ internal class NoPreferredPackageFound : CodedException(
6
+ code = "PREFERRED_PACKAGE_NOT_FOUND",
7
+ message = "Cannot determine preferred package without satisfying it",
8
+ cause = null
9
+ )
10
+
11
+ internal class PackageManagerNotFoundException : CodedException("Package Manager not found")
12
+
13
+ internal class NoMatchingActivityException : CodedException("No matching browser activity found")
@@ -0,0 +1,170 @@
1
+ package expo.modules.webbrowser
2
+
3
+ import expo.modules.core.errors.CurrentActivityNotFoundException
4
+ import android.content.Intent
5
+ import android.graphics.Color
6
+ import android.net.Uri
7
+ import android.os.Bundle
8
+ import android.text.TextUtils
9
+ import androidx.browser.customtabs.CustomTabsIntent
10
+ import androidx.core.os.bundleOf
11
+ import expo.modules.core.utilities.ifNull
12
+ import expo.modules.kotlin.modules.Module
13
+ import expo.modules.kotlin.modules.ModuleDefinition
14
+ import java.lang.IllegalArgumentException
15
+
16
+ private const val SERVICE_PACKAGE_KEY = "servicePackage"
17
+ private const val BROWSER_PACKAGES_KEY = "browserPackages"
18
+ private const val SERVICE_PACKAGES_KEY = "servicePackages"
19
+ private const val PREFERRED_BROWSER_PACKAGE = "preferredBrowserPackage"
20
+ private const val DEFAULT_BROWSER_PACKAGE = "defaultBrowserPackage"
21
+
22
+ private const val MODULE_NAME = "ExpoWebBrowser"
23
+
24
+ class WebBrowserModule : Module() {
25
+ override fun definition() = ModuleDefinition {
26
+ Name(MODULE_NAME)
27
+
28
+ OnCreate {
29
+ customTabsResolver = CustomTabsActivitiesHelper(appContext.activityProvider)
30
+ connectionHelper = CustomTabsConnectionHelper(
31
+ requireNotNull(appContext.reactContext) {
32
+ "Cannot initialize WebBrowser, ReactContext is null"
33
+ }
34
+ )
35
+ }
36
+
37
+ OnActivityDestroys {
38
+ connectionHelper.destroy()
39
+ }
40
+
41
+ AsyncFunction("warmUpAsync") { packageName: String? ->
42
+ val resolvedPackageName = givenOrPreferredPackageName(packageName)
43
+ connectionHelper.warmUp(resolvedPackageName)
44
+ return@AsyncFunction bundleOf(
45
+ SERVICE_PACKAGE_KEY to resolvedPackageName
46
+ )
47
+ }
48
+
49
+ AsyncFunction("coolDownAsync") { packageName: String? ->
50
+ val resolvedPackageName = givenOrPreferredPackageName(packageName)
51
+ val result = if (connectionHelper.coolDown(resolvedPackageName)) {
52
+ bundleOf(
53
+ SERVICE_PACKAGE_KEY to resolvedPackageName
54
+ )
55
+ } else {
56
+ Bundle()
57
+ }
58
+ return@AsyncFunction result
59
+ }
60
+
61
+ AsyncFunction("mayInitWithUrlAsync") { url: String, packageName: String? ->
62
+ val resolvedPackageName = givenOrPreferredPackageName(packageName)
63
+ connectionHelper.mayInitWithUrl(resolvedPackageName, Uri.parse(url))
64
+ return@AsyncFunction bundleOf(
65
+ SERVICE_PACKAGE_KEY to resolvedPackageName
66
+ )
67
+ }
68
+
69
+ // throws CurrentActivityNotFoundException
70
+ AsyncFunction("getCustomTabsSupportingBrowsersAsync") {
71
+ val activities = customTabsResolver.customTabsResolvingActivities
72
+ val services = customTabsResolver.customTabsResolvingServices
73
+ val preferredPackage = customTabsResolver.getPreferredCustomTabsResolvingActivity(activities)
74
+ val defaultPackage = customTabsResolver.defaultCustomTabsResolvingActivity
75
+
76
+ // It might happen, that default activity does not support Chrome Tabs. Then it will be ResolvingActivity and we don't want to return it as a result.
77
+ val defaultCustomTabsPackage: String? = defaultPackage.takeIf { activities.contains(it) }
78
+
79
+ return@AsyncFunction Bundle().apply {
80
+ putStringArrayList(BROWSER_PACKAGES_KEY, activities)
81
+ putStringArrayList(SERVICE_PACKAGES_KEY, services)
82
+ putString(PREFERRED_BROWSER_PACKAGE, preferredPackage)
83
+ putString(DEFAULT_BROWSER_PACKAGE, defaultCustomTabsPackage)
84
+ }
85
+ }
86
+
87
+ // throws CurrentActivityNotFoundException
88
+ AsyncFunction("openBrowserAsync") { url: String, options: OpenBrowserOptions ->
89
+ val intent = createCustomTabsIntent(options).apply {
90
+ data = Uri.parse(url)
91
+ }
92
+
93
+ if (!customTabsResolver.canResolveIntent(intent)) {
94
+ throw NoMatchingActivityException()
95
+ }
96
+
97
+ customTabsResolver.startCustomTabs(intent)
98
+
99
+ return@AsyncFunction bundleOf(
100
+ "type" to "opened"
101
+ )
102
+ }
103
+ }
104
+
105
+ // these must be `internal` to be able to be injected in tests
106
+ internal lateinit var customTabsResolver: CustomTabsActivitiesHelper
107
+ internal lateinit var connectionHelper: CustomTabsConnectionHelper
108
+
109
+ private fun createCustomTabsIntent(options: OpenBrowserOptions): Intent {
110
+ val builder = CustomTabsIntent.Builder()
111
+ val color = options.toolbarColor
112
+ val secondaryColor = options.secondaryToolbarColor
113
+
114
+ try {
115
+ if (!TextUtils.isEmpty(color)) {
116
+ val intColor = Color.parseColor(color)
117
+ builder.setToolbarColor(intColor)
118
+ }
119
+ if (!TextUtils.isEmpty(secondaryColor)) {
120
+ val intSecondaryColor = Color.parseColor(secondaryColor)
121
+ builder.setSecondaryToolbarColor(intSecondaryColor)
122
+ }
123
+ } catch (ignored: IllegalArgumentException) {
124
+ }
125
+
126
+ builder.setShowTitle(options.showTitle)
127
+
128
+ if (options.enableDefaultShareMenuItem) {
129
+ builder.addDefaultShareMenuItem()
130
+ }
131
+
132
+ return builder.build().intent.apply {
133
+ // We cannot use the builder's method enableUrlBarHiding, because there is
134
+ // no corresponding disable method and some browsers enable it by default.
135
+ putExtra(CustomTabsIntent.EXTRA_ENABLE_URLBAR_HIDING, options.enableBarCollapsing)
136
+
137
+ val packageName = options.browserPackage
138
+ if (!TextUtils.isEmpty(packageName)) {
139
+ setPackage(packageName)
140
+ }
141
+
142
+ if (options.shouldCreateTask) {
143
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
144
+
145
+ if (!options.showInRecents) {
146
+ addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
147
+ addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ /**
154
+ * @throws NoPreferredPackageFound
155
+ */
156
+ private fun givenOrPreferredPackageName(packageName: String?): String {
157
+ val resolvedPackageName: String? = try {
158
+ packageName?.takeIf { it.isNotEmpty() }.ifNull {
159
+ customTabsResolver.getPreferredCustomTabsResolvingActivity(null)
160
+ }
161
+ } catch (ex: CurrentActivityNotFoundException) {
162
+ throw NoPreferredPackageFound()
163
+ } catch (ex: PackageManagerNotFoundException) {
164
+ throw NoPreferredPackageFound()
165
+ }
166
+
167
+ return resolvedPackageName?.takeIf { it.isNotEmpty() }
168
+ ?: throw NoPreferredPackageFound()
169
+ }
170
+ }
@@ -0,0 +1,15 @@
1
+ package expo.modules.webbrowser
2
+
3
+ import expo.modules.kotlin.records.Field
4
+ import expo.modules.kotlin.records.Record
5
+
6
+ internal data class OpenBrowserOptions(
7
+ @Field var toolbarColor: String? = null,
8
+ @Field var secondaryToolbarColor: String? = null,
9
+ @Field var browserPackage: String? = null,
10
+ @Field var showTitle: Boolean = false,
11
+ @Field var enableDefaultShareMenuItem: Boolean = false,
12
+ @Field var enableBarCollapsing: Boolean = false,
13
+ @Field var showInRecents: Boolean = false,
14
+ @Field(key = "createTask") var shouldCreateTask: Boolean = true,
15
+ ) : Record
@@ -1,2 +1,3 @@
1
1
  declare const _default: import("expo-modules-core").ProxyNativeModule;
2
2
  export default _default;
3
+ //# sourceMappingURL=ExpoWebBrowser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoWebBrowser.d.ts","sourceRoot":"","sources":["../src/ExpoWebBrowser.ts"],"names":[],"mappings":";AACA,wBAAgE"}
@@ -13,3 +13,4 @@ declare const _default: {
13
13
  };
14
14
  export default _default;
15
15
  export declare function featureObjectToString(features: Record<string, any>): string;
16
+ //# sourceMappingURL=ExpoWebBrowser.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoWebBrowser.web.d.ts","sourceRoot":"","sources":["../src/ExpoWebBrowser.web.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,gBAAgB,EAGjB,MAAM,oBAAoB,CAAC;;;0BAyCnB,MAAM,kBACI,qBAAqB,GACnC,QAAQ,gBAAgB,CAAC;;;;QAWsD;QAChF,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;KACjB;8BA8CM,MAAM,sFAGV,QAAQ,2BAA2B,CAAC;;AAtEzC,wBAyJE;AAqIF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAY3E"}