diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index b56cc434c..bfac06236 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -37,6 +37,7 @@ import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.ext.settings import org.mozilla.fenix.push.PushFxaIntegration +import org.mozilla.fenix.push.WebPushEngineIntegration import org.mozilla.fenix.session.NotificationSessionObserver import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks import org.mozilla.fenix.session.VisibilityLifecycleCallback @@ -182,6 +183,9 @@ open class FenixApplication : LocaleAwareApplication() { // Install the AutoPush singleton to receive messages. PushProcessor.install(it) + // WebPush integration to observe and deliver push messages to engine. + WebPushEngineIntegration(components.core.engine, it).start() + // Perform a one-time initialization of the account manager if a message is received. PushFxaIntegration(it, lazy { components.backgroundServices.accountManager }).launch() diff --git a/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt b/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt new file mode 100644 index 000000000..7a7f2d512 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt @@ -0,0 +1,119 @@ +/* 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 org.mozilla.fenix.push + +import android.util.Base64 +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import mozilla.components.concept.engine.Engine +import mozilla.components.concept.engine.webpush.WebPushDelegate +import mozilla.components.concept.engine.webpush.WebPushHandler +import mozilla.components.concept.engine.webpush.WebPushSubscription +import mozilla.components.feature.push.AutoPushFeature +import mozilla.components.feature.push.AutoPushSubscription +import mozilla.components.feature.push.PushScope +import mozilla.components.support.base.log.logger.Logger + +/** + * Engine integration with the push feature to enable WebPush support. + */ +class WebPushEngineIntegration( + private val engine: Engine, + private val pushFeature: AutoPushFeature +) : AutoPushFeature.Observer { + + private var handler: WebPushHandler? = null + private val delegate = WebPushEngineDelegate(pushFeature) + + fun start() { + handler = engine.registerWebPushDelegate(delegate) + + pushFeature.register(this) + } + + fun stop() { + pushFeature.unregister(this) + } + + override fun onMessageReceived(scope: PushScope, message: ByteArray?) { + CoroutineScope(Dispatchers.Main).launch { + handler?.onPushMessage(scope, message) + } + } + + override fun onSubscriptionChanged(scope: PushScope) { + CoroutineScope(Dispatchers.Main).launch { + handler?.onSubscriptionChanged(scope) + } + } +} + +internal class WebPushEngineDelegate( + private val pushFeature: AutoPushFeature +) : WebPushDelegate { + private val logger = Logger("WebPushEngineDelegate") + + override fun onGetSubscription(scope: String, onSubscription: (WebPushSubscription?) -> Unit) { + // We don't have the appServerKey unless an app is creating a new subscription so we + // allow the key to be null since it won't be overridden from a previous subscription. + pushFeature.subscribe( + scope = scope, + onSubscribeError = { + logger.error("Error on push onGetSubscription.") + onSubscription(null) + }, + onSubscribe = { subscription -> + onSubscription(subscription.toEnginePushSubscription()) + }) + } + + override fun onSubscribe( + scope: String, + serverKey: ByteArray?, + onSubscribe: (WebPushSubscription?) -> Unit + ) { + pushFeature.subscribe( + scope = scope, + // See the full note at the implementation of `toEnginePushSubscription`. + // Issue: https://github.com/mozilla/application-services/issues/2698 + appServerKey = null, + onSubscribeError = { + logger.error("Error on push onSubscribe.") + onSubscribe(null) + }, + onSubscribe = { subscription -> + onSubscribe(subscription.toEnginePushSubscription()) + }) + } + + override fun onUnsubscribe(scope: String, onUnsubscribe: (Boolean) -> Unit) { + pushFeature.unsubscribe( + scope = scope, + onUnsubscribeError = { + logger.error("Error on push onUnsubscribe.") + onUnsubscribe(false) + }, + onUnsubscribe = { result -> + onUnsubscribe(result) + }) + } +} + +internal fun AutoPushSubscription.toEnginePushSubscription() = WebPushSubscription( + scope = this.scope, + publicKey = this.publicKey.toDecodedByteArray(), + endpoint = this.endpoint, + authSecret = this.authKey.toDecodedByteArray(), + // We don't send the `serverKey` because the code path from that will query + // the push database for this key, which leads to an exception thrown. + // Our workaround for now is to not put the server key in to begin with (which + // will probably break a lot of sites). + // See: https://github.com/mozilla/application-services/issues/2698 + appServerKey = null +) + +private fun String.toDecodedByteArray() = + Base64.decode(this.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)