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.
- package/CHANGELOG.md +37 -1
- package/README.md +3 -3
- package/android/build.gradle +30 -10
- package/android/src/main/java/expo/modules/webbrowser/CustomTabsActivitiesHelper.kt +123 -0
- package/android/src/main/java/expo/modules/webbrowser/CustomTabsConnectionHelper.kt +97 -0
- package/android/src/main/java/expo/modules/webbrowser/DeferredClientActionsQueue.kt +45 -0
- package/android/src/main/java/expo/modules/webbrowser/WebBrowserExceptions.kt +13 -0
- package/android/src/main/java/expo/modules/webbrowser/WebBrowserModule.kt +170 -0
- package/android/src/main/java/expo/modules/webbrowser/WebBrowserOptions.kt +15 -0
- package/build/ExpoWebBrowser.d.ts +1 -0
- package/build/ExpoWebBrowser.d.ts.map +1 -0
- package/build/ExpoWebBrowser.web.d.ts +1 -0
- package/build/ExpoWebBrowser.web.d.ts.map +1 -0
- package/build/ExpoWebBrowser.web.js +11 -11
- package/build/ExpoWebBrowser.web.js.map +1 -1
- package/build/WebBrowser.d.ts +7 -5
- package/build/WebBrowser.d.ts.map +1 -0
- package/build/WebBrowser.js +21 -19
- package/build/WebBrowser.js.map +1 -1
- package/build/WebBrowser.types.d.ts +65 -0
- package/build/WebBrowser.types.d.ts.map +1 -0
- package/build/WebBrowser.types.js +44 -0
- package/build/WebBrowser.types.js.map +1 -1
- package/expo-module.config.json +9 -0
- package/ios/{EXWebBrowser.podspec → ExpoWebBrowser.podspec} +10 -3
- package/ios/WebAuthSession.swift +59 -0
- package/ios/WebBrowserExceptions.swift +9 -0
- package/ios/WebBrowserModule.swift +53 -0
- package/ios/WebBrowserOptions.swift +86 -0
- package/ios/WebBrowserSession.swift +63 -0
- package/package.json +2 -2
- package/src/ExpoWebBrowser.web.ts +14 -14
- package/src/WebBrowser.ts +29 -21
- package/src/WebBrowser.types.ts +67 -0
- package/tsconfig.json +1 -1
- package/android/src/main/java/expo/modules/webbrowser/CustomTabsActivitiesHelper.java +0 -33
- package/android/src/main/java/expo/modules/webbrowser/CustomTabsConnectionHelper.java +0 -13
- package/android/src/main/java/expo/modules/webbrowser/DeferredClientActionsQueue.java +0 -50
- package/android/src/main/java/expo/modules/webbrowser/InternalCustomTabsActivitiesHelper.java +0 -127
- package/android/src/main/java/expo/modules/webbrowser/InternalCustomTabsConnectionHelper.java +0 -126
- package/android/src/main/java/expo/modules/webbrowser/WebBrowserModule.java +0 -218
- package/android/src/main/java/expo/modules/webbrowser/WebBrowserPackage.java +0 -27
- package/android/src/main/java/expo/modules/webbrowser/error/NoPreferredPackageFound.java +0 -15
- package/android/src/main/java/expo/modules/webbrowser/error/PackageManagerNotFoundException.java +0 -10
- package/ios/EXWebBrowser/EXWebBrowser.h +0 -9
- package/ios/EXWebBrowser/EXWebBrowser.m +0 -280
- package/unimodule.json +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -10,7 +10,43 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
-
##
|
|
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
|
|
8
|
-
- [Documentation for the latest stable release](https://docs.expo.
|
|
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
|
|
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
|
|
package/android/build.gradle
CHANGED
|
@@ -3,21 +3,36 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '
|
|
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:${
|
|
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",
|
|
63
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 31)
|
|
49
64
|
|
|
50
65
|
compileOptions {
|
|
51
|
-
sourceCompatibility JavaVersion.
|
|
52
|
-
targetCompatibility JavaVersion.
|
|
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",
|
|
76
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 31)
|
|
58
77
|
versionCode 18
|
|
59
|
-
versionName '
|
|
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:${
|
|
90
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
91
|
+
implementation "androidx.core:core-ktx:1.7.0"
|
|
72
92
|
|
|
73
|
-
if (project.findProject(':
|
|
74
|
-
testImplementation project(':
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoWebBrowser.d.ts","sourceRoot":"","sources":["../src/ExpoWebBrowser.ts"],"names":[],"mappings":";AACA,wBAAgE"}
|
|
@@ -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"}
|