Swift DynamoDB Mapper Sending Empty Values - swift

I'm trying to use DynamoDB using the iOS Swift SDK. I'm using Cognito with Facebook as an external identity provider. Cognito is working fine - I've tested user sync and it works OK, so I believe I have the authentication set up. Here's how I'm setting up the SDK (I have the actual values of my identity pool in my code):
let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.USEast1,
identityPoolId:"<my-identity-pool-id>", identityProviderManager: FacebookProvider())
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
And here's my DynamoDB mapped class:
import Foundation
import AWSDynamoDB
class SavedItem : AWSDynamoDBObjectModel, AWSDynamoDBModeling {
var userId : Int?
var timestamp : Int?
class func dynamoDBTableName() -> String {
return "my-table"
}
class func hashKeyAttribute() -> String {
return "userId"
}
class func rangeKeyAttribute() -> String {
return "timestamp"
}
}
I've verified that my code has the correct table and attribute names and that the hash key and range key values on the table are identical, including case sensitivity, with the fields in my SavedItem class.
Here's how I'm instantiating the mapper:
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let savedItem = SavedItem()
savedItem?.userId = 1
savedItem?.timestamp = 2
dynamoDBObjectMapper.save(savedItem!).continueWith(block: { (task:AWSTask<AnyObject>!) -> Any? in
if let error = task.error as? NSError {
print("The request failed. Error: \(error)")
} else {
print("Save callback executing")
}
return nil
})
That code's more or less straight out from the AWS Documentation example. But, here's what I get back in the console when that code executes:
Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=0 "(null)" UserInfo={__type=com.amazon.coral.validate#ValidationException, message=Supplied AttributeValue is empty, must contain exactly one of the supported datatypes}
I bumped console logging up to debug, and it looks like the mapper is not sending any attributes from the SavedItem object. Here's what's in the console for the save request body:
Request body:
{"Key":{"userId":{},"timestamp":{}},"TableName":"my-table","AttributeUpdates":{}}
Any idea why the values are not getting included in the save request body?
Using aws-sdk-ios v2.6.1 on in Swift 4 on iOS 11.

The problem seems to be the type of userId and timestamp. Changing them from Int to NSNumber fixed the problem.

Yes i faced this same issue. and after struggling for 3 days.i found that AWSDynamoDBObjectModel not support in swift 4. please try in swift 3 version. You will get success.

Related

Validate AWS Cognito Token with Swift

I have nearly completed the process for a developer authenticated sign in using AWS. I cannot seem to authenticate the back-end token that I receive and cannot seem to find any current implementations that are performing developer authentication via a third-party back-end. The error that I get is listed below.
As of right now my code looks like this:
Class containing Custom identity provider:
import Foundation
import AWSCognitoIdentityProvider
class CustomIdentityProvider: NSObject, AWSIdentityProviderManager {
var tokens: [NSString: NSString]?
init(tokens: [NSString: NSString]) {
self.tokens = tokens
}
#objc func logins() -> AWSTask<NSDictionary> {
return AWSTask(result: tokens! as NSDictionary)
}
}
AWS-APIManager.swift {snippet}
/* obtained cognito token from my back-end via getOpenIdTokenForDeveloperIdentity*/
/* From here I my app receives an IdentityId and Token */
let client_cognito_id = String(describing: valid_response)
let session_token = json.dictionaryValue["Token"]!
let login_with_amazon = NSString(string: "cognito-identity.amazonaws.com")
let token = NSString(string: String(describing: session_token))
let customProviderManager = CustomIdentityProvider(tokens: [login_with_amazon: token])
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: self.AWS_REGION,
identityPoolId: "us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
identityProviderManager: customProviderManager
)
credentialsProvider.setIdentityProviderManagerOnce(customProviderManager)
credentialsProvider.getIdentityId().continue ({ (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("Error!!!: " + (task.error?.localizedDescription)!)
} else {
// the task result will contain the identity id
let cognitoId = task.result
print(cognitoId)
print("SUCCESS!!!")
}
return nil
})
}
For some odd reason odd reason I can cannot authenticate the token that I have received. I get an error "Invalid login token. Can't pass in a Cognito token.". I've tried to follow the documentation and piece together working code that I have found literally hundreds of sources and cannot seem to be able to move past this part of the authentication process. Any help would be greatly appreciated. Thanks!
I believe the issue here is that although you are supplying the token, you are not setting the identity id that you are getting from your backend. As such, it is calling GetId with a Cognito OpenIdConnectToken, which is not supported.
The simplest client implementation of Developer Authenticated Identities is to extend AWSCognitoCredentialsProviderHelper
Apologies for providing this in Objective C instead of Swift. In your implementation just override the token method.
- (AWSTask<NSString *> *)token {
//get the identity id and token from your server
//You can use AWSTaskCompletionSource if you don't have it and need to get it asynchronously.
//Once you have this information, simply set the identity id and return the token
self.identityId = identityId;
return [AWSTask taskWithResult:token];
}

Exception throw for null data with optional Int type in Realm

I am taking my first foray into using Realm (0.98.1 via Cocoapods, Xcode 7.2) and am running into a small problem that I am not sure how to solve.
I have a model class called Airport that declares a property
let elevationFt = RealmOptional<Int>()
I am creating a set of Airport objects and persisting them in the following way
public func cacheDataToPersistanceStore(data:NSArray) -> Bool {
var success = true
autoreleasepool {
do {
let realm = try Realm()
realm.beginWrite()
for object in data {
guard let dictionaryValues = object as? Dictionary<String, AnyObject> else {
debugPrint("Unable to convert data to correct type")
success = false
return
}
if(dictionaryValues["airportID"] as! Int == 6605) {
realm.create(Airport.self, value: dictionaryValues, update: true)
}
}
try realm.commitWrite()
}
catch(let e) {
debugPrint(e)
success = false
}
}
return success
}
For the airport entry in question, the dictionary that stores the relevant data looks to have a null value for the key "elevationFt", so I assume things will be OK for the optional Int property
Here is a string version of the dictionary:
["gps_code": 01MD, "ident": 01MD, "iata_code": , "local_code": 01MD, "keywords": , "elevationFt": , "type": seaplane_base, "municipality": Annapolis, "iso_country": US, "airportID": 6605, "longitudeDeg": -76.45600128173828, "latitudeDeg": 38.99919891357422, "iso_region": US-MD, "wikipedia_link": , "name": Annapolis Seaplane Base, "scheduled_service": no, "continent": NA, "home_link": ]
However once the create function starts for this set of data, an exception is thrown:
Terminating app due to uncaught exception 'RLMException', reason: 'Invalid value '' for property 'elevationFt''
I am guessing I have something set up incorrectly, but I am not quite sure how to fix this other than to clean my source data for that particular field.
The screenshot from the debugger shows that elevationFt is an empty string, which is not a number or null, so it is not a valid value for an optional int property.

How to invoke an AWS Lambda function in Swift

I can't find any documentation or examples on how to invoke a Lambda function in Swift but I've tried to extrapolate from the documentation using Objective-C and I'm still getting errors:
"Error in myFunction: ValidationException: Supplied AttributeValue is empty, must contain exactly one of the supported datatypes"
It appears that I'm not passing in the parameters to the function correctly when I invoke the lambda function from swift because the script tries to write to DynamoDB but one of the parameters is empty (this lambda script works when I invoke it in javascript/node).
let lambda = AWSLambda.defaultLambda()
let request = AWSLambdaInvocationRequest()
var context = [String: String]()
let jsonString = "{\"email\":\"example#example.com\",\"name\":\"example\"}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
request.clientContext = jsonData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
request.functionName = "myFunction"
lambda.invoke(request).continueWithBlock( {
(currentTask: AWSTask!) -> AWSTask in
if (currentTask.error != nil) {
// failed to execute.
print("Error executing: ", currentTask.error)
task.setError(currentTask.error)
} else {
print("token: ", currentTask.result)
task.setResult(currentTask.result)
}
return currentTask
})
You need to set the payload parameter to a map containing the data you want to pass.
let invocationRequest = AWSLambdaInvokerInvocationRequest()
invocationRequest.functionName = "myFunction"
invocationRequest.invocationType = AWSLambdaInvocationType.RequestResponse
invocationRequest.payload = ["email" : "example#example.com", "name" : "example"]
let lambdaInvoker = AWSLambdaInvoker.defaultLambdaInvoker()
let task = lambdaInvoker.invoke(invocationRequest).continueWithSuccessBlock() { (task) -> AWSTask! in
print("response: ", task.result)
}
Ryan Fitzgerald's answer gives me multiple compile-time errors, but I've had success with this version:
First, I have an initialization function with access credentials. Note that this is not the recommended secure access method for production code, but it is fine for testing and other purposes. It also assumes you have a Constants.swift file where you define the listed constants:
func initializeLambda() {
let credentialsProvider = AWSStaticCredentialsProvider.init(accessKey:Constants.AWS_ACCESS_KEY, secretKey: Constants.AWS_SECRET_KEY)
let defaultServiceConfiguration = AWSServiceConfiguration(region: Constants.AWS_REGION, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
}
For the remainder we can provide a version similar to the previous version. I removed the 'let task' because 'task' is not used in his example. Additionally, I've included the logical outline of some JSON parsing that you are likely to be doing inside the invoke task. Finally, I've changed to a continueWithBlock(). If you use a continueWithSuccessBlock() you will not enter this block when Amazon Lambda reaches its timeout window or if something else goes wrong with the request and typically you do want these situations to be handled here.
self.initializeLambda() //Call our previously written initialization function
let invocationRequest = AWSLambdaInvokerInvocationRequest()
invocationRequest.functionName = "functionName"
invocationRequest.invocationType = AWSLambdaInvocationType.RequestResponse
invocationRequest.payload = ["key1" : "value1", "key2" : "value2"]
let lambdaInvoker = AWSLambdaInvoker.defaultLambdaInvoker()
lambdaInvoker.invoke(invocationRequest).continueWithBlock() { (task: AWSTask) -> AWSTask in
print("response: ", task.result)
//In here you'll likely be parsing a JSON payload
if let payload: AnyObject = task.result?.payload {
if let error: AnyObject = payload.objectForKey("error") {
//If there is an error key in the JSON dictionary...
} else {
//If the JSON dictionary has no error key...
}
return task;
}
}
Tested and verified as functional on Swift 2.2 in Xcode 7.3.
The answers from both the Ryan's were great and useful and I just want to add a couple of additional thoughts.
In most cases, before you can invoke a Lambda, you might need to authenticate, so the errors you get might not necessarily be because of your Lambda calls but due to failing authentication. With AWS, however, there are several different ways to authenticate and this will change based on the credentials you have.
Ryan Davis shows you one way where your backend team has set up an AWS Access Key and an AWS Secret Key.
In my case, I had to authenticate using AWS Cognito Identity Pools and there are also User Pool authentication so you need to figure out what credentials your team has given you and read the appropriate authentication documentation.
Since I needed to use using AWS Cognito Identity Pools, all I had was the region and the identity pool id so in Swift 5 authentication for AWS Cognito Identity Pools
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: Constants.AWS_REGION,
identityPoolId: Constants.AWS_REGION.AWS_IDENTITY_POOL_ID)
let serviceConfiguration = AWSServiceConfiguration(region: Constants.AWS_REGION,
credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
And then the Lambda invocation more or less stays the same but just with slightly updated Swift 5 syntax:
if let invocationRequest = AWSLambdaInvokerInvocationRequest() {
invocationRequest.functionName = "function_name"
invocationRequest.invocationType = AWSLambdaInvocationType.requestResponse
invocationRequest.payload = ["key_1": "value_1"]
let lambdaInvoker = AWSLambdaInvoker.default()
lambdaInvoker.invoke(invocationRequest) { (awsLambdaInvokerInvocationResponse, error) in
guard let payload = awsLambdaInvokerInvocationResponse?.payload as? [String: String] else {
// Handle error here
return
}
if let userId = payload["message"] {
print("USR Id: \(userId)")
}
}
}
You will need to adjust your handling based on the structure of your payload returned to you by the Lambda, in my case it was:
{
"message": "user-id-8868-8475-8757"
}
Finally, remember to import the required libraries for your use case, for my above case I needed:
import AWSCore
import AWSLambda

How to properly Pin in Parse localDataStore?

I have Parse.enableLocalDatastore() in my app delegate before Parse.setApplicationId
Then I have var newPosts = PFObject(className: "Post") as a global variable.
Then I want to get 1,000 latest objects from the "Post" table from localDataStore that I enabled earlier so I do this:
var getNewPosts = PFQuery(className: "Post")
getNewPosts.fromLocalDatastore()
getNewPosts.limit = 1000
getNewPosts.orderByDescending("createdAt")
getNewPosts.findObjectsInBackgroundWithBlock {
(downloadedPosts, error) -> Void in
if downloadedPosts != nil && error == nil {
println(downloadedPosts) // I am getting table with no data
}
}
But I only get empty data rows.
If I comment out the getNewPosts.fromLocalDatastore() line results are fine.
I understand that I am missing the critical Pinning step but not sure from Parse documentation hoe and where to implement it. Can you please help?
You are getting no data....
reasons my be...
Wrong name of class (class names are case sensitive)
Data is not there in local storage
Try synchronous version of findObject: method and pin: method.

Get PFUser object custom values

I have custom value in a PFUser column called "website". I am trying to get this value using the code below but it does not seem to change on the device if I update the value from Parse.com on their website using the data viewer. It also does not update across devices.
Any Ideas?
websiteField.text = currentUser.objectForKey("website") as String
I have managed to get it working with the code below.
var currentUser = PFUser.currentUser()
currentUser.refreshInBackgroundWithBlock { (object, error) -> Void in
println("Refreshed")
currentUser.fetchIfNeededInBackgroundWithBlock { (result, error) -> Void in
self.websiteStr = currentUser.objectForKey("website") as String
self.websiteField.text = self.websiteStr
println("Updated")
println(self.websiteStr)
}
}