Removed demo_warn preference as the warning is only displayed after logging in with demo account.
295 lines
13 KiB
Objective-C
295 lines
13 KiB
Objective-C
#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 ATableViewCell : UITableViewCell
|
|
@end
|
|
|
|
@implementation ATableViewCell
|
|
|
|
@end
|
|
|
|
|
|
@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;
|
|
}
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
self.modalInPresentation = YES;
|
|
}
|
|
self.tableView.userInteractionEnabled = NO;
|
|
[self addActivityIndicatorTo:cell];
|
|
|
|
id callback = ^(NSString* 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);
|
|
}
|
|
[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;
|
|
}
|
|
}
|
|
|
|
- (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 ([getPreference(@"local_warn") boolValue] == YES) {
|
|
setPreference(@"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 = ^(NSString* status, BOOL success) {
|
|
[self dismissViewControllerAnimated:YES completion:nil];
|
|
self.whenItemSelected();
|
|
};
|
|
[[[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(self, localize(@"Error", nil), error.localizedDescription);
|
|
}
|
|
return;
|
|
}
|
|
NSString *urlString = [callbackURL absoluteString];
|
|
// NSLog(@"URL returned = %@", [callbackURL absoluteString]);
|
|
|
|
if ([urlString containsString:@"/auth/?code="]) {
|
|
if (@available(iOS 13.0, *)) {
|
|
self.modalInPresentation = YES;
|
|
}
|
|
self.tableView.userInteractionEnabled = NO;
|
|
[self addActivityIndicatorTo:sender];
|
|
NSArray *components = [urlString componentsSeparatedByString:@"/auth/?code="];
|
|
id callback = ^(NSString* status, BOOL success) {
|
|
if ([status isEqualToString:@"DEMO"] && success) {
|
|
showDialog(self, localize(@"login.warn.title.demomode", nil), localize(@"login.warn.message.demomode", nil));
|
|
}
|
|
[self callbackMicrosoftAuth:status success:success forCell:sender];
|
|
};
|
|
[[[MicrosoftAuthenticator alloc] initWithInput:components[1]] loginWithCallback:callback];
|
|
} else {
|
|
NSArray *components = [urlString componentsSeparatedByString:@"/auth/?error="];
|
|
if ([components[1] hasPrefix:@"access_denied"]) {
|
|
// Ignore access denial responses
|
|
return;
|
|
}
|
|
NSString *outError = [components[1]
|
|
stringByReplacingOccurrencesOfString:@"&error_description=" withString:@": "];
|
|
outError = [outError stringByRemovingPercentEncoding];
|
|
showDialog(self, localize(@"Error", nil), outError);
|
|
}
|
|
}];
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
self.authVC.prefersEphemeralWebBrowserSession = YES;
|
|
self.authVC.presentationContextProvider = self;
|
|
}
|
|
|
|
if ([self.authVC start] == NO) {
|
|
showDialog(self, localize(@"Error", nil), @"Unable to open Safari");
|
|
}
|
|
}
|
|
|
|
- (void)addActivityIndicatorTo:(UITableViewCell *)cell {
|
|
UIActivityIndicatorViewStyle indicatorStyle;
|
|
if (@available(iOS 13.0, *)) {
|
|
indicatorStyle = UIActivityIndicatorViewStyleMedium;
|
|
} else {
|
|
indicatorStyle = UIActivityIndicatorViewStyleGray;
|
|
}
|
|
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:(NSString *)status success:(BOOL)success forCell:(UITableViewCell *)cell {
|
|
if (status != nil) {
|
|
cell.detailTextLabel.text = status;
|
|
if (!success) {
|
|
if (@available(iOS 13.0, *)) {
|
|
self.modalInPresentation = NO;
|
|
}
|
|
self.tableView.userInteractionEnabled = YES;
|
|
[self removeActivityIndicatorFrom:cell];
|
|
NSLog(@"[MSA] Error: %@", status);
|
|
showDialog(self, localize(@"Error", nil), status);
|
|
}
|
|
} else if (success) {
|
|
[self removeActivityIndicatorFrom:cell];
|
|
[self dismissViewControllerAnimated:YES completion:nil];
|
|
self.whenItemSelected();
|
|
}
|
|
}
|
|
|
|
#pragma mark - UIPopoverPresentationControllerDelegate
|
|
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
|
|
return UIModalPresentationNone;
|
|
}
|
|
|
|
#pragma mark - ASWebAuthenticationPresentationContextProviding
|
|
- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session API_AVAILABLE(ios(13.0)){
|
|
return UIApplication.sharedApplication.windows.firstObject;
|
|
}
|
|
|
|
@end
|