Facebook presentFeedDialogModallyWithSession never calls the handler - facebook-ios-sdk

From Facebook tutorial on how to use share, there is a fallback to feed dialog, the call looks like below. The problem is that the handler is never called at all and nothing happens. Fresh SDK, empty test project.
[FBWebDialogs presentFeedDialogModallyWithSession:nil
parameters:params
handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error) {
// we never get here
}];

Related

SignalR Core 1.0 intermittently changes the case of http method for non signalR POST, need fix (AKA Random 404 Errors)

I'm always reluctant to claim that a bug that I'm seeing is actually a .Net Core bug, but after spending 8+ hours investigating the following bug, it looks like a .Net Core SignalR bug to me. I need techniques for tracking this down further and for fixing it.
The first rule of honing in on a bug is to try to create a minimal amount of code that can consistently repro the bug. While I can't reproduce it in a small stand along project, I have worked hard try to zero in on what's happening.
I have a controller with the following action method
[HttpPost]
[Route("/hack/ajax/start")]
public JsonResult AjaxStart([FromBody] JObject data) {
//A call to some method that does some work
return Json(new {
started = true
});
}
Calling this code via a jquery ajax call or Postman works flawlessly every time if I do not have any SignalR Core 1.0 hubs registered in the startup.cs method. However, when I register the following in the startup.cs file I have intermittent issues.
namespace App.Site.Home {
public class HackHub : Hub {
public async Task SendMessage(string status, string progress) {
await Clients.All.SendAsync("serverMsg", status, progress);
}
}
}
Startup.cs ConfigureServices contains
services.AddSignalR();
Startup.cs Configure contains
app.UseSignalR(routes => {
routes.MapHub<App.Site.Home.HackHub>("/hub/hack");
});
If I were to comment out the one line above routes.MapHub<App.Site.Home.HackHub>("/hub/hack"); everything works fine every time. However with this line present, (I.e. some SignalR hub registered) then that's when the fun starts for me, even if I have no code executing on the client or server that makes use of the hub!
The issue is that sometimes when a HTTP POST request is made for the action method above, something in .Net Core (SignalR??) is converting the POST method to Post, and then because Post is not a valid HTTP Method it converts it to a blank method. And since My action method requires an HTTP POST a 404 status code is returned. Many of the HTTP POSTS for that endpoint work fine, but often the issue I just described occurres.
To ensure that my client code was not part of the problem, I was able to reproduce my issue using Postman to make the requests. Further to ensure that POST was actually being sent and not Post, I used Fiddler to watch what was going over the wire. All this is documented below.
Here is the first request (which always works) done via Postman:
Here is the second (identical!) request done via Postman, this one resulted in a 404:
Here is what the first request (the one that worked properly) looked like in fiddler:
Here is what the second request looked like in fiddler:
As you can see, the requests are identical. But the response certainly is not.
So to get a better idea what the server was seeing, I added the following code to the beginning of the startup.cs Configure method. Due to it's placement, for the request this code gets to run before any other application code or middleware.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
//for debugging
app.Use(async (context, next) => {
if(context.Request.Method == "") {
string method = context.Request.Method;
string path = context.Request.Path;
IHttpRequestFeature requestFeature = context.Features.Get<IHttpRequestFeature>();
string kestralHttpMethod = requestFeature.Method;
string stop = path;
}
await next();
});
//more code here...
}
For the first request, the request.Method was POST as one would expect:
But for the second request request.Method was blank!!
To investigate this further, I accessed the requestFeature and checked the Http Method Method there. This is where things get really interesting. If I just hover over the property in the debuggger, it's blank too.
But, If I expand the requestFeature object and look at the Method property there, is it Post!!!
That alone seems like craziness. How can two views of the SAME property in the debugger have different values???! It would seem that some code converted the POST to Post, and at some level the system knows that Post is not a valid http method so in some views of that variable it's converted to a blank string. But that is so weird!
Also, we clearly saw via Postman and Fiddler that POST was sent, so how did it get changed to Post? What code did that? I'd like to claim that it can't be my code since I'm checking the value of the RequestFeature before any of my other code related to the request gets a chance to run. Also, if I comment out the one line of code that registers that SignalR hub, then POST is never converted to Post and I never get a 404. But with that SignalR hub registered I periodically get this behavior.
Are there any SignalR or other .net Core switches I can turn on to get better trace or logging info to see when the POST is getting changed to Post? Is there a way to fix this?
This question was looked into via this GitHub issue https://github.com/aspnet/KestrelHttpServer/issues/2591 which was originally opened up when someone else also observed random 404 errors
I want to especially thank #ben-adams for his help in understanding what was going on.
Let me start by saying that this did not turn out to be a bug in the framework. It was a bug in my code. How can that be given what I was observing?
Well, it's like this...
In some parts of the HttpRequest the method is a string, but it in other parts it's an enum. The enum value for POST is Post. So that's why the case conversion was happening.
The reason that one part of the request was saying Post while the other part showed a Method value of a blank string was because the request object was gummed up because I had accessed it at a time when it was in between requests.
How did I do THAT? you may wonder. Well let me tell you, because the plot thickens...
I turns out that I have some logging code that that gathers context information when it's called and one of the pieces of context info it gathers is the current request.Method. When this logging code is called from a main thread, there is no issue.
However, my system does have some code that runs on background threads that are either started via a Timer or via a ThreadPool.QueueUserWorkItem. If this code hits an exception it will call the same logger code.
When my logger code, running on a background thread, checks for the current httpContext via IHttpContextAccessor I fully expected it to receive null. And certainly this same code in the same situation when accessing the current HttpContext via HttpContext.Current in a non .Net Core website does receive null. But as it turns out, under .Net core, it wasn't receiving null, it was receiving an object. But that object was for a request that had already finished and who's request object had already been reset!!!
Starting in .Net Core 2.0 the HttpContext, and it's child objects like request, gets reset after the connection for a request closes. So the HttpContext object (and it's request object) the logger code was getting when running on a background thread was an object that had been reset. It's request.Path for example was null.
It turns out that a request in this state does not expects it's request.Method property to be accessed. And doing so gums up the works for the next request that comes in. Ultimately this is the source of why the next request that came in ended up returning a 404 error.
So how do we fix this? Why does IHttpContextAccessor return an object rather than null in this out of context situation especially given that the object may very possibly be between requests? The answer is that when I was used Timer or ThreadPool.QueueUserWorkItem to create a background task, the Execution Context was being flowed to the new thread. This is just what happens by default when you use these API methods. But, internally the IHttpContextAccessor uses an AsyncLocal to keep track of the current HttpContext and since my new thread received Execution Context from the main thread it had access to the same AsyncLocal. And so IHttpContextAccessor provided an object rather than the null I was expecting when called from a background thread.
The fix? (Thank you #Ben-Adams!) Instead of calling ThreadPool.QueueUserWorkItem I needed to call ThreadPool.UnsafeQueueUserWorkItem instead. This method DOES NOT flow the current Execution Context to the new thread, and therefore the new thread won't have access to those AsyncLocals from the main thread. Once I did this, IHttpContextAccessor then returned null when called from the background thread instead of returning a object that was in between requests and untouchable. Yea!
When creating a `Timer' I also needed to change my code to do it in a way that would not flow Execution Context. Here is the code I use (which was inspired by some #Ben-Adams suggested):
public static Timer GetNewTimer(TimerCallback callback, object state, int dueTime, int interval) {
bool didSuppress = false;
try {
if (!ExecutionContext.IsFlowSuppressed()) {
//We need to suppress the flow of the execution context so that it does not flow to our
//new asynchronous thread. This is important so that AsyncLocals (like the one used by
//IHttpaccessor) do not flow to the new thread we are pushing our work to. By not flowing the
//execution context, IHttpAccessor wil return null rather than bogusly returning a context for
//a request that is in between requests.
//Related info: https://github.com/aspnet/KestrelHttpServer/issues/2591#issuecomment-399978206
//Info on Execution Context: https://blogs.msdn.microsoft.com/pfxteam/2012/06/15/executioncontext-vs-synchronizationcontext/
ExecutionContext.SuppressFlow();
didSuppress = true;
}
return new Timer(callback, state, dueTime, interval);
} finally {
// Restore the current ExecutionContext
if (didSuppress) {
ExecutionContext.RestoreFlow();
}
}
}
This only leaves one remaining question unanswered. My original question noted that registering a SignalR hub was causing the system to exhibit this random 404 behavior but the system did not exhibit this behavior when no SignalR hub was registered (or so I thought). Why was this? I truly don't know. Perhaps it was putting more resource pressure on some part of the system and thus causing the issue to show up more easily. Not sure. All I know is that the root issue was that I was flowing Execution Context to my background threads without realizing it and that was causing the IHttpContextAccessor's AsyncLocal to be in scope. Not flowing the Execution Context to the background threads fixes that issue.

fbsdkmessagedialog callback for iOS

I'm new to IOS programming, I'm trying to send message to Facebook friends using the below
[FBSDKMessageDialog showWithContent:content delegate:(id)self];
I have added callback as below:
- (void)sharer:(id<FBSDKSharing>)sharer didCompleteWithResults:(NSDictionary *)results
{
NSLog(#"complete");
}
- (void)sharerDidCancel:(id<FBSDKSharing>)sharer
{
NSLog(#"Cancelled")
}
But the sharer method is not called when I send a message in FB messenger rather the sharerDidCancel is called. The sharerDidCancel method is called when user cancels or send a message, how can I differentiate send vs cancel?
Facebook has a confirmed bug concerning this, however the addressed bug there is that it always calls sharer:didCompleteWithResults instead of sharerDidCancel.
Their next release contain a fix for this (v45) and hopefully this fixes our bug too (I'm having the same problem).

Facebook iOS SDK won't open login UI after user changes password (iOS 6)

Some context: the user had previously installed the app, authorized FB, everything worked great, then they changed their FB password (through facebook.com), deleted the app, and have now reinstalled it and are running it for the first time again after reinstall.
I am calling [FBSession openActiveSessionWithReadPermissions:allowLoginUI:completionHandler] with allowLoginUI: YES and the read permissions being "email, user_about_me, user_birthday, user_interests, user_location."
The FBSessionState I am getting in the completionHandler is FBSessionStateClosedLoginFailed. The NSLog of the error is this:
Error Domain=com.facebook.sdk Code=2 "The operation couldn’t be completed. (com.facebook.sdk error 2.)" UserInfo=0x1cd68c00 {com.facebook.sdk:ErrorLoginFailedReason=com.facebook.sdk:ErrorLoginFailedReason, com.facebook.sdk:ErrorInnerErrorKey=Error Domain=com.apple.accounts Code=7 "The Facebook server could not fulfill this access request: Error validating access token: The session has been invalidated because the user has changed the password." UserInfo=0x1cd5b970 {NSLocalizedDescription=The Facebook server could not fulfill this access request: Error validating access token: The session has been invalidated because the user has changed the password.}}
That internal error domain is ACErrorDomain and error code ACErrorPermissionDenied. So, how do I let the user re-authorize the app?
I have tried calling openActiveSessionWithReadPermissions again but that just keeps outputting the same error. I have also tried [FBSession.activeSession closeAndClearTokenInformation] but that doesn't seem to do anything (presumably because there is no activeSession).
Hitting a very similar sort of bug with 3.2.1 Facebook SDK. In my case, I get into FBSessionStateOpen but have been given an invalid access token. As the question states, the normal closeAndClearTokenInformation and even deleting the app doesn't fix it. The only way I have been able to get-back-in under this scenario is to have the user change their password in the setting app. So this is what I do.
// In my completion handler FBSessionStateOpen is called BUT an
// invalid accessToken was detected.
[session closeAndClearTokenInformation];
[FBSession renewSystemCredentials:^(ACAccountCredentialRenewResult result,
NSError *error)
{
if (result == ACAccountCredentialRenewResultFailed ||
result == ACAccountCredentialRenewResultRejected)
{
[self showErrorMessage:NSLocalizedString(#"You may need to re-enter your Facebook password in the iPhone Settings App.\n", nil)];
}
else
{
// attempt opening a session again (after they have updated their account
// settings I end up here)
[self facebookLogin]; // Performs openActiveSessionWithReadPermissions,
// but this time around the token issued should be good.
}
}];
This is the only pragmatic solution I have been able to come up with.
I think you need to get a new access token with code like this...
[FBSession.activeSession closeAndClearTokenInformation];
[[FBSession class] performSelector:#selector(renewSystemAuthorization)];
[FBSession openActiveSessionWithReadPermissions:nil
allowLoginUI:YES
completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
}

Launch Settings from app?

Anyone know if it's now feasible to launch the 'Settings' app from your app? Google Maps does it when you don't have Location Services enabled and you try to locate yourself.
I've seen lots of folks post about it and most answers point to adding a bug in Radar.
In the wild, I know of two apps that show this dialog with ""Turn on Location Services to Allow [app] to Determine Your Location" along side Cancel and Settings buttons where the 'Settings' button will take you to the General Settings.
1) Facebook app version 3.3.2 that runs on iOS 3.1.3 shows this alert on start. I looked at the three20 source code in git-hub but that project is not really a full source of the app but more of a library with the app's components.
2) GroupMe also shows the same alert when adding current location to a new message.
You mean the settings of the app like you see in settings?
First create a settings.bundle in xcode>newfile>settings.bundle.
This will create some prop lists that you can edit from out of the Settings.app or your app self. For the exact code Google can help you a lot further that I can. If you have any specific questions; don't hesitate to ask.
In iOS 5.0 you can open settings using the "prefs://" URL scheme. But in iOS 5.1 it doesn't work. But there is a trick I used for login twitter account using settings inside my app. the code is
TWTweetComposeViewController *tweetViewController = [[TWTweetComposeViewController alloc] init];
// Create the completion handler block.
[tweetViewController setCompletionHandler:^(TWTweetComposeViewControllerResult result)
{
[self dismissModalViewControllerAnimated:YES];
}];
// Present the tweet composition view controller modally.
[self presentModalViewController:tweetViewController animated:YES];
tweetViewController.view.hidden = YES;
for (UIView *view in tweetViewController.view.subviews){
[view removeFromSuperview];
}
Using this code you can switch to settings from your app directly if you are not already logged in twitter account.

Publishing Check-in via Facebook IOS SDK

I'm trying to publish a check-in on a users wall. I don't need to get the longitude and latitude of the users device. But rather have this static as well as the place the user will check-in too. Basically a "check-in" button that already has static coordinates and a message.
I got the demo app up and running but can't seem to find any step by step tutorials on this particular topic. Is there any link around or a sample project you can point me to?
Thanks!
First, you need to have a place id. Assuming you have that you can use the Facebook Graph API to make a feed post to check-in the user.
You can do something like this (using Facebook iOS SDK 3.0):
[FBRequestConnection startForPostStatusUpdate:#"Awesome place!"
place:#"110506962309835"
tags:nil
completionHandler:^(FBRequestConnection *connection,
id result,
NSError *error) {
//verify result
}
];
Or:
[FBRequestConnection startWithGraphPath:#"me/feed"
parameters:#{#"place":#"110506962309835"}
HTTPMethod:#"POST"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
//verify result
}];
You will need to ask the user for publish_stream permission before making this call.

Resources