123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- #import <AuthenticationServices/AuthenticationServices.h>
- #import "authenticator/BaseAuthenticator.h"
- #import "AccountListViewController.h"
- #import "AFNetworking.h"
- #import "LauncherPreferences.h"
- #import "UIImageView+AFNetworking.h"
- #import "ios_uikit_bridge.h"
- #import "utils.h"
- @interface AccountListViewController()<ASWebAuthenticationPresentationContextProviding>
- @property(nonatomic, strong) NSMutableArray *accountList;
- @property(nonatomic) ASWebAuthenticationSession *authVC;
- @end
- @implementation AccountListViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- if (self.accountList == nil) {
- self.accountList = [NSMutableArray array];
- } else {
- [self.accountList removeAllObjects];
- }
- // List accounts
- NSString *listPath = [NSString stringWithFormat:@"%s/accounts", getenv("POJAV_HOME")];
- NSFileManager *fm = [NSFileManager defaultManager];
- NSArray *files = [fm contentsOfDirectoryAtPath:listPath error:nil];
- for(NSString *file in files) {
- NSString *path = [listPath stringByAppendingPathComponent:file];
- BOOL isDir = NO;
- [fm fileExistsAtPath:path isDirectory:(&isDir)];
- if(!isDir && [file hasSuffix:@".json"]) {
- [self.accountList addObject:parseJSONFromFile(path)];
- }
- }
- [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
- }
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return self.accountList.count + 1;
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
- }
- if (indexPath.row == self.accountList.count) {
- cell.imageView.image = [UIImage imageNamed:@"IconAdd"];
- cell.textLabel.text = localize(@"login.option.add", nil);
- return cell;
- }
- NSDictionary *selected = self.accountList[indexPath.row];
- // By default, display the saved username
- cell.textLabel.text = selected[@"username"];
- if ([selected[@"username"] hasPrefix:@"Demo."]) {
- // Remove the prefix "Demo."
- cell.textLabel.text = [selected[@"username"] substringFromIndex:5];
- cell.detailTextLabel.text = localize(@"login.option.demo", nil);
- } else if (selected[@"xboxGamertag"] == nil) {
- cell.detailTextLabel.text = localize(@"login.option.local", nil);
- } else {
- // Display the Xbox gamertag for online accounts
- cell.detailTextLabel.text = selected[@"xboxGamertag"];
- }
- cell.imageView.contentMode = UIViewContentModeCenter;
- [cell.imageView setImageWithURL:[NSURL URLWithString:[selected[@"profilePicURL"] stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]] placeholderImage:[UIImage imageNamed:@"DefaultAccount"]];
- return cell;
- }
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
- [tableView deselectRowAtIndexPath:indexPath animated:NO];
- UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
- if (indexPath.row == self.accountList.count) {
- [self actionAddAccount:cell];
- return;
- }
- self.modalInPresentation = YES;
- self.tableView.userInteractionEnabled = NO;
- [self addActivityIndicatorTo:cell];
- id callback = ^(id status, BOOL success) {
- [self callbackMicrosoftAuth:status success:success forCell:cell];
- };
- [[BaseAuthenticator loadSavedName:self.accountList[indexPath.row][@"username"]] refreshTokenWithCallback:callback];
- }
- - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
- if (editingStyle == UITableViewCellEditingStyleDelete) {
- // TODO: invalidate token
- NSString *str = self.accountList[indexPath.row][@"username"];
- NSFileManager *fm = [NSFileManager defaultManager];
- NSString *path = [NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), str];
- if (self.whenDelete != nil) {
- self.whenDelete(str);
- }
- NSString *xuid = self.accountList[indexPath.row][@"xuid"];
- if (xuid) {
- [MicrosoftAuthenticator clearTokenDataOfProfile:xuid];
- }
- [fm removeItemAtPath:path error:nil];
- [self.accountList removeObjectAtIndex:indexPath.row];
- [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
- }
- }
- - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- if (indexPath.row == self.accountList.count) {
- return UITableViewCellEditingStyleNone;
- } else {
- return UITableViewCellEditingStyleDelete;
- }
- }
- - (NSDictionary *)parseQueryItems:(NSString *)url {
- NSMutableDictionary *result = [NSMutableDictionary new];
- NSArray<NSURLQueryItem *> *queryItems = [NSURLComponents componentsWithString:url].queryItems;
- for (NSURLQueryItem *item in queryItems) {
- result[item.name] = item.value;
- }
- return result;
- }
- - (void)actionAddAccount:(UITableViewCell *)sender {
- UIAlertController *picker = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
- UIAlertAction *actionMicrosoft = [UIAlertAction actionWithTitle:localize(@"login.option.microsoft", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
- [self actionLoginMicrosoft:sender];
- }];
- [picker addAction:actionMicrosoft];
- UIAlertAction *actionLocal = [UIAlertAction actionWithTitle:localize(@"login.option.local", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
- [self actionLoginLocal:sender];
- }];
- [picker addAction:actionLocal];
- UIAlertAction *cancel = [UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil];
- [picker addAction:cancel];
- picker.popoverPresentationController.sourceView = sender;
- picker.popoverPresentationController.sourceRect = sender.bounds;
- [self presentViewController:picker animated:YES completion:nil];
- }
- - (void)actionLoginLocal:(UIView *)sender {
- if (getPrefBool(@"warnings.local_warn")) {
- setPrefBool(@"warnings.local_warn", NO);
- UIAlertController *alert = [UIAlertController alertControllerWithTitle:localize(@"login.warn.title.localmode", nil) message:localize(@"login.warn.message.localmode", nil) preferredStyle:UIAlertControllerStyleActionSheet];
- alert.popoverPresentationController.sourceView = sender;
- alert.popoverPresentationController.sourceRect = sender.bounds;
- UIAlertAction *ok = [UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self actionLoginLocal:sender];}];
- [alert addAction:ok];
- [self presentViewController:alert animated:YES completion:nil];
- return;
- }
- UIAlertController *controller = [UIAlertController alertControllerWithTitle:localize(@"Sign in", nil) message:localize(@"login.option.local", nil) preferredStyle:UIAlertControllerStyleAlert];
- [controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {
- textField.placeholder = localize(@"login.alert.field.username", nil);
- textField.clearButtonMode = UITextFieldViewModeWhileEditing;
- textField.borderStyle = UITextBorderStyleRoundedRect;
- }];
- [controller addAction:[UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
- NSArray *textFields = controller.textFields;
- UITextField *usernameField = textFields[0];
- if (usernameField.text.length < 3 || usernameField.text.length > 16) {
- controller.message = localize(@"login.error.username.outOfRange", nil);
- [self presentViewController:controller animated:YES completion:nil];
- } else {
- id callback = ^(id status, BOOL success) {
- self.whenItemSelected();
- [self dismissViewControllerAnimated:YES completion:nil];
- };
- [[[LocalAuthenticator alloc] initWithInput:usernameField.text] loginWithCallback:callback];
- }
- }]];
- [controller addAction:[UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
- [self presentViewController:controller animated:YES completion:nil];
- }
- - (void)actionLoginMicrosoft:(UITableViewCell *)sender {
- NSURL *url = [NSURL URLWithString:@"https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&response_type=code&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf"];
- self.authVC =
- [[ASWebAuthenticationSession alloc] initWithURL:url
- callbackURLScheme:@"ms-xal-00000000402b5328"
- completionHandler:^(NSURL * _Nullable callbackURL, NSError * _Nullable error)
- {
- if (callbackURL == nil) {
- if (error.code != ASWebAuthenticationSessionErrorCodeCanceledLogin) {
- showDialog(localize(@"Error", nil), error.localizedDescription);
- }
- return;
- }
- // NSLog(@"URL returned = %@", [callbackURL absoluteString]);
- NSDictionary *queryItems = [self parseQueryItems:callbackURL.absoluteString];
- if (queryItems[@"code"]) {
- self.modalInPresentation = YES;
- self.tableView.userInteractionEnabled = NO;
- [self addActivityIndicatorTo:sender];
- id callback = ^(id status, BOOL success) {
- if ([status isKindOfClass:NSString.class] && [status isEqualToString:@"DEMO"] && success) {
- showDialog(localize(@"login.warn.title.demomode", nil), localize(@"login.warn.message.demomode", nil));
- }
- [self callbackMicrosoftAuth:status success:success forCell:sender];
- };
- [[[MicrosoftAuthenticator alloc] initWithInput:queryItems[@"code"]] loginWithCallback:callback];
- } else {
- if ([queryItems[@"error"] hasPrefix:@"access_denied"]) {
- // Ignore access denial responses
- return;
- }
- showDialog(localize(@"Error", nil), queryItems[@"error_description"]);
- }
- }];
- self.authVC.prefersEphemeralWebBrowserSession = YES;
- self.authVC.presentationContextProvider = self;
- if ([self.authVC start] == NO) {
- showDialog(localize(@"Error", nil), @"Unable to open Safari");
- }
- }
- - (void)addActivityIndicatorTo:(UITableViewCell *)cell {
- UIActivityIndicatorViewStyle indicatorStyle = UIActivityIndicatorViewStyleMedium;
- UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:indicatorStyle];
- cell.accessoryView = indicator;
- [indicator sizeToFit];
- [indicator startAnimating];
- }
- - (void)removeActivityIndicatorFrom:(UITableViewCell *)cell {
- UIActivityIndicatorView *indicator = (id)cell.accessoryView;
- [indicator stopAnimating];
- cell.accessoryView = nil;
- }
- - (void)callbackMicrosoftAuth:(id)status success:(BOOL)success forCell:(UITableViewCell *)cell {
- if (status != nil) {
- if (success) {
- cell.detailTextLabel.text = status;
- } else {
- self.modalInPresentation = NO;
- self.tableView.userInteractionEnabled = YES;
- [self removeActivityIndicatorFrom:cell];
- cell.detailTextLabel.text = [status localizedDescription];
- NSData *errorData = ((NSError *)status).userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
- NSString *errorStr = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding];
- NSLog(@"[MSA] Error: %@", errorStr);
- showDialog(localize(@"Error", nil), errorStr);
- }
- } else if (success) {
- self.whenItemSelected();
- [self removeActivityIndicatorFrom:cell];
- [self dismissViewControllerAnimated:YES completion:nil];
- }
- }
- #pragma mark - UIPopoverPresentationControllerDelegate
- - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
- return UIModalPresentationNone;
- }
- #pragma mark - ASWebAuthenticationPresentationContextProviding
- - (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session {
- return UIApplication.sharedApplication.windows.firstObject;
- }
- @end
|