Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## 14.0.0

* [BREAKING] Changed `$sequence` type from `Long` to `String` for `Row` and `Document` models
* Added impersonation support: `setImpersonateUserId()`, `setImpersonateUserEmail()`, `setImpersonateUserPhone()` on `Client`
* Added `impersonator` and `impersonatorUserId` optional fields to `User` model
* Updated `Log` model field descriptions to clarify impersonation behavior for `userId`, `userEmail`, `userName`
* Updated `X-Appwrite-Response-Format` header to `1.9.0`
* Updated API version badge to `1.9.0` and compatibility note to server version `1.9.x` in README

## 13.0.0

* Breaking: Channel factory methods require explicit IDs (no wildcard defaults)
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

![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.8.1-blue.svg?style=flat-square)
![Version](https://img.shields.io/badge/api%20version-1.9.0-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)

**This SDK is compatible with Appwrite server version latest. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-android/releases).**
**This SDK is compatible with Appwrite server version 1.9.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-android/releases).**

Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Android SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)

Expand Down Expand Up @@ -38,7 +38,7 @@ repositories {
Next, add the dependency to your project's `build.gradle(.kts)` file:

```groovy
implementation("io.appwrite:sdk-for-android:13.0.0")
implementation("io.appwrite:sdk-for-android:14.0.0")
```

### Maven
Expand All @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file:
<dependency>
<groupId>io.appwrite</groupId>
<artifactId>sdk-for-android</artifactId>
<version>13.0.0</version>
<version>14.0.0</version>
</dependency>
</dependencies>
```
Expand Down
49 changes: 47 additions & 2 deletions library/src/main/java/io/appwrite/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ class Client @JvmOverloads constructor(
"x-sdk-name" to "Android",
"x-sdk-platform" to "client",
"x-sdk-language" to "android",
"x-sdk-version" to "13.0.0",
"x-appwrite-response-format" to "1.8.0"
"x-sdk-version" to "14.0.0",
"x-appwrite-response-format" to "1.9.0"
)
config = mutableMapOf()

Expand Down Expand Up @@ -168,6 +168,51 @@ class Client @JvmOverloads constructor(
return this
}

/**
* Set ImpersonateUserId
*
* Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data.
*
* @param {string} impersonateuserid
*
* @return this
*/
fun setImpersonateUserId(value: String): Client {
config["impersonateUserId"] = value
addHeader("x-appwrite-impersonate-user-id", value)
return this
}

/**
* Set ImpersonateUserEmail
*
* Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data.
*
* @param {string} impersonateuseremail
*
* @return this
*/
fun setImpersonateUserEmail(value: String): Client {
config["impersonateUserEmail"] = value
addHeader("x-appwrite-impersonate-user-email", value)
return this
}

/**
* Set ImpersonateUserPhone
*
* Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data.
*
* @param {string} impersonateuserphone
*
* @return this
*/
fun setImpersonateUserPhone(value: String): Client {
config["impersonateUserPhone"] = value
addHeader("x-appwrite-impersonate-user-phone", value)
return this
}

/**
* Set self Signed
*
Expand Down
6 changes: 3 additions & 3 deletions library/src/main/java/io/appwrite/models/Document.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ data class Document<T>(
* Document sequence ID.
*/
@SerializedName("\$sequence")
val sequence: Long,
val sequence: String,

/**
* Collection ID.
Expand Down Expand Up @@ -69,7 +69,7 @@ data class Document<T>(
companion object {
operator fun invoke(
id: String,
sequence: Long,
sequence: String,
collectionId: String,
databaseId: String,
createdAt: String,
Expand All @@ -93,7 +93,7 @@ data class Document<T>(
nestedType: Class<T>
) = Document<T>(
id = map["\$id"] as String,
sequence = (map["\$sequence"] as Number).toLong(),
sequence = map["\$sequence"] as String,
collectionId = map["\$collectionId"] as String,
databaseId = map["\$databaseId"] as String,
createdAt = map["\$createdAt"] as String,
Expand Down
6 changes: 3 additions & 3 deletions library/src/main/java/io/appwrite/models/Log.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ data class Log(
val event: String,

/**
* User ID.
* User ID of the actor recorded for this log. During impersonation, this is the original impersonator, not the impersonated target user.
*/
@SerializedName("userId")
val userId: String,

/**
* User Email.
* User email of the actor recorded for this log. During impersonation, this is the original impersonator.
*/
@SerializedName("userEmail")
val userEmail: String,

/**
* User Name.
* User name of the actor recorded for this log. During impersonation, this is the original impersonator.
*/
@SerializedName("userName")
val userName: String,
Expand Down
6 changes: 3 additions & 3 deletions library/src/main/java/io/appwrite/models/Row.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ data class Row<T>(
* Row sequence ID.
*/
@SerializedName("\$sequence")
val sequence: Long,
val sequence: String,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "Row.kt" -type f | head -20

Repository: appwrite/sdk-for-android

Length of output: 117


🏁 Script executed:

cd library/src/main/java/io/appwrite/models && ls -la | head -30

Repository: appwrite/sdk-for-android

Length of output: 1891


🏁 Script executed:

wc -l ./library/src/main/java/io/appwrite/models/Row.kt

Repository: appwrite/sdk-for-android

Length of output: 121


🏁 Script executed:

cat -n ./library/src/main/java/io/appwrite/models/Row.kt

Repository: appwrite/sdk-for-android

Length of output: 3388


🏁 Script executed:

rg "map\[" library/src/main/java/io/appwrite/models/*.kt -A 1 -B 1 | head -80

Repository: appwrite/sdk-for-android

Length of output: 7182


🏁 Script executed:

grep -n "as String" library/src/main/java/io/appwrite/models/Row.kt

Repository: appwrite/sdk-for-android

Length of output: 392


🏁 Script executed:

find . -name "jsonCast*" -o -name "*Extension*" | grep -i extension | head -20

Repository: appwrite/sdk-for-android

Length of output: 326


🏁 Script executed:

cat -n ./library/src/main/java/io/appwrite/extensions/JsonExtensions.kt

Repository: appwrite/sdk-for-android

Length of output: 1345


🏁 Script executed:

grep -rn "sequence" library/src/main/java/io/appwrite/models/Row.kt

Repository: appwrite/sdk-for-android

Length of output: 317


🏁 Script executed:

rg "\$sequence" library/ --type java --type kotlin -B 2 -A 2

Repository: appwrite/sdk-for-android

Length of output: 50


🏁 Script executed:

rg "sequence" library/src/test -B 3 -A 3 | head -60

Repository: appwrite/sdk-for-android

Length of output: 124


🏁 Script executed:

find . -name "*.json" -type f | head -10

Repository: appwrite/sdk-for-android

Length of output: 100


🏁 Script executed:

rg "Number" library/src/main/java/io/appwrite/extensions/JsonExtensions.kt -B 2 -A 2

Repository: appwrite/sdk-for-android

Length of output: 380


🏁 Script executed:

rg "as String" library/src/main/java/io/appwrite/models/ | wc -l

Repository: appwrite/sdk-for-android

Length of output: 71


🏁 Script executed:

rg "toLong()" library/src/main/java/io/appwrite/models/ | head -20

Repository: appwrite/sdk-for-android

Length of output: 2349


🏁 Script executed:

cat -n library/src/main/java/io/appwrite/extensions/TypeExtensions.kt

Repository: appwrite/sdk-for-android

Length of output: 362


🏁 Script executed:

rg "Sequence" library/src/main/java/io/appwrite/models/Row.kt -B 3 -A 3

Repository: appwrite/sdk-for-android

Length of output: 50


🏁 Script executed:

git log --oneline library/src/main/java/io/appwrite/models/Row.kt | head -5

Repository: appwrite/sdk-for-android

Length of output: 110


🏁 Script executed:

git show HEAD:library/src/main/java/io/appwrite/models/Row.kt | head -110 | tail -20

Repository: appwrite/sdk-for-android

Length of output: 745


🏁 Script executed:

rg "as String" library/src/main/java/io/appwrite/models/Row.kt -n

Repository: appwrite/sdk-for-android

Length of output: 392


🏁 Script executed:

git diff HEAD~1 library/src/main/java/io/appwrite/models/Row.kt 2>/dev/null || echo "Cannot show diff"

Repository: appwrite/sdk-for-android

Length of output: 84


Fix unsafe $sequence deserialization cast at line 96.

The hard cast sequence = map["\$sequence"] as String in the from() function can fail at runtime if the API payload deserializes this field as a numeric type (per Gson's ToNumberPolicy.LONG_OR_DOUBLE configuration). Make the parsing tolerant to both String and Number types.

Suggested fix
-            sequence = map["\$sequence"] as String,
+            sequence = when (val rawSequence = map["\$sequence"]) {
+                is String -> rawSequence
+                is Number -> rawSequence.toLong().toString()
+                else -> throw IllegalArgumentException("Invalid \$sequence type: ${rawSequence?.javaClass}")
+            },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library/src/main/java/io/appwrite/models/Row.kt` at line 20, The
deserialization for Row.sequence in the Row.from() function uses an unsafe cast
`sequence = map["$sequence"] as String`; change it to tolerate either String or
Number by checking the runtime type of map["$sequence"] (e.g., if it's a String
use it directly, if it's a Number call .toString(), and handle null/other types
appropriately) so parsing works when Gson returns numeric types; update the
assignment in Row.from() and any related nullability handling to use the safe
converted string.


/**
* Table ID.
Expand Down Expand Up @@ -69,7 +69,7 @@ data class Row<T>(
companion object {
operator fun invoke(
id: String,
sequence: Long,
sequence: String,
tableId: String,
databaseId: String,
createdAt: String,
Expand All @@ -93,7 +93,7 @@ data class Row<T>(
nestedType: Class<T>
) = Row<T>(
id = map["\$id"] as String,
sequence = (map["\$sequence"] as Number).toLong(),
sequence = map["\$sequence"] as String,
tableId = map["\$tableId"] as String,
databaseId = map["\$databaseId"] as String,
createdAt = map["\$createdAt"] as String,
Expand Down
20 changes: 20 additions & 0 deletions library/src/main/java/io/appwrite/models/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ data class User<T>(
@SerializedName("accessedAt")
val accessedAt: String,

/**
* Whether the user can impersonate other users.
*/
@SerializedName("impersonator")
var impersonator: Boolean?,

/**
* ID of the original actor performing the impersonation. Present only when the current request is impersonating another user. Internal audit logs attribute the action to this user, while the impersonated target is recorded only in internal audit payload data.
*/
@SerializedName("impersonatorUserId")
var impersonatorUserId: String?,

) {
fun toMap(): Map<String, Any> = mapOf(
"\$id" to id as Any,
Expand All @@ -142,6 +154,8 @@ data class User<T>(
"prefs" to prefs.toMap() as Any,
"targets" to targets.map { it.toMap() } as Any,
"accessedAt" to accessedAt as Any,
"impersonator" to impersonator as Any,
"impersonatorUserId" to impersonatorUserId as Any,
Comment thread
abnegate marked this conversation as resolved.
)

companion object {
Expand All @@ -165,6 +179,8 @@ data class User<T>(
prefs: Preferences<Map<String, Any>>,
targets: List<Target>,
accessedAt: String,
impersonator: Boolean?,
impersonatorUserId: String?,
) = User<Map<String, Any>>(
id,
createdAt,
Expand All @@ -185,6 +201,8 @@ data class User<T>(
prefs,
targets,
accessedAt,
impersonator,
impersonatorUserId,
)

@Suppress("UNCHECKED_CAST")
Expand All @@ -211,6 +229,8 @@ data class User<T>(
prefs = Preferences.from(map = map["prefs"] as Map<String, Any>, nestedType),
targets = (map["targets"] as List<Map<String, Any>>).map { Target.from(map = it) },
accessedAt = map["accessedAt"] as String,
impersonator = map["impersonator"] as? Boolean,
impersonatorUserId = map["impersonatorUserId"] as? String,
)
}
}