AWS Mobile Blog

Push synchronization, JavaScript sync and eu-west-1 for Amazon Cognito

by Stefano Buliani | on | Permalink | Comments |  Share

Today, we are excited to announce the launch of push synchronization, a new feature in Amazon Cognito that enables your app to receive a silent push notification whenever its data stored in the cloud changes, a new SDK for JavaScript in developer preview that helps you save data in the cloud, cache it on the local browser or device, and synchronize it across the web and mobile devices. We are equally excited to announce the availability of Amazon Cognito in the EU West 1 region. This means you can now use the Cognito console to create an identity pool in your chosen region, and all of your user data will be stored only in that region.

JavaScript sync SDK

The new Cognito Sync Manager SDK for JavaScript allows your web app to store data in the cloud for your users and synchronize across other websites as well as mobile apps. The JavaScript Sync SDK uses the localStorage to create a local cache for the data, exactly like our mobile SDKs. This allows your web apps to work even if there is no connectivity.

We are releasing this SDK in developer preview. Send us your feedback using GitHub issues and our developer forum.

Getting started

  1. The first step is to setup an identity pool in the Amazon Cognito console
  2. Next you need to download the AWS JavaScript SDK for the browser
  3. You also need to download and include the SyncManager SDK

Now you are ready to write your web app, save data to the cloud, and leverage the local cache created by the client SDK. First, include the required JavaScript SDKs in your page.

<script src="js/aws-sdk-2.0.19.min.js" type="text/javascript"></script>
<script src="js/amazon-cognito.js" type="text/javascript"></script>

Now that you have included the JavaScript SDK, you can initialize Amazon Cognito and connect to your identity pool.

// a global variable for the sync client
var cognitoSyncClient = null;

// first we configure the AWS SDK and the Cognito
// Credentials provider
AWS.config.region = 'us-east-1';
var cognitoParams = {
    AccountId: 'AWS_ACCOUNT_ID',
    IdentityPoolId: 'YOUR_IDENITY_POOL_ID',
    RoleArn: 'arn:aws:iam::123123123:UNAUTHENTICATED_ROLE_ARN'
};

// initialize the credentials provider and retrieve the 
// AWS credentials
AWS.config.credentials = new AWS.CognitoIdentityCredentials(cognitoParams);
    
AWS.config.credentials.get(function() {
    // once we have the credentials we can initialize the 
    // Cognito sync client
    cognitoSyncClient = new CognitoSyncManager();
});

Once the sync client is initialized, we can start reading and writing data from datasets. Data written to a Dataset is automatically written to the local cache, so that it’s always available for your web app, and the synchronize method pushes local changes to the cloud store and retrieves new Record from the cloud.

// first we use the sync client to open a Dataset
cognitoSyncClient.openOrCreateDataset("MyDataset", function(err, dataset) {
    // now that we have a dataset we can read and write 
    // key/value pairs from it
    dataset.get("MyKey", function(err, value) {
        // save your string value somewhere or display it
    });

    // we write an object to the MyKey key in the current dataset
    // JSON.stringify transforms our objects into strings to be stored
    dataset.put("MyKey", JSON.stringify(myData), function(err, record) {
        // if there were no errors we can synchronize this data
        // to push it to the cloud sync store
        if ( !err ) {
            dataset.synchronize({
                onSuccess: function(dataset, newRecords) {
                    console.log("data saved to the cloud and newRecords received");
                },
                onFailure: function(err) {
                    console.log("Error while synchronizing data to the cloud: " + err);
                },
                onConflict: function(dataset, conflicts, callback) {
                    // if there are conflicts during the synchronization
                    // we can resolve them in this method
                    var resolved = [];

                    for (var i=0; i < conflicts.length; i++) {

                        // Take remote version.
                        resolved.push(conflicts[i].resolveWithRemoteRecord());

                        // Or... take local version.
                        resolved.push(conflicts[i].resolveWithLocalRecord());

                        // Or... use custom logic.
                        var newValue = conflicts[i].getRemoteRecord().getValue() + conflicts[i].getLocalRecord().getValue();

                        resolved.push(conflicts[i].resovleWithValue(newValue);

                    }

                    dataset.resolve(resolved, function(err) {
                        if ( !err ) 
                            callback(true);
                    });
                },

                onDatasetDeleted: function(dataset, datasetName, callback) {
                    // Return true to delete the local copy of the dataset.
                    return callback(true);
                },

                onDatasetMerged: function(dataset, datasetNames, callback) {
                    // Return false to handle dataset merges outside the synchroniziation callback.
                    return callback(false);

                }
            });
        }
    });
}

Push synchronization

Amazon Cognito helps you save user data in the cloud and synchronize across all of an end user’s devices. You can now choose to use push synchronization to synchronize data as soon as it is changed in the cloud, to make your customers’ experience completely seamless when working with your app across multiple devices.

Amazon Cognito uses the Amazon Simple Notification Service (SNS) to send a silent push notification, and alert all the devices connected to a Cognito identity of a data change. This allows your application to synchronize the changes made in the cloud sync store as soon as new data is available, without having to manually check the sync store every time.

Getting started

  1. The first step is to create an identity pool with Amazon Cognito. Use the console to create a new Identity pool or open an existing one.
  2. Next, navigate to the SNS console and add a new app.
  3. SNS asks you for an application name and the details of your chosen push platform. The SNS documentation guides you through the setup process for each platform.
  4. Once you have set up an application in SNS, you can navigate to the Cognito console. 
  5. From the Identity Pool page select Edit Identity Pool in the top right corner of the screen. The edit screen lets you select the SNS apps the pool should be linked to.

Register the device token

When your application starts, and registers with your chosen push platform, it receives a token. You need to register this token with the Amazon Cognito APIs. In order to use the sync client, you have to authenticate with AWS using the Cognito credentials provider.

iOS

// you need to import the AWSCore library from the AWS iOS SDK
#import <AWSiOSSDKv2/AWSCore.h>
#import <AWSCognitoSync/Cognito.h>
 
// initialize the Cognito credentials provider with the values from
// your identity pool.
AWSCognitoCredentialsProvider *credentialsProvider = [AWSCognitoCredentialsProvider
    credentialsWithRegionType:AWSRegionUSEast1
                    accountId:@"1234567890", // your AWS Account id          
               identityPoolId:@"us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", // your identity pool id        
                unauthRoleArn:@"arn:aws:iam::XXXXXXXXXX:role/YourRoleName",// an unauthenticated role ARN     
                  authRoleArn:@"arn:aws:iam::XXXXXXXXXX:role/YourRoleName" // an authenticated role ARN
];

// initialize the Cognito sync client
AWSCognito *syncClient = [AWSCognito defaultCognito];

// Send the device token received from APNS to Cognito
[[syncClient registerDevice: [devToken base64EncodedDataWithOptions:0]] continueWithBlock:^id(BFTask *task) {
    if(task.error){
        NSLog(@"Unable to registerDevice: %@", task.error);
    } else {
        NSLog(@"Successfully registered device with id: %@", task.result);        
    }
}];

Android

// import the CognitoCredentialsProvider object from the auth package
import com.amazonaws.auth.CognitoCachingCredentialsProvider;
import com.amazonaws.mobileconnectors.cognito.*;
import com.amazonaws.regions.Regions;
 
 
// initialize a credentials provider object with your Activity’s context and
// the values from your identity pool 
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
    getContext(), // get the context for the current activity        
    "1234567890", // your AWS Account id     
    "us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", // your identity pool id    
    "arn:aws:iam::XXXXXXXXXX:role/YourRoleName",// an authenticated role ARN
    "arn:aws:iam::XXXXXXXXXX:role/YourRoleName", // an unauthenticated role ARN
    Regions.US_EAST_1 //Region
);

// Get the sync manager instance and prepare your GCM registration id
CognitoSyncManager client = CognitoSyncClientManager.getInstance();

GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
String registrationId = gcm.register(SENDER_ID);

	try{ // send the device registration id to Cognito client.registerDevice("GCM", registrationId); } catch(RegistrationFailedException rfe){ Log.e(TAG, "Failed to register device for push sync", rfe); } catch(AmazonClientException ace){ Log.e(TAG, "An unknown error caused registration for push sync to fail", ace); }

 

Subscribing to updates

Once you have registered the device with Amazon Cognito, you can subscribe your application to receive push notifications whenever a dataset in the cloud sync store is updated. The Dataset exposes a subscribe method. We have also created utility methods in the AWSCognito client to automatically subscribe your app to all datasets.

iOS

// call the subscribe method on a specific dataset
[[[syncClient openOrCreateDataset:@"MyDataset"] subscribe] continueWithBlock:^id(BFTask *task) {
    if(task.error){
        NSLog(@"Unable to subscribe to dataset: %@", task.error);
    } else {
        NSLog(@"Successfully subscribed to dataset: %@", task.result);        
    }
}];

// you can also use the subscribeToAll method in the AWSCognito object
[[syncClient subscribeAll] continueWithBlock:^id(BFTask *task) {
    if(task.error){
        NSLog(@"Unable to subscribe to all datasets: %@", task.error);
    } else {
        NSLog(@"Successfully subscribed to all datasets: %@", task.result);        
    }
}];

Android

// open the dataset you want to subscribe to 
Dataset trackedDataset = client.openOrCreateDataset("myDataset");
if (client.isDeviceRegistered()) {
    try {
        trackedDataset.subscribe();
    } catch (SubscribeFailedException sfe) {
        Log.e(TAG, "Failed to subscribe to datasets", sfe);
    } catch (AmazonClientException ace) {
        Log.e(TAG, "An unknown error caused the subscription to fail", ace);
    }
}

// You can also use the subscribeToAll method
if (client.isDeviceRegistered()) {
    try {
        client.subscribeAll();
    } catch (SubscribeFailedException sfe) {
        Log.e(TAG, "Failed to subscribe to datasets", sfe);
    } catch (AmazonClientException ace) {
        Log.e(TAG, "An unknown error caused the subscription to fail", ace);
    }
}

At any point, you can unsubscribe from a dataset by calling the unsubscribe method.

iOS

[[[syncClient openOrCreateDataset:@”MyDataset”] unsubscribe] continueWithBlock:^id(BFTask *task) {
    if (task.error){
        NSLog(@"Unable to unsubscribe from dataset: %@", task.error);        
    } else {
        NSLog(@"Successfully unsubscribed from dataset: %@", task.result);        
    }
}];

Android

Dataset trackedDataset = client.openOrCreateDataset("myDataset");
if (client.isDeviceRegistered()) {
    try {
        trackedDataset.unsubscribe();
    } catch (UnsubscribeFailedException sfe) {
        Log.e(TAG, "Failed to unsubscribe to datasets", sfe);
    } catch (AmazonClientException ace) {
        Log.e(TAG, "An unknown error caused the subscription to fail", ace);
    }
}

Receive notifications and initiate sync

In your implementation of the Android BroadcastReceiver object, you can check the latest version of the modified dataset and decide if your app needs to synchronize again.

iOS

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    NSDictionary * data = [(NSDictionary *)[userInfo object] objectForKey:@"data"];
    NSString * identityId = [data objectForKey:@"identityId"];
    NSString * datasetName = [data objectForKey:@"datasetName"];
    // if the notification is relevant for the current dataset and identity
    if ( [self.dataset.name isEqualToString:datasetName] && [self.identityId isEqualToString:identityId] ){
        [[self.dataset synchronize] continueWithBlock:^id(BFTask *task) {
            if(!task.error){
                NSLog(@"Successfully synced dataset");
            }
            return nil;
        }];
    }
}

Android

@Override
public void onReceive(Context context, Intent intent) {
    CognitoSyncManager manager;
    try {
        manager = CognitoSyncClientManager.getInstance();
    } catch (IllegalStateException ise) {
        Log.w(TAG, "Sync manager hasn't been initialized. Auto sync will
not proceed");
        return;
    }

    PushSyncUpdate update = manager.getPushSyncUpdate(intent);

    /*
     * The update has the source (cognito-sync here), identityId of the
     * user, identityPoolId in question, the non-local sync count of the
     * data set and the name of the dataset. All are accessible through
     * relevant getters.
     */
    String source = update.getSource();

    String identityPoolId = update.getIdentityPoolId();
    String identityId = update.getIdentityId();
    String datasetName = update.getDatasetName;
    long syncCount = update.getSyncCount;

    Dataset dataset = manager.openOrCreateDataset(datasetName);
    // need to access last sync count. If sync count is less or equal to
    // last sync count of the dataset, no sync is required.
    long lastSyncCount = dataset.getLastSyncCount();
    if (lastSyncCount < syncCount) {
        dataset.synchronize(new SyncCallback() {
            ...
        });
    }}
}

Conclusions

With the new JavaScript sync SDK, you can easily create identities, save data in the cloud, and synchronize it between your website and mobile apps without any backend infrastructure or code. Combine the JavaScript SDK with push synchronization on both iOS and Android and you can update the user data on your mobile app as soon as a change is synchronized from your web app, or from your app on another device. To learn more, take a look at the AWS Mobile SDK documentation for iOS and Android, visit our developer forums to receive support from the Cognito team, and follow us on Twitter for the latest updates.