I'm attempting to setup a login with XamarinForms (4.1.x) and iOS (12.x) using IdentityServer (hybrid flow). I'm using the Xamarin Forms iOS example found here as a reference. After I enter my credentials, the browser tries to redirect and I get the follow error:
I'm not sure what address is invalid as nothing appears to be logged. Has anyone successfully used Xamarin.Forms with IdentityServer4 for authentication?
It seems like it's the redirect issue to your app.
Make sure you are defining the redirect url schemes properly "RedirectUri = "xamarinformsclients://callback" you have to define that Scheme in infoplist file too!
How people should answer for that kind of question:
You should use Info.plist
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.your.app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>xamarinformsclients</string>
</array>
</dict>
</array>
com.your.app is you bundle app
xamarinformsclients is your part of your RedirectUris = { "xamarinformsclients://callback" },
I've written a Macintosh app that handles a custom protocol:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>My Cool Handler</string>
<key>CFBundleURLSchemes</key>
<array>
<string>coolhandler</string>
</array>
</dict>
</array>
All well and good. It launches. However, I'm clicking on a link like this:
coolhandler://Iwant/toparse/this
In Windows, the registry entry is simple and this work just fine. When my Windows app launches, the whole url is passed as an argument and I can parse it.
protected override void OnStartup(StartupEventArgs e)
{
_url = !e.Args.Any()?"":e.Args[0];
//parse the url
}
Where, in my pList or in the app do I handle this? i.e, how do I pass the url argument to the app?
You'll need to do something like this in C# likely:
Accessing command line arguments in Objective-C
Which would look something like:
string[] args = NSProcessInfo.ProcessInfo.Arguments;
I am trying to implement a contextual menu item that would be presented in services when a text is selected. So e.g., if I select a word in a TextEdit, I want a menu item "Do my stuff" to be presented in contextual menu, which would feed the selected word to my application code for further processing.
By googling a little research, I came to conclusion that I need to implement and register a service. I tried to follow
Providing Services
documentation, but that seems at least a bit outdated (not to mention in some places vague).
From what I understood, I was able to do the following:
I implemented service object:
import Foundation
import AppKit
class ContextualMenuServiceProvider {
func importString(_ pasteBoard: Pasteboard, userData: String, error: String) {
print(">>>> in import string from service consumer")
}
}
I created an entry in Info.plist for service:
<key>NSServices</key>
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Import to $(PRODUCT_NAME)</string>
</dict>
<key>NSMessage</key>
<string>importString</string>
<key>NSPortName</key>
<string>MyApp</string>
<key>NSSendTypes</key>
<array>
<string>public.plain-text</string>
</array>
</dict>
</array>
And finally, I tried to register the service in AppDelegate. Now the documentation says to use:
NSApp.setServicesProvider(encryptor)
// where encryptor is an object of my ContextualMenuServiceProvider
However, it seems that NSApp does not have setServicesProvider method. I tried to use:
NSApp.servicesProvider = ContextualMenuServiceProvider()
property, however, this does not seem to work either.
I test it by running the application from the Xcode, and then, while the application is running, I try to select some text in TextEdit (it should not be restricted to TextEdit, I just use it as an example), and after right click I am looking for my menu item. It does not work.
I was able to find some kind of similar SO questions (e.g., Creating an os x service, or Howto add menu item to Mac OS Finder in Delphi XE2), but I was not able to detect what I am doing wrong from them (not to mention that most of them are old (using setServiceProvider), or using languages like C# or Delphi).
Any idea what is wrong with my code/approach?
Ok, looks like I have made several small mistakes that lead me to believe it was not working. However, in the end I was able to make it work. So if you are facing the same task, here is a solution in Swift 3:
(1) You have to implement a service method:
import Cocoa
class ServiceProvider: NSObject {
let errorMessage = NSString(string: "Could not find the text for parsing.")
#objc func service(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer<NSString>) {
guard let str = pasteboard.string(forType: NSStringPboardType) else {
error.pointee = errorMessage
return
}
let alert = NSAlert()
alert.messageText = "Hello \(str)"
alert.informativeText = "Welcome in the service"
alert.addButton(withTitle: "OK")
alert.runModal()
}
}
Here important thing that I did not know before was that the object providing the service has to subclass NSObject (it can also be ViewController, or any other NSObject subclass). Services API uses selector to call it, and selector technology works with NSObject only.
Also, service method has to have this declaration: it takes 3 arguments, first is NSPasteboard (you can use Pasteboard, but that does not provide string method) that is not labeled, second is optional String labeled userData, third is AutoreleasingUnsafeMutablePointer<NSString> labeled error. Follow this to get best type safety while still making it work. Otherwise the services API won't be able to find it and call it. You can use UnsafeRawPointers for all its arguments, but you will not gain anything by that.
(2) In the app delegate (or documentation says that you can do it anywhere, but I am doing it here) you register the service provider:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSApplication.shared().servicesProvider = ServiceProvider()
NSUpdateDynamicServices()
}
}
It seems to be working also without NSUpdateDynamicServices call, but I guess better be safe than sorry - it should refresh the services in the system so that you do not have to logoff and login the user to get the current service version.
(3) Finally, you have to configure the Info.plist to advertise your service method:
<key>NSServices</key>
<array>
<dict>
<key>NSMessage</key>
<string>service</string>
<key>NSPortName</key>
<string>serviceTest</string>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Test hello world</string>
</dict>
<key>NSRestricted</key>
<false/>
<key>NSRequiredContext</key>
<dict/>
<key>NSSendTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
</dict>
</array>
This is an XML source of the Info.plist - although Apple recommends to use their editor, in Xcode 8.2 I was not able to add an NSRequiredContext key through the editor - I had to open the file as Source code and add it manually. I would recommend editing directly the XML source.
You can find the documentation about the meaning of each key in Services Properties, but I would like to mention some crucial points. First, you NEED the NSRequiredContext key - they mention it in the documentation, but the editor does not support it - edit XML directly and add it even empty (as I do). Second, if you are using NSSendTypes or NSReturnTypes and you want to work with strings, use NSStringPboardType even though its documentation says it's deprecated and should be substituted by NSPasteboardTypeString - the latter won't work. Finally, NSMessage key with service value is the name of the service method. So my service provider object declares following method:
#objc func service(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer<NSString>)
I am using the service value in NSMessage. If you want to change it (e.g., you want several service methods) just don't forget that these two has to match (value in Info.plist configuration and the name of the service method).
(4) In this state it should be working. There is one thing I would like to mention that might help you with debugging. Test it using the method mentioned in documentation at the end by running:
/Applications/TextEdit.app/Contents/MacOS/TextEdit -NSDebugServices com.mycompany.myapp
Running this from Terminal should report whether any service using provided bundle is registered, and also whether it will be presented - e.g., in my case the problem was that I had not provided the mandatory NSRequiredContext. After testing it using this approach I was able to recognise that the service was installed, and the problem was that the services API assumed it should be filtered out. After some experiments and googling I solved it by adding the empty NSRequiredContext.
After each change to the service I recommend to quit TextEdit and run it again, it seems that it keeps a reference to the old service provider object (or something like that, changes were not registered by TextEdit if I did not restart it).
The minimum requirements for text services in info.plist. All items can be added through Xcode (tested on version 14.2):
<key>NSServices</key>
<array>
<dict>
<key>NSMessage</key>
<string>message</string>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Title</string>
</dict>
<key>NSRequiredContext</key>
<dict/>
<key>NSSendTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
</dict>
</array>
In AppDelegate:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.servicesProvider = self
}
#objc func message(_ pasteboard: NSPasteboard, userData: String?, error: NSErrorPointer) {
guard let content = pasteboard.string(forType: .string) else { return }
// Use `content`…
}
}
The system won’t update services every time after app’s launching. Remove the app entirely, then build and run again so the services can be refreshed. Run pbs to check if the service is correctly configured under your bundle identifier:
/System/Library/CoreServices/pbs -dump_pboard
this is the second time my Mac app has been rejected by the MAS.
I use a temporary entitlement so that the user can store his backup file the app created on his device.
This is the response I am getting from the MAS to my rejection:
We've determined that one or more temporary entitlement exceptions requested for
this app are not appropriate and will not be granted:
com.apple.security.temporary-exception.files.home-relative-path.read-write /
Very vague and the second time they are not telling me what is wrong with what i'm doing.
For that I use following entitlement:
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<string>True</string>
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
<array>
<string>/</string>
</array>
And this is how I use the entitlement:
NSSavePanel* saveSelection = [NSSavePanel savePanel];
[saveSelection setPrompt:#"Export"];
[saveSelection setMessage:NSLocalizedString(#"Save your encrypted backup file to:",#"")];
[saveSelection setNameFieldStringValue:date];
[saveSelection beginSheetModalForWindow:kDelegate.window completionHandler:^(NSInteger result) {
if (result==NSFileHandlingPanelOKButton)
{....
}
}
I really do hope somebody can help and thanks a lot in advance!
I finally got it working by adding this to the NSSavePanel:
[saveSelection setAllowedFileTypes:[NSArray arrayWithObject:#"whatever"]];
[saveSelection setAllowsOtherFileTypes:NO];
I don't know why this made it work, but it does... at least in my app.
I've read "Service Implementation Guide" from developer.apple.com and a question here from stackoverflow (on how to create a cocoa service), and followed this tutorial: http://www.cocoadev.com/index.pl?MakingServices and read http://homepage.mac.com/simx/technonova/tips/creating_a_service_for_mac_os_x.html
However, I don't seem to be able to get it working. Here's my info.plist addition:
<key>NSServices</key>
<array>
<dict>
<key>NSKeyEquivalent</key>
<dict>
<key>default</key>
<string>L</string>
</dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>TESTSERVICE</string>
</dict>
<key>NSMessage</key>
<string>doCapitalizeService</string>
<key>NSPortName</key>
<string>app name</string>
<key>NSSendTypes</key>
<array>
<string>NSStringPBoardType</string>
</array>
</dict>
</array>
Where "app name" is the project and app name.
I've added an interface for handling service calls as such:
#interface my_appService : NSObject
- (void)doCapitalizeService:(NSPasteboard *)pboard
userData:(NSString *)data
error:(NSString **)error;
#end
I've registered my service as such:
my_appService *serviceObject = [[my_appService alloc] init];
[NSApp setServicesProvider:serviceObject];
In applicationDidFinishLaunching (BUT!) for the main app interface, as per the cocoadev tutorial.
I can not find my service anywhere (re)log in did not help. Am I missing something obvious here? Any tips are greatly appreciated; oh, and happy easter, everyone :)