From 58068ce55e9e203a77c67dfee78fb2658e3bb6aa Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 24 Jan 2021 19:10:09 -0800 Subject: Create messenger client with cookie checking --- .../kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 3 +- .../com/pitchedapps/frost/facebook/FbConst.kt | 1 + .../com/pitchedapps/frost/facebook/FbCookie.kt | 4 -- .../pitchedapps/frost/fragments/WebFragments.kt | 2 + .../kotlin/com/pitchedapps/frost/utils/Utils.kt | 20 ++++---- .../pitchedapps/frost/views/FrostVideoViewer.kt | 2 +- .../com/pitchedapps/frost/views/FrostWebView.kt | 2 +- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 2 +- .../pitchedapps/frost/web/FrostWebViewClients.kt | 55 +++++++++++++++++++--- .../com/pitchedapps/frost/facebook/FbConstTest.kt | 37 +++++++++++++++ 10 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 app/src/test/kotlin/com/pitchedapps/frost/facebook/FbConstTest.kt diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index 163d151b..8c2e32a7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -44,7 +44,8 @@ data class CookieEntity( ) : Parcelable { override fun toString(): String = "CookieEntity(${hashCode()})" - fun toSensitiveString(): String = "CookieEntity(id=$id, name=$name, cookie=$cookie)" + fun toSensitiveString(): String = + "CookieEntity(id=$id, name=$name, cookie=$cookie cookieMessenger=$cookieMessenger)" } @Dao diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt index fc4e3fae..b0846864 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt @@ -34,6 +34,7 @@ const val FB_URL_MBASIC_BASE = "https://$FACEBOOK_MBASIC_COM/" fun profilePictureUrl(id: Long) = "https://graph.facebook.com/$id/picture?type=large" const val FB_LOGIN_URL = "${FB_URL_BASE}login" const val FB_HOME_URL = "${FB_URL_BASE}home.php" +const val MESSENGER_THREAD_PREFIX = "$HTTPS_MESSENGER_COM/t/" /* * User agent candidates. diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt index b02ec941..4e932d09 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -24,7 +24,6 @@ import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.deleteById import com.pitchedapps.frost.db.save import com.pitchedapps.frost.db.selectById -import com.pitchedapps.frost.db.updateMessengerCookie import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.cookies @@ -121,11 +120,8 @@ class FbCookie(private val prefs: Prefs, private val cookieDao: CookieDao) { L.d { "Switching User; null cookie" } return } - val currentId = prefs.userId withContext(Dispatchers.Main + NonCancellable) { L.d { "Switching User" } - // Save current messenger cookie state. - cookieDao.updateMessengerCookie(currentId, messengerCookie) prefs.userId = cookie.id CookieManager.getInstance().apply { removeAllCookies() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt index 502c37fb..3cac92af 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt @@ -26,6 +26,7 @@ import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.views.FrostWebView import com.pitchedapps.frost.web.FrostWebViewClient import com.pitchedapps.frost.web.FrostWebViewClientMenu +import com.pitchedapps.frost.web.FrostWebViewClientMessenger /** * Created by Allan Wang on 27/12/17. @@ -41,6 +42,7 @@ class WebFragment : BaseFragment() { * Given a webview, output a client */ fun client(web: FrostWebView) = when (baseEnum) { + FbItem.MESSENGER -> FrostWebViewClientMessenger(web) FbItem.MENU -> FrostWebViewClientMenu(web) else -> FrostWebViewClient(web) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index 2d3a10c8..13ce2920 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -35,6 +35,7 @@ import ca.allanwang.kau.email.sendEmail import ca.allanwang.kau.mediapicker.createMediaFile import ca.allanwang.kau.mediapicker.createPrivateMediaFile import ca.allanwang.kau.utils.colorToForeground +import ca.allanwang.kau.utils.ctxCoroutine import ca.allanwang.kau.utils.darken import ca.allanwang.kau.utils.isColorDark import ca.allanwang.kau.utils.navigationBarColor @@ -77,8 +78,6 @@ import java.net.URLEncoder import java.nio.charset.StandardCharsets import java.util.ArrayList import java.util.Locale -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.apache.commons.text.StringEscapeUtils @@ -98,14 +97,6 @@ const val ARG_IMAGE_URL = "arg_image_url" const val ARG_TEXT = "arg_text" const val ARG_COOKIE = "arg_cookie" -/** - * Most context items implement [CoroutineScope] by default. - * We will add a fallback just in case. - * It is expected that the scope returned always has the Android main dispatcher as part of the context. - */ -internal inline val Context.ctxCoroutine: CoroutineScope - get() = this as? CoroutineScope ?: GlobalScope - inline fun Context.launchNewTask( cookieList: ArrayList = arrayListOf(), clearStack: Boolean = false @@ -186,7 +177,9 @@ fun WebOverlayActivity.url(): String { fun Activity.setFrostTheme(themeProvider: ThemeProvider, forceTransparent: Boolean = false) { val isTransparent = - forceTransparent || (Color.alpha(themeProvider.bgColor) != 255) || (Color.alpha(themeProvider.headerColor) != 255) + forceTransparent || (Color.alpha(themeProvider.bgColor) != 255) || (Color.alpha( + themeProvider.headerColor + ) != 255) if (themeProvider.bgColor.isColorDark) { setTheme(if (isTransparent) R.style.FrostTheme_Transparent else R.style.FrostTheme) } else { @@ -313,7 +306,10 @@ inline val String?.isFacebookUrl get() = this != null && (contains(FACEBOOK_COM) || contains(FBCDN_NET)) inline val String?.isMessengerUrl -get() = this != null && contains(MESSENGER_COM) + get() = this != null && contains(MESSENGER_COM) + +inline val String?.isFbCookie + get() = this != null && contains("c_user") /** * [true] if url is a video and can be accepted by VideoViewer diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt index 169e7f73..a76aeea0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt @@ -25,6 +25,7 @@ import android.view.LayoutInflater import android.view.MotionEvent import android.view.ViewTreeObserver import android.widget.FrameLayout +import ca.allanwang.kau.utils.ctxCoroutine import ca.allanwang.kau.utils.fadeIn import ca.allanwang.kau.utils.fadeOut import ca.allanwang.kau.utils.gone @@ -46,7 +47,6 @@ import com.pitchedapps.frost.db.currentCookie import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.ctxCoroutine import com.pitchedapps.frost.utils.frostDownload import org.koin.core.component.KoinComponent import org.koin.core.component.inject diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt index e4752d1b..ecd8c093 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt @@ -24,6 +24,7 @@ import android.util.AttributeSet import android.view.View import android.view.ViewGroup import ca.allanwang.kau.utils.AnimHolder +import ca.allanwang.kau.utils.ctxCoroutine import ca.allanwang.kau.utils.launchMain import com.pitchedapps.frost.contracts.FrostContentContainer import com.pitchedapps.frost.contracts.FrostContentCore @@ -37,7 +38,6 @@ import com.pitchedapps.frost.fragments.WebFragment import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.ctxCoroutine import com.pitchedapps.frost.utils.frostDownload import com.pitchedapps.frost.web.FrostChromeClient import com.pitchedapps.frost.web.FrostJSI diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt index 4e8e8cee..12e10e10 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -18,6 +18,7 @@ package com.pitchedapps.frost.web import android.content.Context import android.webkit.JavascriptInterface +import ca.allanwang.kau.utils.ctxCoroutine import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.WebOverlayActivityBase import com.pitchedapps.frost.contracts.MainActivityContract @@ -28,7 +29,6 @@ import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.WebContext import com.pitchedapps.frost.utils.cookies -import com.pitchedapps.frost.utils.ctxCoroutine import com.pitchedapps.frost.utils.isIndependent import com.pitchedapps.frost.utils.launchImageActivity import com.pitchedapps.frost.utils.showWebContextMenu diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt index 8ec5f975..1495b2e0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -22,11 +22,17 @@ import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import android.webkit.WebViewClient +import ca.allanwang.kau.utils.ctxCoroutine import ca.allanwang.kau.utils.withAlpha +import com.pitchedapps.frost.db.CookieDao +import com.pitchedapps.frost.db.currentCookie +import com.pitchedapps.frost.db.updateMessengerCookie import com.pitchedapps.frost.enums.ThemeCategory import com.pitchedapps.frost.facebook.FACEBOOK_BASE_COM import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbItem +import com.pitchedapps.frost.facebook.HTTPS_MESSENGER_COM +import com.pitchedapps.frost.facebook.MESSENGER_THREAD_PREFIX import com.pitchedapps.frost.facebook.WWW_FACEBOOK_COM import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.injectors.CssAsset @@ -39,6 +45,7 @@ import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.isExplicitIntent import com.pitchedapps.frost.utils.isFacebookUrl +import com.pitchedapps.frost.utils.isFbCookie import com.pitchedapps.frost.utils.isImageUrl import com.pitchedapps.frost.utils.isIndirectImageUrl import com.pitchedapps.frost.utils.isMessengerUrl @@ -46,6 +53,7 @@ import com.pitchedapps.frost.utils.launchImageActivity import com.pitchedapps.frost.utils.resolveActivityForUri import com.pitchedapps.frost.views.FrostWebView import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.launch /** * Created by Allan Wang on 2017-05-31. @@ -71,11 +79,11 @@ open class BaseWebViewClient : WebViewClient() { */ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { - private val fbCookie: FbCookie get() = web.fbCookie - private val prefs: Prefs get() = web.prefs - private val themeProvider: ThemeProvider get() = web.themeProvider - private val refresh: SendChannel = web.parent.refreshChannel - private val isMain = web.parent.baseEnum != null + protected val fbCookie: FbCookie get() = web.fbCookie + protected val prefs: Prefs get() = web.prefs + protected val themeProvider: ThemeProvider get() = web.themeProvider + protected val refresh: SendChannel = web.parent.refreshChannel + protected val isMain = web.parent.baseEnum != null /** * True if current url supports refresh. See [doUpdateVisitedHistory] for updates @@ -279,8 +287,6 @@ private const val EMIT_FINISH = 0 */ class FrostWebViewClientMenu(web: FrostWebView) : FrostWebViewClient(web) { - private val prefs: Prefs get() = web.prefs - override fun onPageFinished(view: WebView, url: String?) { super.onPageFinished(view, url) if (url == null) { @@ -302,3 +308,38 @@ class FrostWebViewClientMenu(web: FrostWebView) : FrostWebViewClient(web) { // Skip } } + +class FrostWebViewClientMessenger(web: FrostWebView) : FrostWebViewClient(web) { + + override fun onPageFinished(view: WebView, url: String?) { + super.onPageFinished(view, url) + messengerCookieCheck(url!!) + } + + private val cookieDao: CookieDao get() = web.cookieDao + private var hasCookie = fbCookie.messengerCookie.isFbCookie + + /** + * Check cookie changes. Unlike fb checks, we will continuously poll for cookie changes during loading. + * There is no lifecycle association between messenger login and facebook login, + * so we'll try to be smart about when to check for state changes. + * + * From testing, it looks like this is called after redirects. + * We can therefore classify no login as pointing to messenger.com, + * and login as pointing to messenger.com/t/[thread id] + */ + private fun messengerCookieCheck(url: String?) { + if (url?.startsWith(HTTPS_MESSENGER_COM) != true) return + val shouldHaveCookie = url.startsWith(MESSENGER_THREAD_PREFIX) + L._d { "Messenger client: $url $shouldHaveCookie" } + if (shouldHaveCookie == hasCookie) return + hasCookie = shouldHaveCookie + web.context.ctxCoroutine.launch { + cookieDao.updateMessengerCookie( + prefs.userId, + if (shouldHaveCookie) fbCookie.messengerCookie else null + ) + L._d { "New cookie ${cookieDao.currentCookie(prefs)?.toSensitiveString()}" } + } + } +} diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbConstTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbConstTest.kt new file mode 100644 index 00000000..8a8c42d6 --- /dev/null +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbConstTest.kt @@ -0,0 +1,37 @@ +package com.pitchedapps.frost.facebook + +import kotlin.test.Test +import kotlin.test.assertFalse + +class FbConstTest { + + private val constants = listOf( + FACEBOOK_COM, + MESSENGER_COM, + FBCDN_NET, + WWW_FACEBOOK_COM, + WWW_MESSENGER_COM, + HTTPS_FACEBOOK_COM, + HTTPS_MESSENGER_COM, + FACEBOOK_BASE_COM, + FB_URL_BASE, + FACEBOOK_MBASIC_COM, + FB_URL_MBASIC_BASE, + FB_LOGIN_URL, + FB_HOME_URL, + MESSENGER_THREAD_PREFIX + ) + + /** + * Make sure we don't have accidental double forward slashes after appending + */ + @Test + fun doubleForwardSlashTest() { + constants.forEach { + assertFalse( + it.replace("https://", "").contains("//"), + "Accidental forward slash for $it" + ) + } + } +} \ No newline at end of file -- cgit v1.2.3