mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat(android): support self-host & multi channels (#12095)
This commit is contained in:
@@ -53,14 +53,10 @@ android {
|
||||
}
|
||||
flavorDimensions = ['chanel']
|
||||
productFlavors {
|
||||
stable {
|
||||
buildConfigField 'String', 'BASE_URL', '"https://app.affine.pro"'
|
||||
resValue 'string', 'host', '"app.affine.pro"'
|
||||
}
|
||||
canary {
|
||||
buildConfigField 'String', 'BASE_URL', '"https://affine.fail"'
|
||||
resValue 'string', 'host', '"affine.fail"'
|
||||
}
|
||||
stable
|
||||
beta
|
||||
internal
|
||||
canary
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -3,9 +3,6 @@ package app.affine.pro
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import app.affine.pro.service.CookieStore
|
||||
import app.affine.pro.utils.dataStore
|
||||
import app.affine.pro.utils.get
|
||||
import app.affine.pro.utils.logger.AffineDebugTree
|
||||
import app.affine.pro.utils.logger.CrashlyticsTree
|
||||
import app.affine.pro.utils.logger.FileTree
|
||||
@@ -13,11 +10,6 @@ import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.crashlytics.setCustomKeys
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltAndroidApp
|
||||
@@ -39,28 +31,6 @@ class AffineApp : Application() {
|
||||
Firebase.crashlytics.setCustomKeys {
|
||||
key("affine_version", CapacitorConfig.getAffineVersion())
|
||||
}
|
||||
// init cookies from local
|
||||
MainScope().launch(Dispatchers.IO) {
|
||||
val sessionCookieStr = applicationContext.dataStore.get(CookieStore.AFFINE_SESSION)
|
||||
val userIdCookieStr = applicationContext.dataStore.get(CookieStore.AFFINE_USER_ID)
|
||||
if (sessionCookieStr.isEmpty() || userIdCookieStr.isEmpty()) {
|
||||
Timber.i("[init] user has not signed in yet.")
|
||||
return@launch
|
||||
}
|
||||
Timber.i("[init] user already signed in.")
|
||||
try {
|
||||
val cookies = listOf(
|
||||
Cookie.parse(BuildConfig.BASE_URL.toHttpUrl(), sessionCookieStr)
|
||||
?: error("Parse session cookie fail:[ cookie = $sessionCookieStr ]"),
|
||||
Cookie.parse(BuildConfig.BASE_URL.toHttpUrl(), userIdCookieStr)
|
||||
?: error("Parse user id cookie fail:[ cookie = $userIdCookieStr ]"),
|
||||
)
|
||||
CookieStore.saveCookies(BuildConfig.BASE_URL.toHttpUrl().host, cookies)
|
||||
FileTree.get()?.checkAndUploadOldLogs()
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "[init] load persistent cookies fail.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package app.affine.pro
|
||||
|
||||
import android.webkit.WebView
|
||||
import app.affine.pro.service.CookieStore
|
||||
import app.affine.pro.utils.dataStore
|
||||
import app.affine.pro.utils.get
|
||||
import app.affine.pro.utils.getCurrentServerBaseUrl
|
||||
import app.affine.pro.utils.logger.FileTree
|
||||
import com.getcapacitor.Bridge
|
||||
import com.getcapacitor.WebViewListener
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import timber.log.Timber
|
||||
|
||||
object AuthInitializer {
|
||||
|
||||
fun initialize(bridge: Bridge) {
|
||||
bridge.addWebViewListener(object : WebViewListener() {
|
||||
override fun onPageLoaded(webView: WebView?) {
|
||||
bridge.removeWebViewListener(this)
|
||||
MainScope().launch(Dispatchers.IO) {
|
||||
try {
|
||||
val server = bridge.getCurrentServerBaseUrl().toHttpUrl()
|
||||
val sessionCookieStr = AffineApp.context().dataStore
|
||||
.get(server.host + CookieStore.AFFINE_SESSION)
|
||||
val userIdCookieStr = AffineApp.context().dataStore
|
||||
.get(server.host + CookieStore.AFFINE_USER_ID)
|
||||
if (sessionCookieStr.isEmpty() || userIdCookieStr.isEmpty()) {
|
||||
Timber.i("[init] user has not signed in yet.")
|
||||
return@launch
|
||||
}
|
||||
Timber.i("[init] user already signed in.")
|
||||
val cookies = listOf(
|
||||
Cookie.parse(server, sessionCookieStr)
|
||||
?: error("Parse session cookie fail:[ cookie = $sessionCookieStr ]"),
|
||||
Cookie.parse(server, userIdCookieStr)
|
||||
?: error("Parse user id cookie fail:[ cookie = $userIdCookieStr ]"),
|
||||
)
|
||||
CookieStore.saveCookies(server.host, cookies)
|
||||
FileTree.get()?.checkAndUploadOldLogs(server)
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "[init] load persistent cookies fail.")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,7 +13,9 @@ import app.affine.pro.plugin.AffineThemePlugin
|
||||
import app.affine.pro.plugin.AuthPlugin
|
||||
import app.affine.pro.plugin.HashCashPlugin
|
||||
import app.affine.pro.plugin.NbStorePlugin
|
||||
import app.affine.pro.repo.WebRepo
|
||||
import app.affine.pro.service.GraphQLService
|
||||
import app.affine.pro.service.SSEService
|
||||
import app.affine.pro.service.WebService
|
||||
import app.affine.pro.utils.dp
|
||||
import com.getcapacitor.BridgeActivity
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
@@ -27,7 +29,13 @@ class MainActivity : BridgeActivity(), AIButtonPlugin.Callback, AffineThemePlugi
|
||||
View.OnClickListener {
|
||||
|
||||
@Inject
|
||||
lateinit var webRepo: WebRepo
|
||||
lateinit var webService: WebService
|
||||
|
||||
@Inject
|
||||
lateinit var sseService: SSEService
|
||||
|
||||
@Inject
|
||||
lateinit var graphQLService: GraphQLService
|
||||
|
||||
init {
|
||||
registerPlugins(
|
||||
@@ -56,6 +64,11 @@ class MainActivity : BridgeActivity(), AIButtonPlugin.Callback, AffineThemePlugi
|
||||
}
|
||||
}
|
||||
|
||||
override fun load() {
|
||||
super.load()
|
||||
AuthInitializer.initialize(bridge)
|
||||
}
|
||||
|
||||
override fun present() {
|
||||
lifecycleScope.launch {
|
||||
fab.show()
|
||||
@@ -85,7 +98,9 @@ class MainActivity : BridgeActivity(), AIButtonPlugin.Callback, AffineThemePlugi
|
||||
|
||||
override fun onClick(v: View) {
|
||||
lifecycleScope.launch {
|
||||
webRepo.init(bridge)
|
||||
webService.update(bridge)
|
||||
sseService.updateServer(bridge)
|
||||
graphQLService.updateServer(bridge)
|
||||
AIActivity.open(this@MainActivity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package app.affine.pro.ai.chat
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.affine.pro.repo.GraphQLRepo
|
||||
import app.affine.pro.repo.SSERepo
|
||||
import app.affine.pro.repo.WebRepo
|
||||
import app.affine.pro.service.GraphQLService
|
||||
import app.affine.pro.service.SSEService
|
||||
import app.affine.pro.service.WebService
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -18,9 +18,9 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ChatViewModel @Inject constructor(
|
||||
private val webRepo: WebRepo,
|
||||
private val graphQLRepo: GraphQLRepo,
|
||||
private val sseRepo: SSERepo,
|
||||
private val webService: WebService,
|
||||
private val graphQLService: GraphQLService,
|
||||
private val sseService: SSEService,
|
||||
) : ViewModel() {
|
||||
|
||||
private lateinit var sessionId: String
|
||||
@@ -32,17 +32,17 @@ class ChatViewModel @Inject constructor(
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
sessionId = graphQLRepo.createCopilotSession(
|
||||
workspaceId = webRepo.workspaceId(),
|
||||
docId = webRepo.docId(),
|
||||
sessionId = graphQLService.createCopilotSession(
|
||||
workspaceId = webService.workspaceId(),
|
||||
docId = webService.docId(),
|
||||
).getOrElse {
|
||||
Timber.w(it, "Create session failed")
|
||||
return@launch
|
||||
}
|
||||
Timber.i("Create session success:[ sessionId = $sessionId].")
|
||||
val historyMessages = graphQLRepo.getCopilotHistories(
|
||||
workspaceId = webRepo.workspaceId(),
|
||||
docId = webRepo.docId(),
|
||||
val historyMessages = graphQLService.getCopilotHistories(
|
||||
workspaceId = webService.workspaceId(),
|
||||
docId = webService.docId(),
|
||||
sessionId = sessionId,
|
||||
).getOrDefault(emptyList()).map {
|
||||
ChatMessage.from(it)
|
||||
@@ -55,12 +55,12 @@ class ChatViewModel @Inject constructor(
|
||||
|
||||
fun sendMessage(message: String) {
|
||||
val sendMessage = suspend {
|
||||
graphQLRepo.createCopilotMessage(
|
||||
graphQLService.createCopilotMessage(
|
||||
sessionId = sessionId,
|
||||
message = message,
|
||||
).onSuccess { messageId ->
|
||||
Timber.i("send message: $messageId")
|
||||
sseRepo.messageStream(sessionId, messageId)
|
||||
sseService.messageStream(sessionId, messageId)
|
||||
.onEach {
|
||||
Timber.d("On sse message: ${it.getOrNull()}")
|
||||
}
|
||||
@@ -70,9 +70,9 @@ class ChatViewModel @Inject constructor(
|
||||
}
|
||||
viewModelScope.launch {
|
||||
if (!this@ChatViewModel::sessionId.isInitialized) {
|
||||
graphQLRepo.getCopilotSession(
|
||||
workspaceId = webRepo.workspaceId(),
|
||||
docId = webRepo.docId(),
|
||||
graphQLService.getCopilotSession(
|
||||
workspaceId = webService.workspaceId(),
|
||||
docId = webService.docId(),
|
||||
).onSuccess { id ->
|
||||
sessionId = id
|
||||
Timber.i("Create session: $id")
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
@@ -144,7 +145,7 @@ class AuthPlugin : Plugin() {
|
||||
call.reject(response.body.string())
|
||||
return@launch
|
||||
}
|
||||
CookieStore.getCookie(endpoint, CookieStore.AFFINE_SESSION)?.let {
|
||||
CookieStore.getCookie(endpoint.toHttpUrl(), CookieStore.AFFINE_SESSION)?.let {
|
||||
Timber.i("$method sign in success.")
|
||||
Timber.d("Update session [$it]")
|
||||
call.resolve(JSObject().put("token", it))
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package app.affine.pro.repo
|
||||
|
||||
import com.getcapacitor.Bridge
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@Singleton
|
||||
class WebRepo @Inject constructor() {
|
||||
|
||||
suspend fun init(bridge: Bridge) {
|
||||
_workspaceId = eval(bridge, "window.getCurrentWorkspaceId()")
|
||||
_docId = eval(bridge, "window.getCurrentDocId()")
|
||||
_docContentInMD = eval(bridge, "window.getCurrentDocContentInMarkdown()")
|
||||
}
|
||||
|
||||
private suspend fun eval(bridge: Bridge, js: String): String {
|
||||
return suspendCoroutine { continuation ->
|
||||
bridge.eval(js) { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var _workspaceId: String
|
||||
private lateinit var _docId: String
|
||||
private lateinit var _docContentInMD: String
|
||||
|
||||
fun workspaceId() = _workspaceId
|
||||
|
||||
fun docId() = _docId
|
||||
|
||||
fun docContentInMD() = _docContentInMD
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package app.affine.pro.service
|
||||
|
||||
import app.affine.pro.BuildConfig
|
||||
import com.apollographql.apollo.ApolloClient
|
||||
import com.apollographql.apollo.api.Mutation
|
||||
import com.apollographql.apollo.api.Query
|
||||
import com.apollographql.apollo.api.Subscription
|
||||
import com.apollographql.apollo.network.okHttpClient
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GraphQLClient @Inject constructor() {
|
||||
|
||||
private val _client: ApolloClient by lazy {
|
||||
ApolloClient.Builder().serverUrl("${BuildConfig.BASE_URL}/graphql")
|
||||
.okHttpClient(OkHttp.client)
|
||||
.build()
|
||||
}
|
||||
|
||||
suspend fun <D : Query.Data> query(query: Query<D>) = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
_client.query(query).execute().dataOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <D : Mutation.Data> mutation(mutation: Mutation<D>) = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
_client.mutation(mutation).execute().dataOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <D : Subscription.Data> subscription(subscription: Subscription<D>) =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
_client.subscription(subscription).execute().dataOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,29 @@
|
||||
package app.affine.pro.repo
|
||||
package app.affine.pro.service
|
||||
|
||||
import app.affine.pro.Prompt
|
||||
import app.affine.pro.service.GraphQLClient
|
||||
import app.affine.pro.utils.getCurrentServerBaseUrl
|
||||
import com.affine.pro.graphql.CreateCopilotMessageMutation
|
||||
import com.affine.pro.graphql.CreateCopilotSessionMutation
|
||||
import com.affine.pro.graphql.GetCopilotHistoriesQuery
|
||||
import com.affine.pro.graphql.GetCopilotSessionsQuery
|
||||
import com.affine.pro.graphql.type.CreateChatMessageInput
|
||||
import com.affine.pro.graphql.type.CreateChatSessionInput
|
||||
import com.apollographql.apollo.ApolloClient
|
||||
import com.apollographql.apollo.api.Mutation
|
||||
import com.apollographql.apollo.api.Optional
|
||||
import com.apollographql.apollo.api.Query
|
||||
import com.apollographql.apollo.api.Subscription
|
||||
import com.apollographql.apollo.network.okHttpClient
|
||||
import com.getcapacitor.Bridge
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GraphQLRepo @Inject constructor(
|
||||
private val client: GraphQLClient
|
||||
) {
|
||||
class GraphQLService @Inject constructor() {
|
||||
|
||||
suspend fun getCopilotSession(workspaceId: String, docId: String) = client.query(
|
||||
suspend fun getCopilotSession(workspaceId: String, docId: String) = query(
|
||||
GetCopilotSessionsQuery(
|
||||
workspaceId = workspaceId,
|
||||
docId = Optional.present(docId)
|
||||
@@ -30,7 +36,7 @@ class GraphQLRepo @Inject constructor(
|
||||
workspaceId: String,
|
||||
docId: String,
|
||||
prompt: Prompt = Prompt.ChatWithAFFiNEAI
|
||||
) = client.mutation(
|
||||
) = mutation(
|
||||
CreateCopilotSessionMutation(
|
||||
CreateChatSessionInput(
|
||||
docId = docId,
|
||||
@@ -46,7 +52,7 @@ class GraphQLRepo @Inject constructor(
|
||||
workspaceId: String,
|
||||
docId: String,
|
||||
sessionId: String,
|
||||
) = client.query(
|
||||
) = query(
|
||||
GetCopilotHistoriesQuery(
|
||||
workspaceId = workspaceId,
|
||||
docId = Optional.present(docId),
|
||||
@@ -60,15 +66,49 @@ class GraphQLRepo @Inject constructor(
|
||||
suspend fun createCopilotMessage(
|
||||
sessionId: String,
|
||||
message: String,
|
||||
) = client.mutation(CreateCopilotMessageMutation(
|
||||
CreateChatMessageInput(
|
||||
sessionId = sessionId,
|
||||
content = Optional.present(message)
|
||||
) = mutation(
|
||||
CreateCopilotMessageMutation(
|
||||
CreateChatMessageInput(
|
||||
sessionId = sessionId,
|
||||
content = Optional.present(message)
|
||||
)
|
||||
)
|
||||
)).mapCatching { data ->
|
||||
).mapCatching { data ->
|
||||
data.createCopilotMessage
|
||||
}
|
||||
|
||||
suspend fun updateServer(bridge: Bridge) {
|
||||
val server = bridge.getCurrentServerBaseUrl()
|
||||
if (this::_client.isInitialized && _client.newBuilder().httpServerUrl == server) return
|
||||
_client = ApolloClient.Builder().serverUrl("$server/graphql")
|
||||
.okHttpClient(OkHttp.client)
|
||||
.build()
|
||||
}
|
||||
|
||||
private lateinit var _client: ApolloClient
|
||||
|
||||
private suspend fun <D : Query.Data> query(query: Query<D>) = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
_client.query(query).execute().dataOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <D : Mutation.Data> mutation(mutation: Mutation<D>) =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
_client.mutation(mutation).execute().dataOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <D : Subscription.Data> subscription(subscription: Subscription<D>) =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
_client.subscription(subscription).execute().dataOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ERROR_NULL_SESSION_ID = "null session id."
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package app.affine.pro.service
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import app.affine.pro.AffineApp
|
||||
import app.affine.pro.utils.dataStore
|
||||
import app.affine.pro.utils.set
|
||||
@@ -53,11 +52,11 @@ object CookieStore {
|
||||
_cookies[host] = cookies
|
||||
MainScope().launch(Dispatchers.IO) {
|
||||
cookies.find { it.name == AFFINE_SESSION }?.let {
|
||||
AffineApp.context().dataStore.set(AFFINE_SESSION, it.toString())
|
||||
AffineApp.context().dataStore.set(host + AFFINE_SESSION, it.toString())
|
||||
}
|
||||
cookies.find { it.name == AFFINE_USER_ID }?.let {
|
||||
Timber.d("Update user id [${it.value}]")
|
||||
AffineApp.context().dataStore.set(AFFINE_USER_ID, it.toString())
|
||||
AffineApp.context().dataStore.set(host + AFFINE_USER_ID, it.toString())
|
||||
Firebase.crashlytics.setUserId(it.value)
|
||||
}
|
||||
}
|
||||
@@ -65,8 +64,8 @@ object CookieStore {
|
||||
|
||||
fun getCookies(host: String) = _cookies[host] ?: emptyList()
|
||||
|
||||
fun getCookie(url: String, name: String) = url.toUri().host
|
||||
?.let { _cookies[it] }
|
||||
fun getCookie(url: HttpUrl, name: String) = url.host
|
||||
.let { _cookies[it] }
|
||||
?.find { cookie -> cookie.name == name }
|
||||
?.value
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.affine.pro.repo
|
||||
package app.affine.pro.service
|
||||
|
||||
import app.affine.pro.BuildConfig
|
||||
import app.affine.pro.service.OkHttp
|
||||
import app.affine.pro.utils.getCurrentServerBaseUrl
|
||||
import com.getcapacitor.Bridge
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
@@ -17,10 +17,16 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SSERepo @Inject constructor() {
|
||||
class SSEService @Inject constructor() {
|
||||
|
||||
private lateinit var _server: String
|
||||
|
||||
suspend fun updateServer(bridge: Bridge) {
|
||||
_server = bridge.getCurrentServerBaseUrl()
|
||||
}
|
||||
|
||||
fun messageStream(sessionId: String, messageId: String) =
|
||||
"${BuildConfig.BASE_URL}/api/copilot/chat/$sessionId/stream?messageId=$messageId".eventSource()
|
||||
"$_server/api/copilot/chat/$sessionId/stream?messageId=$messageId".eventSource()
|
||||
|
||||
data class Event(val id: String?, val type: String?, val data: String)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package app.affine.pro.service
|
||||
|
||||
import app.affine.pro.utils.getCurrentDocContentInMarkdown
|
||||
import app.affine.pro.utils.getCurrentDocId
|
||||
import app.affine.pro.utils.getCurrentWorkspaceId
|
||||
import com.getcapacitor.Bridge
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class WebService @Inject constructor() {
|
||||
|
||||
suspend fun update(bridge: Bridge) {
|
||||
_workspaceId = bridge.getCurrentWorkspaceId()
|
||||
_docId = bridge.getCurrentDocId()
|
||||
_docContentInMD = bridge.getCurrentDocContentInMarkdown()
|
||||
}
|
||||
|
||||
private lateinit var _workspaceId: String
|
||||
private lateinit var _docId: String
|
||||
private lateinit var _docContentInMD: String
|
||||
|
||||
fun workspaceId() = _workspaceId
|
||||
|
||||
fun docId() = _docId
|
||||
|
||||
fun docContentInMD() = _docContentInMD
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package app.affine.pro.service.interceptor
|
||||
|
||||
import android.webkit.CookieManager
|
||||
import app.affine.pro.BuildConfig
|
||||
import com.apollographql.apollo.api.http.HttpRequest
|
||||
import com.apollographql.apollo.network.http.HttpInterceptor
|
||||
import com.apollographql.apollo.network.http.HttpInterceptorChain
|
||||
|
||||
object CookieInterceptor : HttpInterceptor {
|
||||
override suspend fun intercept(
|
||||
request: HttpRequest,
|
||||
chain: HttpInterceptorChain
|
||||
) = chain.proceed(
|
||||
request.newBuilder().addHeader(
|
||||
"Cookie", CookieManager.getInstance().getCookie(BuildConfig.BASE_URL)
|
||||
).build()
|
||||
)
|
||||
}
|
||||
@@ -19,12 +19,4 @@ suspend fun DataStore<Preferences>.set(key: String, value: String) {
|
||||
|
||||
suspend fun DataStore<Preferences>.get(key: String) = data.map {
|
||||
it[stringPreferencesKey(key)] ?: ""
|
||||
}.first()
|
||||
|
||||
suspend fun DataStore<Preferences>.clear(vararg keys: String) {
|
||||
edit { prefs ->
|
||||
keys.forEach { key ->
|
||||
prefs[stringPreferencesKey(key)] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}.first()
|
||||
@@ -0,0 +1,30 @@
|
||||
package app.affine.pro.utils
|
||||
|
||||
import com.getcapacitor.Bridge
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
suspend fun Bridge.getCurrentServerBaseUrl() = eval("window.getCurrentServerBaseUrl()").strict()
|
||||
|
||||
suspend fun Bridge.getCurrentWorkspaceId() = eval("window.getCurrentWorkspaceId()").strict()
|
||||
|
||||
suspend fun Bridge.getCurrentDocId() = eval("window.getCurrentDocId()").strict()
|
||||
|
||||
suspend fun Bridge.getCurrentDocContentInMarkdown() =
|
||||
eval("window.getCurrentDocContentInMarkdown()").strict()
|
||||
|
||||
private suspend fun Bridge.eval(js: String): String {
|
||||
return suspendCoroutine { continuation ->
|
||||
eval(js) { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.strict() = let {
|
||||
if (startsWith("\"") && endsWith("\"")) {
|
||||
substring(1, lastIndex)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -41,19 +42,19 @@ class FileTree(context: Context) : Timber.Tree() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkAndUploadOldLogs() {
|
||||
suspend fun checkAndUploadOldLogs(server: HttpUrl) {
|
||||
val today = dateFormat.format(Date())
|
||||
logDirectory.listFiles()?.forEach { file ->
|
||||
val fileName = file.name
|
||||
if (fileName.endsWith(".log") && !fileName.startsWith(today)) {
|
||||
uploadLogToFirebase(file)
|
||||
uploadLogToFirebase(server, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun uploadLogToFirebase(file: File) =
|
||||
private suspend fun uploadLogToFirebase(server: HttpUrl, file: File) =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val user = CookieStore.getCookie(BuildConfig.BASE_URL, CookieStore.AFFINE_USER_ID)
|
||||
val user = CookieStore.getCookie(server, CookieStore.AFFINE_USER_ID)
|
||||
?: return@suspendCancellableCoroutine
|
||||
val storageRef = Firebase.storage.reference
|
||||
val logFileRef = storageRef.child("android_log/$user/${file.name}")
|
||||
|
||||
Reference in New Issue
Block a user