Как я могу заставить UIWebView открывать страницу входа в Facebook в ответ на запрос OAuth на iOS 5 и iOS 6?

У нас есть:
(1) веб-приложение на основе Facebook API с функциональностью Facebook OAuth («веб-приложение FB»)
(2) браузер на базе UIWebView для iPad («Браузер»)

Наша цель — открыть страницу входа в Facebook для входа в веб-приложение FB (1) внутри браузера на основе UIWebView (2) на iPad.

Здесь есть несколько похожая проблема:
http://facebook.stackoverflow.com/questions/11337285/no-longer-able-to-login-to-ios-app-через-oauth-запрошенная-страница-не-найдена

Однако проблема с этим вопросом возникает после того, как пользователь вводит логин и пароль в форму Facebook. Наша проблема в том, что мы не можем отобразить форму входа в Facebook в первую очередь. Изменение типа приложения с «Web» на «Native/Desktop», как предлагается в этом вопросе, не помогло.

Шаги:
1. Откройте нашу веб-страницу (простую HTML-страницу) в этом UIWebView браузере
2. Нажмите кнопку запуска веб-приложения FB на этой странице
3. OnClick JavaScript попытается инициировать OAuth, который должен открыть экран входа в Facebook, чтобы войти в веб-приложение FB

Текущий результат (проблема):
На устройствах iOS 5.+ и iOS 6.+
- Наша веб-страница остается неизменной
- Страница входа в Facebook НЕ отображается (наша веб-страница по-прежнему отображается)
В iOS 4.3 (работает как положено):
- страница входа в Facebook открывается в том же UIWebView объекте браузера (заменяет нашу веб-страницу)

Ожидаемый результат:
— Отображается страница входа в Facebook, и пользователь может ввести логин и пароль Facebook
— Работает на iOS 5.+ и iOS 6.+ при запуске в браузере Safari на iPad. Страница входа в Facebook открывается в отдельной вкладке (в отличие от UIWebView, в которой нет отдельных вкладок)

Вопрос: Как заставить UIWebView открывать страницу входа в Facebook в ответ на запрос OAuth на iOS 5+ и iOS 6+?

Больше технических деталей:

Мы регистрируем различные NSURLRequest полей изнутри

-(BOOL)webView(UIWebView*)webView shouldStartLoadWithRequest(NSURLREquest*)request navigationType:…   

И мы замечаем некоторую разницу в логах для «правильного» и «неправильного» поведения. Вот как у меня выглядят потоки выполнения:

Во-первых, я нажимаю кнопку запуска «FB Web App», чтобы инициировать OAuth, затем в некоторых случаях

iOS 4.3, «правильный»
запрос на www.facebook.com/dialog/oauth?...
запрос на fbwebapp.com
запрос на m.facebook.com/login.php?... .
--здесь появляется логин facebook

iOS 5.0, «incorrect1»
запрос на www.facebook.com/dialog/oauth?...
запрос на fbwebapp.com
запрос на m.facebook.com/login.php?...

Тогда это может быть
--много m.facebook.com/login.php?...with next… в параметрах
с последующей ошибкой sqlite
--сейчас я вижу «Извините, что-то пошла не так» страница из фейсбука (впервые с таким сталкиваюсь)

iOS 6.0 «incorrect2»
запрос на www.facebook.com/dialog/oauth?...
запрос на fbwebapp.com

-(void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)вызывается ошибка с кодом ошибки -999

Вы можете видеть, что поведение определенно зависит от версии iOS. Но частым случаем является то, что ошибка возникает на этапе получения URL-адреса m.facebook.com/login.php... Но это все, что мы можем обнаружить.

Мы целый день бьемся головой об эту стену в поисках решения. Безнадежно.
Можете ли вы помочь нам открыть страницу входа в Facebook в UIWebView в ответ на OAuth?


person user1812802    schedule 09.11.2012    source источник


Ответы (5)


просто используйте: этот код

if (![FBSDKAccessToken currentAccessToken]) 
{   
    FBSDKLoginManager *manager = [[FBSDKLoginManager alloc]init];
    manager.loginBehavior = FBSDKLoginBehaviorWeb;
    [manager logInWithReadPermissions:@[@"public_profile", @"email", @"user_friends"] handler:
     ^(FBSDKLoginManagerLoginResult *result, NSError *error) {

         NSLog(@"result.token: %@",result.token.tokenString);  
         NSLog(@"%@",result.token.userID);   
         NSLog(@"%hhd",result.isCancelled);     
     }];
}

// здесь manager.loginBehavior = FBSDKLoginBehaviorWeb; все, что вам нужно, чтобы открыть facebook в UIWebview

person Navpreet    schedule 21.05.2015

Сделал это!

Это своего рода взлом, но вход js facebook sdk в UiWebView в iOS 6 наконец-то работает.

Как это можно было сделать? Это чистое решение для обработки функций JS + Facebook JS SDK + UIWebView Delegate.

JS - первый шаг)

кнопка входа (для подключения к facebook) вызывает этот пример функции, которая вызовет диалоги входа/oauth Face JS:

function loginWithFacebookClick(){

FB.login(function(response){
    //normal browsers callback 
});

}

JS - Второй шаг)

добавьте прослушиватель authResponseChange каждый раз, когда пользователь загружает веб-страницу (после FB.init()), чтобы определить статус подключения пользователя:

FB.Event.subscribe('auth.authResponse.Change', function(response){
//UIWebView login 'callback' handler
var auth = response.authResponse;
if(auth.status == 'connected'){
    //user is connected with facebook! just log him at your webapp
}
});

И с помощью функций делегата приложения UIWebView вы можете обрабатывать ответы facebook oauth

Задача C - Третий шаг)

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *url = [[request URL] absoluteString];

//when user status == connected (has a access_token at facebook oauth response)
if([url hasPrefix:@"https://m.facebook.com/dialog/oauth"] && [url rangeOfString:@"access_token="].location != NSNotFound)
{
    [self backToLastPage];
    return NO;
}

return YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{

NSString *url = [[webView.request URL] absoluteString];

if([url hasPrefix:@"https://m.facebook.com/dialog/oauth"])
{
    NSString *bodyHTML = [webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"];

    //Facebook oauth response dead end: is a blank body and a head with a script that does 
    //nothing. But if you got back to your last page, the handler of authResponseChange
    //will catch a connected status if user did his login and auth app
    if([bodyHTML isEqualToString:@""])
    {
        [self backToLastPage];
    }
}

}

Таким образом, когда пользователь «перенаправляется» на последнюю загруженную страницу, вторым шагом будет обработка действий пользователя в диалоговых окнах входа в facebook.

Если я слишком поторопился с этим ответом, пожалуйста, спросите меня! Надеюсь, поможет.

person owlmsj    schedule 21.07.2013
comment
backToLastPage означает, что [webview goBack]? - person iajnr; 13.05.2018

В случае, если кто-то гуглит, вот что сработало для меня:

-(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)inType {
    if ([request.URL.absoluteString containsString:@"m.facebook.com"]) {
        if ([request.URL.absoluteString rangeOfString:@"back"].location == 0) {
            [self.popUp removeFromSuperview];
            self.popUp = nil;
            return NO;
        }
        if (self.popUp) {
            return YES;
        }

        UIWebView *wv = [self popUpWebView];
        [wv loadRequest:request];
        return NO;
    }
    return YES;
}

- (UIWebView *) popUpWebView {
    toolbar height
    UIWebView *webView = [[UIWebView alloc]
            initWithFrame:CGRectMake(0, 0, (float)self.view.bounds.size.width,
                    (float)self.view.bounds.size.height)];
    webView.scalesPageToFit = YES;
    webView.delegate = self;
    // Add to windows array and make active window
    self.popUp = webView;
    [self.view addSubview:webView];
    return webView;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {

    if (self.popUp) {
        NSError *error = nil;
        NSString *jsFromFile = @"window.close=function(){window.location.assign('back://' + window.location);};";
        __unused NSString *jsOverrides = [webView
                stringByEvaluatingJavaScriptFromString:jsFromFile];

        JSContext *openerContext = [self.webView
                valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        JSContext *popupContext = [webView
                valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        popupContext[@"window"][@"opener"] = openerContext[@"window"];
    }


  //this is the secret sauce  
  if (webView == self.popUp
            && [webView.request.URL.absoluteString containsString:@"m.facebook.com"]
            && [[webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"] isEqualToString:@""]) {
        [webView stringByEvaluatingJavaScriptFromString:@"eval(document.getElementsByTagName('script')[0].text)"];
    }
}

Я взял кучу этой реализации из здесь.

В зависимости от вашей веб-реализации, вероятно, будет один дополнительный шаг. Сценарий Facebook фактически выполняет window.close(), затем window.open(), а затем window.close(). Для меня это вызывало проблемы, потому что на веб-стороне, после завершения входа в систему, мое окно (т.е. для веб-представления, в которое я хочу, чтобы пользователь вошел в систему) получало вызов window.close(), исходящий от Facebook SDK. Я предполагаю, что это связано с тем, что Facebook SDK ожидает, что вызов window.open() откроет новое окно, которое он закроет.

Поскольку мы не переопределили функциональность window.open(), вызов window.open() ничего не даст, и Facebook SDK попытается закрыть ваше окно. Это может вызвать всевозможные проблемы, но для меня, поскольку я использую Parse, window.localStorage было установлено на null, поэтому я получал всевозможные ошибки.

Если у вас происходит что-то подобное, у вас есть два варианта исправить это:

  1. Если у вас есть контроль над веб-кодом и вам нужен небольшой хак, добавьте это в window.close=function(){}
  2. Если у вас нет контроля над веб-кодом, вы можете либо добавить переопределение window.close для основного веб-представления, как мы сделали для всплывающего веб-представления, либо переопределить функцию window.open, чтобы открыть другое всплывающее окно (описанное в подробнее здесь)
person taylorstine    schedule 13.01.2016
comment
Спасибо за ваше решение. Просто нужно заменить m.facebook.com на mobile.facebook.com. - person Vivek Sinha; 06.04.2018
comment
@taylorstine - не знаю почему - грядет использование необъявленного идентификатора JSContext. - person iajnr; 13.05.2018
comment
а, хорошо, нужно импортировать - #import ‹JavaScriptCore/JavaScriptCore.h› - person iajnr; 13.05.2018
comment
Сработало идеально! - person iajnr; 13.05.2018

Используйте класс FBDialog, чтобы предложить пользователю войти в систему. Это использует веб-просмотр внутри приложения, поэтому при успешном входе в систему пользователь войдет в любой UIWebView:

NSString *kRedirectURL  = @"fbconnect://success";
NSString *kSDK = @"ios" ;
NSString *kLogin = @"oauth";
NSString *kDialogBaseURL = @"https://m.facebook.com/dialog/";

NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                               AE_FACEBOOK_APPID, @"client_id",
                               @"user_agent", @"type",
                               kRedirectURL, @"redirect_uri",
                               @"touch", @"display",
                               kSDK, @"sdk",
                               nil];

NSString *loginDialogURL = [kDialogBaseURL stringByAppendingString:kLogin];

FBLoginDialog* loginDialog = [[FBLoginDialog alloc] initWithURL:loginDialogURL
                                                    loginParams:params
                                                       delegate:self];
[loginDialog show];

Затем сделайте так, чтобы ваш класс придерживался протокола FBDialogDelegate, и добавьте эту функцию в свой класс:

-(void)fbDialogLogin: (NSString *)token expirationDate:(NSDate *)expirationDate{
    // Store the token and expiration date into the FB SDK
    self.facebook.accessToken = token;
    self.facebook.expirationDate = expirationDate;
    // Then persist these so the SDK picks them up on next load

    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

[defaults setObject:self.facebook.accessToken forKey:ACCESS_TOKEN_KEY];
[defaults setObject:self.facebook.expirationDate forKey:EXPIRATION_DATE_KEY];
[defaults synchronize];
}

ХТХ!

person Ameesh Kapoor    schedule 11.01.2013
comment
, не могли бы вы подробнее рассказать об этом решении? Я новичок в iOS, но уже попробовал несколько примеров Facebook SDK. До встречи - person owlmsj; 07.07.2013

Как войти в facebook в UIWebView.

Цель-c

Используйте ответ Тейлорстина. Он спас мой день. Спасибо, taylorstine

Но я использую Swift 3. Поэтому я просто преобразовал приведенный ниже код из ответа Тейлорстина.

Свифт 3.

func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {

    if let _ = request.url?.absoluteString.range(of: "m.facebook.com" ) {
        if let _ = request.url?.absoluteString.range(of: "back"){
            self.popUp?.removeFromSuperview()
            self.popUp = nil
            return false
        }

        if let _ = self.popUp {
            return true
        }

        let wv = popUpWebView()
        wv.loadRequest(request)

        return false
    }

    return true
}

func popUpWebView() -> UIWebView {

    let webView = UIWebView(frame: self.view.frame)

    webView.delegate = self
    self.popUp = webView
    self.view.addSubview(webView)

    return webView
}

func webViewDidFinishLoad(_ webView: UIWebView) {
    if let _ = self.popUp {

        let jsFromFile = "window.close=function(){window.location.assign('back://' + window.location);};"

        let _ = webView.stringByEvaluatingJavaScript(from: jsFromFile)

        let openerContext = self.webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as! JSContext

        let popupContext = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as! JSContext

        popupContext.setObject("opener", forKeyedSubscript: "window" as (NSCopying & NSObjectProtocol)!)
        popupContext.setObject(openerContext.objectForKeyedSubscript("window"), forKeyedSubscript: "opener"  as (NSCopying & NSObjectProtocol)!)

    }

    if webView == self.popUp
        && (webView.request?.url?.absoluteString.range(of:"m.facebook.com") != nil)
        && webView.stringByEvaluatingJavaScript(from: "document.body.innerHTML") == "" {

        webView.stringByEvaluatingJavaScript(from: "eval(document.getElementsByTagName('script')[0].text)")

    }

}
person solgit    schedule 08.02.2017
comment
self.popUp что это? - person mihatel; 07.02.2019