diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b85ad3..fd96a310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## 25.0.0 + +* Added: `ClientAuth` interface returned by static factory constructors +* Added: Static factory constructors `Client.from`, `Client.fromSession`, `Client.fromDevKey`, and `Client.fromImpersonation` +* Added: Endpoint scheme validation in factory setup (rejects non-`http(s)`/`ws(s)` URLs) +* Added: `Realtime` and generated services accept factory-created `ClientAuth` clients +* Deprecated: `Client()` constructor and setter pattern remain for backwards compatibility but are marked deprecated where the analyzer supports it +* Updated: Consolidated realtime IO and browser implementations into the shared client + ## 24.1.1 * Fixed: Removed `Advisor` service and `Insight`, `InsightCTA`, `InsightList`, `Report`, `ReportList` models (admin-only endpoints, not intended for client SDKs) diff --git a/README.md b/README.md index 84ed4e08..2762ac59 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Add this to your package's `pubspec.yaml` file: ```yml dependencies: - appwrite: ^24.1.1 + appwrite: ^25.0.0 ``` You can install packages from the command line: diff --git a/lib/appwrite.dart b/lib/appwrite.dart index ae51be5e..0256e266 100644 --- a/lib/appwrite.dart +++ b/lib/appwrite.dart @@ -12,6 +12,7 @@ import 'dart:convert'; import 'src/enums.dart'; import 'src/service.dart'; +import 'src/client.dart'; import 'src/input_file.dart'; import 'models.dart' as models; import 'enums.dart' as enums; diff --git a/lib/services/account.dart b/lib/services/account.dart index f4dda775..b1f38b91 100644 --- a/lib/services/account.dart +++ b/lib/services/account.dart @@ -2,8 +2,13 @@ part of '../appwrite.dart'; /// The Account service allows you to authenticate and manage a user account. class Account extends Service { + final ClientAuth _client; + /// Initializes a [Account] service - Account(super.client); + // ignore: use_super_parameters + Account(ClientAuth client) + : _client = client, + super(client); /// Get the currently logged in user. Future get() async { @@ -13,7 +18,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -44,7 +49,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -71,7 +76,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -89,7 +94,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.IdentityList.fromMap(res.data); @@ -106,7 +111,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -128,7 +133,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Jwt.fromMap(res.data); @@ -146,7 +151,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.LogList.fromMap(res.data); @@ -164,7 +169,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -187,7 +192,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaType.fromMap(res.data); @@ -208,7 +213,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaType.fromMap(res.data); @@ -232,7 +237,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -254,7 +259,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -273,7 +278,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -290,7 +295,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -313,7 +318,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaChallenge.fromMap(res.data); @@ -334,7 +339,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaChallenge.fromMap(res.data); @@ -360,7 +365,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -384,7 +389,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -400,7 +405,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaFactors.fromMap(res.data); @@ -414,7 +419,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaFactors.fromMap(res.data); @@ -433,7 +438,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaRecoveryCodes.fromMap(res.data); @@ -450,7 +455,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaRecoveryCodes.fromMap(res.data); @@ -472,7 +477,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaRecoveryCodes.fromMap(res.data); @@ -492,7 +497,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaRecoveryCodes.fromMap(res.data); @@ -513,7 +518,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaRecoveryCodes.fromMap(res.data); @@ -532,7 +537,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.MfaRecoveryCodes.fromMap(res.data); @@ -550,7 +555,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -572,7 +577,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -596,7 +601,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -610,7 +615,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Preferences.fromMap(res.data); @@ -630,7 +635,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -657,7 +662,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -689,7 +694,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -704,7 +709,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.SessionList.fromMap(res.data); @@ -721,7 +726,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -743,7 +748,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -768,7 +773,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -792,7 +797,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -826,7 +831,7 @@ class Account extends Service { if (success != null) 'success': success, if (failure != null) 'failure': failure, if (scopes != null) 'scopes': scopes, - 'project': client.config['project'], + 'project': _client.config['project'], }; final List query = []; @@ -842,7 +847,7 @@ class Account extends Service { } }); - Uri endpoint = Uri.parse(client.endPoint); + Uri endpoint = Uri.parse(_client.endPoint); Uri url = Uri( scheme: endpoint.scheme, host: endpoint.host, @@ -850,7 +855,7 @@ class Account extends Service { path: endpoint.path + apiPath, query: query.join('&')); - return client.webAuth(url, callbackUrlScheme: success); + return _client.webAuth(url, callbackUrlScheme: success); } /// Use this endpoint to create a session from token. Provide the **userId** @@ -871,7 +876,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -893,7 +898,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -909,7 +914,7 @@ class Account extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -928,7 +933,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Session.fromMap(res.data); @@ -949,7 +954,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -967,7 +972,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.User.fromMap(res.data); @@ -994,7 +999,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Target.fromMap(res.data); @@ -1018,7 +1023,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Target.fromMap(res.data); @@ -1037,7 +1042,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -1071,7 +1076,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -1109,7 +1114,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -1141,7 +1146,7 @@ class Account extends Service { if (success != null) 'success': success, if (failure != null) 'failure': failure, if (scopes != null) 'scopes': scopes, - 'project': client.config['project'], + 'project': _client.config['project'], }; final List query = []; @@ -1157,7 +1162,7 @@ class Account extends Service { } }); - Uri endpoint = Uri.parse(client.endPoint); + Uri endpoint = Uri.parse(_client.endPoint); Uri url = Uri( scheme: endpoint.scheme, host: endpoint.host, @@ -1165,7 +1170,7 @@ class Account extends Service { path: endpoint.path + apiPath, query: query.join('&')); - return client.webAuth(url, callbackUrlScheme: success); + return _client.webAuth(url, callbackUrlScheme: success); } /// Sends the user an SMS with a secret key for creating a session. If the @@ -1191,7 +1196,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -1223,7 +1228,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -1257,7 +1262,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -1280,7 +1285,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -1305,7 +1310,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -1328,7 +1333,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); @@ -1351,7 +1356,7 @@ class Account extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Token.fromMap(res.data); diff --git a/lib/services/avatars.dart b/lib/services/avatars.dart index 856714da..e5b1e6d0 100644 --- a/lib/services/avatars.dart +++ b/lib/services/avatars.dart @@ -3,8 +3,13 @@ part of '../appwrite.dart'; /// The Avatars service aims to help you complete everyday tasks related to /// your app image, icons, and avatars. class Avatars extends Service { + final ClientAuth _client; + /// Initializes a [Avatars] service - Avatars(super.client); + // ignore: use_super_parameters + Avatars(ClientAuth client) + : _client = client, + super(client); /// You can use this endpoint to show different browser icons to your users. /// The code argument receives the browser code as it appears in your user [GET @@ -28,10 +33,10 @@ class Avatars extends Service { if (width != null) 'width': width, if (height != null) 'height': height, if (quality != null) 'quality': quality, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -57,10 +62,10 @@ class Avatars extends Service { if (width != null) 'width': width, if (height != null) 'height': height, if (quality != null) 'quality': quality, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -74,10 +79,10 @@ class Avatars extends Service { final Map params = { 'url': url, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -101,10 +106,10 @@ class Avatars extends Service { if (width != null) 'width': width, if (height != null) 'height': height, if (quality != null) 'quality': quality, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -128,10 +133,10 @@ class Avatars extends Service { 'url': url, if (width != null) 'width': width, if (height != null) 'height': height, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -161,10 +166,10 @@ class Avatars extends Service { if (width != null) 'width': width, if (height != null) 'height': height, if (background != null) 'background': background, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -181,10 +186,10 @@ class Avatars extends Service { if (size != null) 'size': size, if (margin != null) 'margin': margin, if (download != null) 'download': download, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -244,10 +249,10 @@ class Avatars extends Service { if (height != null) 'height': height, if (quality != null) 'quality': quality, if (output != null) 'output': output.value, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } diff --git a/lib/services/databases.dart b/lib/services/databases.dart index 21c2866c..8b036e98 100644 --- a/lib/services/databases.dart +++ b/lib/services/databases.dart @@ -3,8 +3,13 @@ part of '../appwrite.dart'; /// The Databases service allows you to create structured collections of /// documents, query and filter lists of documents class Databases extends Service { + final ClientAuth _client; + /// Initializes a [Databases] service - Databases(super.client); + // ignore: use_super_parameters + Databases(ClientAuth client) + : _client = client, + super(client); /// List transactions across all databases. Future listTransactions( @@ -17,7 +22,7 @@ class Databases extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.TransactionList.fromMap(res.data); @@ -35,7 +40,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Transaction.fromMap(res.data); @@ -51,7 +56,7 @@ class Databases extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Transaction.fromMap(res.data); @@ -72,7 +77,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Transaction.fromMap(res.data); @@ -89,7 +94,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -109,7 +114,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Transaction.fromMap(res.data); @@ -140,7 +145,7 @@ class Databases extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.DocumentList.fromMap(res.data); @@ -175,7 +180,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Document.fromMap(res.data); @@ -204,7 +209,7 @@ class Databases extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Document.fromMap(res.data); @@ -239,7 +244,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Document.fromMap(res.data); @@ -272,7 +277,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Document.fromMap(res.data); @@ -300,7 +305,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -334,7 +339,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Document.fromMap(res.data); @@ -368,7 +373,7 @@ class Databases extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Document.fromMap(res.data); diff --git a/lib/services/functions.dart b/lib/services/functions.dart index 6fcd5075..78949d27 100644 --- a/lib/services/functions.dart +++ b/lib/services/functions.dart @@ -3,8 +3,13 @@ part of '../appwrite.dart'; /// The Functions Service allows you view, create and manage your Cloud /// Functions. class Functions extends Service { + final ClientAuth _client; + /// Initializes a [Functions] service - Functions(super.client); + // ignore: use_super_parameters + Functions(ClientAuth client) + : _client = client, + super(client); /// Get a list of all the current user function execution logs. You can use the /// query params to filter your results. @@ -20,7 +25,7 @@ class Functions extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.ExecutionList.fromMap(res.data); @@ -54,7 +59,7 @@ class Functions extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Execution.fromMap(res.data); @@ -71,7 +76,7 @@ class Functions extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Execution.fromMap(res.data); diff --git a/lib/services/graphql.dart b/lib/services/graphql.dart index ff4e17d4..b47f7baf 100644 --- a/lib/services/graphql.dart +++ b/lib/services/graphql.dart @@ -3,8 +3,13 @@ part of '../appwrite.dart'; /// The GraphQL API allows you to query and mutate your Appwrite server using /// GraphQL. class Graphql extends Service { + final ClientAuth _client; + /// Initializes a [Graphql] service - Graphql(super.client); + // ignore: use_super_parameters + Graphql(ClientAuth client) + : _client = client, + super(client); /// Execute a GraphQL mutation. Future query({required Map query}) async { @@ -19,7 +24,7 @@ class Graphql extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -38,7 +43,7 @@ class Graphql extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; diff --git a/lib/services/locale.dart b/lib/services/locale.dart index dd9a5fef..3a5f9d0e 100644 --- a/lib/services/locale.dart +++ b/lib/services/locale.dart @@ -3,8 +3,13 @@ part of '../appwrite.dart'; /// The Locale service allows you to customize your app based on your users' /// location. class Locale extends Service { + final ClientAuth _client; + /// Initializes a [Locale] service - Locale(super.client); + // ignore: use_super_parameters + Locale(ClientAuth client) + : _client = client, + super(client); /// Get the current user location based on IP. Returns an object with user /// country code, country name, continent name, continent code, ip address and @@ -19,7 +24,7 @@ class Locale extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Locale.fromMap(res.data); @@ -34,7 +39,7 @@ class Locale extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.LocaleCodeList.fromMap(res.data); @@ -49,7 +54,7 @@ class Locale extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.ContinentList.fromMap(res.data); @@ -64,7 +69,7 @@ class Locale extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.CountryList.fromMap(res.data); @@ -79,7 +84,7 @@ class Locale extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.CountryList.fromMap(res.data); @@ -94,7 +99,7 @@ class Locale extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.PhoneList.fromMap(res.data); @@ -110,7 +115,7 @@ class Locale extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.CurrencyList.fromMap(res.data); @@ -125,7 +130,7 @@ class Locale extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.LanguageList.fromMap(res.data); diff --git a/lib/services/messaging.dart b/lib/services/messaging.dart index 87760d50..1bb90db4 100644 --- a/lib/services/messaging.dart +++ b/lib/services/messaging.dart @@ -3,8 +3,13 @@ part of '../appwrite.dart'; /// The Messaging service allows you to send messages to any provider type /// (SMTP, push notification, SMS, etc.). class Messaging extends Service { + final ClientAuth _client; + /// Initializes a [Messaging] service - Messaging(super.client); + // ignore: use_super_parameters + Messaging(ClientAuth client) + : _client = client, + super(client); /// Create a new subscriber. Future createSubscriber( @@ -23,7 +28,7 @@ class Messaging extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Subscriber.fromMap(res.data); @@ -43,7 +48,7 @@ class Messaging extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; diff --git a/lib/services/presences.dart b/lib/services/presences.dart index 3018f52e..2a758bd8 100644 --- a/lib/services/presences.dart +++ b/lib/services/presences.dart @@ -1,8 +1,13 @@ part of '../appwrite.dart'; class Presences extends Service { + final ClientAuth _client; + /// Initializes a [Presences] service - Presences(super.client); + // ignore: use_super_parameters + Presences(ClientAuth client) + : _client = client, + super(client); /// List presence logs. Expired entries are filtered out automatically. /// @@ -18,7 +23,7 @@ class Presences extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.PresenceList.fromMap(res.data); @@ -35,7 +40,7 @@ class Presences extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Presence.fromMap(res.data); @@ -63,7 +68,7 @@ class Presences extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Presence.fromMap(res.data); @@ -94,7 +99,7 @@ class Presences extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Presence.fromMap(res.data); @@ -112,7 +117,7 @@ class Presences extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; diff --git a/lib/services/storage.dart b/lib/services/storage.dart index 89f6999f..e47b1914 100644 --- a/lib/services/storage.dart +++ b/lib/services/storage.dart @@ -2,8 +2,13 @@ part of '../appwrite.dart'; /// The Storage service allows you to manage your project files. class Storage extends Service { + final ClientAuth _client; + /// Initializes a [Storage] service - Storage(super.client); + // ignore: use_super_parameters + Storage(ClientAuth client) + : _client = client, + super(client); /// Get a list of all the user files. You can use the query params to filter /// your results. @@ -23,7 +28,7 @@ class Storage extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.FileList.fromMap(res.data); @@ -69,7 +74,7 @@ class Storage extends Service { String idParamName = ''; idParamName = 'fileId'; final paramName = 'file'; - final res = await client.chunkedUpload( + final res = await _client.chunkedUpload( path: apiPath, params: apiParams, paramName: paramName, @@ -93,7 +98,7 @@ class Storage extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.File.fromMap(res.data); @@ -119,7 +124,7 @@ class Storage extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.File.fromMap(res.data); @@ -138,7 +143,7 @@ class Storage extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -155,10 +160,10 @@ class Storage extends Service { final Map params = { if (token != null) 'token': token, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -200,10 +205,10 @@ class Storage extends Service { if (background != null) 'background': background, if (output != null) 'output': output.value, if (token != null) 'token': token, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } @@ -219,10 +224,10 @@ class Storage extends Service { final Map params = { if (token != null) 'token': token, - 'project': client.config['project'], + 'project': _client.config['project'], }; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: params, responseType: ResponseType.bytes); return res.data; } diff --git a/lib/services/tables_db.dart b/lib/services/tables_db.dart index c3478b91..449430c2 100644 --- a/lib/services/tables_db.dart +++ b/lib/services/tables_db.dart @@ -1,8 +1,13 @@ part of '../appwrite.dart'; class TablesDB extends Service { + final ClientAuth _client; + /// Initializes a [TablesDB] service - TablesDB(super.client); + // ignore: use_super_parameters + TablesDB(ClientAuth client) + : _client = client, + super(client); /// List transactions across all databases. Future listTransactions( @@ -15,7 +20,7 @@ class TablesDB extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.TransactionList.fromMap(res.data); @@ -33,7 +38,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Transaction.fromMap(res.data); @@ -49,7 +54,7 @@ class TablesDB extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Transaction.fromMap(res.data); @@ -70,7 +75,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Transaction.fromMap(res.data); @@ -87,7 +92,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -107,7 +112,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Transaction.fromMap(res.data); @@ -135,7 +140,7 @@ class TablesDB extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.RowList.fromMap(res.data); @@ -167,7 +172,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Row.fromMap(res.data); @@ -194,7 +199,7 @@ class TablesDB extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Row.fromMap(res.data); @@ -227,7 +232,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Row.fromMap(res.data); @@ -258,7 +263,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Row.fromMap(res.data); @@ -284,7 +289,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -316,7 +321,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Row.fromMap(res.data); @@ -348,7 +353,7 @@ class TablesDB extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Row.fromMap(res.data); diff --git a/lib/services/teams.dart b/lib/services/teams.dart index 6b4d5e39..98eba060 100644 --- a/lib/services/teams.dart +++ b/lib/services/teams.dart @@ -3,8 +3,13 @@ part of '../appwrite.dart'; /// The Teams service allows you to group users of your project and to enable /// them to share read and write access to your project resources class Teams extends Service { + final ClientAuth _client; + /// Initializes a [Teams] service - Teams(super.client); + // ignore: use_super_parameters + Teams(ClientAuth client) + : _client = client, + super(client); /// Get a list of all the teams in which the current user is a member. You can /// use the parameters to filter your results. @@ -20,7 +25,7 @@ class Teams extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.TeamList.fromMap(res.data); @@ -45,7 +50,7 @@ class Teams extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Team.fromMap(res.data); @@ -59,7 +64,7 @@ class Teams extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Team.fromMap(res.data); @@ -78,7 +83,7 @@ class Teams extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Team.fromMap(res.data); @@ -95,7 +100,7 @@ class Teams extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -120,7 +125,7 @@ class Teams extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.MembershipList.fromMap(res.data); @@ -171,7 +176,7 @@ class Teams extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.post, + final res = await _client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); return models.Membership.fromMap(res.data); @@ -190,7 +195,7 @@ class Teams extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Membership.fromMap(res.data); @@ -216,7 +221,7 @@ class Teams extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Membership.fromMap(res.data); @@ -237,7 +242,7 @@ class Teams extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.delete, + final res = await _client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); return res.data; @@ -268,7 +273,7 @@ class Teams extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.patch, + final res = await _client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); return models.Membership.fromMap(res.data); @@ -285,7 +290,7 @@ class Teams extends Service { final Map apiHeaders = {}; - final res = await client.call(HttpMethod.get, + final res = await _client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); return models.Preferences.fromMap(res.data); @@ -307,7 +312,7 @@ class Teams extends Service { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, + final res = await _client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); return models.Preferences.fromMap(res.data); diff --git a/lib/src/client.dart b/lib/src/client.dart index 940ddb5e..770fdefb 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -2,33 +2,29 @@ import 'enums.dart'; import 'client_stub.dart' if (dart.library.js_interop) 'client_browser.dart' if (dart.library.io) 'client_io.dart'; +import 'exception.dart'; import 'response.dart'; import 'upload_progress.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; /// [Client] that handles requests to Appwrite. /// /// The [Client] is also responsible for managing user's sessions. -abstract class Client { - /// The size for chunked uploads in bytes. - static const int chunkSize = 5 * 1024 * 1024; - +abstract class ClientAuth { /// Holds configuration such as project. - late Map config; - late String _endPoint; - late String? _endPointRealtime; + Map get config; /// Appwrite endpoint. - String get endPoint => _endPoint; + String get endPoint; /// Appwrite realtime endpoint. - String? get endPointRealtime => _endPointRealtime; + String? get endPointRealtime; - /// Initializes a [Client]. - factory Client({ - String endPoint = 'https://cloud.appwrite.io/v1', - bool selfSigned = false, - }) => - createClient(endPoint: endPoint, selfSigned: selfSigned); + /// Open a realtime WebSocket connection. + Future realtimeWebSocket(Uri uri); + + /// Session cookie fallback for browser realtime authentication. + String? realtimeFallbackCookie(); /// Handle OAuth2 session creation. Future webAuth(Uri url, {String? callbackUrlScheme}); @@ -43,60 +39,288 @@ abstract class Client { Function(UploadProgress)? onProgress, }); + /// Sends a "ping" request to Appwrite to verify connectivity. + Future ping(); + + /// Send the API request. + Future call( + HttpMethod method, { + String path = '', + Map headers = const {}, + Map params = const {}, + ResponseType? responseType, + }); +} + +/// Legacy client with mutable setter methods. +abstract class Client implements ClientAuth { + /// The size for chunked uploads in bytes. + static const int chunkSize = 5 * 1024 * 1024; + + /// Initializes a [Client]. + factory Client({ + String endPoint = 'https://cloud.appwrite.io/v1', + bool selfSigned = false, + }) => + createClient(endPoint: endPoint, selfSigned: selfSigned); + + /// Initializes a client with project authentication. + static ClientAuth from({ + String endPoint = 'https://cloud.appwrite.io/v1', + required String projectId, + String? endPointRealtime, + String? locale, + bool selfSigned = false, + }) => + _fromClient( + endPoint: endPoint, + projectId: projectId, + endPointRealtime: endPointRealtime, + locale: locale, + selfSigned: selfSigned, + ); + + static Client _fromClient({ + required String endPoint, + required String projectId, + String? endPointRealtime, + String? locale, + required bool selfSigned, + }) { + _validateEndpoint(endPoint); + if (endPointRealtime != null) { + _validateRealtimeEndpoint(endPointRealtime); + } + + final client = createClient(endPoint: endPoint, selfSigned: selfSigned); + + _setHeader( + client, + 'project', + 'X-Appwrite-Project', + projectId, + ); + if (endPointRealtime != null) { + // ignore: deprecated_member_use_from_same_package + client.setEndPointRealtime(endPointRealtime); + } + if (locale != null) { + _setHeader( + client, + 'locale', + 'X-Appwrite-Locale', + locale, + ); + } + + return client; + } + + static void _validateEndpoint(String endPoint) { + if (!endPoint.startsWith('http://') && !endPoint.startsWith('https://')) { + throw AppwriteException('Invalid endpoint URL: $endPoint'); + } + } + + static void _validateRealtimeEndpoint(String endPointRealtime) { + if (!endPointRealtime.startsWith('ws://') && + !endPointRealtime.startsWith('wss://')) { + throw AppwriteException( + 'Invalid realtime endpoint URL: $endPointRealtime'); + } + } + + static void _setHeader( + Client client, + String configKey, + String header, + String value, + ) { + client.config[configKey] = value; + if (configKey != configKey.toLowerCase()) { + client.config[configKey.toLowerCase()] = value; + } + client.addHeader(header, value); + } + + /// Initializes a client with a user session. + static ClientAuth fromSession({ + String endPoint = 'https://cloud.appwrite.io/v1', + required String projectId, + required String session, + String? endPointRealtime, + String? locale, + bool selfSigned = false, + }) { + final client = _fromClient( + endPoint: endPoint, + projectId: projectId, + endPointRealtime: endPointRealtime, + locale: locale, + selfSigned: selfSigned, + ); + + _setHeader( + client, + 'session', + 'X-Appwrite-Session', + session, + ); + return client; + } + + /// Initializes a client with a development key. + static ClientAuth fromDevKey({ + String endPoint = 'https://cloud.appwrite.io/v1', + required String projectId, + required String devKey, + String? endPointRealtime, + String? locale, + bool selfSigned = false, + }) { + final client = _fromClient( + endPoint: endPoint, + projectId: projectId, + endPointRealtime: endPointRealtime, + locale: locale, + selfSigned: selfSigned, + ); + + _setHeader( + client, + 'devKey', + 'X-Appwrite-Dev-Key', + devKey, + ); + return client; + } + + /// Initializes a client with user impersonation. + static ClientAuth fromImpersonation({ + String endPoint = 'https://cloud.appwrite.io/v1', + required String projectId, + required String session, + String? userId, + String? userEmail, + String? userPhone, + String? endPointRealtime, + String? locale, + bool selfSigned = false, + }) { + final targets = [userId, userEmail, userPhone].whereType().length; + if (targets != 1) { + throw AppwriteException( + 'Exactly one impersonation target must be provided', + ); + } + + final client = _fromClient( + endPoint: endPoint, + projectId: projectId, + endPointRealtime: endPointRealtime, + locale: locale, + selfSigned: selfSigned, + ); + + _setHeader( + client, + 'session', + 'X-Appwrite-Session', + session, + ); + + if (userId != null) { + _setHeader( + client, + 'impersonateUserId', + 'X-Appwrite-Impersonate-User-Id', + userId, + ); + } else if (userEmail != null) { + _setHeader( + client, + 'impersonateUserEmail', + 'X-Appwrite-Impersonate-User-Email', + userEmail, + ); + } else if (userPhone != null) { + _setHeader( + client, + 'impersonateUserPhone', + 'X-Appwrite-Impersonate-User-Phone', + userPhone, + ); + } + + return client; + } + /// Set self signed to [status]. /// /// If self signed is true, [Client] will ignore invalid certificates. /// This is helpful in environments where your Appwrite /// instance does not have a valid SSL certificate. + @Deprecated('Use Client.from or another factory constructor instead.') Client setSelfSigned({bool status = true}); /// Set the Appwrite endpoint. + @Deprecated('Use Client.from or another factory constructor instead.') Client setEndpoint(String endPoint); /// Set the Appwrite realtime endpoint. + @Deprecated('Use Client.from or another factory constructor instead.') Client setEndPointRealtime(String endPoint); /// Set Project. /// /// Your project ID. + @Deprecated('Use Client.from or another factory constructor instead.') Client setProject(String value); /// Set JWT. /// /// Your secret JSON Web Token. + @Deprecated('Use Client.from or another factory constructor instead.') Client setJWT(String value); /// Set Locale. + @Deprecated('Use Client.from or another factory constructor instead.') Client setLocale(String value); /// Set Session. /// /// The user session to authenticate with. + @Deprecated('Use Client.from or another factory constructor instead.') Client setSession(String value); /// Set DevKey. /// /// Your secret dev API key. + @Deprecated('Use Client.from or another factory constructor instead.') Client setDevKey(String value); /// Set Cookie. /// /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes.. + @Deprecated('Use Client.from or another factory constructor instead.') Client setCookie(String value); /// 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.. + @Deprecated('Use Client.from or another factory constructor instead.') Client setImpersonateUserId(String value); /// 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.. + @Deprecated('Use Client.from or another factory constructor instead.') Client setImpersonateUserEmail(String value); /// 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.. + @Deprecated('Use Client.from or another factory constructor instead.') Client setImpersonateUserPhone(String value); /// Add headers that should be sent with all API calls. @@ -104,16 +328,4 @@ abstract class Client { /// Get the current request headers. Map getHeaders(); - - /// Sends a "ping" request to Appwrite to verify connectivity. - Future ping(); - - /// Send the API request. - Future call( - HttpMethod method, { - String path = '', - Map headers = const {}, - Map params = const {}, - ResponseType? responseType, - }); } diff --git a/lib/src/client_base.dart b/lib/src/client_base.dart index cbb1452d..d1915bf5 100644 --- a/lib/src/client_base.dart +++ b/lib/src/client_base.dart @@ -4,45 +4,57 @@ import 'enums.dart'; abstract class ClientBase implements Client { /// Your project ID + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setProject(value); /// Your secret JSON Web Token + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setJWT(value); + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setLocale(value); /// The user session to authenticate with + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setSession(value); /// Your secret dev API key + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setDevKey(value); /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setCookie(value); /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setImpersonateUserId(value); /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setImpersonateUserEmail(value); /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setImpersonateUserPhone(value); + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setSelfSigned({bool status = true}); + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBase setEndpoint(String endPoint); + @Deprecated('Use Client.from or another factory constructor instead.') @override Client setEndPointRealtime(String endPoint); diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 98c71f28..07a369dd 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -1,9 +1,12 @@ +import 'dart:convert'; import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:http/http.dart' as http; import 'package:http/browser_client.dart'; import 'package:web/web.dart' as web; +import 'package:web_socket_channel/html.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; import 'client_mixin.dart'; import 'enums.dart'; import 'exception.dart'; @@ -40,7 +43,7 @@ class ClientBrowser extends ClientBase with ClientMixin { 'x-sdk-name': 'Flutter', 'x-sdk-platform': 'client', 'x-sdk-language': 'flutter', - 'x-sdk-version': '24.1.1', + 'x-sdk-version': '25.0.0', 'X-Appwrite-Response-Format': '1.9.5', }; @@ -57,6 +60,7 @@ class ClientBrowser extends ClientBase with ClientMixin { String get endPoint => _endPoint; /// Your project ID + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setProject(value) { config['project'] = value; @@ -65,6 +69,7 @@ class ClientBrowser extends ClientBase with ClientMixin { } /// Your secret JSON Web Token + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setJWT(value) { config['jWT'] = value; @@ -72,6 +77,7 @@ class ClientBrowser extends ClientBase with ClientMixin { return this; } + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setLocale(value) { config['locale'] = value; @@ -80,6 +86,7 @@ class ClientBrowser extends ClientBase with ClientMixin { } /// The user session to authenticate with + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setSession(value) { config['session'] = value; @@ -88,6 +95,7 @@ class ClientBrowser extends ClientBase with ClientMixin { } /// Your secret dev API key + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setDevKey(value) { config['devKey'] = value; @@ -96,6 +104,7 @@ class ClientBrowser extends ClientBase with ClientMixin { } /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setCookie(value) { config['cookie'] = value; @@ -104,6 +113,7 @@ class ClientBrowser extends ClientBase with ClientMixin { } /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setImpersonateUserId(value) { config['impersonateUserId'] = value; @@ -112,6 +122,7 @@ class ClientBrowser extends ClientBase with ClientMixin { } /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setImpersonateUserEmail(value) { config['impersonateUserEmail'] = value; @@ -120,6 +131,7 @@ class ClientBrowser extends ClientBase with ClientMixin { } /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setImpersonateUserPhone(value) { config['impersonateUserPhone'] = value; @@ -127,11 +139,13 @@ class ClientBrowser extends ClientBase with ClientMixin { return this; } + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setSelfSigned({bool status = true}) { return this; } + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setEndpoint(String endPoint) { if (!endPoint.startsWith('http://') && !endPoint.startsWith('https://')) { @@ -146,6 +160,7 @@ class ClientBrowser extends ClientBase with ClientMixin { return this; } + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientBrowser setEndPointRealtime(String endPoint) { if (!endPoint.startsWith('ws://') && !endPoint.startsWith('wss://')) { @@ -175,6 +190,22 @@ class ClientBrowser extends ClientBase with ClientMixin { } } + @override + Future realtimeWebSocket(Uri uri) async { + await init(); + return HtmlWebSocketChannel.connect(uri); + } + + @override + String? realtimeFallbackCookie() { + final fallbackCookie = web.window.localStorage.getItem('cookieFallback'); + if (fallbackCookie != null) { + final cookie = Map.from(jsonDecode(fallbackCookie)); + return cookie.values.first; + } + return null; + } + @override Future chunkedUpload({ required String path, diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index ff6501c8..91c34702 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'dart:math'; +import 'dart:convert'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:http/http.dart' as http; @@ -17,6 +18,8 @@ import 'response.dart'; import 'package:flutter/foundation.dart'; import 'input_file.dart'; import 'upload_progress.dart'; +import 'package:web_socket_channel/io.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; ClientBase createClient({required String endPoint, required bool selfSigned}) => ClientIO(endPoint: endPoint, selfSigned: selfSigned); @@ -58,7 +61,7 @@ class ClientIO extends ClientBase with ClientMixin { 'x-sdk-name': 'Flutter', 'x-sdk-platform': 'client', 'x-sdk-language': 'flutter', - 'x-sdk-version': '24.1.1', + 'x-sdk-version': '25.0.0', 'X-Appwrite-Response-Format': '1.9.5', }; @@ -83,6 +86,7 @@ class ClientIO extends ClientBase with ClientMixin { } /// Your project ID + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setProject(value) { config['project'] = value; @@ -91,6 +95,7 @@ class ClientIO extends ClientBase with ClientMixin { } /// Your secret JSON Web Token + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setJWT(value) { config['jWT'] = value; @@ -98,6 +103,7 @@ class ClientIO extends ClientBase with ClientMixin { return this; } + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setLocale(value) { config['locale'] = value; @@ -106,6 +112,7 @@ class ClientIO extends ClientBase with ClientMixin { } /// The user session to authenticate with + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setSession(value) { config['session'] = value; @@ -114,6 +121,7 @@ class ClientIO extends ClientBase with ClientMixin { } /// Your secret dev API key + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setDevKey(value) { config['devKey'] = value; @@ -122,6 +130,7 @@ class ClientIO extends ClientBase with ClientMixin { } /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setCookie(value) { config['cookie'] = value; @@ -130,6 +139,7 @@ class ClientIO extends ClientBase with ClientMixin { } /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setImpersonateUserId(value) { config['impersonateUserId'] = value; @@ -138,6 +148,7 @@ class ClientIO extends ClientBase with ClientMixin { } /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setImpersonateUserEmail(value) { config['impersonateUserEmail'] = value; @@ -146,6 +157,7 @@ class ClientIO extends ClientBase with ClientMixin { } /// 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. + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setImpersonateUserPhone(value) { config['impersonateUserPhone'] = value; @@ -153,6 +165,7 @@ class ClientIO extends ClientBase with ClientMixin { return this; } + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setSelfSigned({bool status = true}) { selfSigned = status; @@ -161,6 +174,7 @@ class ClientIO extends ClientBase with ClientMixin { return this; } + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setEndpoint(String endPoint) { if (!endPoint.startsWith('http://') && !endPoint.startsWith('https://')) { @@ -175,6 +189,7 @@ class ClientIO extends ClientBase with ClientMixin { return this; } + @Deprecated('Use Client.from or another factory constructor instead.') @override ClientIO setEndPointRealtime(String endPoint) { if (!endPoint.startsWith('ws://') && !endPoint.startsWith('wss://')) { @@ -249,6 +264,71 @@ class ClientIO extends ClientBase with ClientMixin { _initProgress = false; } + @override + Future realtimeWebSocket(Uri uri) async { + Map? headers; + while (!_initialized && _initProgress) { + await Future.delayed(Duration(milliseconds: 10)); + } + if (!_initialized) { + await init(); + } + final cookies = await _cookieJar.loadForRequest(uri); + headers = {HttpHeaders.cookieHeader: CookieManager.getCookies(cookies)}; + + return IOWebSocketChannel(selfSigned + ? await _connectRealtimeForSelfSignedCert(uri, headers) + : await WebSocket.connect(uri.toString(), headers: headers)); + } + + @override + String? realtimeFallbackCookie() => null; + + // https://github.com/jonataslaw/getsocket/blob/f25b3a264d8cc6f82458c949b86d286cd0343792/lib/src/io.dart#L104 + // and from official dart sdk websocket_impl.dart connect method + Future _connectRealtimeForSelfSignedCert( + Uri uri, Map headers) async { + try { + var r = Random(); + var key = base64.encode(List.generate(16, (_) => r.nextInt(255))); + var client = HttpClient(context: SecurityContext()); + client.badCertificateCallback = + (X509Certificate cert, String host, int port) { + return true; + }; + + uri = Uri( + scheme: uri.scheme == 'wss' ? 'https' : 'http', + userInfo: uri.userInfo, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + fragment: uri.fragment, + ); + + var request = await client.getUrl(uri); + + headers.forEach((key, value) => request.headers.add(key, value)); + + request.headers + ..set(HttpHeaders.connectionHeader, "Upgrade") + ..set(HttpHeaders.upgradeHeader, "websocket") + ..set("Sec-WebSocket-Key", key) + ..set("Cache-Control", "no-cache") + ..set("Sec-WebSocket-Version", "13"); + + var response = await request.close(); + + // ignore: close_sinks + var socket = await response.detachSocket(); + var webSocket = WebSocket.fromUpgradedSocket(socket, serverSide: false); + return webSocket; + } catch (e) { + rethrow; + } + } + Future _interceptRequest(http.BaseRequest request) async { final body = (request is http.Request) ? request.body : ''; for (final i in _interceptors) { diff --git a/lib/src/realtime.dart b/lib/src/realtime.dart index 28e4c43d..df54885e 100644 --- a/lib/src/realtime.dart +++ b/lib/src/realtime.dart @@ -8,7 +8,7 @@ import 'client.dart'; /// Realtime allows you to listen to any events on the server-side in realtime using the subscribe method. abstract class Realtime extends Service { /// Initializes a [Realtime] service - factory Realtime(Client client) => createRealtime(client); + factory Realtime(ClientAuth client) => createRealtime(client); /// Subscribes to Appwrite events and returns a `RealtimeSubscription` object, which can be used /// to listen to events on the channels in realtime and to close the subscription to stop listening. diff --git a/lib/src/realtime_browser.dart b/lib/src/realtime_browser.dart index 3563e7d9..84b009fc 100644 --- a/lib/src/realtime_browser.dart +++ b/lib/src/realtime_browser.dart @@ -1,37 +1,17 @@ -import 'dart:convert'; -import 'dart:async'; -import 'package:web/web.dart' as web; -import 'package:web_socket_channel/html.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; import 'realtime_subscription.dart'; import 'realtime_base.dart'; import 'client.dart'; -import 'client_browser.dart'; import 'realtime_mixin.dart'; -RealtimeBase createRealtime(Client client) => RealtimeBrowser(client); +RealtimeBase createRealtime(ClientAuth client) => RealtimeBrowser(client); class RealtimeBrowser extends RealtimeBase with RealtimeMixin { Map? lastMessage; - RealtimeBrowser(Client client) { + RealtimeBrowser(ClientAuth client) { this.client = client; - getWebSocket = _getWebSocket; - getFallbackCookie = _getFallbackCookie; - } - - Future _getWebSocket(Uri uri) async { - await (client as ClientBrowser).init(); - return HtmlWebSocketChannel.connect(uri); - } - - String? _getFallbackCookie() { - final fallbackCookie = web.window.localStorage.getItem('cookieFallback'); - if (fallbackCookie != null) { - final cookie = Map.from(jsonDecode(fallbackCookie)); - return cookie.values.first; - } - return null; + getWebSocket = client.realtimeWebSocket; + getFallbackCookie = client.realtimeFallbackCookie; } @override diff --git a/lib/src/realtime_io.dart b/lib/src/realtime_io.dart index 762787b5..d8706734 100644 --- a/lib/src/realtime_io.dart +++ b/lib/src/realtime_io.dart @@ -1,41 +1,14 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:math'; -import 'package:flutter/foundation.dart'; -import 'package:web_socket_channel/io.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; -import 'cookie_manager.dart'; import 'realtime_subscription.dart'; import 'realtime_base.dart'; import 'realtime_mixin.dart'; import 'client.dart'; -import 'client_io.dart'; -RealtimeBase createRealtime(Client client) => RealtimeIO(client); +RealtimeBase createRealtime(ClientAuth client) => RealtimeIO(client); class RealtimeIO extends RealtimeBase with RealtimeMixin { - RealtimeIO(Client client) { + RealtimeIO(ClientAuth client) { this.client = client; - getWebSocket = _getWebSocket; - } - - Future _getWebSocket(Uri uri) async { - Map? headers; - while (!(client as ClientIO).initialized && - (client as ClientIO).initProgress) { - await Future.delayed(Duration(milliseconds: 10)); - } - if (!(client as ClientIO).initialized) { - await (client as ClientIO).init(); - } - final cookies = await (client as ClientIO).cookieJar.loadForRequest(uri); - headers = {HttpHeaders.cookieHeader: CookieManager.getCookies(cookies)}; - - final websok = IOWebSocketChannel((client as ClientIO).selfSigned - ? await _connectForSelfSignedCert(uri, headers) - : await WebSocket.connect(uri.toString(), headers: headers)); - return websok; + getWebSocket = client.realtimeWebSocket; } /// Subscribe @@ -64,49 +37,4 @@ class RealtimeIO extends RealtimeBase with RealtimeMixin { metadata: metadata, ); } - - // https://github.com/jonataslaw/getsocket/blob/f25b3a264d8cc6f82458c949b86d286cd0343792/lib/src/io.dart#L104 - // and from official dart sdk websocket_impl.dart connect method - Future _connectForSelfSignedCert( - Uri uri, Map headers) async { - try { - var r = Random(); - var key = base64.encode(List.generate(16, (_) => r.nextInt(255))); - var client = HttpClient(context: SecurityContext()); - client.badCertificateCallback = - (X509Certificate cert, String host, int port) { - return true; - }; - - uri = Uri( - scheme: uri.scheme == 'wss' ? 'https' : 'http', - userInfo: uri.userInfo, - host: uri.host, - port: uri.port, - path: uri.path, - query: uri.query, - fragment: uri.fragment, - ); - - var request = await client.getUrl(uri); - - headers.forEach((key, value) => request.headers.add(key, value)); - - request.headers - ..set(HttpHeaders.connectionHeader, "Upgrade") - ..set(HttpHeaders.upgradeHeader, "websocket") - ..set("Sec-WebSocket-Key", key) - ..set("Cache-Control", "no-cache") - ..set("Sec-WebSocket-Version", "13"); - - var response = await request.close(); - - // ignore: close_sinks - var socket = await response.detachSocket(); - var webSocket = WebSocket.fromUpgradedSocket(socket, serverSide: false); - return webSocket; - } catch (e) { - rethrow; - } - } } diff --git a/lib/src/realtime_mixin.dart b/lib/src/realtime_mixin.dart index 72edf5ee..9d542a37 100644 --- a/lib/src/realtime_mixin.dart +++ b/lib/src/realtime_mixin.dart @@ -27,7 +27,7 @@ String _uniqueSubscriptionId() { } mixin RealtimeMixin { - late Client client; + late ClientAuth client; final Map _subscriptions = {}; final Map> _pendingSubscribes = {}; Map? _pendingPresence; diff --git a/lib/src/realtime_stub.dart b/lib/src/realtime_stub.dart index 3aab4e03..bf65ae4e 100644 --- a/lib/src/realtime_stub.dart +++ b/lib/src/realtime_stub.dart @@ -2,6 +2,6 @@ import 'realtime_base.dart'; import 'client.dart'; /// Implemented in `realtime_browser.dart` and `realtime_io.dart`. -RealtimeBase createRealtime(Client client) => throw UnsupportedError( +RealtimeBase createRealtime(ClientAuth client) => throw UnsupportedError( 'Cannot create a client without dart:html or dart:io.', ); diff --git a/lib/src/service.dart b/lib/src/service.dart index d73e37e6..ca2c0d57 100644 --- a/lib/src/service.dart +++ b/lib/src/service.dart @@ -1,8 +1,5 @@ -import '../models.dart' as models; import 'client.dart'; class Service { - final Client client; - - const Service(this.client); + const Service(ClientAuth _); } diff --git a/pubspec.yaml b/pubspec.yaml index 052a6a98..01f35d1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: appwrite -version: 24.1.1 +version: 25.0.0 description: Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API homepage: https://appwrite.io repository: https://github.com/appwrite/sdk-for-flutter