From 63d79902cbdf4248c813a43efe9b7d5b6acbc8ab Mon Sep 17 00:00:00 2001 From: Rexios Date: Tue, 7 Oct 2025 17:30:54 -0400 Subject: [PATCH 1/5] dio_response_validator 0.3.0 --- vrchat_dart/lib/src/api/src/auth_api.dart | 43 +++++++++---------- .../lib/src/model/api/vrc_response.dart | 5 ++- vrchat_dart/pubspec.yaml | 4 ++ 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/vrchat_dart/lib/src/api/src/auth_api.dart b/vrchat_dart/lib/src/api/src/auth_api.dart index c9b631c3..c43f9ed8 100644 --- a/vrchat_dart/lib/src/api/src/auth_api.dart +++ b/vrchat_dart/lib/src/api/src/auth_api.dart @@ -19,7 +19,7 @@ class AuthApi { /// /// Logging in without a [username]/[password] will use the stored auth /// session if available - Future> login({ + Future> login({ String? username, String? password, }) async { @@ -28,51 +28,48 @@ class AuthApi { final authorization = base64.encode( utf8.encode('$encodedUsername:$encodedPassword'), ); - final response = await _rawApi + final (success, failure) = await _rawApi .getAuthenticationApi() .getCurrentUser(headers: {'Authorization': 'Basic $authorization'}) .validateVrc(); - final failure = response.failure; - if (failure != null) { + if (success != null) { + return (ValidResponse(AuthResponse(), success.response), null); + } else if (failure != null) { final response = failure.response; - - if (response == null) return failure.cast(); + if (response == null) return (null, failure); final data = response.data; - if (data is! Map) return failure.cast(); + if (data is! Map) return (null, failure); final twoFactorAuthTypes = (data['requiresTwoFactorAuth'] as List?) ?.cast() .map(TwoFactorAuthType.values.byName) .toList(); - if (twoFactorAuthTypes != null) { - return ValidatedResponse.success( + if (twoFactorAuthTypes == null) return (null, failure); + + return ( + ValidResponse( AuthResponse(twoFactorAuthTypes: twoFactorAuthTypes), response, - ); - } - - return failure.cast(); + ), + null, + ); + } else { + throw StateError('This should never happen'); } - - final success = response.success!; - _currentUser = success.data; - - return ValidatedResponse.success(AuthResponse(), success.response); } /// Verify a 2fa code - Future> verify2fa( + Future> verify2fa( String code, ) async { - final response = await _rawApi + final (success, failure) = await _rawApi .getAuthenticationApi() .verify2FA(twoFactorAuthCode: TwoFactorAuthCode(code: code)) .validateVrc(); - final failure = response.failure; - if (failure != null) return failure.cast(); + if (failure != null) return (null, failure); // Call the login function to set the [currentUser] return login(); @@ -81,7 +78,7 @@ class AuthApi { /// Logout /// /// This will set [currentUser] to null - Future> logout() async { + Future> logout() async { final response = await _rawApi .getAuthenticationApi() .logout() diff --git a/vrchat_dart/lib/src/model/api/vrc_response.dart b/vrchat_dart/lib/src/model/api/vrc_response.dart index b1a570d4..2bc82659 100644 --- a/vrchat_dart/lib/src/model/api/vrc_response.dart +++ b/vrchat_dart/lib/src/model/api/vrc_response.dart @@ -78,8 +78,9 @@ class VrcError { /// Extension on [Dio] [Response] futures for validation extension VrcResponseValidator on Future> { /// Validate a VRC response, and transform the error to a [VrcError] - Future> validateVrc() => - validate(transformDioError: (e) => VrcError.fromDioError(e) ?? e); + Future> validateVrc() => validate().transform( + transformDioException: (e) => VrcError.fromDioError(e) ?? e, + ); } /// Extension on [ValidatedResponse] to get the [VrcError] if it exists diff --git a/vrchat_dart/pubspec.yaml b/vrchat_dart/pubspec.yaml index 6a177e4d..9e488427 100644 --- a/vrchat_dart/pubspec.yaml +++ b/vrchat_dart/pubspec.yaml @@ -24,3 +24,7 @@ dev_dependencies: fixer: ^0.1.0 path: ^1.9.0 recase: ^4.1.0 + +dependency_overrides: + dio_response_validator: + path: ../../dio_response_validator From f7cabc79331e1dd7ed33d7d242e63c5d2cd7ebca Mon Sep 17 00:00:00 2001 From: Rexios Date: Tue, 7 Oct 2025 17:59:25 -0400 Subject: [PATCH 2/5] Fixing issues --- vrchat_dart/example/bin/main.dart | 20 ++++++++++---------- vrchat_dart/lib/src/api/src/auth_api.dart | 1 + vrchat_dart/pubspec.yaml | 7 ++----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/vrchat_dart/example/bin/main.dart b/vrchat_dart/example/bin/main.dart index a01aaa49..9ce80345 100644 --- a/vrchat_dart/example/bin/main.dart +++ b/vrchat_dart/example/bin/main.dart @@ -19,18 +19,18 @@ void main() async { ), ); - final loginResponse = await api.auth.login( + final (loginSuccess, loginFailure) = await api.auth.login( username: Credentials.username, password: Credentials.password, ); - if (loginResponse.failure != null) { + if (loginFailure != null) { print('authError'); - print(loginResponse.failure); + print(loginFailure); throw Exception('Login failed'); } - final authResponse = loginResponse.success!.data; + final authResponse = loginSuccess!.data; if (authResponse.requiresTwoFactorAuth) { print('requiresTwoFactorAuth'); @@ -48,12 +48,12 @@ void main() async { algorithm: Algorithm.SHA1, isGoogle: true, ); - final twoFactorResponse = await api.auth.verify2fa(code); - if (twoFactorResponse.failure == null) { + final (twoFactorSuccess, twoFactorFailure) = await api.auth.verify2fa(code); + if (twoFactorFailure == null) { print('2fa verification success'); } else { print('2fa verification failure'); - print(twoFactorResponse.failure); + print(twoFactorFailure); } } @@ -68,12 +68,12 @@ void main() async { currentUser.toUser(); currentUser.toLimitedUser(); - final friendsResponse = await api.rawApi + final (friendsSuccess, friendsFailure) = await api.rawApi .getFriendsApi() .getFriends() .validateVrc(); // Call [validateVrc] to handle errors - final error = friendsResponse.failure?.vrcError; + final error = friendsFailure?.vrcError; if (error != null) { print(error); } @@ -85,7 +85,7 @@ void main() async { final limitedTupper = tupper.toLimitedUser(); final friendsAndTupper = [ limitedTupper, - ...friendsResponse.success!.data.map((e) => e.toLimitedUser()), + ...friendsSuccess!.data.map((e) => e.toLimitedUser()), ]; print(friendsAndTupper.first.displayName); diff --git a/vrchat_dart/lib/src/api/src/auth_api.dart b/vrchat_dart/lib/src/api/src/auth_api.dart index c43f9ed8..2297a47c 100644 --- a/vrchat_dart/lib/src/api/src/auth_api.dart +++ b/vrchat_dart/lib/src/api/src/auth_api.dart @@ -34,6 +34,7 @@ class AuthApi { .validateVrc(); if (success != null) { + _currentUser = success.data; return (ValidResponse(AuthResponse(), success.response), null); } else if (failure != null) { final response = failure.response; diff --git a/vrchat_dart/pubspec.yaml b/vrchat_dart/pubspec.yaml index 9e488427..943df98b 100644 --- a/vrchat_dart/pubspec.yaml +++ b/vrchat_dart/pubspec.yaml @@ -13,7 +13,8 @@ dependencies: dio: ^5.2.0 web_socket_channel: ^3.0.0 json_annotation: ^4.7.0 - dio_response_validator: ^0.2.1 + dio_response_validator: + path: ../../dio_response_validator meta: ^1.16.0 dev_dependencies: @@ -24,7 +25,3 @@ dev_dependencies: fixer: ^0.1.0 path: ^1.9.0 recase: ^4.1.0 - -dependency_overrides: - dio_response_validator: - path: ../../dio_response_validator From 457c80b6d221062e612a2305affc30f4cc5dc301 Mon Sep 17 00:00:00 2001 From: Rexios Date: Thu, 9 Oct 2025 14:51:27 -0400 Subject: [PATCH 3/5] Update example --- vrchat_dart/example/bin/main.dart | 62 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/vrchat_dart/example/bin/main.dart b/vrchat_dart/example/bin/main.dart index 9ce80345..ecccd08b 100644 --- a/vrchat_dart/example/bin/main.dart +++ b/vrchat_dart/example/bin/main.dart @@ -24,18 +24,19 @@ void main() async { password: Credentials.password, ); - if (loginFailure != null) { - print('authError'); + if (loginSuccess == null) { + print('Login failed'); print(loginFailure); - throw Exception('Login failed'); + return; } - final authResponse = loginSuccess!.data; + final authResponse = loginSuccess.data; if (authResponse.requiresTwoFactorAuth) { print('requiresTwoFactorAuth'); if (!authResponse.twoFactorAuthTypes.contains(TwoFactorAuthType.totp)) { - throw Exception('Cannot automatically handle 2FA'); + print('Cannot automatically handle 2FA'); + return; } // VRChat is forcing 2FA these days. If you don't have 2FA enabled on your @@ -54,12 +55,14 @@ void main() async { } else { print('2fa verification failure'); print(twoFactorFailure); + return; } } final currentUser = api.auth.currentUser; if (currentUser == null) { - throw Exception('Login failed'); + print('Login failed'); + return; } print('Logged in as ${currentUser.displayName}'); @@ -73,32 +76,53 @@ void main() async { .getFriends() .validateVrc(); // Call [validateVrc] to handle errors - final error = friendsFailure?.vrcError; - if (error != null) { - print(error); + if (friendsSuccess == null) { + print('Fetching friends failed'); + print(friendsFailure); + return; } - final tupper = - (await api.rawApi.getUsersApi().getUser(userId: tupperUid)).data!; + final (tupperSuccess, tupperFailure) = + await api.rawApi.getUsersApi().getUser(userId: tupperUid).validateVrc(); + + if (tupperSuccess == null) { + print('Fetching tupper failed'); + print(tupperFailure); + return; + } // Convenience method to help with storing user objects from different endpoints together - final limitedTupper = tupper.toLimitedUser(); + final limitedTupper = tupperSuccess.data.toLimitedUser(); final friendsAndTupper = [ limitedTupper, - ...friendsSuccess!.data.map((e) => e.toLimitedUser()), + ...friendsSuccess.data.map((e) => e.toLimitedUser()), ]; print(friendsAndTupper.first.displayName); - final worldsResponse = await api.rawApi + final (worldsSuccess, worldsFailure) = await api.rawApi .getWorldsApi() - .searchWorlds(releaseStatus: ReleaseStatus.public); - print(worldsResponse.data!.first.name); + .searchWorlds(releaseStatus: ReleaseStatus.public) + .validateVrc(); + if (worldsSuccess == null) { + print('Fetching worlds failed'); + print(worldsFailure); + return; + } - final getWorldResponse = await api.rawApi + print(worldsSuccess.data.first.name); + + final (worldSuccess, worldFailure) = await api.rawApi .getWorldsApi() - .getWorld(worldId: worldsResponse.data!.first.id); - print(getWorldResponse.data!.name); + .getWorld(worldId: worldsSuccess.data.first.id) + .validateVrc(); + if (worldSuccess == null) { + print('Fetching world failed'); + print(worldFailure); + return; + } + + print(worldSuccess.data.name); // Do not start websocket streaming if this code is running in CI if (Platform.environment.containsKey('GITHUB_ACTIONS')) return; From 8c2eeb59dde80973b8e6e90d61ca0fc99aa7d065 Mon Sep 17 00:00:00 2001 From: Rexios Date: Thu, 9 Oct 2025 15:02:24 -0400 Subject: [PATCH 4/5] wip --- vrchat_dart/lib/src/model/api/vrc_response.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vrchat_dart/lib/src/model/api/vrc_response.dart b/vrchat_dart/lib/src/model/api/vrc_response.dart index 2bc82659..0914a171 100644 --- a/vrchat_dart/lib/src/model/api/vrc_response.dart +++ b/vrchat_dart/lib/src/model/api/vrc_response.dart @@ -78,9 +78,8 @@ class VrcError { /// Extension on [Dio] [Response] futures for validation extension VrcResponseValidator on Future> { /// Validate a VRC response, and transform the error to a [VrcError] - Future> validateVrc() => validate().transform( - transformDioException: (e) => VrcError.fromDioError(e) ?? e, - ); + Future> validateVrc() => + validate().transform(dioException: (e) => VrcError.fromDioError(e) ?? e); } /// Extension on [ValidatedResponse] to get the [VrcError] if it exists From 4e0855e561fb94fad0301a51da34c02e4943b9e2 Mon Sep 17 00:00:00 2001 From: Rexios Date: Sun, 12 Oct 2025 10:36:17 -0400 Subject: [PATCH 5/5] Use hosted drv --- vrchat_dart/pubspec.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vrchat_dart/pubspec.yaml b/vrchat_dart/pubspec.yaml index 943df98b..6c21ca4c 100644 --- a/vrchat_dart/pubspec.yaml +++ b/vrchat_dart/pubspec.yaml @@ -13,8 +13,7 @@ dependencies: dio: ^5.2.0 web_socket_channel: ^3.0.0 json_annotation: ^4.7.0 - dio_response_validator: - path: ../../dio_response_validator + dio_response_validator: ^0.3.1 meta: ^1.16.0 dev_dependencies: