expo-web-browser 10.2.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 +18 -0
- package/android/build.gradle +3 -2
- 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.web.js +5 -5
- package/build/ExpoWebBrowser.web.js.map +1 -1
- package/build/WebBrowser.d.ts +3 -2
- package/build/WebBrowser.d.ts.map +1 -1
- package/build/WebBrowser.js +11 -9
- package/build/WebBrowser.js.map +1 -1
- package/expo-module.config.json +3 -0
- package/ios/WebAuthSession.swift +2 -2
- package/ios/WebBrowserModule.swift +9 -9
- package/package.json +2 -2
- package/src/ExpoWebBrowser.web.ts +6 -6
- package/src/WebBrowser.ts +17 -13
- 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/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,24 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
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
|
+
|
|
13
31
|
## 10.2.1 — 2022-05-24
|
|
14
32
|
|
|
15
33
|
### 🐛 Bug fixes
|
package/android/build.gradle
CHANGED
|
@@ -3,7 +3,7 @@ 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 {
|
|
@@ -75,7 +75,7 @@ android {
|
|
|
75
75
|
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
76
76
|
targetSdkVersion safeExtGet("targetSdkVersion", 31)
|
|
77
77
|
versionCode 18
|
|
78
|
-
versionName '
|
|
78
|
+
versionName '11.0.0'
|
|
79
79
|
}
|
|
80
80
|
lintOptions {
|
|
81
81
|
abortOnError false
|
|
@@ -88,6 +88,7 @@ dependencies {
|
|
|
88
88
|
api "androidx.browser:browser:1.2.0"
|
|
89
89
|
|
|
90
90
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
91
|
+
implementation "androidx.core:core-ktx:1.7.0"
|
|
91
92
|
|
|
92
93
|
if (project.findProject(':expo-modules-test-core')) {
|
|
93
94
|
testImplementation project(':expo-modules-test-core')
|
|
@@ -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
|
|
@@ -15,10 +15,10 @@ function dismissPopup() {
|
|
|
15
15
|
}
|
|
16
16
|
popupWindow.close();
|
|
17
17
|
if (listenerMap.has(popupWindow)) {
|
|
18
|
-
const { listener,
|
|
18
|
+
const { listener, appStateSubscription, interval } = listenerMap.get(popupWindow);
|
|
19
19
|
clearInterval(interval);
|
|
20
20
|
window.removeEventListener('message', listener);
|
|
21
|
-
|
|
21
|
+
appStateSubscription.remove();
|
|
22
22
|
listenerMap.delete(popupWindow);
|
|
23
23
|
const handle = window.localStorage.getItem(getHandle());
|
|
24
24
|
if (handle) {
|
|
@@ -138,7 +138,7 @@ export default {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
|
-
AppState.addEventListener('change', appStateListener);
|
|
141
|
+
const appStateSubscription = AppState.addEventListener('change', appStateListener);
|
|
142
142
|
// Check if the window has been closed every second.
|
|
143
143
|
const interval = setInterval(() => {
|
|
144
144
|
if (popupWindow?.closed) {
|
|
@@ -152,7 +152,7 @@ export default {
|
|
|
152
152
|
listenerMap.set(popupWindow, {
|
|
153
153
|
listener,
|
|
154
154
|
interval,
|
|
155
|
-
|
|
155
|
+
appStateSubscription,
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
158
|
},
|
|
@@ -234,7 +234,7 @@ function normalizePopupFeaturesString(options) {
|
|
|
234
234
|
for (const pair of windowFeaturePairs) {
|
|
235
235
|
const [key, value] = pair.trim().split('=');
|
|
236
236
|
if (key && value) {
|
|
237
|
-
|
|
237
|
+
windowFeatures[key] = value;
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoWebBrowser.web.js","sourceRoot":"","sources":["../src/ExpoWebBrowser.web.ts"],"names":[],"mappings":"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","sourcesContent":["import compareUrls from 'compare-urls';\nimport { CodedError, Platform } from 'expo-modules-core';\nimport { AppState, Dimensions, AppStateStatus } from 'react-native';\n\nimport {\n WebBrowserAuthSessionResult,\n WebBrowserOpenOptions,\n WebBrowserResult,\n WebBrowserResultType,\n WebBrowserWindowFeatures,\n} from './WebBrowser.types';\n\nconst POPUP_WIDTH = 500;\nconst POPUP_HEIGHT = 650;\n\nlet popupWindow: Window | null = null;\n\nconst listenerMap = new Map();\n\nconst getHandle = () => 'ExpoWebBrowserRedirectHandle';\nconst getOriginUrlHandle = (hash: string) => `ExpoWebBrowser_OriginUrl_${hash}`;\nconst getRedirectUrlHandle = (hash: string) => `ExpoWebBrowser_RedirectUrl_${hash}`;\n\nfunction dismissPopup() {\n if (!popupWindow) {\n return;\n }\n popupWindow.close();\n if (listenerMap.has(popupWindow)) {\n const { listener, appStateListener, interval } = listenerMap.get(popupWindow);\n clearInterval(interval);\n window.removeEventListener('message', listener);\n AppState.removeEventListener('change', appStateListener);\n listenerMap.delete(popupWindow);\n\n const handle = window.localStorage.getItem(getHandle());\n if (handle) {\n window.localStorage.removeItem(getHandle());\n window.localStorage.removeItem(getOriginUrlHandle(handle));\n window.localStorage.removeItem(getRedirectUrlHandle(handle));\n }\n\n popupWindow = null;\n }\n}\n\nexport default {\n get name() {\n return 'ExpoWebBrowser';\n },\n async openBrowserAsync(\n url: string,\n browserParams: WebBrowserOpenOptions = {}\n ): Promise<WebBrowserResult> {\n if (!Platform.isDOMAvailable) return { type: WebBrowserResultType.CANCEL };\n const { windowName = '_blank', windowFeatures } = browserParams;\n const features = getPopupFeaturesString(windowFeatures);\n window.open(url, windowName, features);\n return { type: WebBrowserResultType.OPENED };\n },\n dismissAuthSession() {\n if (!Platform.isDOMAvailable) return;\n dismissPopup();\n },\n maybeCompleteAuthSession({ skipRedirectCheck }: { skipRedirectCheck?: boolean }): {\n type: 'success' | 'failed';\n message: string;\n } {\n if (!Platform.isDOMAvailable) {\n return {\n type: 'failed',\n message: 'Cannot use expo-web-browser in a non-browser environment',\n };\n }\n const handle = window.localStorage.getItem(getHandle());\n\n if (!handle) {\n return { type: 'failed', message: 'No auth session is currently in progress' };\n }\n\n const url = window.location.href;\n\n if (skipRedirectCheck !== true) {\n const redirectUrl = window.localStorage.getItem(getRedirectUrlHandle(handle));\n // Compare the original redirect url against the current url with it's query params removed.\n const currentUrl = window.location.origin + window.location.pathname;\n if (!compareUrls(redirectUrl, currentUrl)) {\n return {\n type: 'failed',\n message: `Current URL \"${currentUrl}\" and original redirect URL \"${redirectUrl}\" do not match.`,\n };\n }\n }\n\n // Save the link for app state listener\n window.localStorage.setItem(getOriginUrlHandle(handle), url);\n\n // Get the window that created the current popup\n const parent = window.opener ?? window.parent;\n if (!parent) {\n throw new CodedError(\n 'ERR_WEB_BROWSER_REDIRECT',\n `The window cannot complete the redirect request because the invoking window doesn't have a reference to it's parent. This can happen if the parent window was reloaded.`\n );\n }\n // Send the URL back to the opening window.\n parent.postMessage({ url, expoSender: handle }, parent.location.toString());\n return { type: 'success', message: `Attempting to complete auth` };\n\n // Maybe set timer to throw an error if the window is still open after attempting to complete.\n },\n // This method should be invoked from user input.\n async openAuthSessionAsync(\n url: string,\n redirectUrl?: string,\n openOptions?: WebBrowserOpenOptions\n ): Promise<WebBrowserAuthSessionResult> {\n if (!Platform.isDOMAvailable) return { type: WebBrowserResultType.CANCEL };\n\n redirectUrl = redirectUrl ?? getRedirectUrlFromUrlOrGenerate(url);\n\n if (popupWindow == null || popupWindow?.closed) {\n const features = getPopupFeaturesString(openOptions?.windowFeatures);\n popupWindow = window.open(url, openOptions?.windowName, features);\n\n if (popupWindow) {\n try {\n popupWindow.focus();\n } catch {}\n } else {\n throw new CodedError(\n 'ERR_WEB_BROWSER_BLOCKED',\n 'Popup window was blocked by the browser or failed to open. This can happen in mobile browsers when the window.open() method was invoked too long after a user input was fired.'\n );\n }\n }\n\n const state = await getStateFromUrlOrGenerateAsync(url);\n\n // Save handle for session\n window.localStorage.setItem(getHandle(), state);\n // Save redirect Url for further verification\n window.localStorage.setItem(getRedirectUrlHandle(state), redirectUrl);\n\n return new Promise(async (resolve) => {\n // Create a listener for messages sent from the popup\n const listener = (event: MessageEvent) => {\n if (!event.isTrusted) return;\n // Ensure we trust the sender.\n if (event.origin !== window.location.origin) {\n return;\n }\n const { data } = event;\n // Use a crypto hash to invalid message.\n const handle = window.localStorage.getItem(getHandle());\n // Ensure the sender is also from expo-web-browser\n if (data.expoSender === handle) {\n dismissPopup();\n resolve({ type: 'success', url: data.url });\n }\n };\n\n // Add a listener for receiving messages from the popup.\n window.addEventListener('message', listener, false);\n\n // Create an app state listener as a fallback to the popup listener\n const appStateListener = (state: AppStateStatus) => {\n if (state !== 'active') {\n return;\n }\n const handle = window.localStorage.getItem(getHandle());\n if (handle) {\n const url = window.localStorage.getItem(getOriginUrlHandle(handle));\n if (url) {\n dismissPopup();\n resolve({ type: 'success', url });\n }\n }\n };\n\n AppState.addEventListener('change', appStateListener);\n\n // Check if the window has been closed every second.\n const interval = setInterval(() => {\n if (popupWindow?.closed) {\n if (resolve) resolve({ type: WebBrowserResultType.DISMISS });\n clearInterval(interval);\n dismissPopup();\n }\n }, 1000);\n\n // Store the listener and interval for clean up.\n listenerMap.set(popupWindow, {\n listener,\n interval,\n appStateListener,\n });\n });\n },\n};\n\n// Crypto\nfunction isCryptoAvailable(): boolean {\n if (!Platform.isDOMAvailable) return false;\n return !!(window?.crypto as any);\n}\n\nfunction isSubtleCryptoAvailable(): boolean {\n if (!isCryptoAvailable()) return false;\n return !!(window.crypto.subtle as any);\n}\n\nasync function getStateFromUrlOrGenerateAsync(inputUrl: string): Promise<string> {\n const url = new URL(inputUrl);\n if (url.searchParams.has('state') && typeof url.searchParams.get('state') === 'string') {\n // Ensure we reuse the auth state if it's passed in.\n return url.searchParams.get('state')!;\n }\n // Generate a crypto state for verifying the return popup.\n return await generateStateAsync();\n}\n\nfunction getRedirectUrlFromUrlOrGenerate(inputUrl: string): string {\n const url = new URL(inputUrl);\n if (\n url.searchParams.has('redirect_uri') &&\n typeof url.searchParams.get('redirect_uri') === 'string'\n ) {\n // Ensure we reuse the redirect_uri if it's passed in the input url.\n return url.searchParams.get('redirect_uri')!;\n }\n // Emulate how native uses Constants.linkingUrl\n return location.origin + location.pathname;\n}\n\nconst CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n\nasync function generateStateAsync(): Promise<string> {\n if (!isSubtleCryptoAvailable()) {\n throw new CodedError(\n 'ERR_WEB_BROWSER_CRYPTO',\n `The current environment doesn't support crypto. Ensure you are running from a secure origin (https).`\n );\n }\n const encoder = new TextEncoder();\n\n const data = generateRandom(10);\n const buffer = encoder.encode(data);\n const hashedData = await crypto.subtle.digest('SHA-256', buffer);\n const state = btoa(String.fromCharCode(...new Uint8Array(hashedData)));\n return state;\n}\n\nfunction generateRandom(size: number): string {\n let arr = new Uint8Array(size);\n if (arr.byteLength !== arr.length) {\n arr = new Uint8Array(arr.buffer);\n }\n const array = new Uint8Array(arr.length);\n if (isCryptoAvailable()) {\n window.crypto.getRandomValues(array);\n } else {\n for (let i = 0; i < size; i += 1) {\n array[i] = (Math.random() * CHARSET.length) | 0;\n }\n }\n return bufferToString(array);\n}\n\nfunction bufferToString(buffer): string {\n const state: string[] = [];\n for (let i = 0; i < buffer.byteLength; i += 1) {\n const index = buffer[i] % CHARSET.length;\n state.push(CHARSET[index]);\n }\n return state.join('');\n}\n\n// Window Features\n\n// Ensure feature string is an object\nfunction normalizePopupFeaturesString(\n options?: WebBrowserWindowFeatures | string\n): Record<string, any> {\n let windowFeatures: Record<string, any> = {};\n // This should be avoided because it adds extra time to the popup command.\n if (typeof options === 'string') {\n // Convert string of `key=value,foo=bar` into an object\n const windowFeaturePairs = options.split(',');\n for (const pair of windowFeaturePairs) {\n const [key, value] = pair.trim().split('=');\n if (key && value) {\n windowFeaturePairs[key] = value;\n }\n }\n } else if (options) {\n windowFeatures = options;\n }\n return windowFeatures;\n}\n\n// Apply default values to the input feature set\nfunction getPopupFeaturesString(options?: WebBrowserWindowFeatures | string): string {\n const windowFeatures = normalizePopupFeaturesString(options);\n\n const width = windowFeatures.width ?? POPUP_WIDTH;\n const height = windowFeatures.height ?? POPUP_HEIGHT;\n\n const dimensions = Dimensions.get('screen');\n const top = windowFeatures.top ?? Math.max(0, (dimensions.height - height) * 0.5);\n const left = windowFeatures.left ?? Math.max(0, (dimensions.width - width) * 0.5);\n\n // Create a reasonable popup\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/open#Window_features\n return featureObjectToString({\n ...windowFeatures,\n // Toolbar buttons (Back, Forward, Reload, Stop buttons).\n toolbar: windowFeatures.toolbar ?? 'no',\n menubar: windowFeatures.menubar ?? 'no',\n // Shows the location bar or the address bar.\n location: windowFeatures.location ?? 'yes',\n resizable: windowFeatures.resizable ?? 'yes',\n // If this feature is on, then the new secondary window has a status bar.\n status: windowFeatures.status ?? 'no',\n scrollbars: windowFeatures.scrollbars ?? 'yes',\n top,\n left,\n width,\n height,\n });\n}\n\nexport function featureObjectToString(features: Record<string, any>): string {\n return Object.keys(features).reduce<string>((prev, current) => {\n let value = features[current];\n if (typeof value === 'boolean') {\n value = value ? 'yes' : 'no';\n }\n if (current && value) {\n if (prev) prev += ',';\n return `${prev}${current}=${value}`;\n }\n return prev;\n }, '');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ExpoWebBrowser.web.js","sourceRoot":"","sources":["../src/ExpoWebBrowser.web.ts"],"names":[],"mappings":"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","sourcesContent":["import compareUrls from 'compare-urls';\nimport { CodedError, Platform } from 'expo-modules-core';\nimport { AppState, Dimensions, AppStateStatus, NativeEventSubscription } from 'react-native';\n\nimport {\n WebBrowserAuthSessionResult,\n WebBrowserOpenOptions,\n WebBrowserResult,\n WebBrowserResultType,\n WebBrowserWindowFeatures,\n} from './WebBrowser.types';\n\nconst POPUP_WIDTH = 500;\nconst POPUP_HEIGHT = 650;\n\nlet popupWindow: Window | null = null;\n\nconst listenerMap = new Map();\n\nconst getHandle = () => 'ExpoWebBrowserRedirectHandle';\nconst getOriginUrlHandle = (hash: string) => `ExpoWebBrowser_OriginUrl_${hash}`;\nconst getRedirectUrlHandle = (hash: string) => `ExpoWebBrowser_RedirectUrl_${hash}`;\n\nfunction dismissPopup() {\n if (!popupWindow) {\n return;\n }\n popupWindow.close();\n if (listenerMap.has(popupWindow)) {\n const { listener, appStateSubscription, interval } = listenerMap.get(popupWindow);\n clearInterval(interval);\n window.removeEventListener('message', listener);\n (appStateSubscription as NativeEventSubscription).remove();\n listenerMap.delete(popupWindow);\n\n const handle = window.localStorage.getItem(getHandle());\n if (handle) {\n window.localStorage.removeItem(getHandle());\n window.localStorage.removeItem(getOriginUrlHandle(handle));\n window.localStorage.removeItem(getRedirectUrlHandle(handle));\n }\n\n popupWindow = null;\n }\n}\n\nexport default {\n get name() {\n return 'ExpoWebBrowser';\n },\n async openBrowserAsync(\n url: string,\n browserParams: WebBrowserOpenOptions = {}\n ): Promise<WebBrowserResult> {\n if (!Platform.isDOMAvailable) return { type: WebBrowserResultType.CANCEL };\n const { windowName = '_blank', windowFeatures } = browserParams;\n const features = getPopupFeaturesString(windowFeatures);\n window.open(url, windowName, features);\n return { type: WebBrowserResultType.OPENED };\n },\n dismissAuthSession() {\n if (!Platform.isDOMAvailable) return;\n dismissPopup();\n },\n maybeCompleteAuthSession({ skipRedirectCheck }: { skipRedirectCheck?: boolean }): {\n type: 'success' | 'failed';\n message: string;\n } {\n if (!Platform.isDOMAvailable) {\n return {\n type: 'failed',\n message: 'Cannot use expo-web-browser in a non-browser environment',\n };\n }\n const handle = window.localStorage.getItem(getHandle());\n\n if (!handle) {\n return { type: 'failed', message: 'No auth session is currently in progress' };\n }\n\n const url = window.location.href;\n\n if (skipRedirectCheck !== true) {\n const redirectUrl = window.localStorage.getItem(getRedirectUrlHandle(handle));\n // Compare the original redirect url against the current url with it's query params removed.\n const currentUrl = window.location.origin + window.location.pathname;\n if (!compareUrls(redirectUrl, currentUrl)) {\n return {\n type: 'failed',\n message: `Current URL \"${currentUrl}\" and original redirect URL \"${redirectUrl}\" do not match.`,\n };\n }\n }\n\n // Save the link for app state listener\n window.localStorage.setItem(getOriginUrlHandle(handle), url);\n\n // Get the window that created the current popup\n const parent = window.opener ?? window.parent;\n if (!parent) {\n throw new CodedError(\n 'ERR_WEB_BROWSER_REDIRECT',\n `The window cannot complete the redirect request because the invoking window doesn't have a reference to it's parent. This can happen if the parent window was reloaded.`\n );\n }\n // Send the URL back to the opening window.\n parent.postMessage({ url, expoSender: handle }, parent.location.toString());\n return { type: 'success', message: `Attempting to complete auth` };\n\n // Maybe set timer to throw an error if the window is still open after attempting to complete.\n },\n // This method should be invoked from user input.\n async openAuthSessionAsync(\n url: string,\n redirectUrl?: string,\n openOptions?: WebBrowserOpenOptions\n ): Promise<WebBrowserAuthSessionResult> {\n if (!Platform.isDOMAvailable) return { type: WebBrowserResultType.CANCEL };\n\n redirectUrl = redirectUrl ?? getRedirectUrlFromUrlOrGenerate(url);\n\n if (popupWindow == null || popupWindow?.closed) {\n const features = getPopupFeaturesString(openOptions?.windowFeatures);\n popupWindow = window.open(url, openOptions?.windowName, features);\n\n if (popupWindow) {\n try {\n popupWindow.focus();\n } catch {}\n } else {\n throw new CodedError(\n 'ERR_WEB_BROWSER_BLOCKED',\n 'Popup window was blocked by the browser or failed to open. This can happen in mobile browsers when the window.open() method was invoked too long after a user input was fired.'\n );\n }\n }\n\n const state = await getStateFromUrlOrGenerateAsync(url);\n\n // Save handle for session\n window.localStorage.setItem(getHandle(), state);\n // Save redirect Url for further verification\n window.localStorage.setItem(getRedirectUrlHandle(state), redirectUrl);\n\n return new Promise(async (resolve) => {\n // Create a listener for messages sent from the popup\n const listener = (event: MessageEvent) => {\n if (!event.isTrusted) return;\n // Ensure we trust the sender.\n if (event.origin !== window.location.origin) {\n return;\n }\n const { data } = event;\n // Use a crypto hash to invalid message.\n const handle = window.localStorage.getItem(getHandle());\n // Ensure the sender is also from expo-web-browser\n if (data.expoSender === handle) {\n dismissPopup();\n resolve({ type: 'success', url: data.url });\n }\n };\n\n // Add a listener for receiving messages from the popup.\n window.addEventListener('message', listener, false);\n\n // Create an app state listener as a fallback to the popup listener\n const appStateListener = (state: AppStateStatus) => {\n if (state !== 'active') {\n return;\n }\n const handle = window.localStorage.getItem(getHandle());\n if (handle) {\n const url = window.localStorage.getItem(getOriginUrlHandle(handle));\n if (url) {\n dismissPopup();\n resolve({ type: 'success', url });\n }\n }\n };\n\n const appStateSubscription = AppState.addEventListener('change', appStateListener);\n\n // Check if the window has been closed every second.\n const interval = setInterval(() => {\n if (popupWindow?.closed) {\n if (resolve) resolve({ type: WebBrowserResultType.DISMISS });\n clearInterval(interval);\n dismissPopup();\n }\n }, 1000);\n\n // Store the listener and interval for clean up.\n listenerMap.set(popupWindow, {\n listener,\n interval,\n appStateSubscription,\n });\n });\n },\n};\n\n// Crypto\nfunction isCryptoAvailable(): boolean {\n if (!Platform.isDOMAvailable) return false;\n return !!(window?.crypto as any);\n}\n\nfunction isSubtleCryptoAvailable(): boolean {\n if (!isCryptoAvailable()) return false;\n return !!(window.crypto.subtle as any);\n}\n\nasync function getStateFromUrlOrGenerateAsync(inputUrl: string): Promise<string> {\n const url = new URL(inputUrl);\n if (url.searchParams.has('state') && typeof url.searchParams.get('state') === 'string') {\n // Ensure we reuse the auth state if it's passed in.\n return url.searchParams.get('state')!;\n }\n // Generate a crypto state for verifying the return popup.\n return await generateStateAsync();\n}\n\nfunction getRedirectUrlFromUrlOrGenerate(inputUrl: string): string {\n const url = new URL(inputUrl);\n if (\n url.searchParams.has('redirect_uri') &&\n typeof url.searchParams.get('redirect_uri') === 'string'\n ) {\n // Ensure we reuse the redirect_uri if it's passed in the input url.\n return url.searchParams.get('redirect_uri')!;\n }\n // Emulate how native uses Constants.linkingUrl\n return location.origin + location.pathname;\n}\n\nconst CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n\nasync function generateStateAsync(): Promise<string> {\n if (!isSubtleCryptoAvailable()) {\n throw new CodedError(\n 'ERR_WEB_BROWSER_CRYPTO',\n `The current environment doesn't support crypto. Ensure you are running from a secure origin (https).`\n );\n }\n const encoder = new TextEncoder();\n\n const data = generateRandom(10);\n const buffer = encoder.encode(data);\n const hashedData = await crypto.subtle.digest('SHA-256', buffer);\n const state = btoa(String.fromCharCode(...new Uint8Array(hashedData)));\n return state;\n}\n\nfunction generateRandom(size: number): string {\n let arr = new Uint8Array(size);\n if (arr.byteLength !== arr.length) {\n arr = new Uint8Array(arr.buffer);\n }\n const array = new Uint8Array(arr.length);\n if (isCryptoAvailable()) {\n window.crypto.getRandomValues(array);\n } else {\n for (let i = 0; i < size; i += 1) {\n array[i] = (Math.random() * CHARSET.length) | 0;\n }\n }\n return bufferToString(array);\n}\n\nfunction bufferToString(buffer): string {\n const state: string[] = [];\n for (let i = 0; i < buffer.byteLength; i += 1) {\n const index = buffer[i] % CHARSET.length;\n state.push(CHARSET[index]);\n }\n return state.join('');\n}\n\n// Window Features\n\n// Ensure feature string is an object\nfunction normalizePopupFeaturesString(\n options?: WebBrowserWindowFeatures | string\n): Record<string, any> {\n let windowFeatures: Record<string, any> = {};\n // This should be avoided because it adds extra time to the popup command.\n if (typeof options === 'string') {\n // Convert string of `key=value,foo=bar` into an object\n const windowFeaturePairs = options.split(',');\n for (const pair of windowFeaturePairs) {\n const [key, value] = pair.trim().split('=');\n if (key && value) {\n windowFeatures[key] = value;\n }\n }\n } else if (options) {\n windowFeatures = options;\n }\n return windowFeatures;\n}\n\n// Apply default values to the input feature set\nfunction getPopupFeaturesString(options?: WebBrowserWindowFeatures | string): string {\n const windowFeatures = normalizePopupFeaturesString(options);\n\n const width = windowFeatures.width ?? POPUP_WIDTH;\n const height = windowFeatures.height ?? POPUP_HEIGHT;\n\n const dimensions = Dimensions.get('screen');\n const top = windowFeatures.top ?? Math.max(0, (dimensions.height - height) * 0.5);\n const left = windowFeatures.left ?? Math.max(0, (dimensions.width - width) * 0.5);\n\n // Create a reasonable popup\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/open#Window_features\n return featureObjectToString({\n ...windowFeatures,\n // Toolbar buttons (Back, Forward, Reload, Stop buttons).\n toolbar: windowFeatures.toolbar ?? 'no',\n menubar: windowFeatures.menubar ?? 'no',\n // Shows the location bar or the address bar.\n location: windowFeatures.location ?? 'yes',\n resizable: windowFeatures.resizable ?? 'yes',\n // If this feature is on, then the new secondary window has a status bar.\n status: windowFeatures.status ?? 'no',\n scrollbars: windowFeatures.scrollbars ?? 'yes',\n top,\n left,\n width,\n height,\n });\n}\n\nexport function featureObjectToString(features: Record<string, any>): string {\n return Object.keys(features).reduce<string>((prev, current) => {\n let value = features[current];\n if (typeof value === 'boolean') {\n value = value ? 'yes' : 'no';\n }\n if (current && value) {\n if (prev) prev += ',';\n return `${prev}${current}=${value}`;\n }\n return prev;\n }, '');\n}\n"]}
|