IOS push notifications between devices - GCDAsyncSocket

189 Views Asked by At

Hello Fellow Programmers,

I am attempting to allow users to send push notifications to other users (like sending a friend request, etc.).

The end goal here is for my iOS app to continuously listen to a specific hostname/port URL once a user has logged in to their account (aka a specific view has loaded). My stack is an express server communicating with a MongoDB.

Lets say a user with {account_id} logged in, the path to his account info would be: "http://72.89.157.153:3000/accounts/{account_id}

I would like my app to listen to any requests send to this URL. I am using GCDAsyncSocket libraries to help with the cause. However, when I connect to http://72.89.157.153:3000/ for testing purposes, no delegate function is called. I have seen many people with this same problem, but I can not get any solution I have read to pan out.

Code:

SocketConnection.h

#ifndef SocketConnection_h
#define SocketConnection_h
#import "GCDAsyncSocket.h" // for TCP

@import CocoaAsyncSocket;

@interface SocketConnection : NSObject <GCDAsyncSocketDelegate>

/* GCDAsyncSocket */
@property (strong, nonatomic) GCDAsyncSocket *socket;


// Methods
+(SocketConnection *)sharedConnection;

@end

#endif /* SocketConnection_h */

SocketConnection.m

#import <Foundation/Foundation.h>
#import "SocketConnection.h"
@implementation SocketConnection

+(SocketConnection *)sharedConnection {
    static dispatch_once_t once;
    static SocketConnection *instance;

    dispatch_once(&once, ^{
        instance = [[SocketConnection alloc] init];
    });


    return instance;
}


-(id)init {

    _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

    NSError *err = nil;
    if (![_socket connectToHost:@"http://72.89.157.153" onPort:3000 error:&err]) {
        printf("\nDid Not Return Okay: %s\n", [[err localizedDescription] UTF8String]);
    } else {
        printf("\nReturned Okay\n"); // This is printed
    }

    return self;
}

/* ASNYC DELEGATES */

/* I am expecting this method to be called when connectToHost: is called in init.. */

- (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(NSString *)host port:(UInt16)port {
    printf("I'm connected! Host:%s\n", [host UTF8String]); 
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    printf("I have written That was easy.\n");


}

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag {
    printf("I have read That was easy.\n");

    dispatch_async(dispatch_get_main_queue(), ^{
        @autoreleasepool {
            [_socket readDataWithTimeout:-1 tag:1];
        }


    });

}

@end

Here is the spot in the ViewController where I create an instance of SocketConnection...

-(void)viewDidAppear:(BOOL)animated {
    /* Socket connector */
    SocketConnection *s = [SocketConnection sharedConnection];
    printf("port: %hu\n" ,s.socket.connectedPort); // prints 0 right now
}

If this is not the best way to achieve my goal, please point me in the right direction (link readings, other frameworks, libraries etc.) Any questions please let me know.

Thank you for the help.

1

There are 1 best solutions below

0
Francisco Perez On

okey, for your first goal (allow users to send push notifications to other users and assuming that you have a node.js server side with express and mongodb) try doing this:

First on the server side install apn and node-gcm.

npm i --save apn node-gcm

This two packages are used to send push notifications to ios and android.

Once you have these packages installed make a route on your server side to send the notifications. This can be done with something like this:

const express = require('express');
const path = require('path');
const gcm = require('node-gcm');
const apn = require('apn');

const apnProvider = new apn.Provider({
  token: {
    // YOU CAN FOUND THIS KEYS AND THE CERTIFICATE ON APPLE DEVELOPERS SITE
    key: path.resolve(__dirname, 'PATH TO YOUR CERTIFICATE.P8'),
    keyId: YOUR APN KEY ID,
    teamId: YOUR APN TEAM ID,
  },
  production: false,
});

router.post('/sendNotification', (req, res) => {
const deviceToken = req.body.token;
const message = req.body.message;
const payload = req.body.payload;
const packages = req.body.package;

switch (packages) {
  case 'com.foo.bar': {
  const notification = new apn.Notification();
  notification.topic = 'com.foo.bar';
  notification.expiry = Math.floor(Date.now() / 1000) + 3600;
  notification.badge = 1;
  notification.sound = 'ping.aiff';
  notification.alert = { message };
  notification.payload = { payload };
  apnProvider.send(notification, deviceToken).then((result) => {
    return result === 200 ? res.sendStatus(200, result) : res.sendStatus(400);
  });
  break;
}
case 'com.yourteam.foo.bar': {
  const androidMessage = new gcm.Message({
    priority: 'high',
    contentAvailable: true,
    delayWhileIdle: false,
    timeToLive: 10,
    restrictedPackageName: 'com.yourteam.foo.bar',
    dryRun: false,
    data: {
      title: 'foo',
      icon: '@mipmap/logo',
      notId: parseInt(Math.random() * new Date().getSeconds(), 10),
      message,
    },
  });
  const sender = new gcm.Sender(YOUR_KEY);
  const registrationTokens = [deviceToken];
  sender.send(androidMessage, { registrationTokens }, (err, response) => {
    return err ? res.send(err) : res.send(response);
  });
  break;
}
default:
  return res.sendStatus(400);
}
});

Now to send a push notification you need to do a POST like this one:

IOS

Objective C

#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"content-type": @"application/x-www-form-urlencoded",
                       @"cache-control": @"no-cache"

NSMutableData *postData = [[NSMutableData alloc] initWithData:[@"token=xxxxx" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&message=xxxxx" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&payload=xxxxx" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&package=xxxxx" dataUsingEncoding:NSUTF8StringEncoding]];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://72.89.157.153:3000/notifications/sendNotification"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];

SWIFT

import Foundation

let headers = [
  "content-type": "application/x-www-form-urlencoded",
  "cache-control": "no-cache"
]

let postData = NSMutableData(data: "token=xxxxx".data(using: String.Encoding.utf8)!)
postData.append("&message=xxxxx".data(using: String.Encoding.utf8)!)
postData.append("&payload=xxxxx".data(using: String.Encoding.utf8)!)
postData.append("&package=xxxxx".data(using: String.Encoding.utf8)!)

let request = NSMutableURLRequest(url: NSURL(string: "http://72.89.157.153:3000/notifications/sendNotification")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

WEB (AJAX)

var settings = {
  "async": true,
  "crossDomain": true,
  "url": "http://72.89.157.153:3000/notifications/sendNotification",
  "method": "POST",
  "headers": {
    "content-type": "application/x-www-form-urlencoded",
    "cache-control": "no-cache"
  },
  "data": {
    "token": "xxxxx",
    "message": "xxxxx",
    "payload": "xxxxx",
    "package": "xxxxx"
  }
}

$.ajax(settings).done(function (response) {
  console.log(response);
});

JAVA

OkHttpClient client = new OkHttpClient();

MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "token=xxxxx&message=xxxxx&payload=xxxxx&package=xxxxx");
Request request = new Request.Builder()
  .url("http://72.89.157.153:3000/notifications/sendNotification")
  .post(body)
  .addHeader("content-type", "application/x-www-form-urlencoded")
  .addHeader("cache-control", "no-cache")
  .build();

Response response = client.newCall(request).execute();

Now you can send push notifications to all devices.

Your second goal can be done easy with your server side, when a request is sent to your URL, you can do a POST sending the push notification, for example if someone wanna add you as a friend (lets gonna say that they made a request to http://72.89.157.153:3000/friends/{account_id}), you can send a notification to the user telling him that he have a new friendship request.

Its important that's on mongodb you store the package and the token of your users, so you can send the right notifications.

Hope it helps.