Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 49 additions & 25 deletions vrchat_dart/example/bin/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,24 @@ 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) {
print('authError');
print(loginResponse.failure);
throw Exception('Login failed');
if (loginSuccess == null) {
print('Login failed');
print(loginFailure);
return;
}

final authResponse = loginResponse.success!.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
Expand All @@ -48,18 +49,20 @@ 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);
return;
}
}

final currentUser = api.auth.currentUser;
if (currentUser == null) {
throw Exception('Login failed');
print('Login failed');
return;
}

print('Logged in as ${currentUser.displayName}');
Expand All @@ -68,37 +71,58 @@ 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;
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,
...friendsResponse.success!.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;
Expand Down
44 changes: 21 additions & 23 deletions vrchat_dart/lib/src/api/src/auth_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class AuthApi {
///
/// Logging in without a [username]/[password] will use the stored auth
/// session if available
Future<ValidatedResponse<dynamic, AuthResponse>> login({
Future<TransformedResponse<dynamic, AuthResponse>> login({
String? username,
String? password,
}) async {
Expand All @@ -28,51 +28,49 @@ 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) {
_currentUser = success.data;
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<String, dynamic>) return failure.cast();
if (data is! Map<String, dynamic>) return (null, failure);

final twoFactorAuthTypes = (data['requiresTwoFactorAuth'] as List?)
?.cast<String>()
.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<ValidatedResponse<dynamic, AuthResponse>> verify2fa(
Future<TransformedResponse<dynamic, AuthResponse>> 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();
Expand All @@ -81,7 +79,7 @@ class AuthApi {
/// Logout
///
/// This will set [currentUser] to null
Future<ValidatedResponse<Success, Success>> logout() async {
Future<ValidatedResponse<Success>> logout() async {
final response = await _rawApi
.getAuthenticationApi()
.logout()
Expand Down
4 changes: 2 additions & 2 deletions vrchat_dart/lib/src/model/api/vrc_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ class VrcError {
/// Extension on [Dio] [Response] futures for validation
extension VrcResponseValidator<T> on Future<Response<T>> {
/// Validate a VRC response, and transform the error to a [VrcError]
Future<ValidatedResponse<T, T>> validateVrc() =>
validate(transformDioError: (e) => VrcError.fromDioError(e) ?? e);
Future<ValidatedResponse<T>> validateVrc() =>
validate().transform(dioException: (e) => VrcError.fromDioError(e) ?? e);
}

/// Extension on [ValidatedResponse] to get the [VrcError] if it exists
Expand Down
2 changes: 1 addition & 1 deletion vrchat_dart/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ 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: ^0.3.1
meta: ^1.16.0

dev_dependencies:
Expand Down