User id in iOS to store profiles and check IAP - ios

My app has in-app currency and non-consumable products, it stores user profiles and posts values to leaderboards on my server.
In Android (pure java) I have LVL user ID - it is unique for pair developer-customer, so I easily manage user profiles on all his devices, and I can distinguish between devices using IMEI or Android ID.
In Windows/Windows Phone (monogame) I have LiveID, but devices have no id except self-generated UUID for statistics/ads. Can't be sure it persists reinstalls and updates.
And what about iOS (and maybe OSX) (Xamarin.iOS/monogame)?
As far as I remember in iOS was device id, but then api was deprecated.
What do you use as device/user id?
Maybe there is some user-unique-id that StoreKit has behind the scenes?
Or something related to cloud id, to distinguish users, not devices?
If none is available - is there a way to keep random UUID persistent on device, even if user reinstalls app?

When Apple removed the UUID, they provided the identifierForVendor method (In UIDevice) to replace it. It provides a UUID that is unique for you (the developer) for a particular device. I can't tell you how to call that from xamarin, but would assume it's possible.
If you want something that will persist across app deletes you could create your own UUID and save it to the keychain. You can use app groups to have a shared keychain for all of your apps, and keychain entries DO persist if you delete and reinstall an app.
Tertium: Here is the example code (tested). If you store it in cloud you can use it on all user's devices.
void SaveValueToKeychain(string key, String value)
{
var s = new SecRecord(SecKind.GenericPassword)
{
ValueData = NSData.FromString(value),
Generic = NSData.FromString(key),
Invisible = true,
CreationDate = NSDate.Now
};
var err = SecKeyChain.Add(s);
}
public String GetValueFromKeychain(string key)
{
String ret = null;
SecStatusCode res;
var rec = new SecRecord(SecKind.GenericPassword)
{
Generic = NSData.FromString(key)
};
var match = SecKeyChain.QueryAsRecord(rec, out res);
if (match != null)
{
ret = match.ValueData.ToString();
}
return ret;
}
...
string UUID_KEY = "com.my.app";
String id = GetValueFromKeychain(UUID_KEY);
if (id == null)
{
Guid g = Guid.NewGuid();
String gs = g.ToString().Replace("-", "");
Debug.Write("ID not found, generating: " + gs);
SaveValueToKeychain(UUID_KEY, gs);
id = GetValueFromKeychain(UUID_KEY);
}
else
{
Debug.Write("ID found: " + id);
}

Related

Flutter ios appstore validateReceipt on non-consumable in-app purchase

I seem to be stuck on this. Trying to validate the receipt (server side) on an in-app purchase on IOS (haven't tried with android, yet.)
I'm using the official in_app_purchase pub package.
This is the setup to initialize the purchase:
Future<bool> initiatePurchase() async {
...
(verify store is available)
..
print ("==> Store available, initiating purchase");
final PurchaseParam purchaseParam =
PurchaseParam(productDetails: _productDetails![0]);
await InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);
return true;
}
Here's my verify purchase call:
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
PurchaseVerifRest purchaseRest = PurchaseVerifRest();
Map<String,dynamic> rsp = await purchaseRest.verifyPurchase(
{
"source": purchaseDetails.verificationData.source,
"vfdata": purchaseDetails.verificationData.serverVerificationData
});
// bundle up the source and verificationData in a map and send to rest
// call
return rsp['status'] == 200;
}
On the server side, the code looks like this (NodeJS/express app)
// (in router.post() call - 'purchaseData' is the map sent in the above code,
// the 'vfdata' member is the 'serverVerificationData'
//. in the 'purchaseDetails' object)
if (purchaseData.source == ('app_store')) {
const IOS_SHARED_SECRET = process.env...;
let postData = {
'receipt-data': purchaseData['vfdata'],
'password': IOS_SHARED_SECRET
};
try {
let verif_rsp = await execPost(postData);
retStatus = verif_rsp.statusCode;
msg = verif_rsp.data;
} catch (e) {
retStatus = e.statusCode;
}
}
What I get back, invariably is
210003 - Receipt could not be authenticated
... even though the purchase seems to go through, whether I validate or not.
Details/questions:
Testing with a sandbox account.
This is for a 'non-consumable' product purchase.
I'm assuming that purchaseDetails.verificationData.serverVerificationData is the payload containing the receipt to send to Apple for verification. Is this not correct? Is there another step I need to do to get the receipt data?
I've read in other posts that the verification step is only for recurring subscriptions and not for other types of products. Is this correct? I don't see anything in Apple's docs to indicate this.
Any thoughts appreciated.

Flutter (IOS) in app purchase receipt data

I use the [in app purchase][1] library to make in-app purchases in the application I developed with Flutter. When a purchase is made, in order to verify in-app purchases for Android on the server side, I am sending the datas as follows, which I need to send by the server.
_verifyPurchase(PurchaseDetails purchase) async {
productID = purchase.productID;
//for android it works nice
if(Platform.isAndroid){
orderId = purchase.billingClientPurchase.orderId;
purchaseToken = purchase.billingClientPurchase.purchaseToken;
purchaseVerify(orderId, purchaseToken, productID);
//but it does not work for iOS and the data required for purchase verification does not go to the server
}else if(Platform.isIOS){
transactionId = purchase.skPaymentTransaction.originalTransaction;
verifData = purchase.verificationData.serverVerificationData;
purchaseVerify(transactionId, verifData, productID);
}
}
purchaseVerify(String orderId, String purchaseToken, String productID) async {
var data = {
'orderId' : orderId,
'purchaseToken' : purchaseToken,
'productId' : productID,
};
res = await Network().authData(data, 'purchaseVerify.php');
}
However, although I try to obtain the data required to verify the in-app purchases for iOS as follows, no data is sent to the server side.
How can I get the data needed to validate in-app purchases for iOS and send it to the server side?

Cannot store Certificate to KeyChain using SecKeyChain in Xamarin

After hours spent reading through what's available online to fix this, I decided to post my question here.
My goal is simple: Store an X509Certficate to KeyChain using Xamarin for iOS. This is a self signed certificate that I generated using BouncyCastle library.
I'm successfuly importing it, but when saving to KeyChain using SecKeyChain.Add, the result is always SecStatusCode.Paramwhich the documentation explains is missing or invalid parameter. Here's the method I use
public static bool StoreCertInKeyChain(X509Certificate2 certificate, string password)
{
var data = certificate.Export(X509ContentType.Pkcs12, password);
var options = NSMutableDictionary.FromObjectAndKey(FromObject(password), SecImportExport.Passphrase);
var statusCode = SecImportExport.ImportPkcs12(data, options, out NSDictionary[] result);
if (statusCode != SecStatusCode.Success) return false;
var certChain = result[0][SecImportExport.CertChain];
var record = new SecRecord(SecKind.Certificate)
{
Label = "MyKey",
Account = "Certificate",
ApplicationTag = "MyTag"
};
record.SetValueRef(certChain);
// Using the below code instead, produces the same result
// var cert = new SecCertificate(certChain.Handle);
// record.SetValueRef(cert);
var resultAdd = SecKeyChain.Add(record);
return resultAdd == SecStatusCode.Success;
}
Has anyone ran into this problem? I'm out of ideas what else to try. I followed the examples given on Xamarin documentation site, without success. Thank you
Answering my solution here, in case anyone else runs into the same issue. The problem was that the certificate supplied in the SecRecord wasn't an instance of SecCertificate, so using SecImportExport.ImportPkcs12 was the wrong way to do it. I ended up using SecIdentity.Import instead, which gives a reference to the certificate as well as the private key in it. The certificate and the private key need to be added to key chain separately using an identity. Here's the code that accomplishes this.
var identity = SecIdentity.Import(certificate.Export(X509ContentType.Pkcs12, password), password);
var storedCertificate = SecKeyChain.QueryAsConcreteType(new SecRecord(SecKind.Certificate) { Label = "My Cert" }, out SecStatusCode statusCode);
if (statusCode != SecStatusCode.Success)
{
var record = new SecRecord(SecKind.Certificate);
record.Label = "My Cert";
record.SetValueRef(identity.Certificate);
var result = SecKeyChain.Add(record);
SecKeyChain.AddIdentity(identity);
storedCertificate = SecKeyChain.QueryAsConcreteType(new SecRecord(SecKind.Certificate) { Label = "My Cert" }, out statusCode);
}
var storedIdentity = SecKeyChain.FindIdentity(storedCertificate as SecCertificate);
The certificate can be retrieved using the label, but to get the private key, the identity must be queried using the certificate as parameter in SecKeyChain.FindIdentity. From this point on, access to signing and decryption on the private key is available from the identity instance.

Send notification from web to android device using Firebase

I am trying for a while now to implement this flow: When user adds some files on server app, notification should trigger and send from server to FCM and that from there to pass message saying something like: 'New file has been added'.
Basically I want to inform mobile device user that something on server has been changed.
I have tried many things, but nothing seems to work as I would expect, at least.
On the mobile side I have set up Firebase inside my Xamarin.Android project, and when I am sending notifications directly from Firebase console, I get notifications, and everything is good.
But I don't want to send notifications via Firebase console, I would rather send notification from server (which is ASP.NET MVC project) to Firebase console and then pass it from there to android device.
My first question would be: Has anybody got an idea how can I inform web app about device_id? Is there some way that android device send this information on server? And maybe from there I can store that data and update it occasionally, since it is basically a refresh token.
My second problem is this: Even when I hard code current device_id of an active android device and try to send a message from server whit this code:
public class FirebaseService : IFirebaseService
{
public void SendMessageToClientApplication(string message, string serverApiKey, string senderId, string deviceId)
{
AndroidFCMPushNotificationStatus result = new AndroidFCMPushNotificationStatus();
try
{
result.Successful = false;
result.Error = null;
deviceId = "eMk6mD8P8Dc:APA91bG5Lmqn4Hwb4RZJ1Mkdl8Rf_uYQsQCEfDJK334tzSvIGzdao7o2X6VmtcTEp_Li0mG8iUoUT7-_RnZxQKocHosZwx6ITWdpmQyCwUv60IIIy0vxNlEaccT6RqK6c-cE1C6I3FTT";
var value = message;
WebRequest tRequest = WebRequest.Create("https://fcm.googleapis.com/fcm/send");
tRequest.Method = "post";
tRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
tRequest.Headers.Add(string.Format("Authorization: key={0}", serverApiKey));
tRequest.Headers.Add(string.Format("Sender: id={0}", senderId));
string postData = "collapse_key=score_update&time_to_live=108&delay_while_idle=1&data.message="
+ value + "&data.time=" + DateTime.Now.ToString() + "&registration_id=" + deviceId + "";
Byte[] byteArray = Encoding.UTF8.GetBytes(postData);
tRequest.ContentLength = byteArray.Length;
using (Stream dataStream = tRequest.GetRequestStream())
{
dataStream.Write(byteArray, 0, byteArray.Length);
using (WebResponse tResponse = tRequest.GetResponse())
{
using (Stream dataStreamResponse = tResponse.GetResponseStream())
{
using (StreamReader tReader = new StreamReader(dataStreamResponse))
{
String sResponseFromServer = tReader.ReadToEnd();
result.Response = sResponseFromServer;
}
}
}
}
}
catch (Exception ex)
{
result.Successful = false;
result.Response = null;
result.Error = ex;
}
}
}
I get nothing both in Firebase console and of course nothing on device as well.
I have tried to implement Firebase web as javascript on my server app like this:
<script>
var config = {
apiKey: "mykey",
authDomain: "myauthdomain",
databaseURL: "mydatabaseurl",
projectId: "myprojectid",
storageBucket: "mystoragebucket",
messagingSenderId: "mysenderid"
};
window.onload = function () {
firebase.initializeApp(config);
const messaging = firebase.messaging();
messaging.requestPermission()
.then(function () {
console.log('Notification permission granted.');
return messaging.getToken()
})
.then(function (token) {
console.log(token);
})
.catch(function (err) {
console.log('Unable to get permission to notify.', err);
});
messaging.onMessage(function (payload) {
console.log('onMessage: ', payload);
});
}
</script>
But this code gets some kind of a different device_id(aka token), probably one generated for that server machine.
Does anybody has experience with sending device_id to server app and from there sending notification message to Firebase console? I would appreciate some code examples, tutorials or anything that can help, since I was unable to find something useful during my google search.
My first question would be: Has anybody got an idea how can I inform web app about device_id?
The most common approach is to store the list of device tokens (each device that uses FCM has such a token) in a database, such as the Firebase Database. There is an example of this in the Cloud Functions for Firebase documentation. In this example the devices receiving the messages are web pages, but the approach is the same for iOS and Android.
I also recommend reading Sending notifications between Android devices with Firebase Database and Cloud Messaging. In this article, instead of sending to a device token, each user subscribes to a topic. That prevents having to manage the device tokens in your code.

Facebook iOS API only returning one friend from requestForMyFriends suddenly [duplicate]

I am trying to get my friend name and ids with Graph API v2.0, but data returns empty:
{
"data": [
]
}
When I was using v1.0, everything was OK with the following request:
FBRequest* friendsRequest = [FBRequest requestForMyFriends];
[friendsRequest startWithCompletionHandler: ^(FBRequestConnection *connection,
NSDictionary* result,
NSError *error) {
NSArray* friends = [result objectForKey:#"data"];
NSLog(#"Found: %i friends", friends.count);
for (NSDictionary<FBGraphUser>* friend in friends) {
NSLog(#"I have a friend named %# with id %#", friend.name, friend.id);
}
}];
But now I cannot get friends!
In v2.0 of the Graph API, calling /me/friends returns the person's friends who also use the app.
In addition, in v2.0, you must request the user_friends permission from each user. user_friends is no longer included by default in every login. Each user must grant the user_friends permission in order to appear in the response to /me/friends. See the Facebook upgrade guide for more detailed information, or review the summary below.
If you want to access a list of non-app-using friends, there are two options:
If you want to let your people tag their friends in stories that they publish to Facebook using your App, you can use the /me/taggable_friends API. Use of this endpoint requires review by Facebook and should only be used for the case where you're rendering a list of friends in order to let the user tag them in a post.
If your App is a Game AND your Game supports Facebook Canvas, you can use the /me/invitable_friends endpoint in order to render a custom invite dialog, then pass the tokens returned by this API to the standard Requests Dialog.
In other cases, apps are no longer able to retrieve the full list of a user's friends (only those friends who have specifically authorized your app using the user_friends permission). This has been confirmed by Facebook as 'by design'.
For apps wanting allow people to invite friends to use an app, you can still use the Send Dialog on Web or the new Message Dialog on iOS and Android.
UPDATE: Facebook have published an FAQ on these changes here: https://developers.facebook.com/docs/apps/faq which explain all the options available to developers in order to invite friends etc.
Although Simon Cross's answer is accepted and correct, I thought I would beef it up a bit with an example (Android) of what needs to be done. I'll keep it as general as I can and focus on just the question. Personally I wound up storing things in a database so the loading was smooth, but that requires a CursorAdapter and ContentProvider which is a bit out of scope here.
I came here myself and then thought, now what?!
The Issue
Just like user3594351, I was noticing the friend data was blank. I found this out by using the FriendPickerFragment. What worked three months ago, no longer works. Even Facebook's examples broke. So my issue was 'How Do I create FriendPickerFragment by hand?
What Did Not Work
Option #1 from Simon Cross was not strong enough to invite friends to the app. Simon Cross also recommended the Requests Dialog, but that would only allow five requests at a time. The requests dialog also showed the same friends during any given Facebook logged in session. Not useful.
What Worked (Summary)
Option #2 with some hard work. You must make sure you fulfill Facebook's new rules: 1.) You're a game 2.) You have a Canvas app (Web Presence) 3.) Your app is registered with Facebook. It is all done on the Facebook developer website under Settings.
To emulate the friend picker by hand inside my app I did the following:
Create a tab activity that shows two fragments. Each fragment shows a list. One fragment for available friend (/me/friends) and another for invitable friends (/me/invitable_friends). Use the same fragment code to render both tabs.
Create an AsyncTask that will get the friend data from Facebook. Once that data is loaded, toss it to the adapter which will render the values to the screen.
Details
The AsynchTask
private class DownloadFacebookFriendsTask extends AsyncTask<FacebookFriend.Type, Boolean, Boolean> {
private final String TAG = DownloadFacebookFriendsTask.class.getSimpleName();
GraphObject graphObject;
ArrayList<FacebookFriend> myList = new ArrayList<FacebookFriend>();
#Override
protected Boolean doInBackground(FacebookFriend.Type... pickType) {
//
// Determine Type
//
String facebookRequest;
if (pickType[0] == FacebookFriend.Type.AVAILABLE) {
facebookRequest = "/me/friends";
} else {
facebookRequest = "/me/invitable_friends";
}
//
// Launch Facebook request and WAIT.
//
new Request(
Session.getActiveSession(),
facebookRequest,
null,
HttpMethod.GET,
new Request.Callback() {
public void onCompleted(Response response) {
FacebookRequestError error = response.getError();
if (error != null && response != null) {
Log.e(TAG, error.toString());
} else {
graphObject = response.getGraphObject();
}
}
}
).executeAndWait();
//
// Process Facebook response
//
//
if (graphObject == null) {
return false;
}
int numberOfRecords = 0;
JSONArray dataArray = (JSONArray) graphObject.getProperty("data");
if (dataArray.length() > 0) {
// Ensure the user has at least one friend ...
for (int i = 0; i < dataArray.length(); i++) {
JSONObject jsonObject = dataArray.optJSONObject(i);
FacebookFriend facebookFriend = new FacebookFriend(jsonObject, pickType[0]);
if (facebookFriend.isValid()) {
numberOfRecords++;
myList.add(facebookFriend);
}
}
}
// Make sure there are records to process
if (numberOfRecords > 0){
return true;
} else {
return false;
}
}
#Override
protected void onProgressUpdate(Boolean... booleans) {
// No need to update this, wait until the whole thread finishes.
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
/*
User the array "myList" to create the adapter which will control showing items in the list.
*/
} else {
Log.i(TAG, "Facebook Thread unable to Get/Parse friend data. Type = " + pickType);
}
}
}
The FacebookFriend class I created
public class FacebookFriend {
String facebookId;
String name;
String pictureUrl;
boolean invitable;
boolean available;
boolean isValid;
public enum Type {AVAILABLE, INVITABLE};
public FacebookFriend(JSONObject jsonObject, Type type) {
//
//Parse the Facebook Data from the JSON object.
//
try {
if (type == Type.INVITABLE) {
//parse /me/invitable_friend
this.facebookId = jsonObject.getString("id");
this.name = jsonObject.getString("name");
// Handle the picture data.
JSONObject pictureJsonObject = jsonObject.getJSONObject("picture").getJSONObject("data");
boolean isSilhouette = pictureJsonObject.getBoolean("is_silhouette");
if (!isSilhouette) {
this.pictureUrl = pictureJsonObject.getString("url");
} else {
this.pictureUrl = "";
}
this.invitable = true;
} else {
// Parse /me/friends
this.facebookId = jsonObject.getString("id");
this.name = jsonObject.getString("name");
this.available = true;
this.pictureUrl = "";
}
isValid = true;
} catch (JSONException e) {
Log.w("#", "Warnings - unable to process Facebook JSON: " + e.getLocalizedMessage());
}
}
}
Facebook has revised their policies now. You can’t get the whole friendlist anyway if your app does not have a Canvas implementation and if your app is not a game. Of course there’s also taggable_friends, but that one is for tagging only.
You will be able to pull the list of friends who have authorised the app only.
The apps that are using Graph API 1.0 will be working till April 30th, 2015 and after that it will be deprecated.
See the following to get more details on this:
User Friends
Facebook Application Development FAQ
In Swift 4.2 and Xcode 10.1:
If you want to get the friends list from Facebook, you need to submit your app for review in Facebook. See some of the Login Permissions:
Login Permissions
Here are the two steps:
1) First your app status is must be in Live
2) Get required permissions form Facebook.
1) Enable our app status live:
Go to the apps page and select your app
https://developers.facebook.com/apps/
Select status in the top right in Dashboard.
Submit privacy policy URL
Select category
Now our app is in Live status.
One step is completed.
2) Submit our app for review:
First send required requests.
Example: user_friends, user_videos, user_posts, etc.
Second, go to the Current Request page
Example: user_events
Submit all details
Like this submit for all requests (user_friends , user_events, user_videos, user_posts, etc.).
Finally submit your app for review.
If your review is accepted from Facebook's side, you are now eligible to read contacts, etc.
As Simon mentioned, this is not possible in the new Facebook API. Pure technically speaking you can do it via browser automation.
this is against Facebook policy, so depending on the country where you live, this may not be legal
you'll have to use your credentials / ask user for credentials and possibly store them (storing passwords even symmetrically encrypted is not a good idea)
when Facebook changes their API, you'll have to update the browser automation code as well (if you can't force updates of your application, you should put browser automation piece out as a webservice)
this is bypassing the OAuth concept
on the other hand, my feeling is that I'm owning my data including the list of my friends and Facebook shouldn't restrict me from accessing those via the API
Sample implementation using WatiN:
class FacebookUser
{
public string Name { get; set; }
public long Id { get; set; }
}
public IList<FacebookUser> GetFacebookFriends(string email, string password, int? maxTimeoutInMilliseconds)
{
var users = new List<FacebookUser>();
Settings.Instance.MakeNewIeInstanceVisible = false;
using (var browser = new IE("https://www.facebook.com"))
{
try
{
browser.TextField(Find.ByName("email")).Value = email;
browser.TextField(Find.ByName("pass")).Value = password;
browser.Form(Find.ById("login_form")).Submit();
browser.WaitForComplete();
}
catch (ElementNotFoundException)
{
// We're already logged in
}
browser.GoTo("https://www.facebook.com/friends");
var watch = new Stopwatch();
watch.Start();
Link previousLastLink = null;
while (maxTimeoutInMilliseconds.HasValue && watch.Elapsed.TotalMilliseconds < maxTimeoutInMilliseconds.Value)
{
var lastLink = browser.Links.Where(l => l.GetAttributeValue("data-hovercard") != null
&& l.GetAttributeValue("data-hovercard").Contains("user.php")
&& l.Text != null
).LastOrDefault();
if (lastLink == null || previousLastLink == lastLink)
{
break;
}
var ieElement = lastLink.NativeElement as IEElement;
if (ieElement != null)
{
var htmlElement = ieElement.AsHtmlElement;
htmlElement.scrollIntoView();
browser.WaitForComplete();
}
previousLastLink = lastLink;
}
var links = browser.Links.Where(l => l.GetAttributeValue("data-hovercard") != null
&& l.GetAttributeValue("data-hovercard").Contains("user.php")
&& l.Text != null
).ToList();
var idRegex = new Regex("id=(?<id>([0-9]+))");
foreach (var link in links)
{
string hovercard = link.GetAttributeValue("data-hovercard");
var match = idRegex.Match(hovercard);
long id = 0;
if (match.Success)
{
id = long.Parse(match.Groups["id"].Value);
}
users.Add(new FacebookUser
{
Name = link.Text,
Id = id
});
}
}
return users;
}
Prototype with implementation of this approach (using C#/WatiN) see https://github.com/svejdo1/ShadowApi. It is also allowing dynamic update of Facebook connector that is retrieving a list of your contacts.
Try /me/taggable_friends?limit=5000 using your JavaScript code
Or
try the Graph API:
https://graph.facebook.com/v2.3/user_id_here/taggable_friends?access_token=
If you are still struggling with this issue on a development mode.
Follow the same process as mentioned below:
create a test app of your main app,
create test users, automatically install app for test users and assign them 'user_friend' permission.
Add your test users as a friend with each other.
I followed the same process after going through alot of research and finally it worked.
In the Facebook SDK Graph API v2.0 or above, you must request the user_friends permission from each user in the time of Facebook login since user_friends is no longer included by default in every login; we have to add that.
Each user must grant the user_friends permission in order to appear in the response to /me/friends.
let fbLoginManager : FBSDKLoginManager = FBSDKLoginManager()
fbLoginManager.loginBehavior = FBSDKLoginBehavior.web
fbLoginManager.logIn(withReadPermissions: ["email","user_friends","public_profile"], from: self) { (result, error) in
if (error == nil) {
let fbloginresult : FBSDKLoginManagerLoginResult = result!
if fbloginresult.grantedPermissions != nil {
if (fbloginresult.grantedPermissions.contains("email")) {
// Do the stuff
}
else {
}
}
else {
}
}
}
So at the time of Facebook login, it prompts with a screen which contain all the permissions:
If the user presses the Continue button, the permissions will be set. When you access the friends list using Graph API, your friends who logged into the application as above will be listed
if ((FBSDKAccessToken.current()) != nil) {
FBSDKGraphRequest(graphPath: "/me/friends", parameters: ["fields" : "id,name"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil) {
print(result!)
}
})
}
The output will contain the users who granted the user_friends permission at the time of login to your application through Facebook.
{
data = (
{
id = xxxxxxxxxx;
name = "xxxxxxxx";
}
);
paging = {
cursors = {
after = xxxxxx;
before = xxxxxxx;
};
};
summary = {
"total_count" = 8;
};
}

Resources