AWS Cognito - offline data availability - javascript

I am building a phonegap app and use AWS Cognito to store the User data. In the description of Cognito, it is said, that the data is offline available. This does not work in my code:
var add_data;
function getCognitoData(){
var params = {
IdentityPoolId: COGNITO_IDENTITY_POOL_ID,
Logins: {
'graph.facebook.com': FACEBOOK_TOKEN
}
};
AWS.config.region = AWS_REGION;
AWS.config.credentials = new AWS.CognitoIdentityCredentials(params);
AWS.config.credentials.get(function(err) {
if (err) {
console.log("Error: "+err);
return;
}
console.log("Cognito Identity Id: " + AWS.config.credentials.identityId);
var syncClient = new AWS.CognitoSyncManager();
syncClient.openOrCreateDataset('myDataset', function(err, dataset) {
dataset.get('myKey', function(err, value) {
console.log(value, err);
});
add_data = function(thisid, thisval) {
dataset.put(thisid, thisval, function(err, record){
dataset.synchronize({
onSuccess: function(data, newRecords) {
console.log("success", newRecords);
},
onFailure: function(err) {
console.log("error", err);
},
onConflict: function(dataset, conflicts, callback) {
console.log("sync conflict", dataset, conflicts);
var resolved = [];
for (var i=0; i<conflicts.length; i++) {
resolved.push(conflicts[i].resolveWithRemoteRecord());
}
dataset.resolve(resolved, function() {
return callback(true);
});
}
});
});
}
});
});
}
The AWS Credentials for the Identity Pool and the Facebook Token are previously set, and work in the online mode, but I don't get the dataset data, when being offline.
Am I doing something wrong or is it generally not possible to get the Cognito Dataset data while being offline? I read, that the data is actually being held in the local storage.
I am using the current AWS SKD (Release v2.1.42) and the Amazon Cognito JS.

It's possible to get the data offline. You need to synchronize the dataset to get whatever contents may be inside, otherwise them being empty is expected. Are you doing that? If not, try doing that, but if so, can you update your code above?

There was a bug with the aws-sdk-js causing the offline bug. CognitoSync depends on aws-sdk-js. Should be working now as of aws-sdk-js#2.7.21. Make sure you update.

Related

How to refresh credentials in the AWS JS SDK v3?

I am trying to migrate my V2 application to the V3 SDK and I can't seem to figure out how to refresh the credentials after the following call throws a NotAuthorizedException with "Invalid login token. Token expired: 1615301743 >= 1615108625".
credentials = await cognitoIdentity.send(
new GetIdCommand({
Storage: config,
IdentityPoolId: config.get("IdentityPoolId"),
Logins: {
[`cognito-idp.${awsRegion}.amazonaws.com/${upid}`]: idToken,
},
}),
);
In V2 there was a method called refresh() on the Credentials object which I could call and by doing so refresh the credentials. How to do the same thing with the new API?
The following code sample (Check Use case 4) I've found in the following link:
https://www.npmjs.com/package/amazon-cognito-identity-js
//refreshes credentials using AWS.CognitoIdentity.getCredentialsForIdentity()
AWS.config.credentials.refresh(error => {
if (error) {
console.error(error);
} else {
// Instantiate aws sdk service objects now that the credentials have been updated.
// example: var s3 = new AWS.S3();
console.log('Successfully logged!');
}
});
It works for me when implemented in AWS Lambda. Hope this is what you are looking for.
Regards,
Edit:
I've just tested the following code, it works in my react-js app:
return new Promise((resolve, reject) =>
cognitoUser.authenticateUser(authenticationDetails, {
// If the provided credentials are correct.
onSuccess: function(result) {
var accessToken = result.getAccessToken().getJwtToken();
//POTENTIAL: Region needs to be set if not already set previously elsewhere.
AWS.config.region = 'us-east-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IdentityPoolId, // Your identity pool id here.
Logins: {
// Change the key below according to the specific Region your User Pool is in.
`cognito-idp.${awsRegion}.amazonaws.com/${upid}`: result
.getIdToken()
.getJwtToken(),
},
});
//refreshes credentials using AWS.CognitoIdentity.getCredentialsForIdentity()
AWS.config.credentials.refresh(error => {
if (error) {
console.error(error);
} else {
resolve(AWS.config.credentials)
}
});
},
// If the provided credentials are incorrect.
onFailure: function(err) {
console.log(err);
reject(
err.message || JSON.stringify(err)
);
},
})
);

AWS Cognito: Can't get Credentials

i can't get the Credentials for my CognitoIdentity. When the User is successfully authenticated, he needs to get a Identity to access other AWS Services. In my case thats AWS IoT. But for somehow, i can't get any credentials.
This is the Error Message:
Error retrieving credentials: NotAuthorizedException: Access to
Identity 'eu-central-1:XXXXXXXXXX' is
forbidden.
My Code is almost exactly like the Tutorial on Github:
var cognitoUser = new AWSCognito.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log("Logged in");
console.log('access token + ' + result.getAccessToken().getJwtToken());
// window.location.href = "index.html";
AWS.config.region = AWSConfiguration.region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: AWSConfiguration.IdPoolId,
Logins : {
'cognito-idp.eu-central-1.amazonaws.com/eu-central-1_XXXX' : result.getIdToken().getJwtToken()
}
});
var cognitoIdentity = new AWS.CognitoIdentity();
AWS.config.credentials.get(function(err, data) {
if (!err) {
console.log('retrieved identity: ' + AWS.config.credentials.identityId);
var params = {
IdentityId: AWS.config.credentials.identityId
};
cognitoIdentity.getCredentialsForIdentity(params, function(err, data) {
if (!err) {
thingShadows.updateWebSocketCredentials(data.credentials.AccessKeyId,
data.credentials.SecretKey,
data.credentials.SessionToken);
}
else {
console.log('error retrieving credentials: ' + err);
}
});
}
else {
console.log('error retrieving identity:' + err);
}
});
}
});
Please note that i skipped not related code.
authenticated users have full access to all AWS services i'm using.
I don't think you need to call cognitoIdentity.getCredentialsForIdentity(). Your IAM keys should be put into the AWS.config.credentials object when you call AWS.config.credentials.get(). You can access them directly in the callback you provide when you call it.
In other words, when you're logging out the retrieved identity: to the console, the credentials object should already have your secret key, access key id, and session token in it.
All of this (give or take a curly brace):
var params = {
IdentityId: AWS.config.credentials.identityId
};
cognitoIdentity.getCredentialsForIdentity(params, function(err, data) {
if (!err) {
thingShadows.updateWebSocketCredentials(data.credentials.AccessKeyId,
data.credentials.SecretKey,
data.credentials.SessionToken);
}
else {
console.log('error retrieving credentials: ' + err);
}
});
Can probably be replaced with something like this:
thingShadows.updateWebSocketCredentials(AWS.config.credentials.accessKeyId,
AWS.config.credentials.secretKey,
AWS.config.credentials.sessionToken);
If you pass in a Logins map with the user pool id and access token in it, the getCredentialsForIdentity() call might succeed; I didn't test it. I haven't yet run into a use case where I needed to use this particular API, and I suspect you don't need it either.
Source: I work on a 100% javascript application that uses both authenticated and unauthenticated Cognito identities. We don't call getCredentialsForIdentity() anywhere, and trying to insert it produced the same error you're getting.

node.js Query database using mongoose in socket.io

Right now, I'm writing a small web application using node.js with my partner. In the application, we need to query some data from database using mongoose, then, send the data to the client.Trying to do this, I'm using the code below:
io.sockets.on('connection', function (socket) {
var id = socket.id;
socket.on('request', function (data) {
mongoose.createConnection(dbConfig.url);
var ClassX = require("./models/" + data.request);
var class_query = ClassX.findOne({}, function (err, result) {
if (err) {
console.log("There is an error: "+ err);
return handleError(err);
}
if (!result) {
console.log("No result!");
}
io.sockets.to(id).emit("response", result);
});
});
});
But it doesn't work.
And when the code is running, the log file of the database show some information like below:
2017-02-23T15:40:48.426+0800 I NETWORK [thread1] connection accepted from 127.0.0.1:5015 #3 (3 connections now open)
2017-02-23T15:40:48.428+0800 I NETWORK [conn3] received client metadata from 127.0.0.1:5015 conn3: { driver: { name: "n
odejs", version: "2.2.24" }, os: { type: "Windows_NT", name: "win32", architecture: "x64", version: "10.0.14393" }, plat
form: "Node.js v6.9.5, LE, mongodb-core: 2.1.8" }
The information "[conn3] received client metadata ..." seems weird. And, from the experience of many attempts, I think the callback function of ClassX.finOne() has not been executed. And I totally don't know why.

AWS Lambda function processes same dynamodb stream multiple times. What am I missing?

I have written a node.js lambda function that triggers based on a dynamodb stream when new records are inserted into a particular table.
The function receives only new events, filters for inserted records, and then for each record, uses a couple of fields to retrieve data from other tables. Using this combined data a message is composed and sent via SNS to specific target ARN.
The function performs correctly. All the relevant data is retrieved, and a push notification is sent out.
However, for some reason the function appears to be called several times for the same stream, and processes the newly inserted records several times. The result is the target device receiving the same push notification several times.
Should I be placing the callback in a different place, or am I not calling on the context correctly?
This is the function:
'use strict';
var AWS = require("aws-sdk");
var dynamodb = new AWS.DynamoDB();
var sns = new AWS.SNS();
console.log('Loading function');
exports.handler = (event, context, callback) => {
console.log('Received event:', JSON.stringify(event, null, 2));
event.Records.forEach((record) => {
console.log(record.eventID);
console.log(record.eventName);
console.log('DynamoDB Record: %j', record.dynamodb);
if (record.eventName == 'INSERT') {
var matchId = record.dynamodb.NewImage.eventId.S;
var match_params = {
Key: {
"eventId": {
S: matchId
}
},
TableName: "xxxxxxxxxxx-mobilehub-xxxxxxx-Event"
};
//retrieve the match information from Event table
dynamodb.getItem(match_params, function(err, data) {
var match_description = "";
if (err) {
console.log(err, err.stack);
context.fail('No match event record found in Event table');
} else {
match_description = data.Item.description.S;
var uId = record.dynamodb.NewImage.participantUserId.S; //participantUserId
var user_params = {
Key: {
"userId": {
S: uId
}
},
TableName: "xxxxxxxxxxx-mobilehub-xxxxxxxxx-User"
};
//retrieve the user record from User table
dynamodb.getItem(user_params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
context.fail('Error occurred. See log.');
} else {
console.log(data); // successful response
if (data.length === 0) {
console.log("No User Record Found.");
context.fail('No user found for participantUserId.');
} else {
var deviceARN = data.Item.device_arn.S;
if (deviceARN <= 1) {
console.log("User has not registered their device for push notifications.");
context.fail('User has not registered for notifications');
} else {
var json_message = JSON.stringify({
APNS_SANDBOX: JSON.stringify({
aps: {
alert: "You are playing in an upcoming match " + match_description,
badge: 1,
sound: 'default'
}
})
});
var snsparams = {
Message: json_message,
MessageStructure: 'json',
TargetArn: deviceARN
};
sns.publish(snsparams, function(err, data) {
if (err) {
console.log(err); // an error occurred
context.fail('SNS send failed. See log.');
} else {
console.log(data); // successful response
context.success('Push notification sent to user.');
}
});
}
}
}
});
}
});
}
});
callback(null, `Successfully processed ${event.Records.length} records.`);
};
In my case, I added the same event source multiple times.
Quote from the conversation with an AWS support engineer:
Using my internal tools, I noticed that the Lambda function xxxxxx has
the event source:
arn:aws:events:my_region:my_acct_id:rule/my_event_target
configured twice as push event source. This means that this might be the cause
why you are seeing two invokes at every minute. Would you please
confirm on your side if this event is configured twice for the $LATEST
version of your lambda and also confirm if it's intended?
I hope this could save someelse :)
In your lambda page at the bottom, try tweaking "Concurrency" Unreserved account concurrency to 1 and "Asynchronous invocation" Retry attempts to 0 . As a test try these and observe the behaviour. Might help.

Saving to MongoDB when Socket.io event is emitted

I'm utilizing a MEAN stack and Socket.io to pull images from the real-time Instagram API. Everything is working great, but I now want to begin saving image data to a MongoDB database so I have a "history" of images from locations (rather than simply the most recent photos).
Below is the relevant (working) code I have so far:
Node server-side code to handle new photo updates from Instagram API and emit event to Angular controller:
// for each new post Instagram sends the data
app.post('/callback', function(req, res) {
var data = req.body;
// grab the object_id (as geo_id) of the subscription and send as an argument to the client side
data.forEach(function(data) {
var geo_id = data.object_id;
sendUpdate(geo_id);
});
res.end();
});
// send the url with the geo_id to the client side
// to do the ajax call
function sendUpdate(geo_id) {
io.sockets.emit('newImage', { geo_id: geo_id });
}
Angular controller code when 'newImage' event is received:
socket.on('newImage', function(geo_id) {
// pass geo_id into Instagram API call
Instagram.get(geo_id).success(function(response) {
instagramSuccess(response.geo_id, response);
});
// Instagram API callback
var instagramSuccess = function(scope,res) {
if (res.meta.code !== 200) {
scope.error = res.meta.error_type + ' | ' + res.meta.error_message;
return;
}
if (res.data.length > 0) {
$scope.items = res.data;
} else {
scope.error = "This location has returned no results";
}
};
});
Angular factory to handle calls to Instagram API:
angular.module('InstaFactory', []).factory('Instagram', function($http) {
var base = "https://api.instagram.com/v1";
var client_id = 'MY-CLIENT-ID';
return {
'get': function(geo_id) {
var request = '/geographies/' + geo_id.geo_id + '/media/recent?client_id=' + client_id;
var url = base + request;
var config = {
'params': {
'callback': 'JSON_CALLBACK'
}
};
return $http.jsonp(url, config);
}
};
});
I also have the following Angular Controller which currently GETS details of each location from my Stadia mongoDB model. This model also contains an (empty for now) 'photos' array that I want to PUSH photo details (url, username, user profile url, etc.) onto each time I receive them from Instagram:
angular.module('StadiaFactory', []).factory('Stadia', function($http) {
var base = "http://localhost:6060/api/stadia/";
return {
'get': function(id) {
var request = id;
var url = base + request;
var config = {
'params': {
'callback': 'JSON_CALLBACK'
}
};
return $http.jsonp(url, config);
}
};
});
This is where I get confused. Where do I fire off the PUT request to my Stadia API and does this Node route for my Stadia API look reasonable? Note: I omitted my GET route which works perfectly. PUT is just throwing me for a loop:
// add photos to stadium photos array
app.put('/api/stadia/:stadium_id', function(req, res) {
// use mongoose to get and update stadium
Stadium.findByIdAndUpdate(req.params.stadium_id,
{$push: {"photos": {img: ?, link: ?, username: ?, profile_picture: ?}}},
{safe: true, upsert: true},
function(err, stadium) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err)
res.send(err)
res.jsonp(stadium); // return stadium in JSON format
});
});
Well there are a few problems with your current structure.
When your callback route is called, with a possibility of N objects in it, you're triggering your socket event and retrieving all the last photos of your geography each time. So let's say you will have 3 new objects, you will call 3 times the same thing to get the same data, which is a bit loss when you have the power of the sockets.
You can also have problems if you try to get the object data from the client-side and PUTing it to your server, since all your clients may receive the socket and you could end-up with duplicates, not to mention that this is a lot of traffic for not much, and this will burn your quota API limit, which is also not safe on the client-side since everyone can see your key.
To me, a good way to get something working (even if I don't really know what your :stadium_id param stands for) is to get the info you want directly on the server side in your callback using the request module.
You should only get the pictures, because you can retrieve a lot of things like users, tags or videos that you may don't want to get. So you will have to listen for the image objects, and nothing else.
You could have something like this:
var request = require('request');
var CLIENT_ID = 'yourId';
function newImage(data) {
io.sockets.emit('newImage', data);
}
app.post('/callback', function (req, res) {
//loop in all the new objects
req.body.forEach(function (data) {
if (data.type !== 'image') { return ; }
//BTW I think you should try with the id property instead of object_id
request('https://api.instagram.com/v1/media/' + data.object_id + '?access_token=' + CLIENT_ID,
function (error, response, body) {
if (error) { return ; }
//Here we have one JSON object with all the info about the image
var image = JSON.parse(body);
//Save the new object to your DB. (replace the STADIUM_ID)
Stadium.findByIdAndUpdate(STADIUM_ID, { $push: {'photos':
{ img: image.images.standard_resolution.url,
link: image.link,
username: image.user.username,
profile_picture: image.user.profile_picture
}}},
{ safe: true, upsert: true });
//Send a socket to your client with the new image
newImage({
id: image.id,
img: image.images.standard_resolution.url,
link: image.link,
username: image.user.username,
profile: image.user.profile_picture
});
}
});
res.end();
});
And then in your client, you will only have to push the new images received in the newImage socket event in the $scope.items.

Categories

Resources