add token based security; improve stability;
This commit is contained in:
+12
-2
@@ -152,7 +152,8 @@ object GlobalComponents {
|
||||
|
||||
newComponents.core.engine.warmUp()
|
||||
|
||||
if (previousCustomTabs.isNotEmpty()) {
|
||||
fun restorePreviousCustomTabs() {
|
||||
if (previousCustomTabs.isEmpty()) return
|
||||
for (tab in previousCustomTabs) {
|
||||
val existing = newComponents.core.store.state.findCustomTab(tab.id)
|
||||
if (existing == null) {
|
||||
@@ -164,7 +165,14 @@ object GlobalComponents {
|
||||
}
|
||||
|
||||
if (mode == ComponentsMode.FULL) {
|
||||
restoreBrowserState(newComponents)
|
||||
val restoreJob = restoreBrowserState(newComponents)
|
||||
if (previousCustomTabs.isNotEmpty()) {
|
||||
restoreJob.invokeOnCompletion {
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
restorePreviousCustomTabs()
|
||||
}
|
||||
}
|
||||
}
|
||||
restoreDownloads(newComponents)
|
||||
|
||||
try {
|
||||
@@ -205,6 +213,8 @@ object GlobalComponents {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
newComponents.core.fileUploadsDirCleaner.cleanUploadsDirectory()
|
||||
}
|
||||
} else {
|
||||
restorePreviousCustomTabs()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -13,10 +13,12 @@ object PwaConstants {
|
||||
// Intent extras keys for PWA metadata
|
||||
const val EXTRA_PWA_PROFILE_UUID = "pwa_profile_uuid"
|
||||
const val EXTRA_PWA_CONTEXT_ID = "pwa_context_id"
|
||||
const val EXTRA_PWA_TOKEN = "pwa_token"
|
||||
|
||||
// Profile and file paths
|
||||
const val CURRENT_PROFILE_FILE = "weblibre_profiles/current_profile"
|
||||
const val PROFILE_MAPPING_PREFS = "pwa_profile_mapping"
|
||||
const val PROFILE_MAPPING_TOKEN_PREFIX = "token_"
|
||||
|
||||
// Component initialization timeouts
|
||||
const val COMPONENT_INIT_TIMEOUT_MS = 10000L
|
||||
|
||||
+40
-20
@@ -9,6 +9,7 @@ package eu.weblibre.flutter_mozilla_components.activities
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import eu.weblibre.flutter_mozilla_components.ExternalAppBrowserFragment
|
||||
@@ -33,6 +34,24 @@ import mozilla.components.support.base.log.logger.Logger
|
||||
* Uses an empty taskAffinity so Custom Tabs appear as a separate task from the main app.
|
||||
*/
|
||||
class ExternalAppBrowserActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val TAG = "ExternalAppBrowserActivity"
|
||||
|
||||
const val EXTRA_CUSTOM_TAB_SESSION_ID = "custom_tab_session_id"
|
||||
const val EXTRA_WEB_APP_MANIFEST_URL = "web_app_manifest_url"
|
||||
|
||||
fun createIntent(
|
||||
context: Context,
|
||||
customTabSessionId: String,
|
||||
webAppManifestUrl: String? = null,
|
||||
): Intent {
|
||||
return Intent(context, ExternalAppBrowserActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
|
||||
putExtra(EXTRA_CUSTOM_TAB_SESSION_ID, customTabSessionId)
|
||||
webAppManifestUrl?.let { putExtra(EXTRA_WEB_APP_MANIFEST_URL, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val logger = Logger("ExternalAppBrowserActivity")
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
@@ -48,8 +67,9 @@ class ExternalAppBrowserActivity : AppCompatActivity() {
|
||||
|
||||
val sessionId = customTabSessionId
|
||||
if (sessionId == null) {
|
||||
Log.e(TAG, "No custom tab session ID provided")
|
||||
logger.error("No custom tab session ID provided, finishing.")
|
||||
finish()
|
||||
fallbackToMainActivity()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,8 +107,9 @@ class ExternalAppBrowserActivity : AppCompatActivity() {
|
||||
|
||||
// Timeout reached
|
||||
if (isActive) {
|
||||
Log.e(TAG, "Timeout waiting for components")
|
||||
logger.error("Timeout waiting for components after ${PwaConstants.COMPONENT_INIT_TIMEOUT_MS}ms")
|
||||
finish()
|
||||
fallbackToMainActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,8 +123,9 @@ class ExternalAppBrowserActivity : AppCompatActivity() {
|
||||
|
||||
// Verify session exists
|
||||
if (components.core.store.state.findCustomTab(sessionId) == null) {
|
||||
Log.e(TAG, "Custom tab session $sessionId not found in store")
|
||||
logger.error("Custom tab session $sessionId not found in store, finishing.")
|
||||
finish()
|
||||
fallbackToMainActivity()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -124,11 +146,25 @@ class ExternalAppBrowserActivity : AppCompatActivity() {
|
||||
val sessionId = customTabSessionId ?: return
|
||||
val components = GlobalComponents.components ?: return
|
||||
if (components.core.store.state.findCustomTab(sessionId) == null) {
|
||||
Log.w(TAG, "Custom tab session $sessionId gone on resume")
|
||||
logger.debug("Custom tab session $sessionId gone, finishing activity.")
|
||||
finish()
|
||||
fallbackToMainActivity()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fallbackToMainActivity() {
|
||||
val mainIntent = Intent().apply {
|
||||
setClassName(this@ExternalAppBrowserActivity, "eu.weblibre.gecko.MainActivity")
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
webAppManifestUrl?.let {
|
||||
action = Intent.ACTION_VIEW
|
||||
data = android.net.Uri.parse(it)
|
||||
}
|
||||
}
|
||||
startActivity(mainIntent)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
@@ -168,20 +204,4 @@ class ExternalAppBrowserActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_CUSTOM_TAB_SESSION_ID = "custom_tab_session_id"
|
||||
const val EXTRA_WEB_APP_MANIFEST_URL = "web_app_manifest_url"
|
||||
|
||||
fun createIntent(
|
||||
context: Context,
|
||||
customTabSessionId: String,
|
||||
webAppManifestUrl: String? = null,
|
||||
): Intent {
|
||||
return Intent(context, ExternalAppBrowserActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
|
||||
putExtra(EXTRA_CUSTOM_TAB_SESSION_ID, customTabSessionId)
|
||||
webAppManifestUrl?.let { putExtra(EXTRA_WEB_APP_MANIFEST_URL, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+94
-57
@@ -8,22 +8,23 @@ package eu.weblibre.flutter_mozilla_components.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import eu.weblibre.flutter_mozilla_components.Components
|
||||
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||
import eu.weblibre.flutter_mozilla_components.PwaConstants
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.feature.customtabs.CustomTabIntentProcessor
|
||||
import mozilla.components.feature.intent.ext.getSessionId
|
||||
import mozilla.components.feature.pwa.intent.WebAppIntentProcessor
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import mozilla.components.browser.state.state.ExternalAppType
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -37,17 +38,18 @@ import java.io.File
|
||||
* if the current profile differs from the installation profile.
|
||||
*/
|
||||
class IntentReceiverActivity : Activity() {
|
||||
companion object {
|
||||
private const val TAG = "IntentReceiverActivity"
|
||||
}
|
||||
|
||||
private val logger = Logger("IntentReceiverActivity")
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private var pendingIntent: Intent? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val intent = intent?.let { Intent(it) } ?: Intent()
|
||||
|
||||
logger.debug("onCreate: action=${intent.action} data=${intent.dataString}")
|
||||
Log.d(TAG, "onCreate: action=${intent.action} data=${intent.dataString}")
|
||||
|
||||
// Strip flags that could interfere with task management
|
||||
intent.flags = intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK.inv()
|
||||
@@ -68,9 +70,8 @@ class IntentReceiverActivity : Activity() {
|
||||
return
|
||||
}
|
||||
|
||||
logger.warn("Components not initialized, waiting for initialization...")
|
||||
pendingIntent = intent
|
||||
waitForComponentsWithTimeout()
|
||||
Log.w(TAG, "Components not initialized, routing directly to MainActivity")
|
||||
handleRegularIntent(intent)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,16 +82,21 @@ class IntentReceiverActivity : Activity() {
|
||||
// Check if this is our custom PWA intent with profile metadata
|
||||
val profileUuid = intent.getStringExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID)
|
||||
val contextId = intent.getStringExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID)
|
||||
val token = intent.getStringExtra(PwaConstants.EXTRA_PWA_TOKEN)
|
||||
if (profileUuid != null) {
|
||||
logger.debug("PWA intent with profile metadata: profileUuid=$profileUuid, contextId=$contextId")
|
||||
if (isTrustedPwaLaunch(intent, profileUuid, token)) {
|
||||
Log.d(TAG, "Trusted PWA intent with profile metadata: profileUuid=$profileUuid, contextId=$contextId")
|
||||
handlePwaIntent(intent, profileUuid, contextId)
|
||||
return
|
||||
}
|
||||
|
||||
Log.w(TAG, "Ignoring untrusted PWA profile metadata on VIEW intent")
|
||||
}
|
||||
|
||||
// Fall back to standard intent processors for Custom Tabs and legacy PWAs
|
||||
val components = GlobalComponents.components
|
||||
?: run {
|
||||
logger.error("Components became null during routing")
|
||||
Log.e(TAG, "Components became null during routing")
|
||||
handleRegularIntent(intent)
|
||||
return
|
||||
}
|
||||
@@ -110,13 +116,14 @@ class IntentReceiverActivity : Activity() {
|
||||
)
|
||||
|
||||
for ((name, processor) in processors) {
|
||||
logger.debug("Trying $name processor...")
|
||||
Log.d(TAG, "Trying $name processor...")
|
||||
try {
|
||||
val result = processor.process(intent)
|
||||
logger.debug("$name processor result: $result")
|
||||
Log.d(TAG, "$name processor result: $result")
|
||||
if (result) {
|
||||
val sessionId = intent.getSessionId()
|
||||
logger.debug("$name session ID from intent: $sessionId")
|
||||
?: resolveSessionIdFromStore(name, intent, components)
|
||||
Log.d(TAG, "$name session ID from intent: $sessionId")
|
||||
if (sessionId != null) {
|
||||
val externalIntent = ExternalAppBrowserActivity.createIntent(
|
||||
context = this,
|
||||
@@ -127,18 +134,79 @@ class IntentReceiverActivity : Activity() {
|
||||
finish()
|
||||
return
|
||||
} else {
|
||||
logger.warn("$name processor succeeded but no session ID in intent!")
|
||||
Log.w(TAG, "$name processor succeeded but no session ID in intent!")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error in $name processor", e)
|
||||
Log.e(TAG, "Error in $name processor", e)
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("No processor matched, routing to MainActivity")
|
||||
Log.d(TAG, "No processor matched, routing to MainActivity")
|
||||
handleRegularIntent(intent)
|
||||
}
|
||||
|
||||
private fun isTrustedPwaLaunch(intent: Intent, profileUuid: String, token: String?): Boolean {
|
||||
val url = intent.dataString ?: return false
|
||||
val action = intent.action
|
||||
val hasTrustedAction = action == Intent.ACTION_VIEW || action == "mozilla.components.feature.pwa.VIEW_PWA"
|
||||
if (!hasTrustedAction || token.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val prefs = applicationContext.getSharedPreferences(
|
||||
PwaConstants.PROFILE_MAPPING_PREFS,
|
||||
Context.MODE_PRIVATE,
|
||||
)
|
||||
val tokenKey = "${PwaConstants.PROFILE_MAPPING_TOKEN_PREFIX}${url}::${profileUuid}"
|
||||
val storedToken = prefs.getString(tokenKey, null)
|
||||
if (storedToken == null || storedToken != token) {
|
||||
Log.w(TAG, "PWA token mismatch for $url")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun resolveSessionIdFromStore(
|
||||
processorName: String,
|
||||
intent: Intent,
|
||||
components: Components,
|
||||
): String? {
|
||||
val customTabs = components.core.store.state.customTabs
|
||||
if (customTabs.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return when (processorName) {
|
||||
"PWA" -> {
|
||||
val targetUrl = intent.dataString
|
||||
val pwaTab = customTabs.lastOrNull { tab ->
|
||||
val appType = tab.config.externalAppType
|
||||
val isPwa = appType == ExternalAppType.PROGRESSIVE_WEB_APP ||
|
||||
appType == ExternalAppType.TRUSTED_WEB_ACTIVITY
|
||||
if (!isPwa) {
|
||||
return@lastOrNull false
|
||||
}
|
||||
|
||||
if (targetUrl == null) {
|
||||
true
|
||||
} else {
|
||||
tab.content.url == targetUrl || tab.content.webAppManifest?.startUrl == targetUrl
|
||||
}
|
||||
} ?: customTabs.lastOrNull { tab ->
|
||||
val appType = tab.config.externalAppType
|
||||
appType == ExternalAppType.PROGRESSIVE_WEB_APP ||
|
||||
appType == ExternalAppType.TRUSTED_WEB_ACTIVITY
|
||||
}
|
||||
|
||||
pwaTab?.id
|
||||
}
|
||||
|
||||
else -> customTabs.lastOrNull()?.id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles PWA intents with profile and context metadata.
|
||||
* Checks if current profile matches and shows dialog if different.
|
||||
@@ -150,7 +218,7 @@ class IntentReceiverActivity : Activity() {
|
||||
) {
|
||||
val url = intent.dataString
|
||||
if (url == null) {
|
||||
logger.error("PWA intent has no URL")
|
||||
Log.e(TAG, "PWA intent has no URL")
|
||||
handleRegularIntent(intent)
|
||||
return
|
||||
}
|
||||
@@ -158,10 +226,10 @@ class IntentReceiverActivity : Activity() {
|
||||
val currentProfileUuid = getCurrentProfileUuid()
|
||||
|
||||
if (currentProfileUuid != null && currentProfileUuid != profileUuid) {
|
||||
logger.debug("Profile mismatch: current=$currentProfileUuid, expected=$profileUuid")
|
||||
Log.d(TAG, "Profile mismatch: current=$currentProfileUuid, expected=$profileUuid")
|
||||
showProfileMismatchDialog(url, contextId)
|
||||
} else {
|
||||
logger.debug("Profile match or indeterminate, launching PWA with contextId=$contextId")
|
||||
Log.d(TAG, "Profile match or indeterminate, launching PWA with contextId=$contextId")
|
||||
launchPwaWithContext(url, contextId)
|
||||
}
|
||||
}
|
||||
@@ -180,7 +248,7 @@ class IntentReceiverActivity : Activity() {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed to read current profile UUID", e)
|
||||
Log.e(TAG, "Failed to read current profile UUID", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -202,11 +270,11 @@ class IntentReceiverActivity : Activity() {
|
||||
.setTitle("PWA Profile Mismatch")
|
||||
.setMessage(message)
|
||||
.setPositiveButton("Open Anyway") { _, _ ->
|
||||
logger.debug("User chose to open PWA despite profile mismatch")
|
||||
Log.d(TAG, "User chose to open PWA despite profile mismatch")
|
||||
launchPwaWithContext(url, contextId)
|
||||
}
|
||||
.setNegativeButton("Cancel") { _, _ ->
|
||||
logger.debug("User cancelled PWA launch due to profile mismatch")
|
||||
Log.d(TAG, "User cancelled PWA launch due to profile mismatch")
|
||||
finish()
|
||||
}
|
||||
.setOnCancelListener {
|
||||
@@ -221,7 +289,7 @@ class IntentReceiverActivity : Activity() {
|
||||
private fun launchPwaWithContext(url: String, contextId: String?) {
|
||||
val components = GlobalComponents.components
|
||||
?: run {
|
||||
logger.error("Components not available for PWA launch")
|
||||
Log.e(TAG, "Components not available for PWA launch")
|
||||
handleRegularIntent(intent)
|
||||
return
|
||||
}
|
||||
@@ -238,7 +306,7 @@ class IntentReceiverActivity : Activity() {
|
||||
manifest = manifest
|
||||
)
|
||||
|
||||
logger.debug("Created PWA session: contextId=$contextId, sessionId=$sessionId")
|
||||
Log.d(TAG, "Created PWA session: contextId=$contextId, sessionId=$sessionId")
|
||||
|
||||
val externalIntent = ExternalAppBrowserActivity.createIntent(
|
||||
context = this@IntentReceiverActivity,
|
||||
@@ -248,7 +316,7 @@ class IntentReceiverActivity : Activity() {
|
||||
startActivity(externalIntent)
|
||||
finish()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed to launch PWA with context", e)
|
||||
Log.e(TAG, "Failed to launch PWA with context", e)
|
||||
handleRegularIntent(intent)
|
||||
}
|
||||
}
|
||||
@@ -288,37 +356,6 @@ class IntentReceiverActivity : Activity() {
|
||||
return tab.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for GlobalComponents to be initialized with a timeout.
|
||||
* Once components are ready, shows branded loading screen before routing.
|
||||
* Falls back to MainActivity if timeout is reached (10 seconds).
|
||||
*/
|
||||
private fun waitForComponentsWithTimeout() {
|
||||
coroutineScope.launch {
|
||||
var elapsedMs = 0L
|
||||
|
||||
while (isActive && elapsedMs < PwaConstants.COMPONENT_INIT_TIMEOUT_MS) {
|
||||
if (GlobalComponents.components != null) {
|
||||
logger.debug("Components initialized after ${elapsedMs}ms")
|
||||
pendingIntent?.let { routeIntent(it) }
|
||||
pendingIntent = null
|
||||
return@launch
|
||||
}
|
||||
|
||||
delay(PwaConstants.COMPONENT_INIT_CHECK_INTERVAL_MS)
|
||||
elapsedMs += PwaConstants.COMPONENT_INIT_CHECK_INTERVAL_MS
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
logger.warn("Timeout waiting for components after ${PwaConstants.COMPONENT_INIT_TIMEOUT_MS}ms, falling back to MainActivity")
|
||||
pendingIntent?.let { intent ->
|
||||
handleRegularIntent(intent)
|
||||
}
|
||||
pendingIntent = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRegularIntent(intent: Intent) {
|
||||
val mainActivityIntent = Intent(intent).apply {
|
||||
setClassName(this@IntentReceiverActivity, "eu.weblibre.gecko.MainActivity")
|
||||
|
||||
+23
-4
@@ -37,6 +37,7 @@ import mozilla.components.concept.engine.manifest.WebAppManifest
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Implementation of GeckoPwaApi that provides PWA install and query functionality.
|
||||
@@ -52,6 +53,12 @@ class GeckoPwaApiImpl(
|
||||
}
|
||||
|
||||
private val logger = Logger("GeckoPwaApiImpl")
|
||||
private val appPrefs by lazy {
|
||||
context.applicationContext.getSharedPreferences(
|
||||
PwaConstants.PROFILE_MAPPING_PREFS,
|
||||
Context.MODE_PRIVATE,
|
||||
)
|
||||
}
|
||||
|
||||
private val components by lazy {
|
||||
requireNotNull(GlobalComponents.components) { "Components not initialized" }
|
||||
@@ -138,6 +145,7 @@ class GeckoPwaApiImpl(
|
||||
val (iconBitmap, isMaskable) = loadPwaIcon(manifest)
|
||||
|
||||
val shortcutId = generateShortcutId(manifest.startUrl)
|
||||
val launchToken = generateAndStoreLaunchToken(manifest.startUrl, profileUuid)
|
||||
|
||||
val appName = manifest.shortName ?: manifest.name ?: "Web App"
|
||||
|
||||
@@ -146,6 +154,7 @@ class GeckoPwaApiImpl(
|
||||
data = Uri.parse(manifest.startUrl)
|
||||
putExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID, profileUuid)
|
||||
putExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID, contextId)
|
||||
putExtra(PwaConstants.EXTRA_PWA_TOKEN, launchToken)
|
||||
}
|
||||
|
||||
val shortcut = ShortcutInfo.Builder(context, shortcutId).apply {
|
||||
@@ -247,15 +256,25 @@ class GeckoPwaApiImpl(
|
||||
}
|
||||
|
||||
private fun storeProfileMapping(startUrl: String, profileUuid: String) {
|
||||
context.getSharedPreferences(PwaConstants.PROFILE_MAPPING_PREFS, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
appPrefs.edit()
|
||||
.putString(startUrl, profileUuid)
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun generateAndStoreLaunchToken(startUrl: String, profileUuid: String): String {
|
||||
val token = UUID.randomUUID().toString()
|
||||
val tokenKey = "${PwaConstants.PROFILE_MAPPING_TOKEN_PREFIX}${startUrl}::${profileUuid}"
|
||||
val committed = appPrefs.edit()
|
||||
.putString(tokenKey, token)
|
||||
.commit()
|
||||
if (!committed) {
|
||||
logger.warn("Failed to persist PWA launch token for $startUrl")
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
private fun getProfileMapping(startUrl: String): String? {
|
||||
return context.getSharedPreferences(PwaConstants.PROFILE_MAPPING_PREFS, Context.MODE_PRIVATE)
|
||||
.getString(startUrl, null)
|
||||
return appPrefs.getString(startUrl, null)
|
||||
}
|
||||
|
||||
private fun getCurrentProfileUuid(): String? {
|
||||
|
||||
Reference in New Issue
Block a user