diff --git a/lerna.json b/lerna.json index 125fde7d69c..2eedc5c75c2 100644 --- a/lerna.json +++ b/lerna.json @@ -7,7 +7,8 @@ "packages/analytics", "packages/storage", "packages/aws-amplify", - "packages/adapter-nextjs" + "packages/adapter-nextjs", + "packages/rtn-web-browser" ], "exact": true, "version": "independent", diff --git a/package.json b/package.json index 3404a7d56da..cb830b605b7 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "packages/analytics", "packages/storage", "packages/aws-amplify", - "packages/adapter-nextjs" + "packages/adapter-nextjs", + "packages/rtn-web-browser" ], "nohoist": [ "**/@types/react-native", diff --git a/packages/rtn-web-browser/AmplifyRTNWebBrowser.podspec b/packages/rtn-web-browser/AmplifyRTNWebBrowser.podspec new file mode 100644 index 00000000000..ba97ac5f91a --- /dev/null +++ b/packages/rtn-web-browser/AmplifyRTNWebBrowser.podspec @@ -0,0 +1,35 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +Pod::Spec.new do |s| + s.name = "AmplifyRTNWebBrowser" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => "13.0" } + s.source = { :git => "https://github.com/aws-amplify/amplify-js.git", :tag => "#{s.version}" } + + s.source_files = "ios/**/*.{h,m,mm,swift}" + + s.dependency "React-Core" + + # Don't install the dependencies when we run `pod install` in the old architecture. + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + s.dependency "React-Codegen" + s.dependency "RCT-Folly" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + end +end diff --git a/packages/rtn-web-browser/CHANGELOG.md b/packages/rtn-web-browser/CHANGELOG.md new file mode 100644 index 00000000000..e4d87c4d45c --- /dev/null +++ b/packages/rtn-web-browser/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/packages/rtn-web-browser/android/build.gradle b/packages/rtn-web-browser/android/build.gradle new file mode 100644 index 00000000000..9916e68d8a9 --- /dev/null +++ b/packages/rtn-web-browser/android/build.gradle @@ -0,0 +1,68 @@ +buildscript { + def kotlin_version = rootProject.ext.has('kotlinVersion') + ? rootProject.ext.get('kotlinVersion') + : project.properties['default_kotlinVersion'] + + repositories { + google() + mavenCentral() + } + + dependencies { + if (project == rootProject) { + classpath 'com.android.tools.build:gradle:7.3.1' + } + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +def getExtOrDefault(prop) { + (rootProject.ext.has(prop) ? rootProject.ext.get(prop) : project.properties["default_$prop"]).toInteger() +} + +android { + compileSdkVersion getExtOrDefault('compileSdkVersion') + + defaultConfig { + minSdkVersion getExtOrDefault('minSdkVersion') + targetSdkVersion getExtOrDefault('targetSdkVersion') + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } +} + +repositories { + // React Native installed via NPM location in application + maven { url "$rootDir/../node_modules/react-native/android" } + // React Native installed via NPM location in Amplify monorepo + maven { url "$rootDir/../../../node_modules/react-native/android" } + google() + mavenCentral() +} + +dependencies { + //noinspection GradleDynamicVersion + implementation 'com.facebook.react:react-native:+' + + // Import the browser library to support Custom Tabs + implementation 'androidx.browser:browser:1.5.0' + + // Test dependencies + testImplementation 'junit:junit:4.13.2' + testImplementation 'io.mockk:mockk:1.13.4' + testImplementation 'org.slf4j:slf4j-nop:2.0.7' + testImplementation 'org.robolectric:robolectric:4.9' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' +} diff --git a/packages/rtn-web-browser/android/gradle.properties b/packages/rtn-web-browser/android/gradle.properties new file mode 100644 index 00000000000..02b19a0a2ed --- /dev/null +++ b/packages/rtn-web-browser/android/gradle.properties @@ -0,0 +1,23 @@ +## For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +#Mon Oct 31 14:53:15 PDT 2022 +android.nonTransitiveRClass=true +kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding\=UTF-8 +android.useAndroidX=true +android.enableJetifier=true + +default_kotlinVersion=1.7.20 +default_compileSdkVersion=32 +default_minSdkVersion=24 +default_targetSdkVersion=30 diff --git a/packages/rtn-web-browser/android/gradle/wrapper/gradle-wrapper.jar b/packages/rtn-web-browser/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..41d9927a4d4 Binary files /dev/null and b/packages/rtn-web-browser/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/packages/rtn-web-browser/android/gradle/wrapper/gradle-wrapper.properties b/packages/rtn-web-browser/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..41dfb87909a --- /dev/null +++ b/packages/rtn-web-browser/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/rtn-web-browser/android/gradlew b/packages/rtn-web-browser/android/gradlew new file mode 100755 index 00000000000..1b6c787337f --- /dev/null +++ b/packages/rtn-web-browser/android/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/packages/rtn-web-browser/android/gradlew.bat b/packages/rtn-web-browser/android/gradlew.bat new file mode 100644 index 00000000000..ac1b06f9382 --- /dev/null +++ b/packages/rtn-web-browser/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/rtn-web-browser/android/src/main/AndroidManifest.xml b/packages/rtn-web-browser/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..9a24d1ecac8 --- /dev/null +++ b/packages/rtn-web-browser/android/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.amazonaws.amplify.rtnwebbrowser"> + + <queries> + <intent> + <action android:name="android.support.customtabs.action.CustomTabsService" /> + </intent> + </queries> +</manifest> diff --git a/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/CustomTabsHelper.kt b/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/CustomTabsHelper.kt new file mode 100644 index 00000000000..c47933f2df8 --- /dev/null +++ b/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/CustomTabsHelper.kt @@ -0,0 +1,54 @@ +package com.amazonaws.amplify.rtnwebbrowser + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import androidx.browser.customtabs.CustomTabsService + +private const val DUMMY_URL = "http://www.example.com" + +internal object CustomTabsHelper { + private var customTabsPackage: String? = null + + fun getCustomTabsPackageName(context: Context): String? { + customTabsPackage?.let { return customTabsPackage } + val packageManager = context.packageManager + val activityIntent = Intent(Intent.ACTION_VIEW, Uri.parse(DUMMY_URL)) + // Get default VIEW intent handler + val defaultViewHandlerPackage = packageManager.resolveActivity( + activityIntent, + PackageManager.MATCH_DEFAULT_ONLY + )?.activityInfo?.packageName ?: "" + + // Get all apps that can handle VIEW intents + val resolvedActivityList = + packageManager.queryIntentActivities(activityIntent, PackageManager.MATCH_ALL) + + // Get all apps that can handle both VIEW intents and service calls + val packagesSupportingCustomTabs = ArrayList<String>() + resolvedActivityList.forEach { resolveInfo -> + val serviceIntent = Intent() + .setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION) + .setPackage(resolveInfo.activityInfo.packageName) + packageManager.resolveService(serviceIntent, PackageManager.MATCH_ALL)?.let { + packagesSupportingCustomTabs.add(it.serviceInfo.packageName) + } + } + + customTabsPackage = if (packagesSupportingCustomTabs.isEmpty()) { + // If no packages support custom tabs, return null + null + } else if (defaultViewHandlerPackage.isNotEmpty() && packagesSupportingCustomTabs.contains( + defaultViewHandlerPackage + ) + ) { + // Prefer the default browser if it supports Custom Tabs + defaultViewHandlerPackage + } else { + // Otherwise, pick the next favorite Custom Tabs provider + packagesSupportingCustomTabs[0] + } + return customTabsPackage + } +} diff --git a/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserModule.kt b/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserModule.kt new file mode 100644 index 00000000000..29ff64f346b --- /dev/null +++ b/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserModule.kt @@ -0,0 +1,73 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.amplify.rtnwebbrowser + +import android.content.Intent +import android.net.Uri +import android.util.Patterns +import androidx.browser.customtabs.CustomTabsClient +import androidx.browser.customtabs.CustomTabsIntent +import com.amazonaws.amplify.rtnwebbrowser.CustomTabsHelper.getCustomTabsPackageName +import com.facebook.react.bridge.LifecycleEventListener +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import java.lang.Exception + + +private val TAG = WebBrowserModule::class.java.simpleName + +class WebBrowserModule( + reactContext: ReactApplicationContext, +) : ReactContextBaseJavaModule(reactContext), LifecycleEventListener { + + private var connection: WebBrowserServiceConnection? = null + + init { + reactContext.addLifecycleEventListener(this) + getCustomTabsPackageName(reactApplicationContext)?.let { + connection = WebBrowserServiceConnection(reactApplicationContext) + CustomTabsClient.bindCustomTabsService(reactApplicationContext, it, connection!!) + } + } + + @ReactMethod + fun openAuthSessionAsync(uriStr: String, promise: Promise) { + if (!Patterns.WEB_URL.matcher(uriStr).matches()) { + promise.reject(Throwable("Provided url is invalid")) + return + } + try { + getCustomTabsPackageName(reactApplicationContext)?.let { + val customTabsIntent = CustomTabsIntent.Builder(connection?.getSession()).build() + customTabsIntent.intent.setPackage(it).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + customTabsIntent.launchUrl(reactApplicationContext, Uri.parse(uriStr)) + } ?: run { + promise.reject(Throwable("No eligible browser found on device")) + } + } catch (e: Exception) { + promise.reject(e) + } + promise.resolve(null) + } + + override fun onHostResume() { + // noop - only overridden as this class implements LifecycleEventListener + } + + override fun onHostPause() { + // noop - only overridden as this class implements LifecycleEventListener + } + + override fun onHostDestroy() { + connection?.destroy() + connection = null + } + + + override fun getName() = "AmplifyRTNWebBrowser" + + override fun getConstants(): MutableMap<String, Any> = hashMapOf() +} diff --git a/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserPackage.kt b/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserPackage.kt new file mode 100644 index 00000000000..88df29ed3eb --- /dev/null +++ b/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserPackage.kt @@ -0,0 +1,22 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.amplify.rtnwebbrowser + +import android.view.View +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ReactShadowNode +import com.facebook.react.uimanager.ViewManager + +class WebBrowserPackage : ReactPackage { + + override fun createViewManagers( + reactContext: ReactApplicationContext + ): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf() + + override fun createNativeModules( + reactContext: ReactApplicationContext + ): MutableList<NativeModule> = listOf(WebBrowserModule(reactContext)).toMutableList() +} diff --git a/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserServiceConnection.kt b/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserServiceConnection.kt new file mode 100644 index 00000000000..8a638238d8f --- /dev/null +++ b/packages/rtn-web-browser/android/src/main/kotlin/com/amazonaws/amplify/rtnwebbrowser/WebBrowserServiceConnection.kt @@ -0,0 +1,50 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.amplify.rtnwebbrowser + +import android.content.ComponentName +import android.content.Context +import androidx.browser.customtabs.CustomTabsClient +import androidx.browser.customtabs.CustomTabsServiceConnection +import androidx.browser.customtabs.CustomTabsSession +import com.amazonaws.amplify.rtnwebbrowser.CustomTabsHelper.getCustomTabsPackageName + +internal class WebBrowserServiceConnection( + private val context: Context +) : CustomTabsServiceConnection() { + private var customTabsPackage: String? = getCustomTabsPackageName(context) + private var session: CustomTabsSession? = null + private var client: CustomTabsClient? = null + + init { + session = client?.newSession(null) + } + + fun destroy() { + if (customTabsPackage != null) { + context.unbindService(this) + } + customTabsPackage = null + client = null + session = null + } + + fun getSession(): CustomTabsSession? { + return session + } + + override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { + if (name.packageName === customTabsPackage) { + client.warmup(0L) + session = client.newSession(null) + this.client = client + } + } + + override fun onServiceDisconnected(name: ComponentName) { + if (name.packageName === customTabsPackage) { + destroy() + } + } +} diff --git a/packages/rtn-web-browser/build.js b/packages/rtn-web-browser/build.js new file mode 100644 index 00000000000..5bdcce15dc5 --- /dev/null +++ b/packages/rtn-web-browser/build.js @@ -0,0 +1,7 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +'use strict'; + +const build = require('../../scripts/build'); + +build(process.argv[2], process.argv[3]); diff --git a/packages/rtn-web-browser/index.js b/packages/rtn-web-browser/index.js new file mode 100644 index 00000000000..669528c7e58 --- /dev/null +++ b/packages/rtn-web-browser/index.js @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./dist/aws-amplify-rtn-web-browser.min.js'); +} else { + module.exports = require('./dist/aws-amplify-rtn-web-browser.js'); +} diff --git a/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser-Bridging-Header.h b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser-Bridging-Header.h new file mode 100644 index 00000000000..d2af4b10323 --- /dev/null +++ b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser-Bridging-Header.h @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#import <React/RCTBridgeModule.h> +#import <React/RCTViewManager.h> +#import <React/RCTRootView.h> diff --git a/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.m b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.m new file mode 100644 index 00000000000..35b4998f92c --- /dev/null +++ b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.m @@ -0,0 +1,12 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#import <React/RCTBridgeModule.h> + +@interface RCT_EXTERN_MODULE(AmplifyRTNWebBrowser, NSObject) + +RCT_EXTERN_METHOD(openAuthSessionAsync:(NSString*)url + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) + +@end diff --git a/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.swift b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.swift new file mode 100644 index 00000000000..1a7b3d9d815 --- /dev/null +++ b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.swift @@ -0,0 +1,63 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import AuthenticationServices + +private class PresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding { + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return ASPresentationAnchor() + } +} + +@objc(AmplifyRTNWebBrowser) +class AmplifyRTNWebBrowser: NSObject { + var webBrowserAuthSession: ASWebAuthenticationSession? + private let presentationContextProvider = PresentationContextProvider() + + private func isUrlValid(url: URL) -> Bool { + return url.scheme == "http" || url.scheme == "https" + } + + @objc + func openAuthSessionAsync(_ urlStr: String, + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock) { + guard let url = URL(string: urlStr) else { + reject("ERROR", "provided url is invalid", nil) + return + } + + guard isUrlValid(url: url) else { + reject("ERROR", "provided url is invalid", nil) + return + } + + let authSession = ASWebAuthenticationSession( + url: url, + callbackURLScheme: nil, + completionHandler: { url, error in + if (error as? ASWebAuthenticationSessionError)?.code == .canceledLogin { + reject("ERROR", "user canceled auth session", error) + return + } + if error != nil { + reject("ERROR", "error occurred starting auth session", error) + return + } + resolve(url?.absoluteString) + }) + webBrowserAuthSession = authSession + authSession.presentationContextProvider = presentationContextProvider + authSession.prefersEphemeralWebBrowserSession = true + + DispatchQueue.main.async { + authSession.start() + } + } + + @objc + static func requiresMainQueueSetup() -> Bool { + return true + } +} diff --git a/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.pbxproj b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..bdce09ecb07 --- /dev/null +++ b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.pbxproj @@ -0,0 +1,170 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXFileReference section */ + D31047282A81EA8200CD9A8D /* AmplifyRTNWebBrowser-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AmplifyRTNWebBrowser-Bridging-Header.h"; sourceTree = "<group>"; }; + D31047292A81EA8200CD9A8D /* AmplifyRTNWebBrowser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AmplifyRTNWebBrowser.m; sourceTree = "<group>"; }; + D310472D2A81EA8200CD9A8D /* AmplifyRTNWebBrowser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmplifyRTNWebBrowser.swift; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + ); + name = Products; + sourceTree = "<group>"; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + D31047282A81EA8200CD9A8D /* AmplifyRTNWebBrowser-Bridging-Header.h */, + D31047292A81EA8200CD9A8D /* AmplifyRTNWebBrowser.m */, + D310472D2A81EA8200CD9A8D /* AmplifyRTNWebBrowser.swift */, + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = Facebook; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "AmplifyRTNWebBrowser" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ); + }; +/* End PBXProject section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=*]" = arm64; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=*]" = arm64; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "AmplifyRTNWebBrowser" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..94b2795e225 --- /dev/null +++ b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> +</Workspace> diff --git a/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IDEDidComputeMac32BitWarning</key> + <true/> +</dict> +</plist> diff --git a/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcworkspace/contents.xcworkspacedata b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..87165c39338 --- /dev/null +++ b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> + <FileRef + location = "group:/Users/chrfang/Development/amplify-js/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcodeproj"> + </FileRef> +</Workspace> diff --git a/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/packages/rtn-web-browser/ios/AmplifyRTNWebBrowser.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IDEDidComputeMac32BitWarning</key> + <true/> +</dict> +</plist> diff --git a/packages/rtn-web-browser/package.json b/packages/rtn-web-browser/package.json new file mode 100644 index 00000000000..d21ac899ee0 --- /dev/null +++ b/packages/rtn-web-browser/package.json @@ -0,0 +1,47 @@ +{ + "name": "@aws-amplify/rtn-web-browser", + "version": "1.0.0", + "description": "React Native module for aws-amplify web browser", + "main": "./lib/index.js", + "module": "./lib-esm/index.js", + "typings": "./lib-esm/index.d.ts", + "sideEffects": false, + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "tslint 'src/**/*.ts'", + "test:android": "./android/gradlew test -p ./android", + "build-with-test": "npm run clean && npm test && tsc && webpack", + "build:cjs": "node ./build es5 && webpack && webpack --config ./webpack.config.dev.js", + "build:esm": "node ./build es6", + "build:cjs:watch": "node ./build es5 --watch", + "build:esm:watch": "node ./build es6 --watch", + "build": "npm run clean && npm run build:esm && npm run build:cjs", + "clean": "rimraf lib-esm lib dist", + "format": "echo \"Not implemented\"", + "lint": "tslint 'src/**/*.ts' && npm run ts-coverage", + "ts-coverage": "typescript-coverage-report -p ./tsconfig.build.json -t 88.21" + }, + "react-native": { + "./lib/index": "./lib-esm/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/aws-amplify/amplify-js.git" + }, + "author": "Amazon Web Services", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/aws/aws-amplify/issues" + }, + "homepage": "https://docs.amplify.aws/", + "files": [ + "Amplify*.podspec", + "android", + "ios", + "lib", + "lib-esm", + "src" + ] +} diff --git a/packages/rtn-web-browser/src/apis/openAuthSessionAsync.ts b/packages/rtn-web-browser/src/apis/openAuthSessionAsync.ts new file mode 100644 index 00000000000..242ef8a7897 --- /dev/null +++ b/packages/rtn-web-browser/src/apis/openAuthSessionAsync.ts @@ -0,0 +1,71 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AppState, Linking, NativeModules, Platform } from 'react-native'; +import { WebBrowserNativeModule } from '../types'; + +const module: WebBrowserNativeModule = NativeModules.AmplifyRTNWebBrowser; + +let appStateListener; +let redirectListener; + +export const openAuthSessionAsync = async ( + url: string, + redirectSchemes: string[] +) => { + // enforce HTTPS + const httpsUrl = url.replace('http://', 'https://'); + if (Platform.OS === 'ios') { + return module.openAuthSessionAsync(httpsUrl); + } + + if (Platform.OS === 'android') { + return openAuthSessionAndroid(httpsUrl, redirectSchemes); + } +}; + +const openAuthSessionAndroid = async ( + url: string, + redirectSchemes: string[] +) => { + try { + const [redirectUrl] = await Promise.all([ + Promise.race([ + // wait for app to redirect, resulting in a redirectUrl + getRedirectPromise(redirectSchemes), + // wait for app to return some other way, resulting in null + getAppStatePromise(), + ]), + // open chrome tab + module.openAuthSessionAsync(url), + ]); + return redirectUrl; + } finally { + appStateListener.remove(); + redirectListener.remove(); + } +}; + +const getAppStatePromise = (): Promise<null> => + new Promise(resolve => { + appStateListener = AppState.addEventListener('change', nextAppState => { + // if current state is null, the change is from initialization + if (AppState.currentState === null) { + return; + } + + if (nextAppState === 'active') { + appStateListener.remove(); + resolve(null); + } + }); + }); + +const getRedirectPromise = (redirectSchemes: string[]): Promise<string> => + new Promise(resolve => { + redirectListener = Linking.addEventListener('url', event => { + if (redirectSchemes.some(scheme => event.url.startsWith(scheme))) { + resolve(event.url); + } + }); + }); diff --git a/packages/rtn-web-browser/src/index.ts b/packages/rtn-web-browser/src/index.ts new file mode 100644 index 00000000000..0c4976d2fd7 --- /dev/null +++ b/packages/rtn-web-browser/src/index.ts @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { NativeModules } from 'react-native'; +import { WebBrowserNativeModule } from './types'; +import { openAuthSessionAsync } from './apis/openAuthSessionAsync'; + +const module: WebBrowserNativeModule = NativeModules.AmplifyRTNWebBrowser; + +const mergedModule = { + ...module, + openAuthSessionAsync, +}; + +export { mergedModule as AmplifyRTNWebBrowser }; diff --git a/packages/rtn-web-browser/src/types.ts b/packages/rtn-web-browser/src/types.ts new file mode 100644 index 00000000000..5bfeb687488 --- /dev/null +++ b/packages/rtn-web-browser/src/types.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export type WebBrowserNativeModule = { + openAuthSessionAsync: (url: string) => Promise<string | null>; +}; diff --git a/packages/rtn-web-browser/tsconfig.build.json b/packages/rtn-web-browser/tsconfig.build.json new file mode 100644 index 00000000000..af6adca185d --- /dev/null +++ b/packages/rtn-web-browser/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": {}, + "include": ["lib*/**/*.ts", "src"] +} diff --git a/packages/rtn-web-browser/tsconfig.json b/packages/rtn-web-browser/tsconfig.json new file mode 100755 index 00000000000..4b26b84eb1d --- /dev/null +++ b/packages/rtn-web-browser/tsconfig.json @@ -0,0 +1,21 @@ +//WARNING: If you are manually specifying files to compile then the tsconfig.json is completely ignored, you must use command line flags +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "outDir": "./lib/", + "target": "es5", + "noImplicitAny": false, + "lib": ["dom", "es2019", "esnext.asynciterable"], + "sourceMap": true, + "module": "commonjs", + "moduleResolution": "node", + "allowJs": false, + "declaration": true, + "typeRoots": ["./node_modules/@types", "../../node_modules/@types"], + "types": ["node"], + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["src/setupTests.ts"] +} diff --git a/packages/rtn-web-browser/tslint.json b/packages/rtn-web-browser/tslint.json new file mode 100644 index 00000000000..8eafab1d2b4 --- /dev/null +++ b/packages/rtn-web-browser/tslint.json @@ -0,0 +1,50 @@ +{ + "defaultSeverity": "error", + "plugins": ["prettier"], + "extends": [], + "jsRules": {}, + "rules": { + "prefer-const": true, + "max-line-length": [true, 120], + "no-empty-interface": true, + "no-var-keyword": true, + "object-literal-shorthand": true, + "no-eval": true, + "space-before-function-paren": [ + true, + { + "anonymous": "never", + "named": "never" + } + ], + "no-parameter-reassignment": true, + "align": [true, "parameters"], + "no-duplicate-imports": true, + "one-variable-per-declaration": [false, "ignore-for-loop"], + "triple-equals": [true, "allow-null-check"], + "comment-format": [true, "check-space"], + "indent": [false], + "whitespace": [ + false, + "check-branch", + "check-decl", + "check-operator", + "check-preblock" + ], + "eofline": true, + "variable-name": [ + true, + "check-format", + "allow-pascal-case", + "allow-snake-case", + "allow-leading-underscore" + ], + "semicolon": [ + true, + "always", + "ignore-interfaces", + "ignore-bound-class-methods" + ] + }, + "rulesDirectory": [] +} diff --git a/packages/rtn-web-browser/webpack.config.dev.js b/packages/rtn-web-browser/webpack.config.dev.js new file mode 100644 index 00000000000..ae1e99b276e --- /dev/null +++ b/packages/rtn-web-browser/webpack.config.dev.js @@ -0,0 +1,6 @@ +var config = require('./webpack.config.js'); + +var entry = { + 'aws-amplify-rtn-web-browser': './lib-esm/index.js', +}; +module.exports = Object.assign(config, { entry, mode: 'development' }); diff --git a/packages/rtn-web-browser/webpack.config.js b/packages/rtn-web-browser/webpack.config.js new file mode 100644 index 00000000000..ef56bd8b2a1 --- /dev/null +++ b/packages/rtn-web-browser/webpack.config.js @@ -0,0 +1,39 @@ +module.exports = { + entry: { + 'aws-amplify-rtn-web-browser.min': './lib-esm/index.js', + }, + externals: ['react-native'], + output: { + filename: '[name].js', + path: __dirname + '/dist', + library: 'aws_amplify_rtn_web_browser', + libraryTarget: 'umd', + umdNamedDefine: true, + globalObject: 'this', + devtoolModuleFilenameTemplate: require('../aws-amplify/webpack-utils') + .devtoolModuleFilenameTemplate, + }, + // Enable sourcemaps for debugging webpack's output. + devtool: 'source-map', + resolve: { + extensions: ['.js', '.json'], + }, + mode: 'production', + module: { + rules: [ + { + test: /\.js?$/, + exclude: /node_modules/, + use: [ + 'babel-loader', + { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'], + }, + }, + ], + }, + ], + }, +};