diff --git a/README.md b/README.md
index cfbf9dc..8e3870d 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,7 @@ These samples are supplied directly from the feature teams of Fuse and we welcom
+ Native Facebook Login
AmazonS3
Azure AD B2C login
Facebook login
diff --git a/Samples/NativeFacebookLogin/FacebookAppId.uxl b/Samples/NativeFacebookLogin/FacebookAppId.uxl
new file mode 100644
index 0000000..ced65ff
--- /dev/null
+++ b/Samples/NativeFacebookLogin/FacebookAppId.uxl
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.uno b/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.uno
new file mode 100644
index 0000000..5a9d24f
--- /dev/null
+++ b/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.uno
@@ -0,0 +1,234 @@
+using Fuse;
+using Fuse.Platform;
+using Uno;
+using Uno.Compiler.ExportTargetInterop;
+
+[extern(iOS) Require("Xcode.FrameworkDirectory", "@('FacebookSDKs-iOS':Path)")]
+[extern(iOS) Require("Xcode.Framework", "@('FacebookSDKs-iOS/FBSDKCoreKit.framework':Path)")]
+[extern(iOS) Require("Xcode.Framework", "@('FacebookSDKs-iOS/FBSDKLoginKit.framework':Path)")]
+[extern(iOS) ForeignInclude(Language.ObjC, "FBSDKCoreKit/FBSDKCoreKit.h")]
+[extern(iOS) ForeignInclude(Language.ObjC, "FBSDKLoginKit/FBSDKLoginKit.h")]
+// Reference for excludes - https://stackoverflow.com/questions/46212449/facebook-sdk-dependency-conflict
+[Require("Gradle.Dependency","implementation('com.facebook.android:facebook-android-sdk:[4,5)') { exclude group: 'com.android.support', module: 'support-v4' \nexclude group: 'com.android.support', module: 'support-core-utils' }")]
+[Require("Gradle.Repository","mavenCentral()")]
+[ForeignInclude(Language.Java, "android.content.Intent")]
+[ForeignInclude(Language.Java, "com.facebook.*")]
+[ForeignInclude(Language.Java, "com.facebook.appevents.AppEventsLogger")]
+[ForeignInclude(Language.Java, "org.json.JSONObject")]
+[ForeignInclude(Language.Java, "org.json.JSONException")]
+[ForeignInclude(Language.Java, "android.os.Bundle")]
+[ForeignInclude(Language.Java, "com.facebook.login.*")]
+[ForeignInclude(Language.Java, "com.fuse.Activity")]
+public class FacebookLogin
+{
+ public FacebookLogin()
+ {
+ Lifecycle.Started += Started;
+ Lifecycle.EnteringInteractive += OnEnteringInteractive;
+ Lifecycle.ExitedInteractive += OnExitedInteractive;
+ InterApp.ReceivedURI += OnReceivedUri;
+ }
+
+ [Foreign(Language.ObjC)]
+ extern(iOS) void Started(ApplicationState state)
+ @{
+ [[FBSDKApplicationDelegate sharedInstance]
+ application: [UIApplication sharedApplication]
+ didFinishLaunchingWithOptions: nil];
+ @}
+
+ extern(Android) Java.Object _callbackManager;
+
+ [Foreign(Language.Java)]
+ extern(Android) void Started(ApplicationState state)
+ @{
+ FacebookSdk.sdkInitialize(Activity.getRootActivity());
+ final CallbackManager callbackManager = CallbackManager.Factory.create();
+ @{FacebookLogin:Of(_this)._callbackManager:Set(callbackManager)};
+ Activity.subscribeToResults(new Activity.ResultListener()
+ {
+ @Override
+ public boolean onResult(int requestCode, int resultCode, Intent data)
+ {
+ return callbackManager.onActivityResult(requestCode, resultCode, data);
+ }
+
+ });
+ @}
+
+ extern(!iOS && !Android) void Started(ApplicationState state)
+ {
+ }
+
+ [Foreign(Language.ObjC)]
+ static extern(iOS) void OnEnteringInteractive(ApplicationState state)
+ @{
+ [FBSDKAppEvents activateApp];
+ @}
+
+ [Foreign(Language.Java)]
+ static extern(Android) void OnEnteringInteractive(ApplicationState state)
+ @{
+ AppEventsLogger.activateApp(Activity.getRootActivity());
+ @}
+
+ static extern(!iOS && !Android) void OnEnteringInteractive(ApplicationState state)
+ {
+ }
+
+ [Foreign(Language.Java)]
+ static extern(Android) void OnExitedInteractive(ApplicationState state)
+ @{
+ AppEventsLogger.deactivateApp(Activity.getRootActivity());
+ @}
+
+ static extern(!Android) void OnExitedInteractive(ApplicationState state)
+ {
+ }
+
+ static void OnReceivedUri(string uri)
+ {
+ debug_log "Received Uri: " + uri;
+ if (uri.StartsWith("fb"))
+ {
+ OpenFacebookURL(uri);
+ }
+ }
+
+ [Foreign(Language.ObjC)]
+ static extern(iOS) void OpenFacebookURL(string url)
+ @{
+ [[FBSDKApplicationDelegate sharedInstance]
+ application: [UIApplication sharedApplication]
+ openURL: [NSURL URLWithString:url]
+ sourceApplication: @"com.apple.mobilesafari"
+ annotation: nil];
+ @}
+
+ static extern(!iOS) void OpenFacebookURL(string url)
+ {
+ }
+
+ public class User
+ {
+ extern string _id;
+ extern string _name;
+ extern string _email;
+ extern string _token;
+
+ public User(String id, String name, String email, String token)
+ {
+ _id = id;
+ _name = name;
+ _email = email;
+ _token = token;
+ }
+
+ public string getId() {
+ return _id;
+ }
+
+ public string getName() {
+ return _name;
+ }
+
+ public string getEmail() {
+ return _email;
+ }
+
+ public string getTokenString() {
+ return _token;
+ }
+ }
+
+ [Foreign(Language.ObjC)]
+ public extern(iOS) void Login(Action onSuccess, Action onCancelled, Action onError)
+ @{
+ FBSDKLoginManager* login = [[FBSDKLoginManager alloc] init];
+ [login
+ logInWithReadPermissions: @[@"public_profile", @"email"]
+ fromViewController: [[[UIApplication sharedApplication] keyWindow] rootViewController]
+ handler: ^(FBSDKLoginManagerLoginResult* result, NSError* error)
+ {
+ if (error)
+ {
+ onError([error localizedDescription]);
+ return;
+ }
+ if (result.isCancelled)
+ {
+ onCancelled();
+ return;
+ }
+ if ([FBSDKAccessToken currentAccessToken])
+ {
+ NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
+ [parameters setValue:@"id,name,email" forKey:@"fields"];
+
+ [[[FBSDKGraphRequest alloc] initWithGraphPath:@"me" parameters:parameters] startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
+ if (!error)
+ {
+ id user = @{User(string, string, string, string):New(result[@"id"], result[@"name"], result[@"email"], [FBSDKAccessToken currentAccessToken].tokenString)};
+ onSuccess(user);
+ }
+ }];
+ }
+ }
+ ];
+ @}
+
+ [Foreign(Language.Java)]
+ [Require("Entity", "User(string, string, string, string)")]
+ public extern(Android) void Login(Action onSuccess, Action onCancelled, Action onError)
+ @{
+ CallbackManager callbackManager = (CallbackManager)@{FacebookLogin:Of(_this)._callbackManager:Get()};
+ LoginManager.getInstance().registerCallback(callbackManager,
+ new FacebookCallback()
+ {
+ @Override
+ public void onSuccess(LoginResult loginResult)
+ {
+ final AccessToken accessToken = loginResult.getAccessToken();
+
+ GraphRequest request = GraphRequest.newMeRequest(
+ loginResult.getAccessToken(),
+ new GraphRequest.GraphJSONObjectCallback() {
+ @Override
+ public void onCompleted(
+ JSONObject object,
+ GraphResponse response) {
+ try {
+ String fbUserId = object.getString("id");
+ String fbUserName = object.getString("name");
+ String fbEmail = object.getString("email");
+ String tokenString = accessToken.getToken();
+
+ UnoObject user = @{User(string, string, string, string):New(fbUserId, fbUserName, fbEmail, tokenString)};
+ onSuccess.run(user);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ Bundle parameters = new Bundle();
+ parameters.putString("fields", "id,name,email");
+ request.setParameters(parameters);
+ request.executeAsync();
+ }
+
+ @Override
+ public void onCancel()
+ {
+ onCancelled.run();
+ }
+
+ @Override
+ public void onError(FacebookException exception)
+ {
+ onError.run(exception.toString());
+ }
+ }
+ );
+ LoginManager.getInstance().logInWithReadPermissions(Activity.getRootActivity(), java.util.Arrays.asList("public_profile", "email"));
+ @}
+}
diff --git a/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.unoproj b/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.unoproj
new file mode 100644
index 0000000..8e98979
--- /dev/null
+++ b/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.unoproj
@@ -0,0 +1,17 @@
+{
+ "IsTransitive": true,
+ "Packages": [
+ "Fuse",
+ "Fuse.Platform",
+ "Fuse.Scripting",
+ "FuseJS",
+ "Uno.Permissions",
+ "Uno.Threading",
+ ],
+ "Includes": [
+ "FacebookLogin.uno",
+ "FacebookLogin.uxl",
+ "FacebookLoginModule.uno",
+ "FacebookSDKs-iOS.stuff",
+ ],
+}
diff --git a/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.uxl b/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.uxl
new file mode 100644
index 0000000..e4e2c4d
--- /dev/null
+++ b/Samples/NativeFacebookLogin/FacebookLogin/FacebookLogin.uxl
@@ -0,0 +1,76 @@
+
+
+ @(Facebook.AppID)
+ fb@(Facebook.AppID)
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ fb@(Facebook.AppID)
+
+
+
+ FacebookAppID
+ @(Facebook.AppID)
+ FacebookDisplayName
+ Fuse Test App
+
+ NSAppTransportSecurity
+
+ NSExceptionDomains
+
+ facebook.com
+
+ NSIncludesSubdomains
+ NSThirdPartyExceptionRequiresForwardSecrecy
+
+ fbcdn.net
+
+ NSIncludesSubdomains
+ NSThirdPartyExceptionRequiresForwardSecrecy
+
+ akamaihd.net
+
+ NSIncludesSubdomains
+ NSThirdPartyExceptionRequiresForwardSecrecy
+
+
+
+
+ LSApplicationQueriesSchemes
+
+ fbauth2
+
+ ]]>
+
+
diff --git a/Samples/NativeFacebookLogin/FacebookLogin/FacebookLoginModule.uno b/Samples/NativeFacebookLogin/FacebookLogin/FacebookLoginModule.uno
new file mode 100644
index 0000000..8773e0c
--- /dev/null
+++ b/Samples/NativeFacebookLogin/FacebookLogin/FacebookLoginModule.uno
@@ -0,0 +1,87 @@
+using Fuse.Scripting;
+using Uno.Permissions;
+using Uno.Threading;
+using Uno.UX;
+using Uno;
+
+[UXGlobalModule]
+public class FacebookLoginModule : NativeModule
+{
+ class FacebookLoginPromise : Promise
+ {
+ readonly FacebookLogin _facebookLogin;
+
+ public FacebookLoginPromise(FacebookLogin facebookLogin)
+ {
+ _facebookLogin = facebookLogin;
+ if defined(Android)
+ {
+ Permissions.Request(Permissions.Android.INTERNET).Then(
+ OnPermissionsPermitted,
+ OnPermissionsRejected);
+ }
+ else
+ {
+ Fuse.UpdateManager.AddOnceAction(Login);
+ }
+ }
+
+ void Login()
+ {
+ if defined(iOS || Android)
+ _facebookLogin.Login(this.Resolve, OnCancelled, OnError);
+ else
+ throw new NotImplementedException();
+ }
+
+ void OnCancelled()
+ {
+ Reject(new Exception("Cancelled"));
+ }
+
+ void OnError(string error)
+ {
+ Reject(new Exception(error));
+ }
+
+ extern(Android) void OnPermissionsPermitted(PlatformPermission p)
+ {
+ Fuse.UpdateManager.AddOnceAction(Login);
+ }
+
+ extern(Android) void OnPermissionsRejected(Exception e)
+ {
+ Reject(e);
+ }
+ }
+
+ static readonly FacebookLoginModule _instance;
+ readonly FacebookLogin _facebookLogin;
+
+ public FacebookLoginModule()
+ {
+ if (_instance != null)
+ return;
+
+ _facebookLogin = new FacebookLogin();
+
+ _instance = this;
+ Resource.SetGlobalKey(_instance, "FacebookLogin");
+ AddMember(new NativePromise("login", Login, Converter));
+ }
+
+ Future Login(object[] args)
+ {
+ return new FacebookLoginPromise(_facebookLogin);
+ }
+
+ static Fuse.Scripting.Object Converter(Context context, FacebookLogin.User user)
+ {
+ var wrapperObject = context.NewObject();
+ wrapperObject["id"] = user.getId();
+ wrapperObject["email"] = user.getEmail();
+ wrapperObject["name"] = user.getName();
+ wrapperObject["tokenString"] = user.getTokenString();
+ return wrapperObject;
+ }
+}
diff --git a/Samples/NativeFacebookLogin/FacebookLogin/FacebookSDKs-iOS.stuff b/Samples/NativeFacebookLogin/FacebookLogin/FacebookSDKs-iOS.stuff
new file mode 100644
index 0000000..d2e12de
--- /dev/null
+++ b/Samples/NativeFacebookLogin/FacebookLogin/FacebookSDKs-iOS.stuff
@@ -0,0 +1,6 @@
+if iOS {
+ FacebookSDKs-iOS: "https://lookaside.facebook.com/developers/resources/?id=facebook-ios-sdk-current.zip"
+}
+if Android {
+ FacebookSDKs-Android: "https://lookaside.facebook.com/developers/resources/?id=facebook-android-sdk-current.zip"
+}
diff --git a/Samples/NativeFacebookLogin/FacebookLoginExample.unoproj b/Samples/NativeFacebookLogin/FacebookLoginExample.unoproj
new file mode 100644
index 0000000..a959d37
--- /dev/null
+++ b/Samples/NativeFacebookLogin/FacebookLoginExample.unoproj
@@ -0,0 +1,26 @@
+{
+ "iOS": {
+ "BundleIdentifier": "com.uno.test",
+ },
+ "Android": {
+ "Package": "com.uno.test",
+ "Activity": "MainActivity",
+ "SDK": {
+ "BuildToolsVersion": "29.0.2",
+ "CompileVersion": 29,
+ "MinVersion": 19,
+ "TargetVersion": 29
+ }
+ },
+ "Packages": [
+ "Fuse",
+ "Uno.Permissions",
+ ],
+ "Projects": [
+ "FacebookLogin/FacebookLogin.unoproj",
+ ],
+ "Includes": [
+ "FacebookAppId.uxl",
+ "MainView.ux",
+ ],
+}
diff --git a/Samples/NativeFacebookLogin/MainView.ux b/Samples/NativeFacebookLogin/MainView.ux
new file mode 100644
index 0000000..0b91355
--- /dev/null
+++ b/Samples/NativeFacebookLogin/MainView.ux
@@ -0,0 +1,24 @@
+
+
+ var FacebookLogin = require("FacebookLogin");
+
+ function login() {
+ FacebookLogin.login().then(function(user) {
+ console.log(JSON.stringify(user));
+ // {"id":"XXXXXXXXX","email":"XXXCX@hotmail.com","name":"Jesús Martínez", "tokenString": "NUMBER5_L3TTERS_NUMB3RS_LETTERS"}
+ }, function(err) {
+ console.log("Login failed: " + err);
+ });
+ }
+
+ module.exports = {
+ login: login,
+ };
+
+
+
+
+
+
+
+
diff --git a/Samples/NativeFacebookLogin/README.md b/Samples/NativeFacebookLogin/README.md
new file mode 100644
index 0000000..23d8f41
--- /dev/null
+++ b/Samples/NativeFacebookLogin/README.md
@@ -0,0 +1,24 @@
+# Facebook login using foreign code
+
+This sample shows how we can get Facebook login working with Fuse using the
+Facebook Android and iOS SDKs.
+It is part of the tutorial over at [Fuse's docs page](https://www.fusetools.com/docs/native-interop/facebook-login),
+so head over there if that's not where you came from!
+
+The main login functionality is implemented in the `FacebookLogin` Uno project
+in the `FacebookLogin` folder.
+
+## Setup
+
+You will need to [register for a Facebook App ID](https://developers.facebook.com/docs/apps/register)
+and fill it in as `Facebook.AppID` in `FacebookAppId.uxl`.
+
+## Building
+
+To build for Android, we need to enable Gradle. The Facebook SDK for Android
+dependency will then be handled automatically by Gradle.
+
+Use e.g. `uno build --target=Android -DGRADLE --run`.
+
+To build for iOS, use .e.g `uno build --target=iOS --run`. The Facebook SDK for
+iOS will be downloaded automatically.