24
1
Fork 0

Implement the mechanics of fxa login on android, but don't show ui fo… (#1000)

* Implement the mechanics of fxa login on android, but don't show ui for it yet. Also, scopedKeys are not yet implemented.

* Hopefully fix the package-lock conflict?

* WIP on android scoped keys

* Finish implementing login.

* created android/user.js to handle android logins
This commit is contained in:
Donovan Preston 2018-11-08 16:35:19 -05:00 committed by GitHub
parent ffac4ae5b1
commit cab6f1bafb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 592 additions and 304 deletions

View File

@ -1,13 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4"> <module external.linked.project.id="android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">

View File

@ -1,4 +1,4 @@
/* global window */ /* global window, navigator */
window.LIMITS = { window.LIMITS = {
ANON: { ANON: {
@ -27,14 +27,18 @@ const locale = require('../common/locales');
const home = require('../app/ui/home'); const home = require('../app/ui/home');
const app = choo(); const app = choo();
if (navigator.userAgent === 'Send Android') {
assets.setPrefix('/android_asset');
}
function body(main) { function body(main) {
return function(state, emit) { return function(state, emit) {
return html`<body class="flex flex-col items-center font-sans bg-blue-lightest md:h-screen md:bg-grey-lightest"> return html`<body class="flex flex-col items-center font-sans bg-blue-lightest md:h-screen md:bg-grey-lightest">
${header(state, emit)} <a id="hamburger" class="absolute pin-t pin-r z-50" href="#" onclick=${clickPreferences}>
<a id="hamburger" class="absolute pin-t pin-r z-50" href="#" onclick=${clickPreferences}> <img src=${assets.get('preferences.png')} />
<img src=${assets.get('preferences.png')} /> </a>
</a> ${header(state, emit)}
${main(state, emit)} ${main(state, emit)}
</body>`; </body>`;
function clickPreferences(event) { function clickPreferences(event) {
@ -44,15 +48,22 @@ function body(main) {
}; };
} }
app.use(require('./stores/state').default);
app.use((state, emitter) => { app.use((state, emitter) => {
state.translate = locale.getTranslator(); state.translate = locale.getTranslator();
state.capabilities = {}; //TODO state.capabilities = {
account: true
}; //TODO
window.finishLogin = async function(accountInfo) {
await state.user.finishLogin(accountInfo);
emitter.emit('render');
};
// for debugging // for debugging
window.appState = state; window.appState = state;
window.appEmit = emitter.emit.bind(emitter); window.appEmit = emitter.emit.bind(emitter);
}); });
app.use(require('./stores/state').default);
app.use(require('../app/fileManager').default); app.use(require('../app/fileManager').default);
app.use(require('./stores/intents').default); app.use(require('./stores/intents').default);
app.route('/', body(home)); app.route('/', body(home));

View File

@ -51,19 +51,19 @@
</facet> </facet>
</component> </component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> <output url="file://$MODULE_DIR$/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" /> <output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/compileDebugUnitTestJavaWithJavac/classes" />
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/not_namespaced_r_class_sources/debug/processDebugResources/r" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/not_namespaced_r_class_sources/debugAndroidTest/processDebugAndroidTestResources/r" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
@ -112,31 +112,40 @@
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/.DS_Store" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotation_processor_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/apk_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/build-info" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/build-info" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-libraries" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/checkDebugClasspath" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/compatible_screen_manifest" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-apk" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-apk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-main-apk-res" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_main_apk_resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaPrecompile" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_merged_manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_split_apk_resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javac" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint_jar" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifest-checker" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifest-checker" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/processed_res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/resources" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shader_assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split_list" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/splits-support" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/splits-support" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
@ -146,9 +155,11 @@
</content> </content>
<orderEntry type="jdk" jdkName="Android API 27 Platform" jdkType="Android SDK" /> <orderEntry type="jdk" jdkName="Android API 27 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: net.java.dev.jna:jna-4.5.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:runner-1.0.2" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:runner-1.0.2" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.0@jar" level="project" /> <orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-annotations:27.1.1@jar" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-annotations:27.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.2.61@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable-27.1.1" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-compat-27.1.1" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-compat-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel-1.1.0" level="project" /> <orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel-1.1.0" level="project" />
@ -162,21 +173,26 @@
<orderEntry type="library" name="Gradle: com.github.delight-im:Android-AdvancedWebView-v3.0.0" level="project" /> <orderEntry type="library" name="Gradle: com.github.delight-im:Android-AdvancedWebView-v3.0.0" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-core-3.0.2" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-core-3.0.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: javax.inject:javax.inject:1@jar" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-android:0.23.4@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-fragment-27.1.1" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:support-fragment-27.1.1" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: junit:junit:4.12@jar" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: junit:junit:4.12@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:runtime-1.1.0" level="project" /> <orderEntry type="library" name="Gradle: android.arch.core:runtime-1.1.0" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.61@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-core:1.3@jar" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-core:1.3@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:monitor-1.0.2" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:monitor-1.0.2" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7-27.1.1" level="project" /> <orderEntry type="library" name="Gradle: com.android.support:appcompat-v7-27.1.1" level="project" />
<orderEntry type="library" name="Gradle: org.mozilla.components:service-firefox-accounts-0.26.0" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-idling-resource-3.0.2" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-idling-resource-3.0.2" level="project" />
<orderEntry type="library" name="Gradle: com.android.support.constraint:constraint-layout-solver:1.1.2@jar" level="project" /> <orderEntry type="library" name="Gradle: com.android.support.constraint:constraint-layout-solver:1.1.2@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.2.60@jar" level="project" /> <orderEntry type="library" name="Gradle: org.mozilla.fxa_client:fxa_client-0.5.1" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core-1.1.0" level="project" /> <orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core-1.1.0" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-core-common:0.23.4@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-library:1.3@jar" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-library:1.3@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-integration:1.3@jar" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-integration:1.3@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@jar" level="project" /> <orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:atomicfu-common:0.10.3@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:common:1.1.0@jar" level="project" /> <orderEntry type="library" name="Gradle: android.arch.core:common:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.60@jar" level="project" /> <orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.2.61@jar" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: net.sf.kxml:kxml2:2.3.0@jar" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: net.sf.kxml:kxml2:2.3.0@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime-1.1.0" level="project" /> <orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime-1.1.0" level="project" />
</component> </component>

View File

@ -31,6 +31,7 @@ dependencies {
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.github.delight-im:Android-AdvancedWebView:v3.0.0' implementation 'com.github.delight-im:Android-AdvancedWebView:v3.0.0'
implementation "org.mozilla.components:service-firefox-accounts:${rootProject.ext.android_components_version}"
} }
task generateAndLinkBundle(type: Exec, description: 'Generate the android.js bundle and link it into the assets directory') { task generateAndLinkBundle(type: Exec, description: 'Generate the android.js bundle and link it into the assets directory') {

View File

@ -8,7 +8,6 @@
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">

View File

@ -5,6 +5,7 @@ import android.support.v7.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import im.delight.android.webview.AdvancedWebView import im.delight.android.webview.AdvancedWebView
import android.graphics.Bitmap import android.graphics.Bitmap
import android.content.Context
import android.content.Intent import android.content.Intent
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
@ -13,7 +14,13 @@ import android.webkit.WebMessage
import android.util.Log import android.util.Log
import android.util.Base64 import android.util.Base64
import android.webkit.ConsoleMessage import android.webkit.ConsoleMessage
import android.webkit.JavascriptInterface
import android.webkit.WebChromeClient import android.webkit.WebChromeClient
import mozilla.components.service.fxa.Config
import mozilla.components.service.fxa.FirefoxAccount
import mozilla.components.service.fxa.OAuthInfo
import mozilla.components.service.fxa.Profile
import mozilla.components.service.fxa.FxaResult
internal class LoggingWebChromeClient : WebChromeClient() { internal class LoggingWebChromeClient : WebChromeClient() {
override fun onConsoleMessage(cm: ConsoleMessage): Boolean { override fun onConsoleMessage(cm: ConsoleMessage): Boolean {
@ -23,9 +30,18 @@ internal class LoggingWebChromeClient : WebChromeClient() {
} }
} }
class WebAppInterface(private val mContext: MainActivity) {
@JavascriptInterface
fun beginOAuthFlow() {
mContext.beginOAuthFlow();
}
}
class MainActivity : AppCompatActivity(), AdvancedWebView.Listener { class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
private var mWebView: AdvancedWebView? = null private var mWebView: AdvancedWebView? = null
private var mToShare: String? = null private var mToShare: String? = null
private var mToCall: String? = null
private var mAccount: FirefoxAccount? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -34,14 +50,17 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
mWebView = findViewById<WebView>(R.id.webview) as AdvancedWebView mWebView = findViewById<WebView>(R.id.webview) as AdvancedWebView
mWebView!!.setListener(this, this) mWebView!!.setListener(this, this)
mWebView!!.setWebChromeClient(LoggingWebChromeClient()) mWebView!!.setWebChromeClient(LoggingWebChromeClient())
mWebView!!.addJavascriptInterface(WebAppInterface(this), "Android")
val webSettings = mWebView!!.getSettings() val webSettings = mWebView!!.getSettings()
webSettings.setUserAgentString("Send Android")
webSettings.setAllowUniversalAccessFromFileURLs(true) webSettings.setAllowUniversalAccessFromFileURLs(true)
webSettings.setJavaScriptEnabled(true) webSettings.setJavaScriptEnabled(true)
val intent = getIntent() val intent = getIntent()
val action = intent.getAction() val action = intent.getAction()
val type = intent.getType() val type = intent.getType()
if (Intent.ACTION_SEND.equals(action) && type != null) { if (Intent.ACTION_SEND.equals(action) && type != null) {
if (type.equals("text/plain")) { if (type.equals("text/plain")) {
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT) val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
@ -51,12 +70,25 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
val imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri val imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri
Log.w("INTENT", "image/ " + imageUri) Log.w("INTENT", "image/ " + imageUri)
mToShare = "data:text/plain;base64," + Base64.encodeToString(imageUri.path.toByteArray(), 16).trim() mToShare = "data:text/plain;base64," + Base64.encodeToString(imageUri.path.toByteArray(), 16).trim()
// TODO Currently this causes a Permission Denied error
// val stream = contentResolver.openInputStream(imageUri)
} }
} }
mWebView!!.loadUrl("file:///android_asset/android.html") mWebView!!.loadUrl("file:///android_asset/android.html")
}
fun beginOAuthFlow() {
Config.custom("https://send-fxa.dev.lcip.org").then(fun (value: Config): FxaResult<Unit> {
mAccount = FirefoxAccount(value, "12cc4070a481bc73", "fxaclient://android.redirect")
mAccount?.beginOAuthFlow(arrayOf("profile", "https://identity.mozilla.com/apps/send"), true)?.then(fun (url: String): FxaResult<Unit> {
Log.w("CONFIG", "GOT A URL " + url)
this@MainActivity.runOnUiThread({
mWebView!!.loadUrl(url)
})
return FxaResult.fromValue(Unit)
})
Log.w("CONFIG", "CREATED FIREFOXACCOUNT")
return FxaResult.fromValue(Unit)
})
} }
@SuppressLint("NewApi") @SuppressLint("NewApi")
@ -94,7 +126,48 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
} }
override fun onPageStarted(url: String, favicon: Bitmap?) { override fun onPageStarted(url: String, favicon: Bitmap?) {
if (url.startsWith("fxaclient")) {
// We load this here so the user doesn't see an ugly screen that says "can't handle fxaclient urls"...
mWebView!!.loadUrl("file:///android_asset/android.html")
val parsed = Uri.parse(url)
val code = parsed.getQueryParameter("code")
val state = parsed.getQueryParameter("state")
code?.let { code ->
state?.let { state ->
mAccount?.completeOAuthFlow(code, state)?.whenComplete { info ->
//displayAndPersistProfile(code, state)
val profile = mAccount?.getProfile(false)?.then(fun (profile: Profile): FxaResult<Unit> {
val accessToken = info.accessToken
val keys = info.keys
val avatar = profile.avatar
val displayName = profile.displayName
val email = profile.email
val uid = profile.uid
val toPass = "{\"accessToken\": \"${accessToken}}\", \"keys\": '${keys}', \"avatar\": \"${avatar}\", \"displayName\": \"${displayName}\", \"email\": \"${email}\", \"uid\": \"${uid}\"}"
mToCall = "finishLogin(${toPass})"
this@MainActivity.runOnUiThread({
// But then we also reload this here because we need to make sure onPageFinished runs after mToCall has been set.
// We can't guarantee that onPageFinished has already been called at this point.
mWebView!!.loadUrl("file:///android_asset/android.html")
})
return FxaResult.fromValue(Unit)
})
// TODO get k from it.keys
// TODO get profile from mAccount.getProfile
// TODO get access_token
//mToShare = "data:text/plain;base64," + Base64.encodeToString(toSend.toByteArray(), 16).trim()
}
}
}
}
Log.w("MAIN", "onPageStarted"); Log.w("MAIN", "onPageStarted");
// account.completeOAuthFlow()
} }
override fun onPageFinished(url: String) { override fun onPageFinished(url: String) {
@ -102,14 +175,22 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
if (mToShare != null) { if (mToShare != null) {
Log.w("INTENT", mToShare) Log.w("INTENT", mToShare)
val webView = findViewById<WebView>(R.id.webview) as AdvancedWebView mWebView?.postWebMessage(WebMessage(mToShare), Uri.EMPTY)
webView.postWebMessage(WebMessage(mToShare), Uri.EMPTY) mToShare = null
} }
if (mToCall != null) {
this@MainActivity.runOnUiThread({
mWebView?.evaluateJavascript(mToCall, fun (value: String) {
// noop
})
})
mToCall = null
}
} }
override fun onPageError(errorCode: Int, description: String, failingUrl: String) { override fun onPageError(errorCode: Int, description: String, failingUrl: String) {
Log.w("MAIN", "onPageError") Log.w("MAIN", "onPageError " + description)
} }
override fun onDownloadRequested(url: String, suggestedFilename: String, mimeType: String, contentLength: Long, contentDisposition: String, userAgent: String) { override fun onDownloadRequested(url: String, suggestedFilename: String, mimeType: String, contentLength: Long, contentDisposition: String, userAgent: String) {

View File

@ -1,7 +1,7 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>

View File

@ -2,12 +2,13 @@
buildscript { buildscript {
ext.kotlin_version = '1.2.60' ext.kotlin_version = '1.2.60'
ext.android_components_version = '0.26.0'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.4' classpath 'com.android.tools.build:gradle:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.60" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.60"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@ -18,6 +19,9 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
maven {
url "https://maven.mozilla.org/maven2"
}
jcenter() jcenter()
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
} }

View File

@ -3,7 +3,7 @@ const path = require('path');
child_process.execSync('npm run build'); child_process.execSync('npm run build');
child_process.execSync( child_process.execSync(
`cp -R ${path.resolve(__dirname, '../dist')} ${path.resolve( `cp -R ${path.resolve(__dirname, '../dist/*')} ${path.resolve(
__dirname, __dirname,
'app/src/main/assets' 'app/src/main/assets'
)}` )}`

View File

@ -1,6 +1,6 @@
#Wed May 23 21:14:24 EDT 2018 #Thu Oct 11 12:20:52 EDT 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip

View File

@ -1,6 +1,6 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import User from '../../app/user'; import User from '../user';
import storage from '../../app/storage'; import storage from '../../app/storage';
export default function initialState(state, emitter) { export default function initialState(state, emitter) {

26
android/user.js Normal file
View File

@ -0,0 +1,26 @@
/* global Android */
import User from '../app/user';
import { deriveFileListKey } from '../app/fxa';
export default class AndroidUser extends User {
constructor(storage) {
super(storage);
}
async login() {
Android.beginOAuthFlow();
}
async finishLogin(accountInfo) {
const jwks = JSON.parse(accountInfo.keys);
const ikm = jwks['https://identity.mozilla.com/apps/send'].k;
const profile = {
displayName: accountInfo.displayName,
email: accountInfo.email,
avatar: accountInfo.avatar,
access_token: accountInfo.accessToken
};
profile.fileListKey = await deriveFileListKey(ikm);
this.info = profile;
}
}

View File

@ -146,12 +146,10 @@ export async function preparePkce(storage) {
return arrayToB64(new Uint8Array(challenge)); return arrayToB64(new Uint8Array(challenge));
} }
export async function getFileListKey(storage, bundle) { export async function deriveFileListKey(ikm) {
const jwks = await decryptBundle(storage, bundle);
const jwk = jwks['https://identity.mozilla.com/apps/send'];
const baseKey = await crypto.subtle.importKey( const baseKey = await crypto.subtle.importKey(
'raw', 'raw',
b64ToArray(jwk.k), b64ToArray(ikm),
{ name: 'HKDF' }, { name: 'HKDF' },
false, false,
['deriveKey'] ['deriveKey']
@ -174,3 +172,9 @@ export async function getFileListKey(storage, bundle) {
const rawFileListKey = await crypto.subtle.exportKey('raw', fileListKey); const rawFileListKey = await crypto.subtle.exportKey('raw', fileListKey);
return arrayToB64(new Uint8Array(rawFileListKey)); return arrayToB64(new Uint8Array(rawFileListKey));
} }
export async function getFileListKey(storage, bundle) {
const jwks = await decryptBundle(storage, bundle);
const jwk = jwks['https://identity.mozilla.com/apps/send'];
return deriveFileListKey(jwk.k);
}

View File

@ -16,28 +16,37 @@ export default class User {
this.data = storage.user || {}; this.data = storage.user || {};
} }
get info() {
return this.data || this.storage.user || {};
}
set info(data) {
this.data = data;
this.storage.user = data;
}
get avatar() { get avatar() {
const defaultAvatar = assets.get('user.svg'); const defaultAvatar = assets.get('user.svg');
if (this.data.avatarDefault) { if (this.info.avatarDefault) {
return assets.get('firefox_logo-only.svg'); return assets.get('firefox_logo-only.svg');
} }
return this.data.avatar || defaultAvatar; return this.info.avatar || defaultAvatar;
} }
get name() { get name() {
return this.data.displayName; return this.info.displayName;
} }
get email() { get email() {
return this.data.email; return this.info.email;
} }
get loggedIn() { get loggedIn() {
return !!this.data.access_token; return !!this.info.access_token;
} }
get bearerToken() { get bearerToken() {
return this.data.access_token; return this.info.access_token;
} }
get maxSize() { get maxSize() {
@ -104,15 +113,13 @@ export default class User {
const userInfo = await infoResponse.json(); const userInfo = await infoResponse.json();
userInfo.access_token = auth.access_token; userInfo.access_token = auth.access_token;
userInfo.fileListKey = await getFileListKey(this.storage, auth.keys_jwe); userInfo.fileListKey = await getFileListKey(this.storage, auth.keys_jwe);
this.storage.user = userInfo; this.info = userInfo;
this.data = userInfo;
this.storage.remove('pkceVerifier'); this.storage.remove('pkceVerifier');
} }
logout() { logout() {
this.storage.user = null;
this.storage.clearLocalFiles(); this.storage.clearLocalFiles();
this.data = {}; this.info = {};
} }
async syncFileList() { async syncFileList() {
@ -124,7 +131,7 @@ export default class User {
try { try {
const encrypted = await getFileList(this.bearerToken); const encrypted = await getFileList(this.bearerToken);
const decrypted = await streamToArrayBuffer( const decrypted = await streamToArrayBuffer(
decryptStream(encrypted, b64ToArray(this.data.fileListKey)) decryptStream(encrypted, b64ToArray(this.info.fileListKey))
); );
list = JSON.parse(textDecoder.decode(decrypted)); list = JSON.parse(textDecoder.decode(decrypted));
} catch (e) { } catch (e) {
@ -142,7 +149,7 @@ export default class User {
textEncoder.encode(JSON.stringify(this.storage.files)) textEncoder.encode(JSON.stringify(this.storage.files))
]); ]);
const encrypted = await streamToArrayBuffer( const encrypted = await streamToArrayBuffer(
encryptStream(blobStream(blob), b64ToArray(this.data.fileListKey)) encryptStream(blobStream(blob), b64ToArray(this.info.fileListKey))
); );
await setFileList(this.bearerToken, encrypted); await setFileList(this.bearerToken, encrypted);
} catch (e) { } catch (e) {
@ -152,6 +159,6 @@ export default class User {
} }
toJSON() { toJSON() {
return this.data; return this.info;
} }
} }

View File

@ -1,6 +1,6 @@
const genmap = require('../build/generate_asset_map'); const genmap = require('../build/generate_asset_map');
const isServer = typeof genmap === 'function'; const isServer = typeof genmap === 'function';
const prefix = ''; let prefix = '';
let manifest = {}; let manifest = {};
try { try {
//eslint-disable-next-line node/no-missing-require //eslint-disable-next-line node/no-missing-require
@ -15,6 +15,10 @@ function getAsset(name) {
return prefix + assets[name]; return prefix + assets[name];
} }
function setPrefix(name) {
prefix = name;
}
function getMatches(match) { function getMatches(match) {
return Object.keys(assets) return Object.keys(assets)
.filter(k => match.test(k)) .filter(k => match.test(k))
@ -22,6 +26,7 @@ function getMatches(match) {
} }
const instance = { const instance = {
setPrefix: setPrefix,
get: getAsset, get: getAsset,
match: getMatches, match: getMatches,
setMiddleware: function(middleware) { setMiddleware: function(middleware) {

630
package-lock.json generated

File diff suppressed because it is too large Load Diff