Firebase Google Cloud messaging from device to device - ios

I couldn't understand how can i send message from iOS device to another iOS device, and trying to understand the difference between
Firebase Notifications and Google Cloud Messaging.
Firebase Notifications say's from the server you can send a message to devices.
Google Cloud Messaging: it sends messages from server to devices(downstream) or device to server(upstream) !!
Example of upstream:
[[FIRMessaging message]sendMessage:(nonnull NSDictionary *)message
to:(nonnull NSString *)receiver
withMessageID:(nonnull NSString *)messageID
timeToLive:(int64_t)ttl;
What about if i need to send a push message from device to device ! Does it means after the device sends a messages to server, i have to program the firebase server to send push to client ? its really confusing !

No you cannot do this on iOS using firebase, what you should do is call a service on your firebase which will send a notification to the other device. APNS and GCM are a little different in terms of the server setup.
For GCM you just need the API key to be added in the POST call you make to https://android.googleapis.com/gcm/send which can be done anywhere server, mobile device, etc. All you need is the target devices device token and the API key.
APNS works differently you need attach the server SSL cert that you create on the Apple developer portal to authenticate yourself and send a push notification to a device. I am not sure how you could achieve this on an iOS device.
This thread clarifies the real difference between GCM and Firebase,
Real-time Push notifications with Firebase
https://firebase.google.com/support/faq/#gcm-not
Firebase and GCM are different but they can be used to achieve the same goals. Hope it helps you.

I don't think Firebase is currently equipped to handle this scenario. You need some sever side code to handle it. Either you could get hosting and make like a php endpoint what can used to incorporate the
[[FIRMessaging message]sendMessage:(nonnull NSDictionary *)message
to:(nonnull NSString *)receiver
withMessageID:(nonnull NSString *)messageID
timeToLive:(int64_t)ttl;
code and make it work, OR you need to find another service that can serve as the backend.
https://techcrunch.com/2016/02/02/batch-now-integrates-with-firebase-to-create-a-parse-alternative/
This Batch.com company seems to be the best solution that I have found so far. I have been able to have a users device send a json payload to an endpoint on their server that then sends Customized push notifications to specific targeted devices. It seems like Batch is specifically a Push Notification company, and it seems like the free basic plan is good enough to handle what you will need, silimar to how Parse worked.
Here is the actual code I wrote for sending the push notifications Objective C. (There is also a Swift 2 and Swift 3 example you can download from Batch.com)
NSURL *theURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://api.batch.com/1.1/(your API code here)/transactional/send"]];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0f];
[theRequest setValue:#"(your other API key here" forHTTPHeaderField:#"X-Authorization"];
NSDictionary *messageDict = [[NSDictionary alloc]initWithObjectsAndKeys:#"Hey This is a Push!", #"title", #"But it's a friendly push. Like the kind of push friends do to each other.",#"body", nil];
NSArray *recipientsArray = [[NSArray alloc]initWithArray:someMutableArrayThatHasUsersFirebaseTokens];
NSDictionary *recipientsDict = [[NSDictionary alloc]initWithObjectsAndKeys:recipientsArray, #"custom_ids", nil];
NSDictionary *gistDict = #{#"group_id":#"just some name you make up for this pushes category that doesn't matter",#"recipients":recipientsDict,#"message":messageDict, #"sandbox":#YES};
NSError *jsonError;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:gistDict options:NSJSONWritingPrettyPrinted error:&jsonError];
[theRequest setHTTPMethod:#"POST"];
[theRequest setHTTPBody:jsonData];
NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:theRequest queue:queue1 completionHandler:^(NSURLResponse *response, NSData *POSTReply, NSError *error){
if ([POSTReply length] >0 && error == nil){
dispatch_async(dispatch_get_main_queue(), ^{
NSString *theReply = [[NSString alloc] initWithBytes:[POSTReply bytes] length:[POSTReply length] encoding:NSASCIIStringEncoding];
NSLog(#"BATCH PUSH FINISHED:::%#", theReply);
});
}else {
NSLog(#"BATCH PUSH ERROR!!!:::%#", error);
}
}];
Batch was pretty easy to install with Cocoa Pods.
I also used this code to get it working:
In app delegate:
#import Batch
In didFinishLaunching:
[Batch startWithAPIKey:#"(your api key)"]; // dev
[BatchPush registerForRemoteNotifications];
then later in app delegate:
- (void)tokenRefreshNotification:(NSNotification *)notification {
// Note that this callback will be fired everytime a new token is generated, including the first
// time. So if you need to retrieve the token as soon as it is available this is where that
// should be done.
NSString *refreshedToken = [[FIRInstanceID instanceID] token];
NSLog(#"InstanceID token: %#", refreshedToken);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:refreshedToken forKey:#"pushKey"];
[defaults synchronize];
BatchUserDataEditor *editor = [BatchUser editor];
[editor setIdentifier:refreshedToken]; // Set to `nil` if you want to remove the identifier.
[editor save];
[self connectToFcm];
}
So thats how you do it, besides the setup and installation stuff which is all explained on the Batch.com website.
Once you get your token from firebase, you basically register it on Batch with
BatchUserDataEditor *editor = [BatchUser editor];
[editor setIdentifier:refreshedToken];
[editor save];
in App Delegate. Then when you want your users device1 to send a Push to another device2, assuming you have sent device1's custom id to device2 somehow, you can use that custom id to send the push notification payload to Batch.com's API, and Batch will handle the server side stuff to Apple APN and your Push notifications appear on device2.

Related

Send push notification from iOS using Amazon SNS

I set it up Amazon SNS and iOS App to send Push Notification via SNS Console and receive in iOS. It works properly.
Now, I'm trying to send push notification from a device to another device, but I'm getting the following error:
-[AWSServiceInfo initWithInfoDictionary:checkRegion:] | Couldn't read credentials provider configurations from Info.plist. Please check
your Info.plist if you are providing the SDK configuration values
through Info.plist.
There's my code to send push notification
AWSSNS *publishCall = [AWSSNS defaultSNS];
AWSSNSPublishInput *message = [AWSSNSPublishInput new];
message.subject = #"My First Message";
//This is the ending point
message.targetArn = #"arn:aws:sns:us-west-2:895047407854:endpoint/APNS_SANDBOX/XXXXX/XXXX-XXXX-XXX-XXXX";
message.subject =#"hello";
message.message =#"teste from device";
// message.messageAttributes = messageDict;
//
// message.messageStructure = jsonString;
[publishCall publish:message completionHandler:^(AWSSNSPublishResponse * _Nullable response, NSError * _Nullable error) {
if(error) NSLog(#"%#", [error userInfo]);
if(response) NSLog(#"%#", [response description]);
}];
I don't know what I'm missing.
As per the debug log of AWSInfo.m here you will get this error when you have not configured your defaultCognitoCredentialsProvider
As they will check
_cognitoCredentialsProvider = [AWSInfo defaultAWSInfo].defaultCognitoCredentialsProvider;
if they find _cognitoCredentialsProvider nil then you will get this error.
So configure defaultCognitoCredentialsProvider properly.

Comments please on seemingly 'Too Easy' method to verify receipts

I believe that to say that receipt verification help from Apple is obfuscated is an understatement.
Somehow I have been able to put some code together that does not use OpenSSL or ASN1 which SEEMS TO WORK to let me get access to the Receipt Fields as readable strings for all receipts for a bundle (including the most current which may not have been even generated by the current device).
This is a work in process, as you can see by the todo's, but could someone tell me why I should not use this method because it just seems too easy from all that I have read on the subject?
Also can anyone help me with the todo's? Like what I need to do at todo1 and 2 (I think i can handle 3, 4 and 5)?
In my case I am scanning the 'latest_receipt_info' receipts from most recent to earliest until I find the in-app product_id I am interested in and then determining its expired status from 'expires_date_ms'. Is that the proper way to determine current expired status of a product_id?
Anyway here it is:
NSURL *storeURL;
exms = [[NSUserDefaults standardUserDefaults] doubleForKey:#"exms"]; //globally defined elsewhere
[[NSUserDefaults standardUserDefaults] synchronize];
NSDate *today = [NSDate date];
int64_t nowms = 1000*[today timeIntervalSince1970];
if (nowms> exms){ //todo maybe give a week grace here
isSubscribed= NO; //globally defined elsewhere
// Load the receipt from the app bundle.
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) { /* No local receipt -- handle the error. */ } //todo1
/* ... Now Send the receipt data to server ... */
// Create the JSON object that describes the request
NSError *error;
NSDictionary *requestContents = #{#"receipt-data": [receipt base64EncodedStringWithOptions:0],
#"password": #"put your shared secret here"};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
if (!requestData) { /* ... Handle error ... */ } //todo2
NSString *file=[NSHomeDirectory() stringByAppendingPathComponent:#"iTunesMetadata.plist"];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
// probably a store app
storeURL = [NSURL URLWithString:#"https://buy.itunes.apple.com/verifyReceipt"]; //todo3 test this for sure rather than ifdef debug
}else{
storeURL = [NSURL URLWithString:#"https://sandbox.itunes.apple.com/verifyReceipt"];
}
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:#"POST"];
[storeRequest setHTTPBody:requestData];
// Make a connection to the iTunes Store on a background queue.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* ... Handle error ... */ //todo4
} else {
int64_t edms= 0;
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) { /* ... Handle error ...*/ } //todo5
// Get object from the root object
NSArray *dictionaryObject = (NSArray *)[jsonResponse objectForKey:#"latest_receipt_info"];
NSDictionary *rcpt;
for (int i=[dictionaryObject count]-1;i>-1;i--){
rcpt= [dictionaryObject objectAtIndex:i];
NSString *pid= [rcpt objectForKey:#"product_id"];
if ([pid isEqualToString:#"put your in-app purchase you are interested in here"]){
edms= [[rcpt objectForKey:#"expires_date_ms"] longLongValue];
break;
}
}
NSDate *today = [NSDate date];
int64_t nowms = 1000*[today timeIntervalSince1970];
if (nowms> edms){
isSubscribed= NO;
}else{
isSubscribed= YES;
}
exms= edms;
[[NSUserDefaults standardUserDefaults] setDouble:exms forKey:#"exms"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (isSubscribed== YES) _isexpiredView.hidden= true;
}
}];
}else{
isSubscribed= YES;
}
The mechanical steps of receipt validation aren't hard and your implementation addresses these. Doing it securely is more difficult as you cannot trust the end-user device.
The first issue I see is that you are storing the expiration date in NSUserDefaults and you only validate the receipt if "now" is after the expiration date. So I can simply put an expiration date far into the future into NSUserDefaults and you will never expire the subscription nor check to see if the expiration date is valid. Using an encrypted value and storing in the keychain would make it more secure.
Secondly you are validating the receipt directly from the device to the Apple server. From the Receipt Validation Programming Guide
Use a trusted server to communicate with the App Store. Using your own
server lets you design your app to recognize and trust only your
server, and lets you ensure that your server connects with the App
Store server. It is not possible to build a trusted connection between
a user’s device and the App Store directly because you don’t control
either end of that connection.
What this is saying is that by connecting directly from your device you make it easy for someone to spoof the Apple Server. If you validated that the receipt was signed by Apple then this would be harder, but as it is you blindly trust the expiration date in the receipt.
Your approach is OK as long as you trust your users are honest.

Force app to wait for method completion (data download)

I'm working with an app that requests data from an OAuth2.0 protected server. When I use the GTM OAuth Library to retrieve data, the program continues to run while the data is being downloaded in the background. I need some sort of mechanism to either force my application to wait until the didFinishWithData selector is called,or I need a way to notify my ViewController of the download's completion, so I can then utilize the data immediately.
I've tried conditional blocks, but those aren't doing it for me. I've also tried polling the object whose data I'm interested in, but if I do that, the data never seems to download. I've heard I can somehow utilize the Notification Center to accomplish this task, so I'll look more into that while I'm waiting for replies here.
Here is basically what is going on:
-(void) getAlert{
// Define the URL of the API module we'd like to utilize.
NSURL *url = [[NSURL alloc] initWithString:#"https://access.active911.com/interface/open_api/api/alerts"];
// Constructs a an HTTP request object to send to the server in order to obtain data.
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setValue:#"1" forHTTPHeaderField:#"alert_days"];
// This fetcher sends the request along with the authentication header in a recognizable manner.
GTMHTTPFetcher *fetcher = [[GTMHTTPFetcher alloc] initWithRequest:request];
// Attach the OAuth credentials for the fetcher's use.
[fetcher setAuthorizer:auth];
// Execute the operation.
[fetcher waitForCompletionWithTimeout:10];
NSLog(#"About to get alert");
[fetcher beginFetchWithDelegate:self didFinishSelector:#selector(responseHandler:finishedWithData:finishedWithError:)];
NSLog(#"got alert");
}
-(void)responseHandler:(id)valueNotUsed finishedWithData:(NSData *)data finishedWithError:(NSError *)error{
// Retrieve the server data in a usable object
// All that's being done here is conversion to an NSDictionary
// followed by the creation of subdictionaries from that dictionary
// until our final value can be picked directly out of the resulting dict
NSData *jsonData = [[NSData alloc] initWithData:data];
NSError *dictError;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:jsonData //1
options:kNilOptions
error:&dictError];
NSDictionary *token = [json objectForKeyedSubscript:#"message"];
NSArray *alerts = [token objectForKeyedSubscript:#"alerts"];
NSDictionary *alertData = alerts[0];
mapCode = [alertData objectForKeyedSubscript:#"map_code"];
NSString *city = [alertData objectForKeyedSubscript:#"city"];
NSLog(#"Map code: '%#' with city '%#' and access token %#", mapCode, city, accessToken);
}
And I need to pass the mapCode to my view controller.
Thanks for the help!
First off, please rethink about having the UI halt while you fetch results from the server. This can create an extremely bad UX for the app and only should be done if absolutely necessary.
Second, does your responseHandler method work? And do you only need mapCode in the VC that responseHandler is in?
If so, you don't even need to use Notifications. Simply do:
-(void)responseHandler:(id)valueNotUsed finishedWithData:(NSData *)data finishedWithError:(NSError *)error{
...
...
mapCode = [alertData objectForKeyedSubscript:#"map_code"];
[self updateVCWithMapCode:mapCode];
}
That will call the method after the response has been received. Passing it explicitly too so you don't need to have mapCode be a property as well.

Use Braintree payment api in iOS app

I want to use Braintree API in my iOS app.
My app is used for renting purpose i.e. requester user has to make payment to the owner of asset he wanted for rent.
I have checked following links :
http://www.youtube.com/watch?v=s7GlgBFM20I
https://www.braintreepayments.com/developers
http://www.youtube.com/watch?v=2y8Tsml6JYo
https://www.braintreepayments.com/braintrust/venmo-touch-screencasts-add-one-touch-payments-to-your-app-in-15-minutes
etc.
But I didn't get any idea where to provide receiver's account details using Braintree or Venmo api, For Example we can pass e-mail of receiver in PayPal iOS sdk, and then PayPal pays the amount to the user registered with that e-mail.
The same thing I am searching in Braintree Payment API.
Any help is greatly appreciated.
Thanks in advance.
I am using below code : (Used from a sample code given by braintree)
/* Get called when user pay on Pay button on screen.
User wil see a form for entering his credit card number, CVV and expiration date. */
-(IBAction)payButtonClicked
{
self.paymentViewController =
[BTPaymentViewController paymentViewControllerWithVenmoTouchEnabled:YES];
self.paymentViewController.delegate = self;
[self presentViewController:self.paymentViewController animated:YES completion:nil];
}
// When a user types in their credit card information correctly, the BTPaymentViewController sends you
// card details via the `didSubmitCardWithInfo` delegate method.
//
// NB: you receive raw, unencrypted info in the `cardInfo` dictionary, but
// for easy PCI Compliance, you should use the `cardInfoEncrypted` dictionary
// to securely pass data through your servers to the Braintree Gateway.
- (void)paymentViewController:(BTPaymentViewController *)paymentViewController
didSubmitCardWithInfo:(NSDictionary *)cardInfo
andCardInfoEncrypted:(NSDictionary *)cardInfoEncrypted {
[self savePaymentInfoToServer:cardInfoEncrypted]; // send card through your server to Braintree Gateway
}
// When a user adds a saved card from Venmo Touch to your app, the BTPaymentViewController sends you
// a paymentMethodCode that you can pass through your servers to the Braintree Gateway to
// add the full card details to your Vault.
- (void)paymentViewController:(BTPaymentViewController *)paymentViewController
didAuthorizeCardWithPaymentMethodCode:(NSString *)paymentMethodCode {
// Create a dictionary of POST data of the format
// {"payment_method_code": "[encrypted payment_method_code data from Venmo Touch client]"}
NSMutableDictionary *paymentInfo = [NSMutableDictionary dictionaryWithObject:paymentMethodCode
forKey:#"payment_method_code"];
[self savePaymentInfoToServer:paymentInfo]; // send card through your server to Braintree Gateway
}
#define SAMPLE_CHECKOUT_BASE_URL #"http://venmo-sdk-sample-two.herokuapp.com"
//#define SAMPLE_CHECKOUT_BASE_URL #"http://localhost:4567"
// Pass payment info (eg card data) from the client to your server (and then to the Braintree Gateway).
// If card data is valid and added to your Vault, display a success message, and dismiss the BTPaymentViewController.
// If saving to your Vault fails, display an error message to the user via `BTPaymentViewController showErrorWithTitle`
// Saving to your Vault may fail, for example when
// * CVV verification does not pass
// * AVS verification does not pass
// * The card number was a valid Luhn number, but nonexistent or no longer valid
- (void) savePaymentInfoToServer:(NSDictionary *)paymentInfo {
NSURL *url;
if ([paymentInfo objectForKey:#"payment_method_code"]) {
url = [NSURL URLWithString: [NSString stringWithFormat:#"%#/card/payment_method_code", SAMPLE_CHECKOUT_BASE_URL]];
} else {
url = [NSURL URLWithString: [NSString stringWithFormat:#"%#/card/add", SAMPLE_CHECKOUT_BASE_URL]];
}
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
// You need a customer id in order to save a card to the Braintree vault.
// Here, for the sake of example, we set customer_id to device id.
// In practice, this is probably whatever user_id your app has assigned to this user.
NSString *customerId = [[UIDevice currentDevice] identifierForVendor].UUIDString;
[paymentInfo setValue:customerId forKey:#"customer_id"];
request.HTTPBody = [self postDataFromDictionary:paymentInfo];
request.HTTPMethod = #"POST";
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *body, NSError *requestError)
{
NSError *err = nil;
if (!response && requestError) {
NSLog(#"requestError: %#", requestError);
[self.paymentViewController showErrorWithTitle:#"Error" message:#"Unable to reach the network."];
return;
}
NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:body options:kNilOptions error:&err];
NSLog(#"saveCardToServer: paymentInfo: %# response: %#, error: %#", paymentInfo, responseDictionary, requestError);
if ([[responseDictionary valueForKey:#"success"] isEqualToNumber:#1]) { // Success!
// Don't forget to call the cleanup method,
// `prepareForDismissal`, on your `BTPaymentViewController`
[self.paymentViewController prepareForDismissal];
// Now you can dismiss and tell the user everything worked.
[self dismissViewControllerAnimated:YES completion:^(void) {
[[[UIAlertView alloc] initWithTitle:#"Success" message:#"Saved your card!" delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
[[VTClient sharedVTClient] refresh];
}];
} else { // The card did not save correctly, so show the error from server with convenenience method `showErrorWithTitle`
[self.paymentViewController showErrorWithTitle:#"Error saving your card" message:[self messageStringFromResponse:responseDictionary]];
}
}];
}
I work at Braintree. If you've got more questions or need more help, please reach out to our support team.
The Braintree iOS SDK docs include a quickstart guide, as well as detailed information about the features of the library.
If you're looking for information specifically on how to make payments between users of your app / web site, you should take a look at the marketplace guide and the Venmo APIs.

RestKit RKRequests arent sent immediately

I am using the great RestKit Framework for an iPhone Application.
I have got a method, where I send requests to a webservice. Sometimes four or more requests per 30 seconds.
My sendMethod looks like:
- (void) sendLocation {
NSString *username = [userDefaults objectForKey:kUsernameKey];
NSString *password = [userDefaults objectForKey:kPasswordKey];
NSString *instance = [userDefaults objectForKey:kInstanceKey];
NSString *locationname = self.location.locationname;
NSString *url = [[NSString alloc] initWithFormat:#"http://www.someadress.com/%#", instance];
RKClient *client = [RKClient clientWithBaseURL:url username:username password:password];
// Building my JsonObject
NSDictionary *locationDictionary = [NSDictionary dictionaryWithObjectsAndKeys: username, #"username", locationname, #"locationname", nil];
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjectsAndKeys:locationDictionary, #"location", nil];
NSString *JSON = [jsonDictionary JSONRepresentation];
RKParams *params = [RKRequestSerialization serializationWithData:[JSON dataUsingEncoding:NSUTF8StringEncoding]MIMEType:RKMIMETypeJSON];
[client post:#"/locations" params:params delegate:self];
}
Sometimes (especially when sending more requests after another) the value of the count property of the RKRequestQueue Object is > 1.
When my application enters background and then enters foreground the requests in the queue (when foreground is entered) are sent to my Webservice and the delegate
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {)
for all requests will be called.
So the question is:
Why doesnt RestKit send some requests immediately (my Webservice doesnt receive anything while a request is stored in the queue)???
Does anyone know a solution or had/has the same problem?
I noticed this line where you create the RKClient:
RKClient *client = [RKClient clientWithBaseURL:url username:username password:password];
This basically creates new instance each time sendLocation method is called - are you sure this is your desired behavior? If the url, username and password does not change, you can access previously created client by calling [RKClient sharedClient]. In your current approach, new request queue is created for each new client.
Now back to the point. Take a look on this property of the RKRequestQueue:
/**
* The number of concurrent requests supported by this queue
* Defaults to 5
*/
#property (nonatomic) NSUInteger concurrentRequestsLimit;
As you can see this defaults to 5, so if you have more than that in any of your queues they will wait until the ongoing requests are processed. You also mentioned that the requests stack up when the application is moved to background, and then when it enters the foreground all of them are dispatched. You can control how your requests behave like this:
- (void)backgroundUpload {
RKRequest* request = [[RKClient sharedClient] post:#"somewhere" delegate:self];
request.backgroundPolicy = RKRequestBackgroundPolicyNone; // Take no action with regard to backgrounding
request.backgroundPolicy = RKRequestBackgroundPolicyCancel; // If the app switches to the background, cancel the request
request.backgroundPolicy = RKRequestBackgroundPolicyContinue; // Continue the request in the background
request.backgroundPolicy = RKRequestBackgroundPolicyRequeue; // Cancel the request and place it back on the queue for next activation
}
I have found this solution here. Scroll down to Background Upload/Download section.

Resources