diff --git a/README.md b/README.md index 22851cba..cd8042fa 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Add this to your package's `pubspec.yaml` file: ```yml dependencies: - appwrite: ^13.0.0 + appwrite: ^13.1.0 ``` You can install packages from the command line: diff --git a/lib/services/account.dart b/lib/services/account.dart index a94a37d2..b812a903 100644 --- a/lib/services/account.dart +++ b/lib/services/account.dart @@ -85,7 +85,7 @@ class Account extends Service { return models.User.fromMap(res.data); } - /// List Identities + /// List identities /// /// Get the list of identities for the currently logged in user. Future listIdentities({List? queries}) async { @@ -187,7 +187,7 @@ class Account extends Service { return models.User.fromMap(res.data); } - /// Create Authenticator + /// Create authenticator /// /// Add an authenticator app to be used as an MFA factor. Verify the /// authenticator using the [verify @@ -210,7 +210,7 @@ class Account extends Service { return models.MfaType.fromMap(res.data); } - /// Verify Authenticator + /// Verify authenticator /// /// Verify an authenticator app after adding it using the [add /// authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) @@ -234,7 +234,7 @@ class Account extends Service { return models.User.fromMap(res.data); } - /// Delete Authenticator + /// Delete authenticator /// /// Delete an authenticator for a user by ID. Future deleteMfaAuthenticator({required enums.AuthenticatorType type}) async { @@ -253,7 +253,7 @@ class Account extends Service { return res.data; } - /// 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) @@ -276,7 +276,7 @@ class Account extends Service { return models.MfaChallenge.fromMap(res.data); } - /// 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 @@ -302,7 +302,7 @@ class Account extends Service { return res.data; } - /// List Factors + /// List factors /// /// List the factors available on the account to be used as a MFA challange. Future listMfaFactors() async { @@ -320,7 +320,7 @@ class Account extends Service { return models.MfaFactors.fromMap(res.data); } - /// 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 @@ -341,7 +341,7 @@ class Account extends Service { return models.MfaRecoveryCodes.fromMap(res.data); } - /// 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 @@ -363,7 +363,7 @@ class Account extends Service { return models.MfaRecoveryCodes.fromMap(res.data); } - /// 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 diff --git a/lib/services/locale.dart b/lib/services/locale.dart index 6c780391..5779b36a 100644 --- a/lib/services/locale.dart +++ b/lib/services/locale.dart @@ -29,7 +29,7 @@ class Locale extends Service { return models.Locale.fromMap(res.data); } - /// 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/lib/services/storage.dart b/lib/services/storage.dart index d4ba05a2..082b5d4d 100644 --- a/lib/services/storage.dart +++ b/lib/services/storage.dart @@ -133,7 +133,7 @@ class Storage extends Service { return models.File.fromMap(res.data); } - /// 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/lib/services/teams.dart b/lib/services/teams.dart index a9c7450d..ea043121 100644 --- a/lib/services/teams.dart +++ b/lib/services/teams.dart @@ -116,7 +116,8 @@ class Teams extends Service { /// 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. + /// members have read access to this endpoint. Hide sensitive attributes from + /// the response by toggling membership privacy in the Console. Future listMemberships( {required String teamId, List? queries, String? search}) async { final String apiPath = @@ -193,7 +194,8 @@ class Teams extends Service { /// Get team membership /// /// Get a team member by the membership unique id. All team members have read - /// access for this resource. + /// access for this resource. Hide sensitive attributes from the response by + /// toggling membership privacy in the Console. Future getMembership( {required String teamId, required String membershipId}) async { final String apiPath = '/teams/{teamId}/memberships/{membershipId}' diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 36402189..cbb30342 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -43,7 +43,7 @@ class ClientBrowser extends ClientBase with ClientMixin { 'x-sdk-name': 'Flutter', 'x-sdk-platform': 'client', 'x-sdk-language': 'flutter', - 'x-sdk-version': '13.0.0', + 'x-sdk-version': '13.1.0', 'X-Appwrite-Response-Format': '1.6.0', }; diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index cab345ff..bfd770a3 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -64,7 +64,7 @@ class ClientIO extends ClientBase with ClientMixin { 'x-sdk-name': 'Flutter', 'x-sdk-platform': 'client', 'x-sdk-language': 'flutter', - 'x-sdk-version': '13.0.0', + 'x-sdk-version': '13.1.0', 'X-Appwrite-Response-Format': '1.6.0', }; diff --git a/lib/src/enums/image_format.dart b/lib/src/enums/image_format.dart index 2ab5d7b9..acc07f27 100644 --- a/lib/src/enums/image_format.dart +++ b/lib/src/enums/image_format.dart @@ -5,7 +5,8 @@ enum ImageFormat { jpeg(value: 'jpeg'), gif(value: 'gif'), png(value: 'png'), - webp(value: 'webp'); + webp(value: 'webp'), + avif(value: 'avif'); const ImageFormat({required this.value}); diff --git a/lib/src/models/document.dart b/lib/src/models/document.dart index 6406f06a..f8164b61 100644 --- a/lib/src/models/document.dart +++ b/lib/src/models/document.dart @@ -18,7 +18,7 @@ class Document implements Model { final String $updatedAt; /// Document permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). - final List $permissions; + final List $permissions; final Map data; Document({ diff --git a/lib/src/models/execution.dart b/lib/src/models/execution.dart index 40886cd7..9f9b8917 100644 --- a/lib/src/models/execution.dart +++ b/lib/src/models/execution.dart @@ -12,7 +12,7 @@ class Execution implements Model { final String $updatedAt; /// Execution roles. - final List $permissions; + final List $permissions; /// Function ID. final String functionId; diff --git a/lib/src/models/file.dart b/lib/src/models/file.dart index 2e6b9d8f..4bca1fe3 100644 --- a/lib/src/models/file.dart +++ b/lib/src/models/file.dart @@ -15,7 +15,7 @@ class File implements Model { final String $updatedAt; /// File permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). - final List $permissions; + final List $permissions; /// File name. final String name; diff --git a/lib/src/models/membership.dart b/lib/src/models/membership.dart index fb3fb523..1dd1186d 100644 --- a/lib/src/models/membership.dart +++ b/lib/src/models/membership.dart @@ -14,10 +14,10 @@ class Membership implements Model { /// User ID. final String userId; - /// User name. + /// User name. Hide this attribute by toggling membership privacy in the Console. final String userName; - /// User email address. + /// User email address. Hide this attribute by toggling membership privacy in the Console. final String userEmail; /// Team ID. @@ -35,11 +35,11 @@ class Membership implements Model { /// User confirmation status, true if the user has joined the team or false otherwise. final bool confirm; - /// 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. final bool mfa; /// User list of roles - final List roles; + final List roles; Membership({ required this.$id, diff --git a/lib/src/models/mfa_recovery_codes.dart b/lib/src/models/mfa_recovery_codes.dart index 83483901..c304de00 100644 --- a/lib/src/models/mfa_recovery_codes.dart +++ b/lib/src/models/mfa_recovery_codes.dart @@ -3,7 +3,7 @@ part of '../../models.dart'; /// MFA Recovery Codes class MfaRecoveryCodes implements Model { /// Recovery codes. - final List recoveryCodes; + final List recoveryCodes; MfaRecoveryCodes({ required this.recoveryCodes, diff --git a/lib/src/models/session.dart b/lib/src/models/session.dart index 1f55d400..11732bd1 100644 --- a/lib/src/models/session.dart +++ b/lib/src/models/session.dart @@ -81,7 +81,7 @@ class Session implements Model { final bool current; /// Returns a list of active session factors. - final List factors; + final List factors; /// Secret used to authenticate the user. Only included if the request was made with an API key final String secret; diff --git a/lib/src/models/target.dart b/lib/src/models/target.dart index 68460126..4be8b545 100644 --- a/lib/src/models/target.dart +++ b/lib/src/models/target.dart @@ -26,6 +26,9 @@ class Target implements Model { /// The target identifier. final String identifier; + /// Is the target expired. + final bool expired; + Target({ required this.$id, required this.$createdAt, @@ -35,6 +38,7 @@ class Target implements Model { this.providerId, required this.providerType, required this.identifier, + required this.expired, }); factory Target.fromMap(Map map) { @@ -47,6 +51,7 @@ class Target implements Model { providerId: map['providerId']?.toString(), providerType: map['providerType'].toString(), identifier: map['identifier'].toString(), + expired: map['expired'], ); } @@ -60,6 +65,7 @@ class Target implements Model { "providerId": providerId, "providerType": providerType, "identifier": identifier, + "expired": expired, }; } } diff --git a/lib/src/models/user.dart b/lib/src/models/user.dart index 889005cf..e5942793 100644 --- a/lib/src/models/user.dart +++ b/lib/src/models/user.dart @@ -30,7 +30,7 @@ class User implements Model { final bool status; /// Labels for the user. - final List labels; + final List labels; /// Password update time in ISO 8601 format. final String passwordUpdate; diff --git a/lib/src/realtime_mixin.dart b/lib/src/realtime_mixin.dart index be87c1bf..60ec7491 100644 --- a/lib/src/realtime_mixin.dart +++ b/lib/src/realtime_mixin.dart @@ -27,8 +27,10 @@ mixin RealtimeMixin { int _retries = 0; StreamSubscription? _websocketSubscription; bool _creatingSocket = false; + Timer? _heartbeatTimer; Future _closeConnection() async { + _stopHeartbeat(); await _websocketSubscription?.cancel(); await _websok?.sink.close(status.normalClosure, 'Ending session'); _lastUrl = null; @@ -36,6 +38,20 @@ mixin RealtimeMixin { _reconnect = false; } + void _startHeartbeat() { + _stopHeartbeat(); + _heartbeatTimer = Timer.periodic(Duration(seconds: 20), (_) { + if (_websok != null) { + _websok!.sink.add(jsonEncode({"type": "ping"})); + } + }); + } + + void _stopHeartbeat() { + _heartbeatTimer?.cancel(); + _heartbeatTimer = null; + } + _createSocket() async { if (_creatingSocket || _channels.isEmpty) return; _creatingSocket = true; @@ -78,6 +94,10 @@ mixin RealtimeMixin { })); } } + _startHeartbeat(); // Start heartbeat after successful connection + break; + case 'pong': + debugPrint('Received heartbeat response from realtime server'); break; case 'event': final message = RealtimeMessage.fromMap(data.data); @@ -91,8 +111,10 @@ mixin RealtimeMixin { break; } }, onDone: () { + _stopHeartbeat(); _retry(); }, onError: (err, stack) { + _stopHeartbeat(); for (var subscription in _subscriptions.values) { subscription.controller.addError(err, stack); } diff --git a/pubspec.yaml b/pubspec.yaml index 20102ea9..d647dfae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: appwrite -version: 13.0.0 +version: 13.1.0 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 homepage: https://appwrite.io repository: https://github.com/appwrite/sdk-for-flutter diff --git a/test/services/account_test.dart b/test/services/account_test.dart index 3f76e30c..2473d490 100644 --- a/test/services/account_test.dart +++ b/test/services/account_test.dart @@ -1011,10 +1011,11 @@ void main() { '\$id': '259125845563242502', '\$createdAt': '2020-10-15T06:38:00.000+00:00', '\$updatedAt': '2020-10-15T06:38:00.000+00:00', - 'name': 'Aegon apple token', + 'name': 'Apple iPhone 12', 'userId': '259125845563242502', 'providerType': 'email', - 'identifier': 'token',}; + 'identifier': 'token', + 'expired': true,}; when(client.call( @@ -1035,10 +1036,11 @@ void main() { '\$id': '259125845563242502', '\$createdAt': '2020-10-15T06:38:00.000+00:00', '\$updatedAt': '2020-10-15T06:38:00.000+00:00', - 'name': 'Aegon apple token', + 'name': 'Apple iPhone 12', 'userId': '259125845563242502', 'providerType': 'email', - 'identifier': 'token',}; + 'identifier': 'token', + 'expired': true,}; when(client.call( diff --git a/test/services/functions_test.dart b/test/services/functions_test.dart index 8707d054..9f13895a 100644 --- a/test/services/functions_test.dart +++ b/test/services/functions_test.dart @@ -85,7 +85,7 @@ void main() { 'requestPath': '/articles?id=5', 'requestHeaders': [], 'responseStatusCode': 200, - 'responseBody': 'Developers are awesome.', + 'responseBody': '', 'responseHeaders': [], 'logs': '', 'errors': '', @@ -117,7 +117,7 @@ void main() { 'requestPath': '/articles?id=5', 'requestHeaders': [], 'responseStatusCode': 200, - 'responseBody': 'Developers are awesome.', + 'responseBody': '', 'responseHeaders': [], 'logs': '', 'errors': '', diff --git a/test/src/models/execution_test.dart b/test/src/models/execution_test.dart index 33f487c5..8160e51e 100644 --- a/test/src/models/execution_test.dart +++ b/test/src/models/execution_test.dart @@ -17,7 +17,7 @@ void main() { requestPath: '/articles?id=5', requestHeaders: [], responseStatusCode: 200, - responseBody: 'Developers are awesome.', + responseBody: '', responseHeaders: [], logs: '', errors: '', @@ -38,7 +38,7 @@ void main() { expect(result.requestPath, '/articles?id=5'); expect(result.requestHeaders, []); expect(result.responseStatusCode, 200); - expect(result.responseBody, 'Developers are awesome.'); + expect(result.responseBody, ''); expect(result.responseHeaders, []); expect(result.logs, ''); expect(result.errors, ''); diff --git a/test/src/models/target_test.dart b/test/src/models/target_test.dart index 15d18262..20b4dfeb 100644 --- a/test/src/models/target_test.dart +++ b/test/src/models/target_test.dart @@ -9,10 +9,11 @@ void main() { $id: '259125845563242502', $createdAt: '2020-10-15T06:38:00.000+00:00', $updatedAt: '2020-10-15T06:38:00.000+00:00', - name: 'Aegon apple token', + name: 'Apple iPhone 12', userId: '259125845563242502', providerType: 'email', identifier: 'token', + expired: true, ); final map = model.toMap(); @@ -21,10 +22,11 @@ void main() { expect(result.$id, '259125845563242502'); expect(result.$createdAt, '2020-10-15T06:38:00.000+00:00'); expect(result.$updatedAt, '2020-10-15T06:38:00.000+00:00'); - expect(result.name, 'Aegon apple token'); + expect(result.name, 'Apple iPhone 12'); expect(result.userId, '259125845563242502'); expect(result.providerType, 'email'); expect(result.identifier, 'token'); + expect(result.expired, true); }); }); }