diff --git a/LICENSE b/LICENSE index 5479bb8..c1602fc 100644 --- a/LICENSE +++ b/LICENSE @@ -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 43b411f..86f5dcd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Appwrite Web SDK ![License](https://img.shields.io/github/license/appwrite/sdk-for-web.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) @@ -33,7 +33,7 @@ import { Client, Account } from "appwrite"; To install with a CDN (content delivery network) add the following scripts to the bottom of your tag, but before you use any Appwrite services: ```html - + ``` diff --git a/package.json b/package.json index 88e6d4f..9f9caa0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "appwrite", "homepage": "https://appwrite.io/support", "description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API", - "version": "16.0.2", + "version": "17.0.0", "license": "BSD-3-Clause", "main": "dist/cjs/sdk.js", "exports": { diff --git a/src/client.ts b/src/client.ts index d434c64..70ced88 100644 --- a/src/client.ts +++ b/src/client.ts @@ -19,14 +19,14 @@ type Headers = { */ type RealtimeResponse = { /** - * Type of the response: 'error', 'event', 'connected', or 'response'. + * Type of the response: 'error', 'event', 'connected', 'response' or 'pong'. */ - type: 'error' | 'event' | 'connected' | 'response'; + type: 'error' | 'event' | 'connected' | 'response' | 'pong'; /** * Data associated with the response based on the response type. */ - data: RealtimeResponseAuthenticated | RealtimeResponseConnected | RealtimeResponseError | RealtimeResponseEvent; + data: RealtimeResponseAuthenticated | RealtimeResponseConnected | RealtimeResponseError | RealtimeResponseEvent | undefined; } /** @@ -129,6 +129,8 @@ type RealtimeRequestAuthenticate = { session: string; } +type TimeoutHandle = ReturnType | number; + /** * Realtime interface representing the structure of a realtime communication object. */ @@ -139,9 +141,14 @@ type Realtime = { socket?: WebSocket; /** - * Timeout duration for communication operations. + * Timeout for reconnect operations. */ - timeout?: number; + timeout?: TimeoutHandle; + + /** + * Heartbeat interval for the realtime connection. + */ + heartbeat?: TimeoutHandle; /** * URL for establishing the WebSocket connection. @@ -196,6 +203,11 @@ type Realtime = { */ createSocket: () => void; + /** + * Function to create a new heartbeat interval. + */ + createHeartbeat: () => void; + /** * Function to clean up resources associated with specified channels. * @@ -303,7 +315,7 @@ class Client { 'x-sdk-name': 'Web', 'x-sdk-platform': 'client', 'x-sdk-language': 'web', - 'x-sdk-version': '16.0.2', + 'x-sdk-version': '17.0.0', 'X-Appwrite-Response-Format': '1.6.0', }; @@ -394,6 +406,7 @@ class Client { private realtime: Realtime = { socket: undefined, timeout: undefined, + heartbeat: undefined, url: '', channels: new Set(), subscriptions: new Map(), @@ -419,6 +432,17 @@ class Client { return 60_000; } }, + createHeartbeat: () => { + if (this.realtime.heartbeat) { + clearTimeout(this.realtime.heartbeat); + } + + this.realtime.heartbeat = window?.setInterval(() => { + this.realtime.socket?.send(JSON.stringify({ + type: 'ping' + })); + }, 20_000); + }, createSocket: () => { if (this.realtime.channels.size < 1) { this.realtime.reconnect = false; @@ -452,6 +476,7 @@ class Client { this.realtime.socket.addEventListener('message', this.realtime.onMessage); this.realtime.socket.addEventListener('open', _event => { this.realtime.reconnectAttempts = 0; + this.realtime.createHeartbeat(); }); this.realtime.socket.addEventListener('close', event => { if ( @@ -506,6 +531,8 @@ class Client { }) } break; + case 'pong': + break; // Handle pong response if needed case 'error': throw message.data; default: @@ -669,6 +696,10 @@ class Client { return response; } + async ping(): Promise { + return this.call('GET', new URL(this.config.endpoint + '/ping')); + } + async call(method: string, url: URL, headers: Headers = {}, params: Payload = {}, responseType = 'json'): Promise { const { uri, options } = this.prepareRequest(method, url, headers, params); diff --git a/src/enums/image-format.ts b/src/enums/image-format.ts index 7e96fd8..bcbe3e9 100644 --- a/src/enums/image-format.ts +++ b/src/enums/image-format.ts @@ -4,4 +4,6 @@ export enum ImageFormat { Gif = 'gif', Png = 'png', Webp = 'webp', + Heic = 'heic', + Avif = 'avif', } \ No newline at end of file diff --git a/src/models.ts b/src/models.ts index 2fcb905..5e9eabc 100644 --- a/src/models.ts +++ b/src/models.ts @@ -842,11 +842,11 @@ export namespace Models { */ userId: string; /** - * User name. + * User name. Hide this attribute by toggling membership privacy in the Console. */ userName: string; /** - * User email address. + * User email address. Hide this attribute by toggling membership privacy in the Console. */ userEmail: string; /** @@ -870,7 +870,7 @@ export namespace Models { */ 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. */ mfa: boolean; /** @@ -1198,5 +1198,9 @@ export namespace Models { * The target identifier. */ identifier: string; + /** + * Is the target expired. + */ + expired: boolean; } } diff --git a/src/services/account.ts b/src/services/account.ts index 55b22bd..a922dbc 100644 --- a/src/services/account.ts +++ b/src/services/account.ts @@ -129,7 +129,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * List Identities + * List identities * * Get the list of identities for the currently logged in user. * @@ -273,7 +273,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * 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. * @@ -302,7 +302,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * Verify Authenticator + * Verify authenticator * * Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method. * @@ -338,7 +338,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * Delete Authenticator + * Delete authenticator * * Delete an authenticator for a user by ID. * @@ -367,7 +367,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * 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. * @@ -399,16 +399,16 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * 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 {string} challengeId * @param {string} otp * @throws {AppwriteException} - * @returns {Promise<{}>} + * @returns {Promise} */ - async updateMfaChallenge(challengeId: string, otp: string): Promise<{}> { + async updateMfaChallenge(challengeId: string, otp: string): Promise { if (typeof challengeId === 'undefined') { throw new AppwriteException('Missing required parameter: "challengeId"'); } @@ -438,7 +438,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * List Factors + * List factors * * List the factors available on the account to be used as a MFA challange. * @@ -463,7 +463,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * 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. * @@ -488,7 +488,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * 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. * @@ -513,7 +513,7 @@ This endpoint can also be used to convert an anonymous account to a normal one, ); } /** - * 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. * @@ -1186,6 +1186,7 @@ A user is limited to 10 active sessions at a time by default. [Learn more about /** * 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 {string} targetId * @param {string} identifier @@ -1228,6 +1229,7 @@ A user is limited to 10 active sessions at a time by default. [Learn more about /** * 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 {string} targetId * @param {string} identifier @@ -1263,6 +1265,7 @@ A user is limited to 10 active sessions at a time by default. [Learn more about /** * 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 {string} targetId * @throws {AppwriteException} @@ -1336,7 +1339,7 @@ A user is limited to 10 active sessions at a time by default. [Learn more about /** * 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. + * 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). diff --git a/src/services/avatars.ts b/src/services/avatars.ts index 21376ea..f5fbc63 100644 --- a/src/services/avatars.ts +++ b/src/services/avatars.ts @@ -54,10 +54,6 @@ When one dimension is specified and the other is 0, the image is scaled with pre payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } /** @@ -103,10 +99,6 @@ When one dimension is specified and the other is 0, the image is scaled with pre payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } /** @@ -142,10 +134,6 @@ This endpoint does not follow HTTP redirects. payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } /** @@ -191,10 +179,6 @@ When one dimension is specified and the other is 0, the image is scaled with pre payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } /** @@ -240,10 +224,6 @@ This endpoint does not follow HTTP redirects. payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } /** @@ -291,10 +271,6 @@ When one dimension is specified and the other is 0, the image is scaled with pre payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } /** @@ -341,10 +317,6 @@ When one dimension is specified and the other is 0, the image is scaled with pre payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } } diff --git a/src/services/locale.ts b/src/services/locale.ts index 5439c13..ad02a6c 100644 --- a/src/services/locale.ts +++ b/src/services/locale.ts @@ -37,7 +37,7 @@ export class Locale { ); } /** - * 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/src/services/storage.ts b/src/services/storage.ts index ba4c479..a76a508 100644 --- a/src/services/storage.ts +++ b/src/services/storage.ts @@ -178,7 +178,7 @@ If you're creating a new file using one of the Appwrite SDKs, all the chunk ); } /** - * Delete File + * Delete file * * Delete a file by its unique ID. Only users with write permissions have access to delete this resource. * @@ -242,10 +242,6 @@ If you're creating a new file using one of the Appwrite SDKs, all the chunk payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } /** @@ -324,10 +320,6 @@ If you're creating a new file using one of the Appwrite SDKs, all the chunk payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } /** @@ -362,10 +354,6 @@ If you're creating a new file using one of the Appwrite SDKs, all the chunk payload['project'] = this.client.config.project; - for (const [key, value] of Object.entries(Client.flatten(payload))) { - uri.searchParams.append(key, value); - } - return uri.toString(); } } diff --git a/src/services/teams.ts b/src/services/teams.ts index 9dda30d..ffbdcab 100644 --- a/src/services/teams.ts +++ b/src/services/teams.ts @@ -182,7 +182,7 @@ export class Teams { /** * 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 {string} teamId * @param {string[]} queries @@ -282,7 +282,7 @@ Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatShee /** * 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 {string} teamId * @param {string} membershipId