Remote Notifications From My Own Server For Multiple Devices

Discussion in 'iOS Programming' started by RipsMctits, Feb 2, 2015.

  1. RipsMctits macrumors regular

    Joined:
    Oct 19, 2011
    #1
    I'm pretty new when it comes to this server-side stuff, and I've only just been making apps for a few months. I have my push notifications set up through the server, and everything is working fine when I want to send to an individual device. I'm using a PHP script from the SimplePush tutorial by Ray Wenderlich where I put in the device token manually for each push.

    What I need to do is figure out how to write the token to my SQL database, retrieve it, and push the notifications in a loop for each token received by the server. Right now, my code to send the token to the server is something I grabbed from stackoverflow and I don't think it will work for what I want to do. I get my token and I get the message that the token was sent successfully to the URL, but I don't get anything written to my server. I put this into a new class called DataUpdater and set all of that up. It thinks it's working.

    Code:
    + (void)sendUserToken {
        if ([[NSUserDefaults standardUserDefaults] boolForKey:@"apnsTokenSentSuccessfully"]) {
            NSLog(@"apnsTokenSentSuccessfully already");
            return;
        }
        
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://PATH TO SERVER/appdev/filecreate.php?token=%@",[[NSUserDefaults standardUserDefaults] objectForKey:@"apnsToken"]]]; //set here your URL
        
        NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        [NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
         {
             if (error == nil) {
                 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"apnsTokenSentSuccessfully"];
                 NSLog(@"Token has been sent successfully");
                 //you can check server response here if you need
             }
         }];
    }
    I'm pretty sure the above is useless for what I'm trying to do. Do I need to use PHP here? How do I store the token in the database? How do I retrieve it? How do I set up the notification script to grab the information and use it for each individual token?

    My PHP looks like this:

    Code:
    <?php
    
    // Put your device token here (without spaces):
    $deviceToken = 'xxxxx';
    
    // Put your private key's passphrase here:
    $passphrase = 'push';
    
    // Put your alert message here:
    $message = "There's a new sale!";
    $sound = "EEPush.wav";
    $badge = '1';
    
    ////////////////////////////////////////////////////////////////////////////////
    
    $ctx = stream_context_create();
    stream_context_set_option($ctx, 'ssl', 'local_cert', '/home/noahsias/appdev/SimplePush/ck.pem');
    stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
    
    // Open a connection to the APNS server
    $fp = stream_socket_client(
    	'ssl://gateway.sandbox.push.apple.com:2195', $err,
    	$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
    
    if (!$fp)
    	exit("Failed to connect: $err $errstr" . PHP_EOL);
    
    echo 'Connected to APNS' . PHP_EOL;
    
    // Create the payload body
    $body['aps'] = array(
    	'alert' => $message,
            'badge' => intval($badge),
            'sound' => $sound
    	);
    
    // Encode the payload as JSON
    $payload = json_encode($body);
    
    // Build the binary notification
    $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
    
    // Send it to the server
    $result = fwrite($fp, $msg, strlen($msg));
    
    if (!$result)
    	echo 'Message not delivered' . PHP_EOL;
    else
    	echo 'Message successfully delivered' . PHP_EOL;
    
    // Close the connection to the server
    fclose($fp);
    
     
  2. AppSwage macrumors newbie

    Joined:
    Jan 18, 2015
    #2
    From what you are describing this isn't an iOS problem but a server side one. I don't know what server you are using and there are countless options. I use Google App Engine and Python for my server side work (although I haven't for push notifications.) It is simply a matter of receiving a JSON request and writing it to a DB. You would then need either a DB trigger that fires when a record is written or a CRON job set to run at whatever interval you want. The job of those jobs is to query the DB for tokens and then use them to send back to Apple. You obviously have the part down about how to send tokens so you need to go learn how to store data in a database and how to trigger or write a CRON job. How is going to be different based on your server. It should not be difficult.
     
  3. RipsMctits, Feb 2, 2015
    Last edited: Feb 2, 2015

    RipsMctits thread starter macrumors regular

    Joined:
    Oct 19, 2011
    #3
    I'm using Arvixe. So you're saying the initial token file should be writing into the directory I outlined in my post (appdev) with no problem? If so, that's problem number one. I get the message that the token has been sent to the URL, but nothing gets written to that directory. I have no problems working with the server in any other capacity. Keep in mind that is NOT the URL to the database I already created with MySQL.

    I was under the impression that a file is created first with the token from the code I have in Xcode, in whichever directory I specify, then I have to pull that token information into my database somehow. Is that correct, or am I making this more complicated than it needs to be? If that's the case, any ideas on why the initial token file isn't being written to the directory?
     
  4. DennisBlah macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #4
    I dont understand why you want to make a file for each token?
    Nobody knows what your php looks like so also cant help with that

    If you have any knowledge about mysql and php
    Just setup an database
    Table: my_tokens
    Fields:
    t_id int(10) auto increment primary key
    t_token varchar(32)
    t_date date

    Date is just what I use to check which tokens are old and proberbly are changed
    In my own database I do different.

    When a divice registers and got its token
    Just post it to an php file
    Which will INSERT the data to my_tokens

    Thats all it has to do

    Next you need a different php file for sending (the php you mentioned)

    In here you might want to make a loop through your table

    Or make a function of the whole script you have so it looks nicer.

    Then you got a table with all your tokens
    An script to store the tokens
    And a script to send messages to your token(s)
     
  5. RipsMctits, Feb 2, 2015
    Last edited: Feb 2, 2015

    RipsMctits thread starter macrumors regular

    Joined:
    Oct 19, 2011
    #5
    I don't. I want to write it to the database that I made, but I don't know why. I'm not even sure of the correct way to do it. Some people had suggested other places to do it this way. I tried to explain in the original post, but I may not have been clear. I already setup a database, I just don't know how to access it or write to it. I've never done anything server-side, and I only just started making apps a few months ago. Self-taught, so I'm missing a lot.


    Isn't the ObjC bit pointing to my server supposed to be writing the php file for the token? Maybe I'm not pointing it to the right place?

     
  6. DennisBlah macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #6
    Hi RipsMctits,

    the thing is, you do not create a new php file for every token.
    Instead of an createfile.php
    You want an saveToken.php (for example)
    I'm using the following in my app (I left out an connection check)
    Code:
    - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    
        //Stripping < > and spaces from token string
        NSString* myToken = [[[NSString stringWithFormat:@"%@",deviceToken]
                               stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];
        
    
        thisToken = [NSString stringWithFormat: @"%@", myToken];
    
        //Just temporary to check if device gets its token
        NSLog(@"My token is: %@", thisToken);
              
        NSString *post = [NSString stringWithFormat: @"theToken=%@", thisToken];
        NSURL *url=[NSURL URLWithString:@"http://domain.com/path/to/file/saveToken.php"];
        
        NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
        NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[postData length]];
        
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:url];
        [request setHTTPMethod:@"POST"];
        [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
        [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        [request setValue:@"Niesters-App" forHTTPHeaderField:@"User-Agent"];
        [request setHTTPBody:postData];
        //[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[url host]];
        
        NSError *error = [[NSError alloc] init];
        NSHTTPURLResponse *response = nil;
        NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
        NSInteger result = -1;
        if ([response statusCode] >=200 && [response statusCode] <300) {
            NSString *responseData = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
            
            SBJsonParser *jsonParser = [SBJsonParser new];
            NSDictionary *jsonData = (NSDictionary *) [jsonParser objectWithString:responseData error:nil];
            result = [(NSNumber *) [jsonData objectForKey:@"result"] integerValue];
            if(logging) {
                if(result == 0)
                    NSLog(@"Token not saved! Due error in code or w.e....");
                else if(result == 1)
                    NSLog(@"Submit token failed..");
                else if(result == 2)
                    NSLog(@"Token stored to database!");
                else
                    NSLog(@"Unknown... %@", [jsonData objectForKey:@"result"]);
            }
        } else {
            NSLog(@"Some error on the page...");
        }
        
        
    }
    
    This will send an POST with the token to the page saveToken.php with an POST variable of 'theToken'

    Down here a sample of the saveToken.php file.
    Code:
    <?php
        error_reporting(-1);
        header('Content-type: application/json');
        $result = '{"result":0}';
    
        $hostname = 'theHostOfTheDatabase';
        $username = 'usernameOfDatabase';
        $password = 'passwordForUsername';
        $database = 'databaseName';
    
    
        if(!@mysql_connect($hostname,$username,$password)) {
            $result = '{"result":"Could not connect to database!"}';
        } else {
            if(!isset($_POST['theToken'])) {
                $result = '{"result":"No token in POST"}';
            } else {
                if(!mysql_query('INSERT ONTO my_tokens (t_token) VALUES ("' . $_POST['theToken'] . '")')) {
                    $result = '{"result":1}';
                } else {
                    $result = '{"result":2}';
                }
            }
        }
    
        echo $result;
    
    This is how you can store your tokens into a table. This will also make duplicated tokens! This is not my actual source, I left out security checks etc etc.

    There might be some typo's. Made this on fly

    p.s. change ONTO in the php file to INTO as this forum will block me out for attempting to SQL injection.
     
  7. RipsMctits thread starter macrumors regular

    Joined:
    Oct 19, 2011
    #7
    Thank you for the help, Dennis. That's exactly what I needed to understand. I would've had no idea how to handle all the JSON stuff. And the PHP is something I just started having to deal with now, so it's all new to me. I'm trying to learn as I go, but I'm starting to think that may not be the best approach if I want to continue developing apps in the future. Once I get this working, I'll be able to fudge my way through most basic app functions, but being fluent is another story.
     
  8. RipsMctits thread starter macrumors regular

    Joined:
    Oct 19, 2011
    #8
    I'm getting some errors with "thisToken" and SBJsonParser. Is the JSON Parser command something I have to add a delegate for Xcode to recognize (i.e. Mail or Map commands/functions have to have a delegate declared for them to work)? What is missing with thisToken? Do I have to define it somewhere else in the code for it to know what it is?
     
  9. DennisBlah macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #9
    Ow yes, I'm sorry. I'm using SBJson class.
    I've uploaded the version I'm using:
    SBJson.zip

    Just extract it and include into your project and your good to go.

    About thisToken.

    I have set an NSString *thisToken; in the top of my appdelegate. Because I'm using it several times.
    In the top of the code change
    Code:
    thisToken = [NSString stringWithFormat: @"%@", myToken];
    
    to
    Code:
    NSString *thisToken = [NSString stringWithFormat: @"%@", myToken];
    
     
  10. RipsMctits thread starter macrumors regular

    Joined:
    Oct 19, 2011
    #10
    That's awesome. You're really helping me out here, and I appreciate it a lot. I just have one more issue, and this is again one of those fundamental things that I'm sure is a simple fix. I'm getting an undeclared identifier error on the if(logging) part. I'm really trying to understand the what's and the why's here, and there's a lot of information to take in. I know what it's for and what it's doing, but I again don't know what I need to define in my code to...declare the identifier I guess.

    I really appreciate the detailed answers and you showing me the code responsible for all of it. That is usually the part that is missing in most of the answers I find around the web.
     
  11. DennisBlah macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #11
    The 'logging' variable is a boolean, I actually should left it out...
    I just copied and pasted few things for you and put together.
    Someone helped me out a little bit with a little part of this, and though could do the same for someone else.

    if you just put BOOL logging = YES; somewhere, you will get to see the NSLog's appear in your console. I use this everywhere, usually I put it on top of my classes, then when I'm throwing it to the appstore I put it on NO because the logs are not of any use in a 'production' version.
     
  12. RipsMctits thread starter macrumors regular

    Joined:
    Oct 19, 2011
    #12
    I'm getting output in Xcode from if(result == 0) as "Token not saved! Due error in code or w.e...." as per the code. Does that mean that there is an issue related to the $result = '{"result":0}'; part of saveToken.php? I'm thinking this means there is an issue with the information I'm using in that part of saveToken.php. Correct? I just want to make sure I'm understanding all of the file relationships correctly.

    I already setup my username, database, password etc and I think I have that input correctly. I'm using localhost as my hostname.
     
  13. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #13
    I recommend that you look at the following variables, either in a debugger or by logging them:
    Code:
    NSData *urlData
    NSString *responseData
    NSDictionary *jsonData
    
    The jsonData should be the entire dictionary you expect, with the "result" key in the expected place in its structure.

    The responseData should be the JSON text string you expect, and it should correspond to jsonData exactly.

    The urlData should be the hex bytes that exactly correspond to the UTF-8 encoding of the JSON text string you expect.

    If any of these are null or empty, then find the last one that's valid and narrow the problem down.

    If any of these are not the expected data, then you should find the last one that's valid (which may be the response being sent from the server), and look at it.

    If you don't understand what the data is at some step in the process, then post the input and the output so we know what's happening.

    In general terms, you need to confirm that each element leading up to the "result" key is what you expect and need it to be. You also need to break down the processing and look at the data going into and out of each step.

    This is fundamental debugging: Confirm Your Expectations, and Break Things Down. Apply these principles to narrow down where the problem lies.

    If the server is sending JSON data where "result" is 0, then the problem lies on the server side, not the client side at all.
     
  14. DennisBlah macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #14
    The values returned to the App are created in the php, indeed.
    You can see it like... The app is opening an browser, and posts the variable 'theToken' from
    Code:
    NSString *post = [NSString stringWithFormat: @"theToken=%@", thisToken];
    
    Then it will return the output. As you see in the php $result will be shown in the end. Which contains {"result": <some number>}

    result is 0 for default.

    That is what the app reads and will do whatever it needs. Show an error or w.e
    In the sample case it only does an NSLog for testing.

    It looks like your not getting into the IF statements in the php.
    I did not really copy pasted exactly from my code. I'm not 100% it will work like I put it down.

    Change
    Code:
    $result = '{"result":"Could not connect to database!"}';
    
    to
    Code:
    $result = '{"result":3}';
    
    Do you then get a result = 3 ?
    That will mean your database information you entered is incorrect.

    If you get 1 then there is something wrong with the INSERT query (your table structure is different then mentioned in the query, or invalid values passed on)

    If you get 2 then the token is saved.
     

Share This Page