1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-11-09 12:32:30 +01:00

Install background message handler once

This commit is contained in:
Allan Wang 2023-06-19 02:06:49 -07:00
parent dc3126b622
commit 240dc864e1
No known key found for this signature in database
GPG Key ID: C93E3F9C679D7A56
9 changed files with 127 additions and 95 deletions

View File

@ -25,6 +25,7 @@ import com.pitchedapps.frost.db.FrostDb
import com.pitchedapps.frost.ext.FrostAccountId
import com.pitchedapps.frost.ext.idData
import com.pitchedapps.frost.ext.launchActivity
import com.pitchedapps.frost.extension.FrostCoreExtension
import com.pitchedapps.frost.main.MainActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -45,8 +46,8 @@ import kotlinx.coroutines.withContext
class StartActivity : AppCompatActivity(), CoroutineScope by MainScope() {
@Inject lateinit var frostDb: FrostDb
@Inject lateinit var dataStore: FrostDataStore
@Inject lateinit var frostCoreExtension: FrostCoreExtension
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -54,7 +55,9 @@ class StartActivity : AppCompatActivity(), CoroutineScope by MainScope() {
launch {
val id = withContext(Dispatchers.IO) { getCurrentAccountId() }
logger.atInfo().log("Starting Frost with id %d", id)
frostCoreExtension.install()
logger.atInfo().log("Starting Frost with id %s", id)
launchActivity<MainActivity>(
intentBuilder = {

View File

@ -1,42 +0,0 @@
/*
* Copyright 2023 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.pitchedapps.frost.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalContext
import com.pitchedapps.frost.ext.components
import com.pitchedapps.frost.extension.FrostCoreExtension
/** Disposable effect to attack [FrostCoreExtension]. */
@Composable
fun FrostCoreExtensionEffect() {
val components = LocalContext.current.components
DisposableEffect(components.core.store) {
val feature =
FrostCoreExtension(
runtime = components.core.engine,
store = components.core.store,
converter = components.extensionModelConverter,
)
feature.start()
onDispose { feature.stop() }
}
}

View File

@ -16,18 +16,18 @@
*/
package com.pitchedapps.frost.extension
import androidx.lifecycle.LifecycleOwner
import com.google.common.flogger.FluentLogger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.mapNotNull
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.concept.engine.webextension.Port
import mozilla.components.concept.engine.webextension.WebExtensionRuntime
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.lib.state.ext.flow
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import mozilla.components.support.webextensions.WebExtensionController
import org.json.JSONObject
@ -39,49 +39,56 @@ import org.json.JSONObject
*
* https://github.com/mozilla-mobile/android-components/blob/main/components/feature/accounts/src/main/java/mozilla/components/feature/accounts/FxaWebChannelFeature.kt
*/
class FrostCoreExtension(
private val customTabSessionId: String? = null,
private val runtime: WebExtensionRuntime,
@Singleton
class FrostCoreExtension
@Inject
internal constructor(
private val engine: Engine,
private val store: BrowserStore,
private val converter: ExtensionModelConverter,
) : LifecycleAwareFeature {
) {
private val extensionController =
WebExtensionController(
WEB_CHANNEL_EXTENSION_ID,
WEB_CHANNEL_EXTENSION_URL,
WEB_CHANNEL_MESSAGING_ID
WEB_CHANNEL_MESSAGING_ID,
)
private var scope: CoroutineScope? = null
override fun start() {
logger.atInfo().log("start")
fun install() {
logger.atInfo().log("extension background start")
val messageHandler = FrostBackgroundMessageHandler()
extensionController.registerBackgroundMessageHandler(
messageHandler,
WEB_CHANNEL_BACKGROUND_MESSAGING_ID,
)
extensionController.install(runtime)
scope =
store.flowScoped { flow ->
flow
.mapNotNull { state -> state.findCustomTabOrSelectedTab(customTabSessionId) }
.ifChanged { it.engineState.engineSession }
.collect {
it.engineState.engineSession?.let { engineSession ->
logger.atInfo().log("Register content message handler ${it.id}")
registerContentMessageHandler(engineSession)
}
}
}
extensionController.install(
engine,
onSuccess = {
logger.atInfo().log("extension install success")
extensionController.sendBackgroundMessage(
JSONObject().apply { put("test", 0) },
WEB_CHANNEL_BACKGROUND_MESSAGING_ID
)
},
onError = { t -> logger.atWarning().withCause(t).log("extension install failure") },
)
}
override fun stop() {
logger.atInfo().log("stop")
scope?.cancel()
suspend fun installContent(owner: LifecycleOwner? = null, customTabSessionId: String? = null) {
logger.atInfo().log("extension content start")
store
.flow(owner)
.mapNotNull { state -> state.findCustomTabOrSelectedTab(customTabSessionId) }
.ifChanged { it.engineState.engineSession }
.collect {
it.engineState.engineSession?.let { engineSession ->
logger.atInfo().log("Register content message handler ${it.id}")
registerContentMessageHandler(engineSession)
}
}
}
private fun registerContentMessageHandler(engineSession: EngineSession) {

View File

@ -154,7 +154,7 @@ object FrostModule {
if (action is EngineAction.LoadUrlAction) {
logger.atInfo().log("BrowserAction: LoadUrlAction %s", action.url)
} else {
logger.atFine().log("BrowserAction: %s - %s", action::class.simpleName, action)
logger.atInfo().log("BrowserAction: %s - %s", action::class.simpleName, action)
}
next(action)
}

View File

@ -22,9 +22,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.ContentAlpha
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -35,12 +38,26 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.pitchedapps.frost.compose.FrostCoreExtensionEffect
import com.pitchedapps.frost.compose.FrostWeb
import com.pitchedapps.frost.ext.GeckoContextId
import com.pitchedapps.frost.ext.components
import mozilla.components.browser.state.helper.Target
@Composable
fun MainScreen2(modifier: Modifier) {
// Scaffold(
// modifier = modifier,
// topBar = {
// MainTopBar(modifier = modifier)
// },
// )
}
@Composable
fun MainTopBar(modifier: Modifier) {
// TopAppBar(title = { /*TODO*/ })
}
/**
* Screen for MainActivity.
*
@ -53,9 +70,10 @@ fun MainScreen(modifier: Modifier = Modifier, tabs: List<MainTabItem>) {
if (tabs.isEmpty()) return // not ready
// val contextId = GeckoContextId("test-context")
val contextId = vm.contextIdFlow.collectAsState(initial = null).value ?: return // not ready
FrostCoreExtensionEffect()
LaunchedEffect(vm) { vm.frostCoreExtension.installContent() }
val onTabSelect =
remember(vm) {
@ -95,9 +113,12 @@ private fun MainContainer(
}
Column(modifier = modifier) {
MainHeader(
modifier = Modifier.statusBarsPadding(),
title = tabs.getOrNull(tabIndex)?.title ?: "",
)
if (tabs.size > 1) {
MainTabRow(
modifier = Modifier.statusBarsPadding(),
selectedIndex = tabIndex,
items = tabs,
onTabSelect = onTabSelect,
@ -113,6 +134,18 @@ private fun MainContainer(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainHeader(
title: String,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
title = { Text(text = title) },
)
}
@Composable
fun MainTabRow(
selectedIndex: Int,

View File

@ -24,6 +24,7 @@ import androidx.lifecycle.ViewModel
import com.pitchedapps.frost.ext.GeckoContextId
import com.pitchedapps.frost.ext.idData
import com.pitchedapps.frost.ext.toContextId
import com.pitchedapps.frost.extension.FrostCoreExtension
import com.pitchedapps.frost.hilt.FrostComponents
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
@ -37,6 +38,7 @@ class MainScreenViewModel
internal constructor(
@ApplicationContext context: Context,
val components: FrostComponents,
val frostCoreExtension: FrostCoreExtension,
) : ViewModel() {
val contextIdFlow: Flow<GeckoContextId?> =

View File

@ -1,5 +1,5 @@
{
"manifest_version": 3,
"manifest_version": 2,
"name": "frostcore",
"version": "1.0.0",
"description": "Core web extension for Frost",
@ -8,9 +8,6 @@
"id": "frost_gecko_core@pitchedapps"
}
},
"host_permissions": [
"*://*/*"
],
"background": {
"scripts": [
"js/background/cookies.js"
@ -35,10 +32,17 @@
}
],
"permissions": [
"<all_urls>",
"activeTab",
"contextMenus",
"contextualIdentities",
"cookies",
"history",
"management",
"tabs",
"nativeMessaging",
"nativeMessagingFromContent",
"geckoViewAddons"
"geckoViewAddons",
"webRequest"
]
}

View File

@ -3,15 +3,42 @@ async function updateCookies(changeInfo: browser.cookies._OnChangedChangeInfo) {
const application = "frostBackgroundChannel"
browser.runtime.sendNativeMessage(application, changeInfo)
}
return
async function readCookies() {
const application = "frostBackgroundChannel"
browser.runtime.sendNativeMessage(application, 'start cookie fetch for')
browser.runtime.sendNativeMessage(application, 'start cookie fetch')
// Testing with domains or urls didn't work
const cookies = await browser.cookies.getAll({});
browser.runtime.sendNativeMessage(application, cookies)
const cookies2 = await browser.cookies.getAll({ storeId: "firefox-container-frost-context-1" })
const cookieStores = await browser.cookies.getAllCookieStores();
browser.runtime.sendNativeMessage(application, { name: "cookies", data: cookies.length, stores: cookieStores.map((s) => s.id), data2: cookies2.length, data3: cookies.filter(s => s.storeId != 'firefox-default').length })
}
browser.cookies.onChanged.addListener(updateCookies);
async function handleMessage(request: any, sender: browser.runtime.MessageSender, sendResponse: (response?: any) => void) {
browser.runtime.sendNativeMessage("frostBackgroundChannel", 'pre send')
await new Promise(resolve => setTimeout(resolve, 1000));
browser.runtime.sendNativeMessage("frostBackgroundChannel", 'post send')
sendResponse({ received: request, asdf: "asdf" })
}
// Reading cookies with storeId might not be fully supported on Android
// https://stackoverflow.com/q/76505000/4407321
// Using manifest 3 stopped getAll from working
// Reading now always shows storeId as firefox-default
// Setting a cookie with a custom container does not seem to work
// browser.cookies.onChanged.addListener(updateCookies);
// browser.tabs.onActivated.addListener(readCookies);
// browser.runtime.onStartup.addListener(readCookies);
// browser.runtime.onMessage.addListener(handleMessage);

View File

@ -1,16 +1,14 @@
{
"compilerOptions": {
"target": "es3",
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"strict": true,
"allowJs": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"isolatedModules": false,
"lib": [
"ES2017",
"ES6",
"dom"
],
"strictNullChecks": true,