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
85
86
87
88
89
90
91
92
93
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();
}
}
?>

0 Comments

Leave a Reply

Avatar placeholder

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