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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Change Log

## 29.2.0

* Added: `analytics.createEvent` in a new `Analytics` service to send tracking events
* Added: optional `state` on `account.createOAuth2Session` and `account.createOAuth2Token`, returned unchanged on the success and failure URLs
* Added: optional `membershipId` on `teams.createMembership`
* Fixed: `avatars.updatePhoto` sends `PUT`, so uploading a profile photo no longer returns 404
* Fixed: OAuth2 query parameters (`success`, `failure`, `scopes`, `state`) are URL-encoded, so values containing `&`, `+` or `#` survive the redirect

## 29.1.0

* Added: `account` passkey methods `listPasskeys`, `createPasskey`, `getPasskey`, `updatePasskey`, `deletePasskey` and `updatePasskeyVerification`
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
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:29.1.0")
implementation("io.appwrite:sdk-for-android:29.2.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>29.1.0</version>
<version>29.2.0</version>
</dependency>
</dependencies>
```
Expand Down
1 change: 1 addition & 0 deletions docs/examples/java/account/create-o-auth-2-session.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ account.createOAuth2Session(
"https://example.com", // success (optional)
"https://example.com", // failure (optional)
List.of(), // scopes (optional)
"<STATE>", // state (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
Expand Down
1 change: 1 addition & 0 deletions docs/examples/java/account/create-o-auth-2-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ account.createOAuth2Token(
"https://example.com", // success (optional)
"https://example.com", // failure (optional)
List.of(), // scopes (optional)
"<STATE>", // state (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
Expand Down
38 changes: 38 additions & 0 deletions docs/examples/java/analytics/create-event.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
```java
import android.util.Log;

import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Analytics;

Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID

Analytics analytics = new Analytics(client);

analytics.createEvent(
"<PROPERTY_ID>", // propertyId
"<NAME>", // name
"https://example.com", // url
"<DOMAIN>", // domain (optional)
"<REFERRER>", // referrer (optional)
0L, // screenWidth (optional)
"<SESSION_HASH>", // sessionHash (optional)
0L, // scrollDepth (optional)
0L, // engagementTime (optional)
List.of(), // props (optional)
"<USER_ID>", // userId (optional)
"<IP>", // ip (optional)
"<USER_AGENT>", // userAgent (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}

Log.d("Appwrite", result.toString());
})
);

```
1 change: 1 addition & 0 deletions docs/examples/java/teams/create-membership.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ teams.createMembership(
"+12065550100", // phone (optional)
"https://example.com", // url (optional)
"<NAME>", // name (optional)
"<MEMBERSHIP_ID>", // membershipId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
Expand Down
1 change: 1 addition & 0 deletions docs/examples/kotlin/account/create-o-auth-2-session.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ account.createOAuth2Session(
success = "https://example.com", // (optional)
failure = "https://example.com", // (optional)
scopes = listOf(), // (optional)
state = "<STATE>", // (optional)
)
```
1 change: 1 addition & 0 deletions docs/examples/kotlin/account/create-o-auth-2-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ account.createOAuth2Token(
success = "https://example.com", // (optional)
failure = "https://example.com", // (optional)
scopes = listOf(), // (optional)
state = "<STATE>", // (optional)
)
```
27 changes: 27 additions & 0 deletions docs/examples/kotlin/analytics/create-event.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
```kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Analytics

val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID

val analytics = Analytics(client)

val result = analytics.createEvent(
propertyId = "<PROPERTY_ID>",
name = "<NAME>",
url = "https://example.com",
domain = "<DOMAIN>", // (optional)
referrer = "<REFERRER>", // (optional)
screenWidth = 0, // (optional)
sessionHash = "<SESSION_HASH>", // (optional)
scrollDepth = 0, // (optional)
engagementTime = 0, // (optional)
props = listOf(), // (optional)
userId = "<USER_ID>", // (optional)
ip = "<IP>", // (optional)
userAgent = "<USER_AGENT>", // (optional)
)
```
1 change: 1 addition & 0 deletions docs/examples/kotlin/teams/create-membership.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ val result = teams.createMembership(
phone = "+12065550100", // (optional)
url = "https://example.com", // (optional)
name = "<NAME>", // (optional)
membershipId = "<MEMBERSHIP_ID>", // (optional)
)
```
9 changes: 5 additions & 4 deletions library/src/main/java/io/appwrite/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class Client @JvmOverloads constructor(
"x-sdk-name" to "Android",
"x-sdk-platform" to "client",
"x-sdk-language" to "android",
"x-sdk-version" to "29.1.0",
"x-sdk-version" to "29.2.0",
"x-appwrite-response-format" to "2.3.0"
)
config = mutableMapOf()
Expand Down Expand Up @@ -551,10 +551,11 @@ class Client @JvmOverloads constructor(
paramName: String,
idParamName: String? = null,
onProgress: ((UploadProgress) -> Unit)? = null,
method: String = "POST",
): T {
if (params[paramName] == null) {
return call(
method = "POST",
method = method,
path,
headers,
params,
Expand Down Expand Up @@ -585,7 +586,7 @@ class Client @JvmOverloads constructor(
data
)
return call(
method = "POST",
method = method,
path,
headers,
params,
Expand Down Expand Up @@ -656,7 +657,7 @@ class Client @JvmOverloads constructor(
)

val chunkResult = call(
method = "POST",
method = method,
path,
chunkHeaders,
chunkParams,
Expand Down
14 changes: 10 additions & 4 deletions library/src/main/java/io/appwrite/services/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1997,6 +1997,7 @@ class Account(client: Client) : Service(client) {
* @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.
* @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.
* @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long.
* @param state An opaque value your app generates and keeps, for example in a cookie. It is returned unchanged as the `state` query parameter on the success and failure URLs, so your app can check that the sign-in it receives is one it started. Printable ASCII only (RFC 6749 Appendix A.5). Max length: 256 chars.
*/
@JvmOverloads
suspend fun createOAuth2Session(
Expand All @@ -2005,6 +2006,7 @@ class Account(client: Client) : Service(client) {
success: String? = null,
failure: String? = null,
scopes: List<String>? = null,
state: String? = null,
) {
val apiPath = ("/account/sessions/oauth2/{provider}"
.replace("{provider}", provider.value)
Expand All @@ -2013,6 +2015,7 @@ class Account(client: Client) : Service(client) {
"success" to success,
"failure" to failure,
"scopes" to scopes,
"state" to state,
Comment thread
hansi-codes[bot] marked this conversation as resolved.
"project" to client.config["project"],
)
val apiQuery = mutableListOf<String>()
Expand All @@ -2023,11 +2026,11 @@ class Account(client: Client) : Service(client) {
}
is List<*> -> {
(it.value as List<*>).forEach { v ->
apiQuery.add("${it.key}[]=${v.toString()}")
apiQuery.add("${Uri.encode(it.key)}[]=${Uri.encode(v.toString())}")
}
}
else -> {
apiQuery.add("${it.key}=${it.value.toString()}")
apiQuery.add("${Uri.encode(it.key)}=${Uri.encode(it.value.toString())}")
}
}
}
Expand Down Expand Up @@ -2481,6 +2484,7 @@ class Account(client: Client) : Service(client) {
* @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.
* @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.
* @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long.
* @param state An opaque value your app generates and keeps, for example in a cookie. It is returned unchanged as the `state` query parameter on the success and failure URLs, so your app can check that the sign-in it receives is one it started. Printable ASCII only (RFC 6749 Appendix A.5). Max length: 256 chars.
*/
@JvmOverloads
suspend fun createOAuth2Token(
Expand All @@ -2489,6 +2493,7 @@ class Account(client: Client) : Service(client) {
success: String? = null,
failure: String? = null,
scopes: List<String>? = null,
state: String? = null,
) {
val apiPath = ("/account/tokens/oauth2/{provider}"
.replace("{provider}", provider.value)
Expand All @@ -2497,6 +2502,7 @@ class Account(client: Client) : Service(client) {
"success" to success,
"failure" to failure,
"scopes" to scopes,
"state" to state,
"project" to client.config["project"],
)
val apiQuery = mutableListOf<String>()
Expand All @@ -2507,11 +2513,11 @@ class Account(client: Client) : Service(client) {
}
is List<*> -> {
(it.value as List<*>).forEach { v ->
apiQuery.add("${it.key}[]=${v.toString()}")
apiQuery.add("${Uri.encode(it.key)}[]=${Uri.encode(v.toString())}")
}
}
else -> {
apiQuery.add("${it.key}=${it.value.toString()}")
apiQuery.add("${Uri.encode(it.key)}=${Uri.encode(it.value.toString())}")
}
}
}
Expand Down
83 changes: 83 additions & 0 deletions library/src/main/java/io/appwrite/services/Analytics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.appwrite.services

import android.net.Uri
import io.appwrite.Client
import io.appwrite.Service
import io.appwrite.exceptions.AppwriteException
import io.appwrite.extensions.classOf
import io.appwrite.models.*
import okhttp3.Cookie
import java.io.File

/**
*
*/
class Analytics(client: Client) : Service(client) {
/**
* Send a tracking event from a browser, native app, or server-side SDK.
*
* @param propertyId Analytics property ID.
* @param name Event name. "pageview" is just a conventional event name; events are not modeled specially.
* @param url Full page URL or screen identifier.
* @param domain Hostname (e.g. example.com).
* @param referrer Referrer URL.
* @param screenWidth Viewport width in CSS pixels.
* @param sessionHash Optional session hash (provided by SDK).
* @param scrollDepth Scroll depth percentage 0-100.
* @param engagementTime Engagement time in seconds, 0-4294967295.
* @param props Custom string properties as a flat key=value list (max 32 entries, alternating key,value).
* @param userId Override user ID. Requires API-key auth with analytics.write scope.
* @param ip Override IP address. Requires API-key auth with analytics.write scope.
* @param userAgent Override user agent. Requires API-key auth with analytics.write scope.
* @return [Any]
*/
@JvmOverloads
suspend fun createEvent(
propertyId: String,
name: String,
url: String,
domain: String? = null,
referrer: String? = null,
screenWidth: Long? = null,
sessionHash: String? = null,
scrollDepth: Long? = null,
engagementTime: Long? = null,
props: List<String>? = null,
userId: String? = null,
ip: String? = null,
userAgent: String? = null,
): Any {
if (propertyId.isEmpty()) {
throw AppwriteException("Missing required parameter: \"propertyId\"")
}
val apiPath = ("/analytics/properties/{propertyId}/events"
.replace("{propertyId}", propertyId)
)
val apiParams = mutableMapOf<String, Any?>(
"name" to name,
"url" to url,
"domain" to domain,
"referrer" to referrer,
"screenWidth" to screenWidth,
"sessionHash" to sessionHash,
"scrollDepth" to scrollDepth,
"engagementTime" to engagementTime,
"props" to props,
"userId" to userId,
"ip" to ip,
"userAgent" to userAgent,
)
val apiHeaders = mutableMapOf<String, String>(
"X-Appwrite-Project" to client.config["project"].orEmpty(),
"content-type" to "application/json",
"accept" to "application/json",
)
return client.call(
"POST",
apiPath,
apiHeaders,
apiParams,
responseType = Any::class.java,
)
}
}
1 change: 1 addition & 0 deletions library/src/main/java/io/appwrite/services/Avatars.kt
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ class Avatars(client: Client) : Service(client) {
paramName = paramName,
idParamName = idParamName,
onProgress = onProgress,
method = "PUT",
)
}

Expand Down
3 changes: 3 additions & 0 deletions library/src/main/java/io/appwrite/services/Teams.kt
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ class Teams(client: Client) : Service(client) {
* @param phone Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.
* @param url URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.
* @param name Name of the new team member. Max length: 128 chars.
* @param membershipId Membership 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.
* @return [io.appwrite.models.Membership]
*/
@JvmOverloads
Expand All @@ -550,6 +551,7 @@ class Teams(client: Client) : Service(client) {
phone: String? = null,
url: String? = null,
name: String? = null,
membershipId: String? = null,
): io.appwrite.models.Membership {
if (teamId.isEmpty()) {
throw AppwriteException("Missing required parameter: \"teamId\"")
Expand All @@ -564,6 +566,7 @@ class Teams(client: Client) : Service(client) {
"roles" to roles,
"url" to url,
"name" to name,
"membershipId" to membershipId,
)
val apiHeaders = mutableMapOf<String, String>(
"X-Appwrite-Project" to client.config["project"].orEmpty(),
Expand Down