I'm writing an integration app harness that will test the functionality of our SDK in the real world and I'm planning to do so using iOS Action App extensions as subprocesses to initiate the Action Extensions as test cases. I followed the article below to accomplish that
https://ianmcdowell.net/blog/nsextension/
And so I created one action extension so far, which is to test starting the library manager of the SDK. It works on the simulator but when tested on the device the test fails, because it requires the Location Services to run the SDK.
I could override that service, but Xcode will generate this logs that complains about not accessing the NSUserDefaults. This adds 30 seconds of testing so I rather avoid that
2017-12-07 10:38:55.907542-0500 LibraryManagerTestExtension[2619:13235133] [User Defaults] Couldn't read values in CFPrefsPlistSource<0x1017041d0> (Domain: com.apple.Accessibility, User: kCFPreferencesCurrentUser, ByHost: No, Container: kCFPreferencesNoContainer, Contents Need Refresh: Yes): accessing preferences outside an application's container requires user-preference-read or file-read-data sandbox access, detaching from cfprefsd
2017-12-07 10:39:25.932663-0500 LibraryManagerTestExtension[2619:13235133] failed to open connection to AppleKeyStore
2017-12-07 10:39:25.933117-0500 LibraryManagerTestExtension[2619:13235133] Unexpected AppleKeyStore error: -536870212
2017-12-07 10:39:25.933951-0500 LibraryManagerTestExtension[2619:13235133] MKBDeviceUnlockedSinceBoot: MKBDeviceUnlockedSinceBoot fails with error(-1) for handle(0) with AKS error(-536870212)
2017-12-07 10:39:25.934237-0500 LibraryManagerTestExtension[2619:13235133] Attempting to create a background session before first device unlock!
2017-12-07 10:39:25.938302-0500 LibraryManagerTestExtension[2619:13235250] An error occurred on the xpc connection: Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.nsurlsessiond was invalidated." UserInfo={NSDebugDescription=The connection to service named com.apple.nsurlsessiond was invalidated.}
Here are my codes. Hope you can provide me some guidance on what things I'm missing and needed to be added to get the permissions.
import Foundation
import IntegrationExtension
import VSTB
public class LibraryManagerTestExtension : NSObject, NSExtensionRequestHandling {
var extensionContext : NSExtensionContext!
var libraryManager : QPLibraryManager!
public func beginRequest(with context: NSExtensionContext) {
print("Beginning request with context: %#", context.description);
extensionContext = context
guard let configContent = getConfigContent() else {
extensionContext.cancelRequest(withError: QPError(code: -1, description: "ConfigContent is empty"))
return
}
startLibraryManager(configContent)
}
func getConfigContent() -> [String: Any]? {
//Get the config path, which is the 1st first of the input items
let inputItems = extensionContext.inputItems
print("Input Items: \(inputItems)")
assert(inputItems.count == 1)
let inputItem = inputItems.first as? NSExtensionItem
assert(inputItem?.attachments?.count == 1)
return inputItem?.attachments?.first as? [String: Any]
}
fileprivate func startLibraryManager(_ contentConfig: [String: Any]) {
let foo = isOpenAccessGranted()
print("Access: \(foo)")
let configuration = QPLibraryConfiguration(dictionary: contentConfig)
//Disable location services by overriding it with custom values. This adds 30 seconds of testing though, which needs to be avoided
configuration?.setStartupLibraryConfigurationValue(0, for: .IOS_LOCATION_MANAGER_MODE)
let userLocation : [String : Any] = ["country" : "IN",
"territory" : "",
"city" : "Chennai",
"latitude" : 0,
"longitude" : 0]
configuration?.setRuntimeLibraryConfigurationValue(userLocation, for: .USER_LOCATION)
libraryManager = QPLibraryManager()
libraryManager.delegate = self
libraryManager.start(with: configuration)
}
}
extension LibraryManagerTestExtension : QPLibraryManagerDelegate {
public func libraryManager(_ libraryManager: QPLibraryManager!, didStartWith association: QPLibraryManagerAssociation!) {
//Handle Library Start
let item = NSExtensionItem()
extensionContext.completeRequest(returningItems: [item]) { (expired : Bool) in
print("Completed Request")
}
}
public func libraryManager(_ libraryManager: QPLibraryManager!, didFailToStartWith association: QPLibraryManagerAssociation!, error: QPError!) {
//Handle Library failed
extensionContext.cancelRequest(withError: error)
}
}
Here's the .plist file. Note that the action extension is a non-ui extensions that are manually triggered by the app. App groups are also enabled on both app and extension
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>RequestOpenAccess</key>
<true/>
<key>NSExtensionActivationRule</key>
<string>FALSEPREDICATE</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.app.non-ui-extension</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).LibraryManagerTestExtension</string>
</dict>
Related
I am trying to create an ios vpn client using Tunnelkit. I am following this tutorial.
https://github.com/passepartoutvpn/tunnelkit
am able to compile and run the application, but when I try to connect, the app crashes and throwing.
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error:
TunnelKit.OpenVPNTunnelProvider.ProviderConfigurationError.credentials(details:
"keychain.set()")
Anyone who had already set up tunnel kit OpenVPN, please help to resolve this issue.
func connect() {
let server = textServer.text!
let domain = textDomain.text!
let hostname = ((domain == "") ? server : [server, domain].joined(separator: "."))
let port = UInt16(textPort.text!)!
let socketType: SocketType = switchTCP.isOn ? .tcp : .udp
let credentials = OpenVPN.Credentials(textUsername.text!, textPassword.text!)
let cfg = Configuration.make(hostname: hostname, port: port, socketType: socketType)
let proto = try! cfg.generatedTunnelProtocol(
withBundleIdentifier: tunnelIdentifier,
appGroup: appGroup,
credentials: credentials
)
let neCfg = NetworkExtensionVPNConfiguration(title: "new title", protocolConfiguration: proto, onDemandRules: [])
vpn.reconnect(configuration: neCfg) { (error) in
if let error = error {
print("configure error: \(error)")
return
}
}
}
You need to follow the integration steps.
https://github.com/passepartoutvpn/tunnelkit#demo
Enable App Groups and Keychain Sharing capabilities
make sure the appGroup value is the same which you set in your target/Signings & Capabilities/App Groups
I am using the latest Swift documentation to write NetService, which am able to publish and search. But I am unable to remove service from the publisher. Not able to understand the role of RunLoop here. However, if I kill the app then service is getting removed.
This is the code am using to publish the service.
class ServicePublisher : NSObject {
var nsNetService : NetService!
init?(domain : String, type : String, name : String, port : Int32){
nsNetService = NetService(domain: domain, type: type, name: name, port: port)
if nsNetService == nil{
return nil
}
}
func publish() {
nsNetService.publish(options:NetService.Options.listenForConnections)
}
func setDelegate(delegate : NetServiceDelegate) {
nsNetService.delegate = delegate
}
func stop(){
nsNetService.stop()
}
}
nsNetService.stop() should stop the service and get it removed from the DNS SD. NetServiceBrowser is also able to detect the removal of service. My mistake was that I did not attached the UIButton with the function which was supposed to call stop() api and hence no service removal.
I'm currently facing a problem with implementing a specific feature into an NFC based iOS 13 app. When reading a tag, I'd like to get the unique hardware id and the NDEF message in one session. So far I've checked different sample projects, including code provided by Apple and was able to get the information I'm interested in, but not in the same reading session.
I simplified the following code snippets to better focus on the problem (missing error checks, etc.).
Using an NFCTagReaderSession to get the unique hardware id:
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
let tag = tags.first!
session.connect(to: tag) { (error: Error?) in
if case let .miFare(mifareTag) = tag {
print(mifareTag.identifier as NSData) //this is the info I'm interested in
}
}
}
The payload type of a message record however seems to be only available in a NFCNDEFReaderSession:
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
let tag = tags.first!
session.connect(to: tag, completionHandler: { (error: Error?) in
tag.queryNDEFStatus(completionHandler: { (ndefStatus: NFCNDEFStatus, capacity: Int, error: Error?) in
tag.readNDEF(completionHandler: { (message: NFCNDEFMessage?, error: Error?) in
var statusMessage: String
if nil != error || nil == message {
statusMessage = "Fail to read NDEF from tag"
} else {
statusMessage = "Found 1 NDEF message"
let payload = message.records.first!
if let type = String(data: payload.type, encoding: .utf8) {
print("type:%#", type) //this is the info I'm interested in
}
}
session.alertMessage = statusMessage
session.invalidate()
})
})
})
}
As you can see, I can either get the hardware id, using a NFCTagReaderSession or the payload type of a message record, using a NFCNDEFReaderSession. Am I missing something here or are there indeed two different reading sessions required to get the information I'm interested in? Thanks in advance.
I finally found the solution for this :)
You can actually read the NDEF data in the delegate functions of your NFCTagReaderSession but in iOS 13 you have to use another delegate.
It seems in iOS 11 and 12 NFCTag was a protocol but in iOS 13 NFCTag became an enum and the former protocol was renamed to __NFCTag.
An __NFCTag can be casted to an NFCNDEFTag and then you can read NDEF data as usual.
To get an __NFCTag in your delegate functions you need to use __NFCTagReaderSessionDelegate instead.
To initialize your session you prepend __ to the pollingOption argument label of the initializer.
To connect to the tag you need to use __connect.
Here is how to read the identifier and the NDEF data.
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [__NFCTag]) {
let tag = tags.first!
session.__connect(to: tag) { (error: Error?) in
if let ndefTag = tag as? NFCNDEFTag {
if let miFareTag = ndefTag as? NFCMiFareTag {
// here you can get miFareTag.identifier like in your first code sample
}
ndefTag.readNDEF(completionHandler: { (message, error) in
// here you can read NDEF data like in your second code sample
})
}
}
}
I had the same issue and found solution in Apple's sample project, which I hardly recommend to read.
FYI:
Avoid using classes and methods with underscores as a prefix, it could lead you to the application rejection on AppStore.
I am developing a message filter app extension. I searched about it and got to know about identity lookup which is newly introduced concept in ios 11.0. I came to know that I have to insert keys in info.plist
<key>NSExtension</key>
<dict>
<key>NSExtensionPrincipalClass</key>
<string>MessageFilterExtension</string>
<key>NSExtensionAttributes</key>
<dict>
<key>ILMessageFilterExtensionNetworkURL</key>
<string>https://www.example-sms-filter-application.com/api</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.identitylookup.message-filter</string>
</dict>
But after inserting these, When in build app, I am getting black screen. I have just inserted this code only. nothing else in view controller.swift file and made a file MessageFilterExtension.swift with same class name.
This is the documentation: https://developer.apple.com/documentation/identitylookup/creating_a_message_filter_app_extension
As mentioned here, to create an extension all you have to do is create a new iOS app and from the Xcode menu select File/New/Target and select Message Filter Extension.
In your extension a class that inherits from ILMessageFilterExtension is automatically created for you. It also conforms to the ILMessageFilterQueryHandling protocol which has a single requirement, the handle(_ queryRequest:context:completion:) method.
In this method a query request object of type ILMessageFilterQueryRequest is passed to you and you will have access to the sender and messageBody of the message.
After you apply your filter rules, you need to call the completion closure which will take a single parameter of type ILMessageFilterQueryResponse that you need to create and set it’s action property.
If you want to prevent the message from being shown you need to set the action type to .filter. The other 2 options .none and .allow will have no effect, the message will be shown normally.
That’s all you have to do to create a Message Filter Extension.
Below is an example from the Filter Spam SMS app that uses a CoreData shared container to load the list of keywords created by the user, which are used to filter the message body:
import IdentityLookup
final class MessageFilterExtension: ILMessageFilterExtension {
var words:[Item] = []
let stack = CoreDataStack()
func loadItems() {
let context = stack.persistentContainer.viewContext
let itemDAO = ItemDAO(managedObjectContext: context)
let allItems = itemDAO.fetchItmes()
self.words = allItems.flatMap({ item in
return item.value != nil ? item : nil
})
}
}
extension MessageFilterExtension: ILMessageFilterQueryHandling {
func handle(_ queryRequest: ILMessageFilterQueryRequest, context: ILMessageFilterExtensionContext, completion: #escaping (ILMessageFilterQueryResponse) -> Void) {
let action = self.offlineAction(for: queryRequest)
let response = ILMessageFilterQueryResponse()
response.action = action
completion(response)
}
private func offlineAction(for queryRequest: ILMessageFilterQueryRequest) -> ILMessageFilterAction {
guard let messageBody = queryRequest.messageBody?.lowercased() else { return .none }
self.loadItems()
for word in self.words {
if let value = word.value,
messageBody.contains(value.lowercased()) {
return .filter
}
}
return .allow
}
}
So I can read information from my database when (in the simulator) I'm logged into my iCloud, however, everyone who has my app will (obviously) not be. When I try to access database when Im not logged in, this error message appears:
<CKError 0x7fc1e3416510: "Request Rate Limited" (7/2008); "This operation has been rate limited"; Retry after 3.0 seconds>
followed by:
Error Domain=NSCocoaErrorDomain Code=4097 "The operation couldn’t be completed. (Cocoa error 4097.)" (connection to service named com.apple.cloudd) UserInfo=0x7fc1e352f800 {NSDebugDescription=connection to service named com.apple.cloudd}
Code:
//VARIABLES********************************************************
#IBOutlet var questions: UILabel!
var resultsOfDB : String = ""
var indexes : [Int] = []
var counter : Int = 0
var newStr : String = ""
//*****************************************************************
#IBAction func getNewQbutton(sender: AnyObject) {
let container = CKContainer.defaultContainer()
var publicDB = container.publicCloudDatabase
let myQuery = CKQuery(recordType: "QuestionsTable", predicate: NSPredicate(value: true))
publicDB.performQuery(myQuery, inZoneWithID: nil){
results, error in
if error != nil {
println(error)
}
else
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.resultsOfDB = results.description
//for each character in resultsOfDB
for character in self.resultsOfDB{
if(character == "\""){
self.indexes.append(self.counter)
}
self.counter++
}
self.newStr = self.resultsOfDB.substringWithRange(Range<String.Index>(start: advance(self.resultsOfDB.startIndex, self.indexes[0] + 1), end: advance(self.resultsOfDB.endIndex, -(self.counter - self.indexes[1]))))
self.questions.text = self.newStr
})
}
}
}
Does anyone know how someone can read in my database when they aren't logged in to my iCloud account? Thanks!
The public database is only readable with no login in the production environment, not in the development environment.
Apple's documentation says:
In development, when you run your app through Xcode on a simulator or a device, you need to enter iCloud credentials to read records in the public database. In production, the default permissions allow non-authenticated users to read records in the public database but do not allow them to write records.
See CloudKit Quick Start.
This is a simulator bug (The Apple team is likely to fix this in the coming updates).
If your simulator is not logged into iCloud, this error will occur.
However, this error will not occur in the virtual device even if that device does not have iCloud logged in