diff --git a/.gitignore b/.gitignore index c385c37..654bc81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .nuget +*.csproj.user *.opensdf *.sdf *.suo diff --git a/FBWinSDK/FBSDK-UWP/FBSDK-UWP/FBSDK-UWP.vcxproj b/FBWinSDK/FBSDK-UWP/FBSDK-UWP/FBSDK-UWP.vcxproj index 4fdb50c..d452fe9 100644 --- a/FBWinSDK/FBSDK-UWP/FBSDK-UWP/FBSDK-UWP.vcxproj +++ b/FBWinSDK/FBSDK-UWP/FBSDK-UWP/FBSDK-UWP.vcxproj @@ -318,6 +318,7 @@ popd + ..\..\FBWinSDK\FBWinSDK.Shared\FacebookProfilePictureControl.xaml @@ -353,6 +354,7 @@ popd + ..\..\FBWinSDK\FBWinSDK.Shared\FacebookProfilePictureControl.xaml diff --git a/FBWinSDK/FBSDK-UWP/FBSDK-UWP/FBSDK-UWP.vcxproj.filters b/FBWinSDK/FBSDK-UWP/FBSDK-UWP/FBSDK-UWP.vcxproj.filters index c7d9a48..e973c65 100644 --- a/FBWinSDK/FBSDK-UWP/FBSDK-UWP/FBSDK-UWP.vcxproj.filters +++ b/FBWinSDK/FBSDK-UWP/FBSDK-UWP/FBSDK-UWP.vcxproj.filters @@ -120,6 +120,9 @@ Source Files + + Source Files + @@ -205,6 +208,9 @@ Header Files + + Header Files + diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FBWinSDK.Shared.vcxitems b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FBWinSDK.Shared.vcxitems index b0ed8d6..42f035c 100644 --- a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FBWinSDK.Shared.vcxitems +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FBWinSDK.Shared.vcxitems @@ -27,6 +27,7 @@ + $(MSBuildThisFileDirectory)FacebookProfilePictureControl.xaml @@ -61,6 +62,7 @@ + diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FBWinSDK.Shared.vcxitems.filters b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FBWinSDK.Shared.vcxitems.filters index 11153ed..f50211c 100644 --- a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FBWinSDK.Shared.vcxitems.filters +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FBWinSDK.Shared.vcxitems.filters @@ -81,6 +81,9 @@ Header Files + + Header Files + @@ -154,6 +157,9 @@ Source Files + + Source Files + diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookError.h b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookError.h index 85f7572..b9c5284 100644 --- a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookError.h +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookError.h @@ -31,6 +31,7 @@ namespace Facebook */ public enum class ErrorSubcode : int { + ErrorSubcodeAppNotAuthorized = 458, ErrorSubcodeSessionInvalidated = 466 }; diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookLoginButton.cpp b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookLoginButton.cpp index 265c466..07232ed 100644 --- a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookLoginButton.cpp +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookLoginButton.cpp @@ -17,6 +17,7 @@ #include "pch.h" #include "FacebookLoginButton.h" #include "FacebookSession.h" +#include "FacebookPermissions.h" using namespace Concurrency; using namespace Facebook; @@ -32,7 +33,6 @@ FBLoginButton::FBLoginButton() : { String^ styleKey = FBLoginButton::typeid->FullName; this->DefaultStyleKey = styleKey; - m_permissions = ref new Vector(); } void FBLoginButton::OnApplyTemplate( @@ -58,61 +58,21 @@ void FBLoginButton::OnApplyTemplate( //} // -IVector^ FBLoginButton::Permissions::get() +FBPermissions^ FBLoginButton::Permissions::get() { return m_permissions; } -void FBLoginButton::Permissions::set(IVector^ Values) +void FBLoginButton::Permissions::set(FBPermissions^ Permissions) { - m_permissions->Clear(); - IIterator^ it = nullptr; - for (it = Values->First(); it->HasCurrent; it->MoveNext()) - { - String^ value = it->Current; - m_permissions->Append(value); - } + m_permissions = Permissions; } void FBLoginButton::InitWithPermissions( - IVector^ permissions + FBPermissions^ Permissions ) { - if (!m_permissions) - { - m_permissions = ref new Vector(0); - } - - m_permissions->Clear(); - - for (IIterator^ iter = permissions->First(); - iter->HasCurrent; - iter->MoveNext()) - { - m_permissions->Append(iter->Current); - } -} - -String^ FBLoginButton::GetPermissions( - ) -{ - String^ permissions = nullptr; - if (m_permissions) - { - permissions = ref new String(); - - for (unsigned int i = 0; i < m_permissions->Size; i++) - { - if (i) - { - permissions += ","; - } - - permissions += m_permissions->GetAt(i); - } - } - - return permissions; + m_permissions = Permissions; } void FBLoginButton::OnClick( @@ -128,14 +88,7 @@ void FBLoginButton::OnClick( } else { - String^ permissions = GetPermissions(); - PropertySet^ parameters = ref new PropertySet(); - if (permissions != nullptr) - { - parameters->Insert(L"scope", permissions); - } - - create_task(s->LoginAsync(parameters)) + create_task(s->LoginAsync(m_permissions)) .then([=](FBResult^ result) { if (result->Succeeded) diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookLoginButton.h b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookLoginButton.h index 8319684..686124c 100644 --- a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookLoginButton.h +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookLoginButton.h @@ -17,6 +17,7 @@ #pragma once #include "FacebookSession.h" +#include "FacebookPermissions.h" namespace Facebook { @@ -58,16 +59,16 @@ namespace Facebook //} //! Publish permissions for user - property Windows::Foundation::Collections::IVector^ + property Facebook::FBPermissions^ Permissions { - Windows::Foundation::Collections::IVector^ get(); - void set(Windows::Foundation::Collections::IVector^); + Facebook::FBPermissions^ get(); + void set(Facebook::FBPermissions^); } //! Ask for read permissions at login void InitWithPermissions( - Windows::Foundation::Collections::IVector^ permissions + Facebook::FBPermissions^ permissions ); event FBLoginErrorHandler^ FBLoginError; @@ -85,6 +86,6 @@ namespace Facebook ); // SessionLoginBehavior m_loginBehavior; - Platform::Collections::Vector^ m_permissions; + Facebook::FBPermissions^ m_permissions; }; } \ No newline at end of file diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookPermissions.cpp b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookPermissions.cpp new file mode 100644 index 0000000..a1155b6 --- /dev/null +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookPermissions.cpp @@ -0,0 +1,44 @@ +#include + +#include "pch.h" +#include "FacebookPermissions.h" + +using namespace Facebook; +using namespace Platform; +using namespace Windows::Foundation::Collections; + +IVectorView^ FBPermissions::Values::get() +{ + return _values; +}; + +void FBPermissions::Values::set( + IVectorView^ value + ) +{ + _values = value; +} + +Platform::String^ FBPermissions::ToString( + ) +{ + String^ permissions = nullptr; + if (_values) + { + permissions = ref new String(); + + for (unsigned int i = 0; i < _values->Size; i++) + { + if (i) + { + permissions += ","; + } + + permissions += _values->GetAt(i); + } + } + + return permissions; +} + + diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookPermissions.h b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookPermissions.h new file mode 100644 index 0000000..5a46d85 --- /dev/null +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookPermissions.h @@ -0,0 +1,22 @@ +#pragma once + +namespace Facebook +{ + public ref class FBPermissions sealed + { + public: + + property Windows::Foundation::Collections::IVectorView^ Values + { + Windows::Foundation::Collections::IVectorView^ get(); + void set(Windows::Foundation::Collections::IVectorView^ value); + }; + + virtual Platform::String^ ToString( + ) override; + + private: + Windows::Foundation::Collections::IVectorView^ _values; + }; +} + diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookSession.cpp b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookSession.cpp index 8973b3f..df3f0e6 100644 --- a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookSession.cpp +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookSession.cpp @@ -501,7 +501,7 @@ Windows::Foundation::IAsyncOperation^ FBSession::ShowRequestsDialog( // the concurrency event object was deprecated in the Win10 SDK tools. // Switched to plane old Windows event, but that didn't work at all, // so polling for now. - while (m_showingDialog && !dialogResponse); + while (m_showingDialog && !dialogResponse) { dialogResponse = m_dialog->GetDialogResponse(); Sleep(0); @@ -674,9 +674,6 @@ String^ FBSession::GetRedirectUriString( ) { Uri^ endURI = WebAuthenticationBroker::GetCurrentApplicationCallbackUri(); - String^ blerg = endURI->DisplayUri; - OutputDebugString(blerg->Data()); - OutputDebugString(L"\n"); return endURI->DisplayUri; } @@ -856,22 +853,33 @@ task FBSession::RunWebViewLoginOnUIThread( } IAsyncOperation^ FBSession::LoginAsync( - PropertySet^ Parameters + FBPermissions^ Permissions ) { m_dialog = ref new FacebookDialog(); return create_async([=]() { + PropertySet^ parameters = ref new PropertySet(); + if (Permissions) + { + parameters->Insert(L"scope", Permissions->ToString()); + } + + if (LoggedIn) + { + parameters->Insert(L"auth_type", L"rerequest"); + } + return create_task([=]() -> FBResult^ { FBResult^ result = nullptr; - task authTask = TryLoginViaWebView(Parameters); + task authTask = TryLoginViaWebView(parameters); result = authTask.get(); if (!result) { - authTask = TryLoginViaWebAuthBroker(Parameters); + authTask = TryLoginViaWebAuthBroker(parameters); result = authTask.get(); } diff --git a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookSession.h b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookSession.h index 9a4009c..cdee94c 100644 --- a/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookSession.h +++ b/FBWinSDK/FBWinSDK/FBWinSDK.Shared/FacebookSession.h @@ -20,6 +20,7 @@ #include "FacebookResult.h" #include "FBUser.h" #include "FacebookDialog.xaml.h" +#include "FacebookPermissions.h" namespace Facebook { @@ -125,7 +126,7 @@ namespace Facebook ); Windows::Foundation::IAsyncOperation^ LoginAsync( - Windows::Foundation::Collections::PropertySet^ Parameters + Facebook::FBPermissions^ Permissions ); private: diff --git a/samples/LoginCpp-UWP/LoginCpp/LoginCpp.vcxproj b/samples/LoginCpp-UWP/LoginCpp/LoginCpp.vcxproj index e276808..d26b68b 100644 --- a/samples/LoginCpp-UWP/LoginCpp/LoginCpp.vcxproj +++ b/samples/LoginCpp-UWP/LoginCpp/LoginCpp.vcxproj @@ -223,19 +223,5 @@ -<<<<<<< HEAD - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - -======= ->>>>>>> 26_change_sample_sdk_references diff --git a/samples/LoginCpp-UWP/LoginCpp/MainPage.xaml.cpp b/samples/LoginCpp-UWP/LoginCpp/MainPage.xaml.cpp index a9f7e3a..dacf4de 100644 --- a/samples/LoginCpp-UWP/LoginCpp/MainPage.xaml.cpp +++ b/samples/LoginCpp-UWP/LoginCpp/MainPage.xaml.cpp @@ -21,13 +21,13 @@ #include "pch.h" #include "MainPage.xaml.h" -#include "UserInfo.xaml.h" #include "OptionsPage.xaml.h" using namespace concurrency; using namespace Facebook; using namespace LoginCpp; using namespace Platform; +using namespace Platform::Collections; using namespace Windows::ApplicationModel; using namespace Windows::ApplicationModel::Activation; using namespace Windows::ApplicationModel::Core; @@ -85,21 +85,19 @@ void MainPage::SetSessionAppIds() s->WinAppId = winAppId; } -String^ MainPage::BuildPermissionsString( +FBPermissions^ MainPage::BuildPermissions( ) { - String^ result = ref new String(); + FBPermissions^ result = ref new FBPermissions(); + Vector^ v = ref new Vector(); for (unsigned int i = 0; i < ARRAYSIZE(requested_permissions); i++) { - if (i) - { - result += L","; - } - - result += ref new String(requested_permissions[i]); + v->Append(ref new String(requested_permissions[i])); } + result->Values = v->GetView(); + return result; } @@ -134,46 +132,95 @@ BOOL MainPage::DidGetAllRequestedPermissions( return success; } +BOOL MainPage::WasAppPermissionRemovedByUser( + FBResult^ result + ) +{ + return (result && + (!result->Succeeded) && + (result->ErrorInfo->Code == (int)Facebook::ErrorCode::ErrorCodeOauthException)); +} + +BOOL MainPage::ShouldRerequest( + FBResult^ result + ) +{ + return (result && + result->Succeeded && + !DidGetAllRequestedPermissions()); +} + void MainPage::NavigateToOptionsPage() { LoginButton->Content = L"Logout"; - // We're redirecting to a page that shows simple user info, so - // have to dispatch back to the UI thread. - CoreWindow^ wind = CoreApplication::MainView->CoreWindow; - - if (wind) + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync( + Windows::UI::Core::CoreDispatcherPriority::Normal, + ref new Windows::UI::Core::DispatchedHandler([this]() { - CoreDispatcher^ disp = wind->Dispatcher; - if (disp) - { - disp->RunAsync( - Windows::UI::Core::CoreDispatcherPriority::Normal, - ref new Windows::UI::Core::DispatchedHandler([this]() - { - LoginCpp::App^ a = dynamic_cast(Application::Current); - Windows::UI::Xaml::Controls::Frame^ f = a->CreateRootFrame(); - f->Navigate(OptionsPage::typeid); - })); - } - } + LoginCpp::App^ a = dynamic_cast(Application::Current); + Windows::UI::Xaml::Controls::Frame^ f = a->CreateRootFrame(); + f->Navigate(OptionsPage::typeid); + })); } -task MainPage::LoginViaRerequest( - PropertySet^ Parameters - ) +void MainPage::TryRerequest( + BOOL retry + ) { - Parameters->Insert(L"auth_type", L"rerequest"); - return create_task(FBSession::ActiveSession->Logout()) - .then([=]() - { - SetSessionAppIds(); + // If we're logged out, the session has cleared the FB and Windows app IDs, + // so they need to be set again. We load these IDs via the ResourceLoader + // class, which must be accessed on the UI thread, which is why this call + // is outside the task context. + SetSessionAppIds(); - return FBSession::ActiveSession->LoginAsync(Parameters); - }); + create_task(FBSession::ActiveSession->LoginAsync(BuildPermissions())) + .then([=](FBResult^ result) + { + if (result->Succeeded) + { + if (retry && (!DidGetAllRequestedPermissions())) + { + // Login call has to happen on UI thread, so circle back around to it + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync( + Windows::UI::Core::CoreDispatcherPriority::Normal, + ref new Windows::UI::Core::DispatchedHandler([=]() + { + TryRerequest(false); + })); + } + else + { + NavigateToOptionsPage(); + } + } + }); } -void MainPage::login_OnClicked(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +void MainPage::LogoutAndRetry( + ) +{ + // It's necessary to logout prior to reattempting login, because it could + // be that the session has cached an access token that is no longer valid, + // e.g. if the user revoked the app in Settings. FBSession::Logout clears + // the session's cached access token, among other things. + create_task(FBSession::ActiveSession->Logout()) + .then([=]() + { + // Login call has to happen on UI thread, so circle back around to it + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync( + Windows::UI::Core::CoreDispatcherPriority::Normal, + ref new Windows::UI::Core::DispatchedHandler([=]() + { + TryRerequest(TRUE); + })); + }); +} + +void MainPage::login_OnClicked( + Object^ sender, + RoutedEventArgs^ e + ) { FBSession^ sess = FBSession::ActiveSession; @@ -187,48 +234,32 @@ void MainPage::login_OnClicked(Platform::Object^ sender, Windows::UI::Xaml::Rout } else { - PropertySet^ parameters = ref new PropertySet(); - - parameters->Insert(L"scope", BuildPermissionsString()); - - create_task(sess->LoginAsync(parameters)).then([=](FBResult^ result) + create_task(sess->LoginAsync(BuildPermissions())).then([=](FBResult^ result) { - task nextResult = create_task([=]() + // There may be other cases where an a failed login request should + // prompt the app to retry login, but this one is common enough that + // it's helpful to demonstrate handling it here. If the predicate + // returns TRUE, the user has gone to facebook.com in the browser, + // and removed our app from their list off allowed apps in Settings. + if (WasAppPermissionRemovedByUser(result)) { - return result; - }); - - if ((!result->Succeeded) && - ((result->ErrorInfo->Code == 190) && (result->ErrorInfo->Subcode == 466))) - { - nextResult = LoginViaRerequest(parameters); - } - - return nextResult; - }) - .then([=](FBResult^ loginResult) - { - task finalResult = create_task([=]() - { - return loginResult; - }); - - if (loginResult->Succeeded) - { - if (!DidGetAllRequestedPermissions()) - { - finalResult = LoginViaRerequest(parameters); - } - } - - return finalResult; - }) - .then([=](FBResult^ finalResult) - { - if (finalResult->Succeeded) + LogoutAndRetry(); + } + else if (ShouldRerequest(result)) + { + // Login call has to happen on UI thread, so circle back around to it + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync( + Windows::UI::Core::CoreDispatcherPriority::Normal, + ref new Windows::UI::Core::DispatchedHandler([=]() + { + TryRerequest(FALSE); + })); + } + else if (result->Succeeded) { + // Got a token and all our permissions. NavigateToOptionsPage(); } - }); + }); } } diff --git a/samples/LoginCpp-UWP/LoginCpp/MainPage.xaml.h b/samples/LoginCpp-UWP/LoginCpp/MainPage.xaml.h index c341207..f28ea90 100644 --- a/samples/LoginCpp-UWP/LoginCpp/MainPage.xaml.h +++ b/samples/LoginCpp-UWP/LoginCpp/MainPage.xaml.h @@ -37,18 +37,29 @@ namespace LoginCpp void MainPage::SetSessionAppIds( ); - Platform::String^ BuildPermissionsString( + Facebook::FBPermissions^ BuildPermissions( ); BOOL DidGetAllRequestedPermissions( ); - + + BOOL MainPage::WasAppPermissionRemovedByUser( + Facebook::FBResult^ result + ); + + BOOL ShouldRerequest( + Facebook::FBResult^ result + ); + void NavigateToOptionsPage( ); - concurrency::task MainPage::LoginViaRerequest( - Windows::Foundation::Collections::PropertySet^ Parameters - ); + void MainPage::TryRerequest( + BOOL retry + ); + + void MainPage::LogoutAndRetry( + ); void login_OnClicked( Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e diff --git a/samples/LoginCpp/LoginCpp/LoginCpp.Windows/MainPage.xaml.cpp b/samples/LoginCpp/LoginCpp/LoginCpp.Windows/MainPage.xaml.cpp index f419b00..8c29010 100644 --- a/samples/LoginCpp/LoginCpp/LoginCpp.Windows/MainPage.xaml.cpp +++ b/samples/LoginCpp/LoginCpp/LoginCpp.Windows/MainPage.xaml.cpp @@ -23,148 +23,198 @@ #include "MainPage.xaml.h" #include "OptionsPage.xaml.h" -using namespace LoginCpp; - using namespace concurrency; +using namespace Facebook; +using namespace LoginCpp; using namespace Platform; +using namespace Platform::Collections; using namespace Windows::ApplicationModel; using namespace Windows::ApplicationModel::Activation; using namespace Windows::ApplicationModel::Core; using namespace Windows::ApplicationModel::Resources; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; -using namespace Windows::UI::Xaml; +using namespace Windows::Security::Authentication::Web; using namespace Windows::UI::Core; +using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Xaml::Controls::Primitives; using namespace Windows::UI::Xaml::Data; using namespace Windows::UI::Xaml::Input; using namespace Windows::UI::Xaml::Media; using namespace Windows::UI::Xaml::Navigation; -using namespace Facebook; -// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238 +// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 #define FBAppIDName L"FBApplicationId" #define FBStoreAppIDName L"FBWindowsAppId" #define PermissionGranted L"granted" -const wchar_t* requested_permissions[] = +const wchar_t* requested_permissions[] = { - L"public_profile", - L"user_friends", - L"user_likes", - L"user_groups", - L"user_location" + L"public_profile", + L"user_friends", + L"user_likes", + L"user_groups", + L"user_location" }; MainPage::MainPage() { InitializeComponent(); - SetSessionAppIds(); + + String^ whatever = WebAuthenticationBroker::GetCurrentApplicationCallbackUri()->DisplayUri + L"\n"; + OutputDebugString(whatever->Data()); + + SetSessionAppIds(); } void MainPage::SetSessionAppIds() { - FBSession^ s = FBSession::ActiveSession; + FBSession^ s = FBSession::ActiveSession; - // Assumes the Facebook App ID and Windows Phone Store ID have been saved - // in the default resource file. - ResourceLoader^ rl = ResourceLoader::GetForCurrentView(); + // Assumes the Facebook App ID and Windows Phone Store ID have been saved + // in the default resource file. + ResourceLoader^ rl = ResourceLoader::GetForCurrentView(); - String^ appId = rl->GetString(FBAppIDName); - String^ winAppId = rl->GetString(FBStoreAppIDName); + String^ appId = rl->GetString(FBAppIDName); + String^ winAppId = rl->GetString(FBStoreAppIDName); - // IDs are both sent to FB app, so it can validate us. - s->FBAppId = appId; - s->WinAppId = winAppId; + // IDs are both sent to FB app, so it can validate us. + s->FBAppId = appId; + s->WinAppId = winAppId; } -String^ MainPage::BuildPermissionsString( - ) +FBPermissions^ MainPage::BuildPermissions( + ) { - String^ result = ref new String(); + FBPermissions^ result = ref new FBPermissions(); + Vector^ v = ref new Vector(); - for (unsigned int i = 0; i < ARRAYSIZE(requested_permissions); i++) - { - if (i) - { - result += L","; - } + for (unsigned int i = 0; i < ARRAYSIZE(requested_permissions); i++) + { + v->Append(ref new String(requested_permissions[i])); + } - result += ref new String(requested_permissions[i]); - } + result->Values = v->GetView(); - return result; + return result; } BOOL MainPage::DidGetAllRequestedPermissions( + ) +{ + BOOL success = FALSE; + FBAccessTokenData^ data = FBSession::ActiveSession->AccessTokenData; + unsigned int grantedCount = 0; + + if (data) + { + for (unsigned int i = 0; i < ARRAYSIZE(requested_permissions); i++) + { + String^ perm = ref new String(requested_permissions[i]); + if (data->Permissions && (data->Permissions->HasKey(perm))) + { + String^ Value = data->Permissions->Lookup(perm); + if (!String::CompareOrdinal(Value, PermissionGranted)) + { + grantedCount++; + } + } + } + + if (grantedCount == ARRAYSIZE(requested_permissions)) + { + success = TRUE; + } + } + + return success; +} + +BOOL MainPage::WasAppPermissionRemovedByUser( + FBResult^ result ) { - BOOL success = FALSE; - FBAccessTokenData^ data = FBSession::ActiveSession->AccessTokenData; - unsigned int grantedCount = 0; + return (result && + (!result->Succeeded) && + ((result->ErrorInfo->Code == (int)Facebook::ErrorCode::ErrorCodeOauthException) && + (result->ErrorInfo->Subcode == (int)Facebook::ErrorSubcode::ErrorSubcodeSessionInvalidated))); +} - if (data) - { - for (unsigned int i = 0; i < ARRAYSIZE(requested_permissions); i++) - { - String^ perm = ref new String(requested_permissions[i]); - if (data->Permissions && (data->Permissions->HasKey(perm))) - { - String^ Value = data->Permissions->Lookup(perm); - if (!String::CompareOrdinal(Value, PermissionGranted)) - { - grantedCount++; - } - } - } - - if (grantedCount == ARRAYSIZE(requested_permissions)) - { - success = TRUE; - } - } - - return success; +BOOL MainPage::ShouldRerequest( + FBResult^ result + ) +{ + return (result && + result->Succeeded && + !DidGetAllRequestedPermissions()); } void MainPage::NavigateToOptionsPage() { - LoginButton->Content = L"Logout"; + LoginButton->Content = L"Logout"; - // We're redirecting to a page that shows simple user info, so - // have to dispatch back to the UI thread. - CoreWindow^ wind = CoreApplication::MainView->CoreWindow; - - if (wind) - { - CoreDispatcher^ disp = wind->Dispatcher; - if (disp) - { - disp->RunAsync( - Windows::UI::Core::CoreDispatcherPriority::Normal, - ref new Windows::UI::Core::DispatchedHandler([this]() - { - LoginCpp::App^ a = dynamic_cast(Application::Current); - Windows::UI::Xaml::Controls::Frame^ f = a->CreateRootFrame(); - f->Navigate(OptionsPage::typeid); - })); - } - } + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync( + Windows::UI::Core::CoreDispatcherPriority::Normal, + ref new Windows::UI::Core::DispatchedHandler([this]() + { + LoginCpp::App^ a = dynamic_cast(Application::Current); + Windows::UI::Xaml::Controls::Frame^ f = a->CreateRootFrame(); + f->Navigate(OptionsPage::typeid); + })); } -task MainPage::LoginViaRerequest( - PropertySet^ Parameters +void MainPage::TryRerequest( + BOOL retry ) { - Parameters->Insert(L"auth_type", L"rerequest"); - return create_task(FBSession::ActiveSession->Logout()) + // If we're logged out, the session has cleared the FB and Windows app IDs, + // so they need to be set again. We load these IDs via the ResourceLoader + // class, which must be accessed on the UI thread, which is why this call + // is outside the task context. + SetSessionAppIds(); + + create_task(FBSession::ActiveSession->LoginAsync(BuildPermissions())) + .then([=](FBResult^ result) + { + if (result->Succeeded) + { + if (retry && (!DidGetAllRequestedPermissions())) + { + // Login call has to happen on UI thread, so circle back around to it + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync( + Windows::UI::Core::CoreDispatcherPriority::Normal, + ref new Windows::UI::Core::DispatchedHandler([=]() + { + TryRerequest(false); + })); + } + else + { + NavigateToOptionsPage(); + } + } + }); +} + +void MainPage::LogoutAndRetry( + ) +{ + // It's necessary to logout prior to reattempting login, because it could + // be that the session has cached an access token that is no longer valid, + // e.g. if the user revoked the app in Settings. FBSession::Logout clears + // the session's cached access token, among other things. + create_task(FBSession::ActiveSession->Logout()) .then([=]() { - SetSessionAppIds(); - - return FBSession::ActiveSession->LoginAsync(Parameters); + // Login call has to happen on UI thread, so circle back around to it + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync( + Windows::UI::Core::CoreDispatcherPriority::Normal, + ref new Windows::UI::Core::DispatchedHandler([=]() + { + TryRerequest(TRUE); + })); }); } @@ -182,49 +232,32 @@ void MainPage::login_OnClicked(Platform::Object^ sender, Windows::UI::Xaml::Rout } else { - PropertySet^ parameters = ref new PropertySet(); - - parameters->Insert(L"scope", BuildPermissionsString()); - - create_task(sess->LoginAsync(parameters)).then([=](FBResult^ result) - { - task nextResult = create_task([=]() - { - return result; - }); - - if ((!result->Succeeded) && - ((result->ErrorInfo->Code == 190) && (result->ErrorInfo->Subcode == 466))) - { - nextResult = LoginViaRerequest(parameters); + create_task(sess->LoginAsync(BuildPermissions())).then([=](FBResult^ result) + { + // There may be other cases where an a failed login request should + // prompt the app to retry login, but this one is common enough that + // it's helpful to demonstrate handling it here. If the predicate + // returns TRUE, the user has gone to facebook.com in the browser, + // and removed our app from their list off allowed apps in Settings. + if (WasAppPermissionRemovedByUser(result)) + { + LogoutAndRetry(); } - - return nextResult; - }) - .then([=](FBResult^ loginResult) - { - task finalResult = create_task([=]() + else if (ShouldRerequest(result)) { - return loginResult; - }); - - if (loginResult->Succeeded) - { - if (!DidGetAllRequestedPermissions()) + // Login call has to happen on UI thread, so circle back around to it + CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync( + Windows::UI::Core::CoreDispatcherPriority::Normal, + ref new Windows::UI::Core::DispatchedHandler([=]() { - finalResult = LoginViaRerequest(parameters); - } - } - - return finalResult; - }) - .then([=](FBResult^ finalResult) - { - if (finalResult->Succeeded) - { - NavigateToOptionsPage(); + TryRerequest(FALSE); + })); } + else if (result && result->Succeeded) + { + // Got a token, and all the permissions we wanted - go ahead to the options page + NavigateToOptionsPage(); + } }); } } - diff --git a/samples/LoginCpp/LoginCpp/LoginCpp.Windows/MainPage.xaml.h b/samples/LoginCpp/LoginCpp/LoginCpp.Windows/MainPage.xaml.h index 2cdc116..f28ea90 100644 --- a/samples/LoginCpp/LoginCpp/LoginCpp.Windows/MainPage.xaml.h +++ b/samples/LoginCpp/LoginCpp/LoginCpp.Windows/MainPage.xaml.h @@ -33,23 +33,36 @@ namespace LoginCpp public: MainPage(); - private: - void MainPage::SetSessionAppIds( + private: + void MainPage::SetSessionAppIds( + ); + + Facebook::FBPermissions^ BuildPermissions( + ); + + BOOL DidGetAllRequestedPermissions( + ); + + BOOL MainPage::WasAppPermissionRemovedByUser( + Facebook::FBResult^ result ); - Platform::String^ BuildPermissionsString( + BOOL ShouldRerequest( + Facebook::FBResult^ result ); - BOOL DidGetAllRequestedPermissions( + void NavigateToOptionsPage( + ); + + void MainPage::TryRerequest( + BOOL retry ); - void NavigateToOptionsPage( + void MainPage::LogoutAndRetry( ); - concurrency::task MainPage::LoginViaRerequest( - Windows::Foundation::Collections::PropertySet^ Parameters - ); - - void login_OnClicked(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + void login_OnClicked( + Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e + ); }; } diff --git a/samples/LoginCpp/LoginCpp/LoginCpp.WindowsPhone/LoginCpp.WindowsPhone.vcxproj.user b/samples/LoginCpp/LoginCpp/LoginCpp.WindowsPhone/LoginCpp.WindowsPhone.vcxproj.user new file mode 100644 index 0000000..3f8a826 --- /dev/null +++ b/samples/LoginCpp/LoginCpp/LoginCpp.WindowsPhone/LoginCpp.WindowsPhone.vcxproj.user @@ -0,0 +1,7 @@ + + + + WindowsPhoneEmulatorDebugger + 8BDF218D-FDBB-4A97-90F9-3AA33B559A92;Mobile Emulator 10.0.10240.0 WVGA 4 inch 512MB + + \ No newline at end of file diff --git a/samples/LoginCpp/LoginCpp/LoginCpp.WindowsPhone/MainPage.xaml b/samples/LoginCpp/LoginCpp/LoginCpp.WindowsPhone/MainPage.xaml index 38752b8..d4decc8 100644 --- a/samples/LoginCpp/LoginCpp/LoginCpp.WindowsPhone/MainPage.xaml +++ b/samples/LoginCpp/LoginCpp/LoginCpp.WindowsPhone/MainPage.xaml @@ -9,7 +9,7 @@ Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> -