A Super Lean Longpolling Javascript/PHP Chat

Published by Ryan on

Using a text file and cleverly evaluating byte counts, we can create a fairly smart base for a chat powered by a simple JS front end and PHP back end. Using jQuery and bootstrap, the actual code is less than 200 lines!

First, try out the demo:

http://ryanrodd.com/chat

Viewing source on the demo page will reveal the front end secrets, but the chat program loop is actually relatively simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    var chat = function() {
        var username="";
        var currentBytes = 0;
        var channel = "";
        var timer = {};
        function listenToChannel(ch, cb) {
            var timer = setInterval(function(){
                $.get("chat.php?channel",{name:ch},function(resp) {
                    if(resp.size > currentBytes) {
                        if(typeof(cb) == "function") {
                            cb(currentBytes);
                        }
                        currentBytes = resp.size;
                    }
                });
            },1000);
            return timer;
        }
        function loadNewMessages(ch, start, cb) {
            $.get("chat.php?messages",{channel:ch,bytes:start},function(resp) {
                $.each(resp, function(i, msg){
                    $("#room-messages").append($("<div></div>")
                        .append($("<div></div>")
                            .addClass("chat-user")
                            .html(msg.user))
                        .append($("<div></div>")
                            .addClass("chat-text")
                            .html(msg.text)
                    ).addClass("chat-line"));
                });
                $("#room-messages").scrollTop($("#room-messages")[0].scrollHeight);
                if(typeof(cb) == "function") {
                    cb();
                }
            });
        }
        $(document).ready(function(){
            var channelSizes = {};
            // Always get rooms when page loads
            $.get("chat.php?channels",function(resp) {
                $("#channel-select").html("<option>Choose room...</option>");
                $.each(resp,function(i, channel) {
                    channelSizes[channel.value]=channel.size;
                    $("#channel-select").append("<option value='"
                        +channel.value+"'>"+channel.name+"</option>");
                });
                $("#channel-select").append("<option value='new'>New...</option>");
            });
            // When a channel is selected, load chat
            $("#start-chat").click(function(){
                channel = $("#channel-select").val();
                username = $("#username").val();
                currentBytes = channelSizes[channel];
                timer = listenToChannel(channel, function(bytes){
                    loadNewMessages(channel, bytes);
                });
                $("#choose-room-div").hide();
                $("#chat-room-div").slideDown();
            });
            $("#send-chat").click(function(){
                if($("#message-text").val()=="")
                    return;
                var data = {
                    "message": $("#message-text").val(),
                    "user": username,
                    "channel":channel
                };
                $.post("chat.php?sendmessage",data,function(resp){
                    loadNewMessages(currentBytes);
                    $("#message-text").val("");
                });
            });
            $("#leave-chat").click(function(){
                clearInterval(timer);
                $("#choose-room-div").slideDown();
                $("#chat-room-div").hide();
            });
            $('#message-text').keydown(function (e){
                if(e.keyCode == 13){
                    $("#send-chat").trigger("click");
                }
            })
        });
    }();

 
And here’s the backend (chat.php).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php
// Get transcript size
function getChannel($name) {
    $ret = [];
    $file = __DIR__ . "/transcripts/".$name;
    $ret['size'] = filesize($file);
    return json_encode($ret);
}
// Return a list of channels
function getChannels() {
    $ret = [];
    $dir = __DIR__ . "/transcripts/";
    if ($handle = opendir($dir)) {
        while (false !== ($entry = readdir($handle))) {
            //echo $entry;
            if(is_file($dir.$entry)) {
                list($name, $date) = explode("|",$entry);
                $ret[] = [
                    'name' => $name,
                    'date' => $date,
                    'value' => $entry,
                    'size' => filesize($dir.$entry)
                ];
            }
        }
        closedir($handle);
    }
    return json_encode($ret);
}
// Create a new channel
function setChannel($name) {

}
// Get the messages in a channel starting at a buffer
function getChannelMessages($channel, $bytes=0) {
    $ret = [];
    $file = __DIR__ . "/transcripts/".$channel;
    if($handle = fopen($file, 'r')) {
        fseek($handle, $bytes);
        while (($line = fgets($handle)) !== false) {
            // process the line read.
            list($msg,$user,$time) = explode("|",$line);
            $ret[] = [
                'text' => $msg,
                'user' => $user,
                'time' => $time
            ];
        }
        fclose($handle);
    }
    return json_encode($ret);
}
// Add a message to a channel file
function setChannelMessage($channel, $user, $message) {
    $file = __DIR__ . "/transcripts/".$channel;
    $message = str_replace("|","",$message);
    $message = htmlentities($message);
    $line = $message."|".$user."|".time().PHP_EOL;
    $handle = fopen($file, 'a');
    fwrite($handle, $line);
    fclose($handle);
    $ret['size'] = filesize($file);
    return json_encode($ret);
}
/*
 * Output to the browser
 */

header('Content-Type: application/json');

// Post routes
if(isset($_POST)) {
    if(isset($_GET['setChannel'])) {
        setChannel($_POST['name']);
    }
    if(isset($_GET['sendmessage'])) {
        echo setChannelMessage($_POST['channel'],$_POST['user'],$_POST['message']);
    }
}

// Get routes
if(isset($_GET)) {
    if(isset($_GET['messages'])) {
        echo getChannelMessages($_GET['channel'],$_GET['bytes']);
        die();
    }
    if(isset($_GET['channel'])) {
        echo getChannel($_GET['name']);
    }
    if(isset($_GET['channels'])) {
        echo getChannels();
    }
}
?>

Leave a Reply

Your email address will not be published. Required fields are marked *