commit f68beae024ff76f9f4c051ae1edec49fe8cea484 parent df2bf6e7c1b78d0a49729321651759da9ed3bfc1 Author: gela <gela.malekpour@gmail.com> Date: Thu, 23 Oct 2025 03:13:07 +0000 Bug 1977879 - Add FxRelay from app services to Android Components r=android-reviewers,jonalmeida Differential Revision: https://phabricator.services.mozilla.com/D265859 Diffstat:
15 files changed, 242 insertions(+), 0 deletions(-)
diff --git a/mobile/android/android-components/.buildconfig.yml b/mobile/android/android-components/.buildconfig.yml @@ -1787,6 +1787,7 @@ projects: - components:lib-publicsuffixlist - components:lib-state - components:service-digitalassetlinks + - components:service-firefox-relay - components:service-glean - components:service-location - components:service-nimbus @@ -2050,6 +2051,15 @@ projects: - components:support-test-libstate - components:support-utils - components:tooling-lint + components:service-firefox-relay: + description: A library to communicate with the Relay services API + path: components/service/firefox-relay + publish: false + upstream_dependencies: + - components:concept-base + - components:concept-fetch + - components:support-base + - components:tooling-lint components:service-glean: description: A client-side telemetry SDK for collecting metrics and sending them to the Mozilla telemetry service diff --git a/mobile/android/android-components/components/service/firefox-relay/README.md b/mobile/android/android-components/components/service/firefox-relay/README.md @@ -0,0 +1,20 @@ +# [Android Components](../../../README.md) > Service > Relay + +A library for communicating with the Firefox Relay API. + +## Usage + +### Setting up the dependency + +Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) +([Setup repository](../../../README.md#maven-repository)): + +```Groovy +implementation "org.mozilla.components:service-firefox-relay:{latest-version}" +``` + +## License + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/ diff --git a/mobile/android/android-components/components/service/firefox-relay/build.gradle b/mobile/android/android-components/components/service/firefox-relay/build.gradle @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +buildscript { + repositories { + gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> + maven { + url = repository + if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { + allowInsecureProtocol = true + } + } + } + } + + dependencies { + classpath libs.kotlin.serialization + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +android { + namespace = 'mozilla.components.service.fxrelay' +} + +dependencies { + api ComponentsDependencies.mozilla_appservices_fxrelay + implementation project(':components:support-base') + implementation libs.kotlinx.coroutines.core +} + +apply from: '../../../common-config.gradle' +apply from: '../../../publish.gradle' +ext.configurePublish(config.componentsGroupId, project.name, project.ext.description) diff --git a/mobile/android/android-components/components/service/firefox-relay/lint-baseline.xml b/mobile/android/android-components/components/service/firefox-relay/lint-baseline.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<issues format="6" by="lint 8.13.0" type="baseline" client="gradle" dependencies="false" name="AGP (8.13.0)" variant="all" version="8.13.0"> + +</issues> diff --git a/mobile/android/android-components/components/service/firefox-relay/proguard-rules.pro b/mobile/android/android-components/components/service/firefox-relay/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/mobile/android/android-components/components/service/firefox-relay/src/main/AndroidManifest.xml b/mobile/android/android-components/components/service/firefox-relay/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<manifest /> diff --git a/mobile/android/android-components/components/service/firefox-relay/src/main/java/mozilla/components/service/fxrelay/FxRelay.kt b/mobile/android/android-components/components/service/firefox-relay/src/main/java/mozilla/components/service/fxrelay/FxRelay.kt @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.service.fxrelay + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import mozilla.appservices.relay.RelayAddress +import mozilla.appservices.relay.RelayApiException +import mozilla.appservices.relay.RelayClient +import mozilla.components.support.base.log.logger.Logger + +/** + * Service wrapper for Firefox Relay APIs. + * + * @param serverUrl The base URL of the Firefox Relay service (for example, + * `https://relay.firefox.com`). This defines the endpoint + * that the [RelayClient] will connect to. + * @param authToken An optional authentication token used to authorize API + * requests. If `null` or invalid, calls that require + * authentication will fail gracefully via [handleRelayExceptions]. + */ +class FxRelay( + serverUrl: String, + authToken: String? = null, +) { + private val logger = Logger("FxRelay") + private val client: RelayClient = RelayClient(serverUrl, authToken) + + /** + * Defines supported Relay operations for logging and error handling. + */ + enum class RelayOperation { + CREATE_ADDRESS, + ACCEPT_TERMS, + FETCH_ALL_ADDRESSES, + } + + /** + * Runs a provided [block], handling known [RelayApiException] variants gracefully. + * + * @param operation The [RelayOperation] being performed, included in log output. + * @param fallback A lambda to execute if [block] fails with a [RelayApiException]. + * Typically returns a safe fallback value so callers don't crash. + * @param block A suspendable lambda to execute which may fail with a [RelayApiException]. + * @return The result of [block] if successful, or the result of [fallback] if a [RelayApiException] occurs. + * + * @throws Exception if any unexpected exception (not a [RelayApiException]) is thrown by [block]. + */ + private suspend fun <T> handleRelayExceptions( + operation: RelayOperation, + fallback: () -> T, + block: suspend () -> T, + ): T { + return try { + block() + } catch (e: RelayApiException) { + when (e) { + is RelayApiException.Api -> { + logger.error( + "Relay API error during $operation " + + "(status=${e.status}, code=${e.code}): ${e.detail}", + e, + ) + } + + is RelayApiException.Network -> { + logger.error("Relay network error during $operation: ${e.reason}", e) + } + + is RelayApiException.Other -> { + logger.error("Unexpected Relay error during $operation: ${e.reason}", e) + } + } + fallback() + } + } + + /** + * Accept the Relay terms of service. + */ + suspend fun acceptTerms() = withContext(Dispatchers.IO) { + handleRelayExceptions(RelayOperation.ACCEPT_TERMS, { false }) { + client.acceptTerms() + true + } + } + + /** + * Create a new Relay address. + * + * @param description description for the address + * @param generatedFor where this alias is generated for + * @param usedOn where this alias will be used + */ + suspend fun createAddress( + description: String, + generatedFor: String, + usedOn: String, + ): RelayAddress? = withContext(Dispatchers.IO) { + handleRelayExceptions(RelayOperation.CREATE_ADDRESS, { null }) { + client.createAddress(description, generatedFor, usedOn) + } + } + + /** + * Fetch all Relay addresses. + */ + suspend fun fetchAllAddresses(): List<RelayAddress> = withContext(Dispatchers.IO) { + handleRelayExceptions( + RelayOperation.FETCH_ALL_ADDRESSES, { emptyList() }, + ) { + client.fetchAddresses() + } + } +} diff --git a/mobile/android/android-components/components/service/firefox-relay/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/mobile/android/android-components/components/service/firefox-relay/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1,2 @@ +mock-maker-inline +// This allows mocking final classes (classes are final by default in Kotlin) diff --git a/mobile/android/android-components/components/service/firefox-relay/src/test/resources/robolectric.properties b/mobile/android/android-components/components/service/firefox-relay/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +sdk=35 diff --git a/mobile/android/android-components/components/support/appservices/build.gradle b/mobile/android/android-components/components/support/appservices/build.gradle @@ -14,6 +14,7 @@ dependencies { api project(':components:support-base') // Log.Priority is in the public api. implementation ComponentsDependencies.mozilla_appservices_errorsupport + implementation ComponentsDependencies.mozilla_appservices_fxrelay implementation (ComponentsDependencies.mozilla_appservices_httpconfig) { // Override the version of concept-fetch that A-S depends on, // since we want to replace it with our own. diff --git a/mobile/android/android-components/plugins/dependencies/src/main/java/DependenciesPlugin.kt b/mobile/android/android-components/plugins/dependencies/src/main/java/DependenciesPlugin.kt @@ -257,4 +257,5 @@ object ComponentsDependencies { val mozilla_appservices_errorsupport = "${ApplicationServicesConfig.groupId}:errorsupport:${ApplicationServicesConfig.version}" val mozilla_appservices_rust_log_forwarder = "${ApplicationServicesConfig.groupId}:rust-log-forwarder:${ApplicationServicesConfig.version}" val mozilla_appservices_sync15 = "${ApplicationServicesConfig.groupId}:sync15:${ApplicationServicesConfig.version}" + val mozilla_appservices_fxrelay = "${ApplicationServicesConfig.groupId}:relay:${ApplicationServicesConfig.version}" } diff --git a/mobile/android/android-components/samples/browser/build.gradle b/mobile/android/android-components/samples/browser/build.gradle @@ -116,6 +116,7 @@ dependencies { implementation project(':components:lib-fetch-httpurlconnection') implementation project(":components:lib-publicsuffixlist") implementation project(':components:service-digitalassetlinks') + implementation project(":components:service-firefox-relay") // Add a dependency on glean to simplify the testing workflow // for engineers that want to test Gecko metrics exfiltrated via the Glean // SDK. See bug 1592935 for more context. diff --git a/mobile/android/android-components/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt b/mobile/android/android-components/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt @@ -84,6 +84,7 @@ import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.service.digitalassetlinks.local.StatementApi import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker +import mozilla.components.service.fxrelay.FxRelay import mozilla.components.service.location.LocationService import mozilla.components.service.sync.logins.SyncableLoginsStorage import mozilla.components.support.base.android.NotificationsDelegate @@ -271,6 +272,10 @@ open class DefaultComponents(private val applicationContext: Context) { StatementRelationChecker(StatementApi(client)) } + val relayService by lazy { + FxRelay("https://relay.firefox.com") + } + // Intent val tabIntentProcessor by lazy { TabIntentProcessor(tabsUseCases, searchUseCases.newTabSearch) @@ -349,6 +354,16 @@ open class DefaultComponents(private val applicationContext: Context) { SimpleBrowserMenuItem("Restore after crash") { sessionUseCases.crashRecovery.invoke() }, + SimpleBrowserMenuItem("Relay") { + MainScope().launch { + val addressList = relayService.fetchAllAddresses() + Toast.makeText( + applicationContext, + "Fetched ${addressList.size} addresses", + Toast.LENGTH_SHORT, + ).show() + } + }, BrowserMenuDivider(), ) diff --git a/mobile/android/android-components/samples/browser/src/main/java/org/mozilla/samples/browser/SampleApplication.kt b/mobile/android/android-components/samples/browser/src/main/java/org/mozilla/samples/browser/SampleApplication.kt @@ -21,6 +21,7 @@ import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.base.log.sink.AndroidLogSink import mozilla.components.support.ktx.android.content.isMainProcess import mozilla.components.support.ktx.android.content.runOnlyInMainProcess +import mozilla.components.support.rusthttp.RustHttpConfig import mozilla.components.support.rustlog.RustLog import mozilla.components.support.webextensions.WebExtensionSupport import mozilla.telemetry.glean.BuildInfo @@ -52,6 +53,7 @@ class SampleApplication : Application() { override fun onCreate() { super.onCreate() + RustHttpConfig.setClient(lazy { components.client }) RustLog.enable() Log.addSink(AndroidLogSink()) diff --git a/taskcluster/config.yml b/taskcluster/config.yml @@ -304,6 +304,7 @@ treeherder: 'service-digitalassetlinks': 'service-digitalassetlinks' 'service-experiments': 'service-experiments' 'service-firefox-accounts': 'service-firefox-accounts' + 'service-firefox-relay': 'service-firefox-relay' 'service-fretboard': 'service-fretboard' 'service-glean': 'service-glean' 'service-location': 'service-location'