diff --git a/LICENSE.md b/LICENSE.md index 5479bb8..c1602fc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2024 Appwrite (https://appwrite.io) and individual contributors. +Copyright (c) 2025 Appwrite (https://appwrite.io) and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index f8dab1a..957018d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Maven Central](https://img.shields.io/maven-central/v/io.appwrite/sdk-for-android.svg?color=green&style=flat-square) ![License](https://img.shields.io/github/license/appwrite/sdk-for-android.svg?style=flat-square) -![Version](https://img.shields.io/badge/api%20version-1.6.0-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-1.6.1-blue.svg?style=flat-square) [![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator) [![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord) @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:6.0.0") +implementation("io.appwrite:sdk-for-android:6.1.1") ``` ### Maven @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 6.0.0 + 6.1.1 ``` diff --git a/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt b/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt index f53113d..4302327 100644 --- a/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt +++ b/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt @@ -81,8 +81,8 @@ class AccountsViewModel : ViewModel() { account.createOAuth2Session( activity, OAuthProvider.FACEBOOK, - "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/success", - "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/failure" + "appwrite-callback-6070749e6acd4://cloud.appwrite.io/auth/oauth2/success", + "appwrite-callback-6070749e6acd4://cloud.appwrite.io/auth/oauth2/failure" ) } catch (e: Exception) { _error.postValue(Event(e)) diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 93a3e06..b7d698e 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -60,7 +60,7 @@ class Client @JvmOverloads constructor( internal lateinit var http: OkHttpClient internal val headers: MutableMap - + val config: MutableMap internal val cookieJar = ListenableCookieJar(CookieManager( @@ -86,11 +86,11 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "6.0.0", + "x-sdk-version" to "6.1.1", "x-appwrite-response-format" to "1.6.0" ) config = mutableMapOf() - + setSelfSigned(selfSigned) } @@ -154,10 +154,10 @@ class Client @JvmOverloads constructor( /** * Set self Signed - * + * * @param status * - * @return this + * @return this */ fun setSelfSigned(status: Boolean): Client { selfSigned = status @@ -206,10 +206,10 @@ class Client @JvmOverloads constructor( /** * Set endpoint and realtime endpoint. - * + * * @param endpoint * - * @return this + * @return this */ fun setEndpoint(endpoint: String): Client { this.endpoint = endpoint @@ -235,32 +235,51 @@ class Client @JvmOverloads constructor( /** * Add Header - * + * * @param key * @param value * - * @return this + * @return this */ fun addHeader(key: String, value: String): Client { headers[key] = value return this } + /** + * Sends a "ping" request to Appwrite to verify connectivity. + * + * @return String + */ + suspend fun ping(): String { + val apiPath = "/ping" + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf("content-type" to "application/json") + + return call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = String::class.java + ) + } + /** * Send the HTTP request - * + * * @param method * @param path * @param headers * @param params * - * @return [T] + * @return [T] */ @Throws(AppwriteException::class) suspend fun call( - method: String, - path: String, - headers: Map = mapOf(), + method: String, + path: String, + headers: Map = mapOf(), params: Map = mapOf(), responseType: Class, converter: ((Any) -> T)? = null @@ -398,7 +417,7 @@ class Client @JvmOverloads constructor( var offset = 0L var result: Map<*, *>? = null - if (idParamName?.isNotEmpty() == true && params[idParamName] != "unique()") { + if (idParamName?.isNotEmpty() == true) { // Make a request to check if a file already exists val current = call( method = "GET", @@ -495,14 +514,14 @@ class Client @JvmOverloads constructor( .charStream() .buffered() .use(BufferedReader::readText) - + val error = if (response.headers["content-type"]?.contains("application/json") == true) { val map = body.fromJson>() AppwriteException( - map["message"] as? String ?: "", + map["message"] as? String ?: "", (map["code"] as Number).toInt(), - map["type"] as? String ?: "", + map["type"] as? String ?: "", body ) } else { @@ -524,6 +543,14 @@ class Client @JvmOverloads constructor( it.resume(true as T) return } + responseType == String::class.java -> { + val body = response.body!! + .charStream() + .buffered() + .use(BufferedReader::readText) + it.resume(body as T) + return + } responseType == ByteArray::class.java -> { it.resume(response.body!! .byteStream() @@ -554,4 +581,4 @@ class Client @JvmOverloads constructor( } }) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/ImageFormat.kt b/library/src/main/java/io/appwrite/enums/ImageFormat.kt index c6a3b0f..7249f2a 100644 --- a/library/src/main/java/io/appwrite/enums/ImageFormat.kt +++ b/library/src/main/java/io/appwrite/enums/ImageFormat.kt @@ -12,7 +12,11 @@ enum class ImageFormat(val value: String) { @SerializedName("png") PNG("png"), @SerializedName("webp") - WEBP("webp"); + WEBP("webp"), + @SerializedName("heic") + HEIC("heic"), + @SerializedName("avif") + AVIF("avif"); override fun toString() = value } \ No newline at end of file diff --git a/library/src/main/java/io/appwrite/models/Document.kt b/library/src/main/java/io/appwrite/models/Document.kt index 5a7d371..01204de 100644 --- a/library/src/main/java/io/appwrite/models/Document.kt +++ b/library/src/main/java/io/appwrite/models/Document.kt @@ -41,7 +41,7 @@ data class Document( * Document permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). */ @SerializedName("\$permissions") - val permissions: List, + val permissions: List, /** * Additional properties @@ -66,7 +66,7 @@ data class Document( databaseId: String, createdAt: String, updatedAt: String, - permissions: List, + permissions: List, data: Map ) = Document>( id, @@ -88,7 +88,7 @@ data class Document( databaseId = map["\$databaseId"] as String, createdAt = map["\$createdAt"] as String, updatedAt = map["\$updatedAt"] as String, - permissions = map["\$permissions"] as List, + permissions = map["\$permissions"] as List, data = map.jsonCast(to = nestedType) ) } diff --git a/library/src/main/java/io/appwrite/models/Execution.kt b/library/src/main/java/io/appwrite/models/Execution.kt index 44724fa..1b6226a 100644 --- a/library/src/main/java/io/appwrite/models/Execution.kt +++ b/library/src/main/java/io/appwrite/models/Execution.kt @@ -29,7 +29,7 @@ data class Execution( * Execution roles. */ @SerializedName("\$permissions") - val permissions: List, + val permissions: List, /** * Function ID. @@ -139,7 +139,7 @@ data class Execution( id = map["\$id"] as String, createdAt = map["\$createdAt"] as String, updatedAt = map["\$updatedAt"] as String, - permissions = map["\$permissions"] as List, + permissions = map["\$permissions"] as List, functionId = map["functionId"] as String, trigger = map["trigger"] as String, status = map["status"] as String, diff --git a/library/src/main/java/io/appwrite/models/File.kt b/library/src/main/java/io/appwrite/models/File.kt index 5c2b731..897fc4a 100644 --- a/library/src/main/java/io/appwrite/models/File.kt +++ b/library/src/main/java/io/appwrite/models/File.kt @@ -35,7 +35,7 @@ data class File( * File permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). */ @SerializedName("\$permissions") - val permissions: List, + val permissions: List, /** * File name. @@ -98,7 +98,7 @@ data class File( bucketId = map["bucketId"] as String, createdAt = map["\$createdAt"] as String, updatedAt = map["\$updatedAt"] as String, - permissions = map["\$permissions"] as List, + permissions = map["\$permissions"] as List, name = map["name"] as String, signature = map["signature"] as String, mimeType = map["mimeType"] as String, diff --git a/library/src/main/java/io/appwrite/models/Membership.kt b/library/src/main/java/io/appwrite/models/Membership.kt index 17ab303..4a899ee 100644 --- a/library/src/main/java/io/appwrite/models/Membership.kt +++ b/library/src/main/java/io/appwrite/models/Membership.kt @@ -32,13 +32,13 @@ data class Membership( val userId: String, /** - * User name. + * User name. Hide this attribute by toggling membership privacy in the Console. */ @SerializedName("userName") val userName: String, /** - * User email address. + * User email address. Hide this attribute by toggling membership privacy in the Console. */ @SerializedName("userEmail") val userEmail: String, @@ -74,7 +74,7 @@ data class Membership( val confirm: Boolean, /** - * Multi factor authentication status, true if the user has MFA enabled or false otherwise. + * Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console. */ @SerializedName("mfa") val mfa: Boolean, @@ -83,7 +83,7 @@ data class Membership( * User list of roles */ @SerializedName("roles") - val roles: List, + val roles: List, ) { fun toMap(): Map = mapOf( @@ -120,7 +120,7 @@ data class Membership( joined = map["joined"] as String, confirm = map["confirm"] as Boolean, mfa = map["mfa"] as Boolean, - roles = map["roles"] as List, + roles = map["roles"] as List, ) } } \ No newline at end of file diff --git a/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt b/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt index ad8bc45..3fab41a 100644 --- a/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt +++ b/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt @@ -11,7 +11,7 @@ data class MfaRecoveryCodes( * Recovery codes. */ @SerializedName("recoveryCodes") - val recoveryCodes: List, + val recoveryCodes: List, ) { fun toMap(): Map = mapOf( @@ -24,7 +24,7 @@ data class MfaRecoveryCodes( fun from( map: Map, ) = MfaRecoveryCodes( - recoveryCodes = map["recoveryCodes"] as List, + recoveryCodes = map["recoveryCodes"] as List, ) } } \ No newline at end of file diff --git a/library/src/main/java/io/appwrite/models/RealtimeModels.kt b/library/src/main/java/io/appwrite/models/RealtimeModels.kt index bdd8752..cc1af63 100644 --- a/library/src/main/java/io/appwrite/models/RealtimeModels.kt +++ b/library/src/main/java/io/appwrite/models/RealtimeModels.kt @@ -17,7 +17,7 @@ data class RealtimeCallback( open class RealtimeResponse( val type: String, - val data: Any + val data: Any? ) data class RealtimeResponseEvent( diff --git a/library/src/main/java/io/appwrite/models/Session.kt b/library/src/main/java/io/appwrite/models/Session.kt index 1c6a7b2..02dbb28 100644 --- a/library/src/main/java/io/appwrite/models/Session.kt +++ b/library/src/main/java/io/appwrite/models/Session.kt @@ -167,7 +167,7 @@ data class Session( * Returns a list of active session factors. */ @SerializedName("factors") - val factors: List, + val factors: List, /** * Secret used to authenticate the user. Only included if the request was made with an API key @@ -246,7 +246,7 @@ data class Session( countryCode = map["countryCode"] as String, countryName = map["countryName"] as String, current = map["current"] as Boolean, - factors = map["factors"] as List, + factors = map["factors"] as List, secret = map["secret"] as String, mfaUpdatedAt = map["mfaUpdatedAt"] as String, ) diff --git a/library/src/main/java/io/appwrite/models/Target.kt b/library/src/main/java/io/appwrite/models/Target.kt index bd4d207..54b4694 100644 --- a/library/src/main/java/io/appwrite/models/Target.kt +++ b/library/src/main/java/io/appwrite/models/Target.kt @@ -55,6 +55,12 @@ data class Target( @SerializedName("identifier") val identifier: String, + /** + * Is the target expired. + */ + @SerializedName("expired") + val expired: Boolean, + ) { fun toMap(): Map = mapOf( "\$id" to id as Any, @@ -65,6 +71,7 @@ data class Target( "providerId" to providerId as Any, "providerType" to providerType as Any, "identifier" to identifier as Any, + "expired" to expired as Any, ) companion object { @@ -81,6 +88,7 @@ data class Target( providerId = map["providerId"] as? String?, providerType = map["providerType"] as String, identifier = map["identifier"] as String, + expired = map["expired"] as Boolean, ) } } \ No newline at end of file diff --git a/library/src/main/java/io/appwrite/models/User.kt b/library/src/main/java/io/appwrite/models/User.kt index 095467a..fd03fc2 100644 --- a/library/src/main/java/io/appwrite/models/User.kt +++ b/library/src/main/java/io/appwrite/models/User.kt @@ -65,7 +65,7 @@ data class User( * Labels for the user. */ @SerializedName("labels") - val labels: List, + val labels: List, /** * Password update time in ISO 8601 format. @@ -155,7 +155,7 @@ data class User( hashOptions: Any?, registration: String, status: Boolean, - labels: List, + labels: List, passwordUpdate: String, email: String, phone: String, @@ -201,7 +201,7 @@ data class User( hashOptions = map["hashOptions"] as? Any?, registration = map["registration"] as String, status = map["status"] as Boolean, - labels = map["labels"] as List, + labels = map["labels"] as List, passwordUpdate = map["passwordUpdate"] as String, email = map["email"] as String, phone = map["phone"] as String, diff --git a/library/src/main/java/io/appwrite/services/Account.kt b/library/src/main/java/io/appwrite/services/Account.kt index c3655ea..cb22b33 100644 --- a/library/src/main/java/io/appwrite/services/Account.kt +++ b/library/src/main/java/io/appwrite/services/Account.kt @@ -190,7 +190,7 @@ class Account(client: Client) : Service(client) { ) /** - * List Identities + * List identities * * Get the list of identities for the currently logged in user. * @@ -370,7 +370,7 @@ class Account(client: Client) : Service(client) { ) /** - * Create Authenticator + * Create authenticator * * Add an authenticator app to be used as an MFA factor. Verify the authenticator using the [verify authenticator](/docs/references/cloud/client-web/account#updateMfaAuthenticator) method. * @@ -404,7 +404,7 @@ class Account(client: Client) : Service(client) { /** - * Verify Authenticator + * Verify authenticator * * Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method. * @@ -441,7 +441,7 @@ class Account(client: Client) : Service(client) { } /** - * Verify Authenticator + * Verify authenticator * * Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method. * @@ -460,7 +460,7 @@ class Account(client: Client) : Service(client) { ) /** - * Delete Authenticator + * Delete authenticator * * Delete an authenticator for a user by ID. * @@ -489,7 +489,7 @@ class Account(client: Client) : Service(client) { /** - * Create MFA Challenge + * Create MFA challenge * * Begin the process of MFA verification after sign-in. Finish the flow with [updateMfaChallenge](/docs/references/cloud/client-web/account#updateMfaChallenge) method. * @@ -523,18 +523,18 @@ class Account(client: Client) : Service(client) { /** - * Create MFA Challenge (confirmation) + * Create MFA challenge (confirmation) * * Complete the MFA challenge by providing the one-time password. Finish the process of MFA verification by providing the one-time password. To begin the flow, use [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) method. * * @param challengeId ID of the challenge. * @param otp Valid verification token. - * @return [Any] + * @return [io.appwrite.models.Session] */ suspend fun updateMfaChallenge( challengeId: String, otp: String, - ): Any { + ): io.appwrite.models.Session { val apiPath = "/account/mfa/challenge" val apiParams = mutableMapOf( @@ -544,18 +544,23 @@ class Account(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "content-type" to "application/json", ) + val converter: (Any) -> io.appwrite.models.Session = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Session.from(map = it as Map) + } return client.call( "PUT", apiPath, apiHeaders, apiParams, - responseType = Any::class.java, + responseType = io.appwrite.models.Session::class.java, + converter, ) } /** - * List Factors + * List factors * * List the factors available on the account to be used as a MFA challange. * @@ -586,7 +591,7 @@ class Account(client: Client) : Service(client) { /** - * Get MFA Recovery Codes + * Get MFA recovery codes * * Get recovery codes that can be used as backup for MFA flow. Before getting codes, they must be generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method. An OTP challenge is required to read recovery codes. * @@ -617,7 +622,7 @@ class Account(client: Client) : Service(client) { /** - * Create MFA Recovery Codes + * Create MFA recovery codes * * Generate recovery codes as backup for MFA flow. It's recommended to generate and show then immediately after user successfully adds their authehticator. Recovery codes can be used as a MFA verification type in [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) method. * @@ -648,7 +653,7 @@ class Account(client: Client) : Service(client) { /** - * Regenerate MFA Recovery Codes + * Regenerate MFA recovery codes * * Regenerate recovery codes that can be used as backup for MFA flow. Before regenerating codes, they must be first generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method. An OTP challenge is required to regenreate recovery codes. * @@ -1209,7 +1214,9 @@ class Account(client: Client) : Service(client) { return@forEach } is List<*> -> { - apiQuery.add("${it.key}[]=${it.value.toString()}") + (it.value as List<*>).forEach { v -> + apiQuery.add("${it.key}[]=${v.toString()}") + } } else -> { apiQuery.add("${it.key}=${it.value.toString()}") @@ -1465,7 +1472,7 @@ class Account(client: Client) : Service(client) { /** * Create push target * - * + * Use this endpoint to register a device for push notifications. Provide a target ID (custom or generated using ID.unique()), a device identifier (usually a device token), and optionally specify which provider should send notifications to this target. The target is automatically linked to the current session and includes device information like brand and model. * * @param targetId Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. * @param identifier The target identifier (token, email, phone etc.) @@ -1506,7 +1513,7 @@ class Account(client: Client) : Service(client) { /** * Update push target * - * + * Update the currently logged in user's push notification target. You can modify the target's identifier (device token) and provider ID (token, email, phone etc.). The target must exist and belong to the current user. If you change the provider ID, notifications will be sent through the new messaging provider instead. * * @param targetId Target ID. * @param identifier The target identifier (token, email, phone etc.) @@ -1543,7 +1550,7 @@ class Account(client: Client) : Service(client) { /** * Delete push target * - * + * Delete a push notification target for the currently logged in user. After deletion, the device will no longer receive push notifications. The target must exist and belong to the current user. * * @param targetId Target ID. * @return [Any] @@ -1613,7 +1620,7 @@ class Account(client: Client) : Service(client) { /** * Create magic URL token * - * Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). + * Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * @param userId Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. * @param email User email. @@ -1688,7 +1695,9 @@ class Account(client: Client) : Service(client) { return@forEach } is List<*> -> { - apiQuery.add("${it.key}[]=${it.value.toString()}") + (it.value as List<*>).forEach { v -> + apiQuery.add("${it.key}[]=${v.toString()}") + } } else -> { apiQuery.add("${it.key}=${it.value.toString()}") diff --git a/library/src/main/java/io/appwrite/services/Locale.kt b/library/src/main/java/io/appwrite/services/Locale.kt index ad3a6f6..01b3017 100644 --- a/library/src/main/java/io/appwrite/services/Locale.kt +++ b/library/src/main/java/io/appwrite/services/Locale.kt @@ -47,7 +47,7 @@ class Locale(client: Client) : Service(client) { /** - * List Locale Codes + * List locale codes * * List of all locale codes in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). * diff --git a/library/src/main/java/io/appwrite/services/Realtime.kt b/library/src/main/java/io/appwrite/services/Realtime.kt index 1bffd3a..b06eacf 100644 --- a/library/src/main/java/io/appwrite/services/Realtime.kt +++ b/library/src/main/java/io/appwrite/services/Realtime.kt @@ -29,6 +29,8 @@ class Realtime(client: Client) : Service(client), CoroutineScope { private companion object { private const val TYPE_ERROR = "error" private const val TYPE_EVENT = "event" + private const val TYPE_PONG = "pong" + private const val HEARTBEAT_INTERVAL = 20_000L // 20 seconds private const val DEBOUNCE_MILLIS = 1L @@ -40,6 +42,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { private var reconnectAttempts = 0 private var subscriptionsCounter = 0 private var reconnect = true + private var heartbeatJob: Job? = null } private fun createSocket() { @@ -80,9 +83,25 @@ class Realtime(client: Client) : Service(client), CoroutineScope { } private fun closeSocket() { + stopHeartbeat() socket?.close(RealtimeCode.POLICY_VIOLATION.value, null) } + private fun startHeartbeat() { + stopHeartbeat() + heartbeatJob = launch { + while (isActive) { + delay(HEARTBEAT_INTERVAL) + socket?.send("""{"type":"ping"}""") + } + } + } + + private fun stopHeartbeat() { + heartbeatJob?.cancel() + heartbeatJob = null + } + private fun getTimeout() = when { reconnectAttempts < 5 -> 1000L reconnectAttempts < 15 -> 5000L @@ -145,6 +164,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { override fun onOpen(webSocket: WebSocket, response: Response) { super.onOpen(webSocket, response) reconnectAttempts = 0 + startHeartbeat() } override fun onMessage(webSocket: WebSocket, text: String) { @@ -155,16 +175,17 @@ class Realtime(client: Client) : Service(client), CoroutineScope { when (message.type) { TYPE_ERROR -> handleResponseError(message) TYPE_EVENT -> handleResponseEvent(message) + TYPE_PONG -> {} } } } private fun handleResponseError(message: RealtimeResponse) { - throw message.data.jsonCast() + throw message.data?.jsonCast() ?: RuntimeException("Data is not present") } private suspend fun handleResponseEvent(message: RealtimeResponse) { - val event = message.data.jsonCast>() + val event = message.data?.jsonCast>() ?: return if (event.channels.isEmpty()) { return } @@ -181,6 +202,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { super.onClosing(webSocket, code, reason) + stopHeartbeat() if (!reconnect || code == RealtimeCode.POLICY_VIOLATION.value) { reconnect = true return @@ -203,6 +225,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { super.onFailure(webSocket, t, response) + stopHeartbeat() t.printStackTrace() } } diff --git a/library/src/main/java/io/appwrite/services/Storage.kt b/library/src/main/java/io/appwrite/services/Storage.kt index 8f21473..1f990c2 100644 --- a/library/src/main/java/io/appwrite/services/Storage.kt +++ b/library/src/main/java/io/appwrite/services/Storage.kt @@ -189,7 +189,7 @@ class Storage(client: Client) : Service(client) { /** - * Delete File + * Delete file * * Delete a file by its unique ID. Only users with write permissions have access to delete this resource. * diff --git a/library/src/main/java/io/appwrite/services/Teams.kt b/library/src/main/java/io/appwrite/services/Teams.kt index fd0ae92..b586f11 100644 --- a/library/src/main/java/io/appwrite/services/Teams.kt +++ b/library/src/main/java/io/appwrite/services/Teams.kt @@ -275,7 +275,7 @@ class Teams(client: Client) : Service(client) { /** * List team memberships * - * Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. + * Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console. * * @param teamId Team ID. * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm @@ -369,7 +369,7 @@ class Teams(client: Client) : Service(client) { /** * Get team membership * - * Get a team member by the membership unique id. All team members have read access for this resource. + * Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console. * * @param teamId Team ID. * @param membershipId Membership ID.