0

I have been making progress but stuck for days on how to get my user's device tokens into a MySQL database using PHP on the serverside and Swift code on my iOS app. It successfully registers for the push notifications but will not send to my server. The code snippets are below. In addition, I will want to also send the IP of the device to match up with current users of my app. Any help is appreciated!

The Swift code:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    // 1. Convert device token to string
    let tokenParts = deviceToken.map { data -> String in
        return String(format: "%02.2hhx", data)
    }
    let token = tokenParts.joined()
    // 2. Print device token to use for PNs payloads
    print("Successfully registered for notifications!")
    print("Device Token: \(token)")
    URLRequest(url: URL(string:"https://MY_URL.com/registerPushApple.php?key=MY_KEY&token=\(token)")!)

}

The PHP code:

//Put in database
date_default_timezone_set('America/New_York');
$date = date("Y-m-d");
$device_ip = $_GET['device_ip'];
$device_token = $_GET['token'];
$key = $_GET['key'];

if($key == "MY_KEY") {

    // Insert Info into Database
    $mysqli->query("INSERT INTO device_info (date, device_ip, device_token) VALUES ('$date', '$device_ip', '$device_token')");


} else {

    die();

}

// close connection 
$mysqli->close();
Mike Barbaro
  • 245
  • 1
  • 17
  • You're getting device_ip in your php function but in the url call you haven't send such index why? – Abhishek Dec 12 '19 at 05:32
  • I should have clarified that I have not yet got to that part. More importantly I need the device token to store. – Mike Barbaro Dec 12 '19 at 05:35
  • By the way, I don’t know what this `key` is, but if it’s for APNs or whatever, I’d keep that out of the request and manage this on the server. You don’t want to distribute apps with APNs certs/keys in them (much less be transmitting this over the ‘net). Bottom line, decide whether it should be part of the request or whether it’s better stored on the server in some safe location. – Rob Dec 12 '19 at 06:27

1 Answers1

1

A few observations:

  1. Your Swift code isn’t sending the request to the server. You might do something like:

    var request = URLRequest(url: URL(string:"https://MY_URL.com/registerPushApple.php")!)
    request.httpBody = "key=MY_KEY&token=\(token)".data(using: .utf8)
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, error == nil else {
            print(error ?? "Unknown error")
            return
        }
    
        // OK, if you got here, the server successfully responded, but you now
        // need to parse it with `JSONDecoder` and make sure there weren’t any 
        // SQL errors reported. But I’m just going to print the response JSON for now:
    
        print(String(data: data, encoding: .utf8))
    }
    task.resume()
    

    Note:

    • it’s a POST request;
    • we’re sending our data in the httpBody of the request, not the URL; and
    • we’re performing the request with resume.
       

    I’d also suggest percent-encoding the request body just in case any of your values contain any reserved characters. Or use a library like Alamofire, which gets you out of the weeds of building well-formed requests.

  2. Your PHP code isn’t checking to see if the INSERT succeeded or not, and if not, respond with enough information for you to diagnose what’s going on. For example:

    <?php
    
    header('Content-type: application/json');
    
    // You didn’t send IP in your request, so you won’t find it in `$_GET` or `$_POST`
    // Perhaps https://stackoverflow.com/a/2031935/1271826
    
    function get_ip_address(){
        foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key){
            if (array_key_exists($key, $_SERVER) === true){
                foreach (explode(',', $_SERVER[$key]) as $ip){
                    $ip = trim($ip); // just to be safe
    
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false){
                        return $ip;
                    }
                }
            }
        }
    }
    
    $mysqli = new mysqli('localhost', 'user', 'pwd', 'db');
    
    // check connection 
    
    if ($mysqli->connect_errno) {
        echo json_encode(array('success' => false, 'message' => $mysqli->connect_error, 'sqlerrno' => $mysqli->connect_errno));
        exit();
    }
    
    $mysqli->set_charset('utf8');
    
    // perform the insert
    $device_ip = get_ip_address();
    $device_token = $_POST['token'];
    $key = $_POST['key'];
    $sql = 'INSERT INTO device_info (date, device_ip, device_token, key) VALUES (NOW(), ?, ?, ?)';
    
    if ($stmt = $mysqli->prepare($sql)) {
        $stmt->bind_param('sss', $device_ip, $device_token, $key);
    
        if (!$stmt->execute())
            $response = array('success' => false, 'message' => $mysqli->error, 'sqlerrno' => $mysqli->errno, 'sqlstate' => $mysqli->sqlstate);
        else
            $response = array('success' => true);
    
        $stmt->close();
    } else {
        $response = array('success' => false, 'message' => $mysqli->error, 'sqlerrno' => $mysqli->errno, 'sqlstate' => $mysqli->sqlstate);
    }
    
    $mysqli->close();
    
    echo json_encode($response);
    
    ?>
    

    Note:

    • you want to bind your values to ? in the SQL to protect against SQL injection attacks (or use real_escape_string); and
    • you want to check that the insert succeeded and return meaningful JSON responses, whether successful or not.

Now, I just dashed this off, so please forgive any typographical errors. But hopefully this gives you the basic idea.

Clearly, you shouldn’t just print the JSON that’s returned, but parse it with JSONDecoder and look at the result for errors.

Likewise, your PHP should probably do some graceful checking that the parameters have been found in the request and return nice JSON error response if not. But hopefully this gets you going in the right direction.


You said:

I will want to also send the IP of the device to match up with current users of my app.

No, you don’t want the device to add its IP number in the request, because the device will only know its local IP number (e.g., if on wifi, it’s often something like 192.168.0.x and many users may have the same local IP number on their respective LANs). Obviously, as far as the server is concerned, that’s often meaningless. You should have the PHP extract the IP number from the request it received like shown above.

On top of this, I don’t think the IP number is a good way to verify users, anyway. If you walk into a cafe and hop on their wifi: The IP number has changed. You turn on your VPN: The IP number changes again. You lose your Internet connection and reconnect: The IP number likely changes again. Bottom line, IP numbers (even if you let the server grab it rather than asking the device to figure it out) are subject to changes and are not a good way to verify a user.

Don't get me wrong: I don’t think capturing the IP number is necessarily a bad idea. But use it for informational purposes only. If you want to validate the user, there are generally other, better ways to do this.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • So everything was working perfectly following your solution. It displayed the device token in the console then sent it to my server along with the IP. Then all of a sudden it just stopped working and when testing after tapping allow notifications nothing showed in the console or my server. I did not do anything else but add the app launch icon. I'm confused now and stuck. Any thoughts? I tried changing my target to iOS 12 and then 13 but no luck... – Mike Barbaro Dec 12 '19 at 23:18
  • 1
    @MikeBarbaro - Make sure you look at [the response](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1) with Apple's APNs endpoint, and then you can diagnose what's going on. For example, you can get `BadTopic` if your server uses the wrong "topic" (i.e. the bundle identifier) when sending the request. So, no need to guess, but write PHP code that captures and returns/logs the APNs response so you can diagnose any issues. Otherwise you're flying blind. – Rob Dec 13 '19 at 01:24
  • 1
    If you're still having troubles, I'd suggest you post PHP-specific question showing code illustrating how you're sending the push notification plus the details of what error you're getting, if any. When you do that, be clear whether you're using the newer Auth Key approach (which I find easier to use) or the older API. See [APNs Overview](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1). – Rob Dec 13 '19 at 01:27