Building a chat app that uses a node.js server in IOS

I am trying to build an iPhone(native) chat app that uses node.js on socket.io.

What is the best way to create chat application on IOS

Is there is any way to create chat application with the node.js server in IOS

Could anyone give me suggestion?

Thanks for you suggestion

Of cuz you could create chat application using Socket.io with iOS/Android and HTML!

There are 2 ways for you to do it!

i) Implement your own socket communication with Socket.io, (this is diffucult, because you need to write most of the network implementation by yourself!)

Socket.io will interface as stream, that you need to connect from your iOS!

You could refer the iOS dev guide on how to implement the stream!

https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/UsingSocketsandSocketStreams.html

There is an active thread that discuss about this too!

iPhone Objective-C socket communication with Socket.IO

ii) Use existing library or wrapper that people make for iOS, you could find the link below!

This library mainly done the networking part, and you just need to implement your app logics!

iOS

https://github.com/pkyeck/socket.IO-objc

Android

https://github.com/nkzawa/socket.io-client.java

It would be good for you to start with the library first and then try to make your own implementation! :))

In answer to your further question. It's awfully easy to use PubNub. Here's the entire code for iOS.

 // (In ViewController.m add to viewDidLoad:)
   [PubNub setConfiguration:[PNConfiguration defaultConfiguration]];

   [PubNub connect];

   //Define a channel
   PNChannel *channel_1 =
     [PNChannel channelWithName:@"a" shouldObservePresence:YES];

   //Subscribe to the channel
   [PubNub subscribeOnChannel:channel_1];

   //Publish on the channel
   [PubNub sendMessage:@"Hello from PubNub iOS!" toChannel:channel_1];

 // in AppDelegate.m,...
   - (void)pubnubClient:(PubNub *)client didReceiveMessage:(PNMessage *)message
          { NSLog( @"%@", message.message ); }

if you also wanna test it on a web page (so, chat back and fore between iOS and web pages), here's the whole functional code. Android is even easier.

PUBNUB.subscribe({
channel : 'demo_channel',
message : function(m){alert(m)}
});

PUBNUB.publish({
channel : 'demo_channel',
message : 'Hello World!'
});

To actually try it right now (to test the speed, etc.) click here

http://www.pubnub.com/developers/quick-start/

enter image description here

So, it's that easy.

(Regarding parse.com. You would probably use parse to handle general user accounts, etc. It's unbelievably easy. You can find any number of examples, tutorials example example Parse's various examples for iOS (here) include full working examples of social apps etc in a few lines of code.)

Furthermore, incredibly, it's even super-easy to use PubNub from parse cloud code (!), then you can do anything. Here, a guy from PubNub explains the whole thing -- it's like 4 lines of code. Publishing messages from Parse via PubNub

PubNub is unbelievably reliable, they have huge datacenters all over the globe (10 or more I think). You can click on their web site to see all the big companies that use them. I'm pretty sure "there's no faster network", it's now the basic "network" for anything r/t.

I suggest following:

  1. Develop HTML5 application with node.js and NOSQL database (CouchDb in my example) on the server
  2. Do not use socket.io for your first playground, because its complex. You must first understand the websockets very well.

Do not burden yourself with pre-prepared frameworks and tons of code. You need a clear, simple code in order to make modifications you need later. You need full control and understanding of the code you have.

I attached working sample (thanks to Ales Smodis). For the sample bellow to work, you need to install 2 node.js packets:

npm install websocket
npm install nano 

And you need to create databse and insert at least 1 user into CouchDb database:

curl -X PUT http://localhost:5984/chatroom
curl -X PUT -H 'Content-Type: application/json' --data '{"username":"john","password":"john"}' http://localhost:5984/chatroom/john

$(document).ready(function () {

    var connection,
        username = 'Tester',
        password = 'Tester',
        historyCounter = 0,
        members = {},
        // displays
        jqLogin = $('#login'),
        jqChatroom = $('#chatroom'),
        // login display components
        jqWaiting = $('#waiting'),
        jqNickname = $('#nickname'),
        jqPassword = $('#password'),
        // chat display components
        jqHistory = $('#history'),
        jqMembers = $('#members'),
        jqLine = $('#line');

    function addLine(nick, line) {
        var jq = $('<p><span class="nick"></span><span class="line"></span></p>'),
            jqNick = jq.find('.nick'),
            jqLine = jq.find('.line'),
            i, lines;
        jqNick.text(nick ? nick + ': ' : '*** ');
        jqLine.text(line);
        jqHistory.append(jq);
        historyCounter++;
        for (lines = jqHistory.children(), i = 0; historyCounter > 100; i++, historyCounter--) {
            $(lines[i]).remove();
        }
    }

    function addMsg(msgObj) {
        var msgHandler = states[activeState].messageHandlers[msgObj.type] || function () { addLine(null, 'Neveljaven paket tipa ' + msgObj.type); };
        msgHandler(msgObj);
    }

    function clearMembers() {
        var nickname;
        for (nickname in members) {
            members[nickname].remove();
            delete members[nickname];
        }
        jqMembers.empty(); // just in case
    }

    function addMember(nickname) {
        var jq = $('<li></li>');
        jq.text(nickname);
        jqMembers.append(jq);
        members[nickname] = jq;
    }

    function removeMember(nickname) {
        if (nickname in members) {
            members[nickname].remove();
            delete members[nickname];
        }
    }

    function connect () {
        connection = new WebSocket('ws://127.0.0.1:8080');

        connection.onopen = function () {
            states[activeState].onopen();
        };

        connection.onmessage = function (message) {
            try {
                addMsg(JSON.parse(message.data));
            }
            catch (e) {
                addLine(null, 'Exception while handling a server message: ' + e.toString());
            }
        };

        connection.onclose = function () {
            states[activeState].onclose();
        };
    }

    function loginKeypress(event) {
        if (13 !== event.keyCode) return;
        username = jqNickname.val();
        password = jqPassword.val();
        if (!username) jqNickname[0].focus();
        else if (!password) jqPassword[0].focus();
        else {
            jqWaiting.css('display', '');
            jqNickname.unbind('keydown', loginKeypress);
            jqPassword.unbind('keydown', loginKeypress);
            connect();
        }
    }

    function inputKeypress(event) {
        var line;
        if (13 === event.keyCode) {
            line = jqLine.val();
            if (line.length === 0) return;
            jqLine.val('');
            connection.send(JSON.stringify({ 'type': 'line', 'line': line }));
        }
    }

    var states = {
            'login': {
                'start': function () {
                    jqChatroom.css('display', 'none');
                    jqWaiting.css('display', 'none');
                    jqLogin.css('display', '');
                    jqNickname.val('');
                    jqPassword.val('');
                    jqNickname[0].focus();
                    activeState = 'login';
                    jqNickname.bind('keydown', loginKeypress);
                    jqPassword.bind('keydown', loginKeypress);
                },
                'onopen': function () {
                    connection.send(JSON.stringify({ 'type': 'login', 'username': username, 'password': password }));
                },
                'messageHandlers': {
                    'state': function (msgObj) {
                        var i, history, users;
                        states.chat.start();
                        history = msgObj.history;
                        jqHistory.empty();
                        historyCounter = 0;
                        for (i = 0; i < history.length; i++) addMsg(history[i]);
                        users = msgObj.users;
                        clearMembers();
                        for (i = 0; i < users.length; i++) addMember(users[i]);
                    }
                },
                'unhandledMessage': function (msgObj) {
                    connection.close(4000, 'Unhandled message type');
                },
                'onclose': function () {
                    states.login.start();
                }
            },

            'chat': {
                'start': function () {
                    jqLogin.css('display', 'none');
                    jqWaiting.css('display', 'none');
                    jqChatroom.css('display', '');
                    jqHistory.empty();
                    historyCounter = 0;
                    activeState = 'chat';
                    jqLine.bind('keydown', inputKeypress);
                },
                'onopen': function () {
                    connection.close(4001, 'Connection opened while chatting');
                },
                'messageHandlers': {
                    'line': function (msgObj) {
                        addLine(msgObj.nick, msgObj.line);
                    },
                    'join': function (msgObj) {
                        addLine(null, 'Priklopil: ' + msgObj.nick);
                        addMember(msgObj.nick);
                    },
                    'leave': function (msgObj) {
                        addLine(null, 'Odklopil: ' + msgObj.nick);
                        removeMember(msgObj.nick);
                    }
                },
                'unhandledMessage': function (msgObj) {
                    connection.close(4000, 'Unhandled message type');
                },
                'onclose': function () {
                    addLine(null, 'Connection closed');
                    jqLine.unbind('keydown', inputKeypress);
                }
            }
        },
        activeState = 'login';

    states.login.start();
});
// node.js code
var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs'),
    nano = require('nano')('http://localhost:5984'),
    chatroomDb = nano.use('chatroom'),
    websocket = require('websocket'),
    chatHistory = [],
    activeUsers = {};

var filesDir = path.join(process.cwd(), 'web');

var mimeTypes = {
    '.html': 'text/html',
    '.css': 'text/css',
    '.js': 'text/javascript'
};

var getContentType = function (extension) {
    var mimeType = mimeTypes[extension];
    return mimeType ? mimeType : 'application/octet-stream';
};

var server = http.createServer(function (request, response) {
    var relativePath = url.parse(request.url).pathname,
        absolutePath = path.join(filesDir, relativePath);
    var handler = function (err, stats) {
        if (stats) {
            if (stats.isDirectory()) {
                absolutePath = path.join(absolutePath, 'index.html');
                fs.stat(absolutePath, handler);
                return;
            }
            if (stats.isFile()) {
                response.writeHead(200, getContentType(path.extname(absolutePath)));
                var stream = fs.createReadStream(absolutePath);
                stream.pipe(response);
                return;
            }
        }
        response.writeHead(404, {'Content-Type': 'text/plain'});
        response.write('Not found\r\n');
        response.end();
    };
    console.log('HTTP request for ' + relativePath);
    fs.stat(absolutePath, handler);
});
server.listen(8080, function () {});

wsServer = new websocket.server({
    'httpServer': server
});

function addLine (type, nick, line) {
    var msg = { 'type': type, 'nick': nick, 'line': line },
        jsonMsg = JSON.stringify(msg),
        username;
    chatHistory.push(msg);
    while (chatHistory.length > 100) chatHistory.shift();
    for (username in activeUsers) {
        activeUsers[username].sendMessage(jsonMsg);
    }
}

wsServer.on('request', function (request) {
    console.log('New websocket connection from ' + request.origin);
    // TODO: verify that request.origin is our web site

    var connection = request.accept(null, request.origin);

    var username = null;

    connection.on('message', function (message) {
        if (message.type !== 'utf8') {
            console.log('Refusing a non-utf8 message');
            return;
        }
        console.log('Processing message: ' + message.utf8Data);
        try {
            var m = JSON.parse(message.utf8Data);
            switch (m.type) {

                case 'login':
                    chatroomDb.get(m.username, function (err, body) {
                        if (err || (body.password !== m.password)) {
                            connection.close();
                            return;
                        }
                        username = m.username;
                        addLine('join', username, null);
                        activeUsers[username] = {
                            'sendMessage': function (jsonMsg) {
                                connection.sendUTF(jsonMsg);
                            }
                        };
                        var users = [], u;
                        for (u in activeUsers) users.push(u);
                        connection.sendUTF(JSON.stringify({ 'type': 'state', 'history': chatHistory, 'users': users }));
                    });
                    break;

                case 'line':
                    if (!username) {
                        connection.close();
                        break;
                    }
                    addLine('line', username, m.line);
                    break;
            }
        }
        catch (e) {
            console.log(e);
        }
    });

    connection.on('close', function (connection) {
        console.log('Connection closed');
        if (username) {
            delete activeUsers[username];
            addLine('leave', username, null);
        }
    });
});

console.log('Server running');
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Chatroom</title>
      <style>
        html, body {
    width: 100%;
    height: 100%;
    padding: 0;
    border: none;
    margin: 0;
}

#heading {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 30px;
    margin: 0;
    padding: 0;
    line-height: 30px;
    text-align: center;
    font-size: 20px;
    background-color: green;
}

#outer {
    position: absolute;
    top: 30px;
    bottom: 0;
    left: 0;
    right: 0;
    margin: 20px;
    min-height: 400px;
    min-width: 400px;
    background-color: lime;
}

#inner {
    height: 100%;
    background-color: #ffc0cb;
}

#chat {
    position: absolute;
    top: 0;
    left: 0;
    right: 200px;
    bottom: 0;
    background-color: #ffd700;
}

#members {
    position: absolute;
    top: 0;
    right: 10px;
    width: 180px;
    bottom: 0;
    background-color: #ff00ff;
    list-style-type: none;
    padding: 10px;
    padding: 0;
    border: none;
}

#history {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 2em;
    right: 0;
    background-color: #00ffff;
    padding: 10px;
}

#input {
    position: absolute;
    height: 2em;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #90ee90;
    line-height: 2em;
}

#line {
    width: 100%;
    margin: 0;
    border: none;
    padding: 0;
}

#history > p {
    margin: 2px;
}

.nick {
    white-space: pre;
    display: table-cell;
}

.line {
    display: table-cell;
}

#login {
    height: 100%;
    display: table;
    margin: 0 auto;
}
#login > .svg {
    vertical-align: middle;
    display: table-cell;
}
#login-wrapper1 {
    display: table;
    height: 100%;
    width: 100%;
}
#login-wrapper2 {
    display: table-cell;
    vertical-align: middle;
}
#login-table {
    margin: 0 auto;
}
#login-table .label {
    text-align: right;
}

#waiting {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    opacity: 0.3;
}

       </style> 
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
        <script src="client.js"></script>
    </head>
    <body>
        <div id="login">
            <div class="svg">

                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    xmlns:xlink="http://www.w3.org/1999/xlink"
                    viewBox="0 0 400 400"
                    width="400"
                    height="400">
                    <defs>
                        <path id="curved-text" d="M0,0 c 50 -50 150 -50 200 0" />
                    </defs>
                    <g>
                        <text
                            transform="translate(-100,40)"
                            font-weight="bold"
                            font-variant="small-caps"
                            font-family="Arial sans-serif"
                            font-size="30"
                            fill="none"
                            stroke="orange"
                            text-anchor="middle">
                            <textPath xlink:href="#curved-text" startOffset="50%">Chatroom</textPath>
                        </text>
                        <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0" to="360" dur="5s" fill="remove" additive="sum" repeatCount="indefinite" />
                        <animateMotion dur="10s" repeatCount="indefinite" path="M100,200 a100,100 0 1 1 200,0 a100,100 0 1 1 -200,0" />
                    </g>
                    <foreignObject
                        x="0"
                        y="0"
                        width="400"
                        height="400"
                        style="height:400px;">

                        <div xmlns="http://www.w3.org/1999/xhtml" id="login-wrapper1">
                            <div id="login-wrapper2">
                                <table id="login-table">
                                    <tbody>
                                        <tr>
                                            <td class="label">Vzdevek:</td><td><input type="text" id="nickname" /></td>
                                        </tr>
                                        <tr>
                                            <td class="label">Geslo:</td><td><input type="password" id="password" /></td>
                                        </tr>
                                    </tbody>
                                </table>
                            </div>
                        </div>

                    </foreignObject>
                </svg>

            </div>
        </div>

        <div id="chatroom" style="display:none;">
            <h1 id="heading">Chatroom</h1>

            <div id="outer">
                <div id="inner">
                    <div id="chat">
                        <div id="history">
                            <p><span class="nick">Matej: </span><span class="line">Hi</span></p>
                            <p><span class="nick">Borut: </span><span class="line">How are you?</span></p>
                            <p><span class="nick">Martina: </span><span class="line">Ok, thanks!</span></p>
                        </div>
                        <div id="input"><input id="line" type="text"></div>
                    </div>
                    <ul id="members">
                        <li>Matej</li>
                        <li>Borut</li>
                        <li>Martina</li>
                    </ul>
                </div>
            </div>
        </div>

        <div id="waiting" style="display:none;"></div>

    </body>
</html>