Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

updated coffee generated client

  • Loading branch information...
commit ad8159f6bc5f5f9471bba7dec0a33d6a24bab5b8 1 parent 7cbdd71
@twilson63 twilson63 authored
Showing with 1,604 additions and 776 deletions.
  1. +410 −0 client.coffee
  2. +233 −316 client.js
  3. +507 −0 client_nocoffee.js
  4. +11 −13 example.js
  5. +258 −260 fu.js
  6. +185 −187 server.js
View
410 client.coffee
@@ -0,0 +1,410 @@
+CONFIG: {
+ debug: false
+ nick: "#" # set in onConnect
+ id: null # set in onConnect
+ last_message_time: 1
+ focus: true #event listeners bound in onConnect
+ unread: 0 #updated in the message-processing loop
+}
+
+nicks: []
+
+Date::toRelativeTime: (now_threshold) ->
+ delta: new Date() - this
+ now_threshold: parseInt(now_threshold, 10)
+ if isNaN(now_threshold) then now_threshold: 0
+ if delta <= now_threshold then return 'Just now'
+ units: null
+ conversions: {
+ millisecond: 1, # ms -> ms
+ second: 1000, # ms -> sec
+ minute: 60, # sec -> min
+ hour: 60, # min -> hour
+ day: 24, # hour -> day
+ month: 30, # day -> month (roughly)
+ year: 12 # month -> year
+ }
+
+ for key, value of conversions
+ if delta < value
+ break
+ else
+ units: key
+ delta: delta / value
+
+ delta: Math.floor(delta)
+ if delta isnt 1 then units += 's'
+ return [delta, units].join(" ")
+
+
+Date.fromString: (str) -> new Date(Date.parse(str))
+
+#updates the users link to reflect the number of active users
+updateUsersLink: ->
+ t: nicks.length.toString() + " user"
+ if nicks.length isnt 1 then t += "s"
+ $("#usersLink").text t
+
+
+#handles another person joining chat
+userJoin: (nick, timestamp) ->
+ #put it in the stream
+ addMessage nick, "joined", timestamp, "join"
+ #if we already know about this user, ignore it
+ for n in nicks
+ if n is nick then return
+ #otherwise, add the user to the list
+ nicks.push(nick)
+ #update the UI
+ updateUsersLink()
+
+#handles someone leaving
+userPart: (nick, timestamp) ->
+ #put it in the stream
+ addMessage nick, "left", timestamp, "part"
+ #remove the user from the list
+ for n in nicks
+ if n is nick
+ nicks.splice(i,1)
+ break
+
+ #update the UI
+ updateUsersLink();
+
+# utility functions
+
+util: {
+ urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
+
+ # html sanitizer
+ toStaticHTML: (inputHtml) ->
+ inputHtml: inputHtml.toString()
+ return inputHtml.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
+
+ #pads n with zeros on the left,
+ #digits is minimum length of output
+ #zeroPad(3, 5); returns "005"
+ #zeroPad(2, 500); returns "500"
+ zeroPad: (digits, n) ->
+ n = n.toString()
+ while n.length < digits
+ n = '0' + n
+ return n
+
+ #it is almost 8 o'clock PM here
+ #timeString(new Date); returns "19:49"
+ timeString: (date) ->
+ minutes: date.getMinutes().toString()
+ hours: date.getHours().toString()
+ return @zeroPad(2, hours) + ":" + @zeroPad(2, minutes)
+
+
+ #does the argument only contain whitespace?
+ isBlank: (text) ->
+ blank: /^\s*$/
+ return text.match(blank) isnt null
+}
+
+#used to keep the most recent messages visible
+scrollDown: ->
+ window.scrollBy 0, 100000000000000000
+ $("#entry").focus()
+
+#inserts an event into the stream for display
+#the event may be a msg, join or part type
+#from is the user, text is the body and time is the timestamp, defaulting to now
+#_class is a css class to apply to the message, usefull for system events
+addMessage: (from, text, time, _class) ->
+ if text is null then return
+
+ if time is null
+ # if the time is null or undefined, use the current time.
+ time: new Date()
+ else if (time instanceof Date) is false
+ # if it's a timestamp, interpret it
+ time: new Date(time)
+
+ #every message you see is actually a table with 3 cols:
+ # the time,
+ # the person who caused the event,
+ # and the content
+ messageElement: $(document.createElement("table"))
+
+ messageElement.addClass("message")
+ if _class then messageElement.addClass(_class)
+
+ # sanitize
+ text: util.toStaticHTML(text)
+
+ # If the current user said this, add a special css class
+ nick_re: new RegExp(CONFIG.nick)
+ if nick_re.exec(text) then messageElement.addClass("personal")
+
+ # replace URLs with links
+ text: text.replace(util.urlRE, '<a target="_blank" href="$&">$&</a>')
+
+ content: """
+ <tr>
+ <td class="date">${ util.timeString(time) }</td>
+ <td class="nick">${ util.toStaticHTML(from) }</td>
+ <td class="msg-text">$text</td>
+ </tr>
+ """
+ messageElement.html content
+
+ #the log is the stream that we view
+ $("#log").append(messageElement)
+
+ #always view the most recent message when it is added
+ scrollDown()
+
+
+updateRSS: ->
+ bytes: parseInt(rss)
+ if bytes
+ megabytes: bytes / (1024*1024)
+ megabytes: Math.round(megabytes*10)/10
+ $("#rss").text(megabytes.toString())
+
+
+updateUptime: ->
+ $("#uptime").text(starttime.toRelativeTime()) if starttime
+
+transmission_errors: 0
+first_poll: true
+
+
+#process updates if we have any, request updates from the server,
+# and call again with response. the last part is like recursion except the call
+# is being made from the response handler, and not at some point during the
+# function's execution.
+longPoll: (data) ->
+ if transmission_errors > 2
+ showConnect()
+ return
+
+ if data and data.rss
+ rss: data.rss
+ updateRSS()
+
+
+ #process any updates we may have
+ #data will be null on the first call of longPoll
+ if data and data.messages
+ for message in data.messages
+ #track oldest message so we only request newer messages from server
+ if message.timestamp > CONFIG.last_message_time
+ CONFIG.last_message_time: message.timestamp
+
+ #dispatch new messages to their appropriate handlers
+ switch message.type
+ when "msg"
+ if !CONFIG.focus
+ CONFIG.unread++
+ addMessage message.nick, message.text, message.timestamp
+ when "join" then userJoin(message.nick, message.timestamp)
+ when "part" then userPart(message.nick, message.timestamp)
+
+
+ #update the document title to include unread message count if blurred
+ updateTitle()
+
+ #only after the first request for messages do we want to show who is here
+ if first_poll
+ first_poll: false
+ who()
+
+
+
+ #make another request
+ $.ajax({
+ cache: false
+ type: "GET"
+ url: "/recv"
+ dataType: "json"
+ data: { since: CONFIG.last_message_time, id: CONFIG.id }
+ error: ->
+ addMessage("", "long poll error. trying again...", new Date(), "error")
+ transmission_errors += 1
+ #don't flood the servers on error, wait 10 seconds before retrying
+ setTimeout(longPoll, 10*1000)
+ success: (data) ->
+ transmission_errors = 0;
+ #if everything went well, begin another request immediately
+ #the server will take a long time to respond
+ #how long? well, it will wait until there is another message
+ #and then it will return it to us and close the connection.
+ #since the connection is closed when we get data, we longPoll again
+ longPoll(data)
+
+ })
+
+
+#submit a new message to the server
+send: (msg) ->
+ if CONFIG.debug is false
+ # XXX should be POST
+ # XXX should add to messages immediately
+ jQuery.get("/send", {id: CONFIG.id, text: msg}, (data) ->
+ return true
+ "json")
+
+#Transition the page to the state that prompts the user for a nickname
+showConnect: ->
+ $("#connect").show()
+ $("#loading").hide()
+ $("#toolbar").hide()
+ $("#nickInput").focus()
+
+#transition the page to the loading screen
+showLoad: ->
+ $("#connect").hide()
+ $("#loading").show()
+ $("#toolbar").hide()
+
+#transition the page to the main chat view, putting the cursor in the textfield
+showChat: (nick) ->
+ $("#toolbar").show()
+ $("#entry").focus()
+ $("#connect").hide()
+ $("#loading").hide()
+ scrollDown()
+
+#we want to show a count of unread messages when the window does not have focus
+updateTitle: ->
+ if CONFIG.unread
+ document.title: "(" + CONFIG.unread.toString() + ") node chat"
+ else
+ document.title = "node chat"
+
+
+# daemon start time
+starttime: null
+# daemon memory usage
+rss: null
+
+
+#handle the server's response to our nickname and join request
+onConnect: (session) ->
+ if session.error
+ alert("error connecting: " + session.error)
+ showConnect()
+ return
+
+ CONFIG.nick: session.nick
+ CONFIG.id: session.id
+ starttime: new Date(session.starttime)
+ rss: session.rss
+ updateRSS()
+ updateUptime()
+
+ #update the UI to show the chat
+ showChat(CONFIG.nick)
+
+ #listen for browser events so we know to update the document title
+ $(window).bind("blur", ->
+ CONFIG.focus: false
+ updateTitle()
+ )
+
+ $(window).bind("focus", ->
+ CONFIG.focus: true
+ CONFIG.unread: 0
+ updateTitle()
+ )
+
+
+#add a list of present chat members to the stream
+outputUsers: ->
+ nick_string: if nicks.length > 0 then nicks.join(", ") else "(none)"
+ addMessage "users:", nick_string, new Date(), "notice"
+ return false
+
+#get a list of the users presently in the room, and add it to the stream
+who: ->
+ jQuery.get("/who", {}, (data, status) ->
+ if status isnt "success" then return
+ nicks = data.nicks
+ outputUsers()
+ "json"
+ )
+
+$(document).ready( ->
+ alert('hello world')
+ #submit new messages when the user hits enter if the message isnt blank
+ $("#entry").keypress( (e) ->
+ if e.keyCode isnt 13 then return
+ msg: $("#entry").attr("value").replace("\n", "")
+ if not util.isBlank(msg) then send(msg)
+ $("#entry").attr("value", "")
+ )
+
+ $("#usersLink").click(outputUsers)
+
+ #try joining the chat when the user clicks the connect button
+ $("#connectButton").click( ->
+ #lock the UI while waiting for a response
+ showLoad()
+ nick: $("#nickInput").attr("value")
+
+ #dont bother the backend if we fail easy validations
+ if nick.length > 50
+ alert("Nick too long. 50 character max.")
+ showConnect()
+ return false
+
+
+ #more validations
+ if /[^\w_\-^!]/.exec(nick)
+ alert("Bad character in nick. Can only have letters, numbers, and '_', '-', '^', '!'");
+ showConnect()
+ return false
+
+
+ #make the actual join request to the server
+ $.ajax({
+ cache: false
+ type: "GET" # XXX should be POST
+ dataType: "json"
+ url: "/join"
+ data: { nick: nick }
+ error: ->
+ alert("error connecting to server")
+ showConnect()
+ success: onConnect
+ })
+ return false
+ )
+
+ # update the daemon uptime every 10 seconds
+ setInterval( ->
+ updateUptime()
+ 10*1000)
+
+ if CONFIG.debug
+ $("#loading").hide()
+ $("#connect").hide()
+ scrollDown()
+ return
+
+ # remove fixtures
+ $("#log table").remove()
+
+ #begin listening for updates right away
+ #interestingly, we don't need to join a room to get its updates
+ #we just don't show the chat stream to the user until we create a session
+ longPoll()
+
+ showConnect()
+)
+
+#if we can, notify the server that we're going away.
+$(window).unload( ->
+ jQuery.get("/part", {id: CONFIG.id}, (data) ->
+ return
+ "json")
+)
+
+
+
+
View
549 client.js
@@ -1,507 +1,424 @@
-var CONFIG = { debug: false
- , nick: "#" // set in onConnect
- , id: null // set in onConnect
- , last_message_time: 1
- , focus: true //event listeners bound in onConnect
- , unread: 0 //updated in the message-processing loop
- };
-
-var nicks = [];
-
-// CUT ///////////////////////////////////////////////////////////////////
-/* This license and copyright apply to all code until the next "CUT"
-http://github.com/jherdman/javascript-relative-time-helpers/
-
-The MIT License
-
-Copyright (c) 2009 James F. Herdman
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
-
- * Returns a description of this past date in relative terms.
- * Takes an optional parameter (default: 0) setting the threshold in ms which
- * is considered "Just now".
- *
- * Examples, where new Date().toString() == "Mon Nov 23 2009 17:36:51 GMT-0500 (EST)":
- *
- * new Date().toRelativeTime()
- * --> 'Just now'
- *
- * new Date("Nov 21, 2009").toRelativeTime()
- * --> '2 days ago'
- *
- * // One second ago
- * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime()
- * --> '1 second ago'
- *
- * // One second ago, now setting a now_threshold to 5 seconds
- * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime(5000)
- * --> 'Just now'
- *
- */
+var CONFIG, addMessage, first_poll, longPoll, nicks, onConnect, outputUsers, rss, scrollDown, send, showChat, showConnect, showLoad, starttime, transmission_errors, updateRSS, updateTitle, updateUptime, updateUsersLink, userJoin, userPart, util, who;
+var __hasProp = Object.prototype.hasOwnProperty;
+CONFIG = {
+ debug: false,
+ nick: "#",
+ // set in onConnect
+ id: null,
+ // set in onConnect
+ last_message_time: 1,
+ focus: true,
+ //event listeners bound in onConnect
+ unread: 0
+ //updated in the message-processing loop
+};
+nicks = [];
Date.prototype.toRelativeTime = function(now_threshold) {
- var delta = new Date() - this;
-
+ var _a, conversions, delta, key, units, value;
+ delta = new Date() - this;
now_threshold = parseInt(now_threshold, 10);
-
- if (isNaN(now_threshold)) {
- now_threshold = 0;
- }
-
+ isNaN(now_threshold) ? (now_threshold = 0) : null;
if (delta <= now_threshold) {
return 'Just now';
}
-
- var units = null;
- var conversions = {
- millisecond: 1, // ms -> ms
- second: 1000, // ms -> sec
- minute: 60, // sec -> min
- hour: 60, // min -> hour
- day: 24, // hour -> day
- month: 30, // day -> month (roughly)
- year: 12 // month -> year
+ units = null;
+ conversions = {
+ millisecond: 1,
+ // ms -> ms
+ second: 1000,
+ // ms -> sec
+ minute: 60,
+ // sec -> min
+ hour: 60,
+ // min -> hour
+ day: 24,
+ // hour -> day
+ month: 30,
+ // day -> month (roughly)
+ year: 12
+ // month -> year
};
-
- for (var key in conversions) {
- if (delta < conversions[key]) {
+ _a = conversions;
+ for (key in _a) { if (__hasProp.call(_a, key)) {
+ value = _a[key];
+ if (delta < value) {
break;
} else {
- units = key; // keeps track of the selected key over the iteration
- delta = delta / conversions[key];
+ units = key;
+ delta = delta / value;
}
- }
-
- // pluralize a unit when the difference is greater than 1.
+ }}
delta = Math.floor(delta);
- if (delta !== 1) { units += "s"; }
+ delta !== 1 ? units += 's' : null;
return [delta, units].join(" ");
};
-
-/*
- * Wraps up a common pattern used with this plugin whereby you take a String
- * representation of a Date, and want back a date object.
- */
Date.fromString = function(str) {
return new Date(Date.parse(str));
};
-
-// CUT ///////////////////////////////////////////////////////////////////
-
-
-
//updates the users link to reflect the number of active users
-function updateUsersLink ( ) {
- var t = nicks.length.toString() + " user";
- if (nicks.length != 1) t += "s";
- $("#usersLink").text(t);
-}
-
+updateUsersLink = function() {
+ var t;
+ t = nicks.length.toString() + " user";
+ nicks.length !== 1 ? t += "s" : null;
+ return $("#usersLink").text(t);
+};
//handles another person joining chat
-function userJoin(nick, timestamp) {
+userJoin = function(nick, timestamp) {
+ var _a, _b, _c, n;
//put it in the stream
addMessage(nick, "joined", timestamp, "join");
//if we already know about this user, ignore it
- for (var i = 0; i < nicks.length; i++)
- if (nicks[i] == nick) return;
- //otherwise, add the user to the list
+ _b = nicks;
+ for (_a = 0, _c = _b.length; _a < _c; _a++) {
+ n = _b[_a];
+ if (n === nick) {
+ return null;
+ }
+ //otherwise, add the user to the list
+ }
nicks.push(nick);
//update the UI
- updateUsersLink();
-}
-
+ return updateUsersLink();
+};
//handles someone leaving
-function userPart(nick, timestamp) {
+userPart = function(nick, timestamp) {
+ var _a, _b, _c, n;
//put it in the stream
addMessage(nick, "left", timestamp, "part");
//remove the user from the list
- for (var i = 0; i < nicks.length; i++) {
- if (nicks[i] == nick) {
- nicks.splice(i,1)
+ _b = nicks;
+ for (_a = 0, _c = _b.length; _a < _c; _a++) {
+ n = _b[_a];
+ if (n === nick) {
+ nicks.splice(i, 1);
break;
}
}
//update the UI
- updateUsersLink();
-}
-
+ return updateUsersLink();
+};
// utility functions
-
util = {
- urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
-
- // html sanitizer
+ urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
+ // html sanitizer
toStaticHTML: function(inputHtml) {
inputHtml = inputHtml.toString();
- return inputHtml.replace(/&/g, "&amp;")
- .replace(/</g, "&lt;")
- .replace(/>/g, "&gt;");
- },
-
+ return inputHtml.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+ },
//pads n with zeros on the left,
//digits is minimum length of output
//zeroPad(3, 5); returns "005"
//zeroPad(2, 500); returns "500"
- zeroPad: function (digits, n) {
+ zeroPad: function(digits, n) {
n = n.toString();
- while (n.length < digits)
+ while (n.length < digits) {
n = '0' + n;
+ }
return n;
},
-
//it is almost 8 o'clock PM here
//timeString(new Date); returns "19:49"
- timeString: function (date) {
- var minutes = date.getMinutes().toString();
- var hours = date.getHours().toString();
+ timeString: function(date) {
+ var hours, minutes;
+ minutes = date.getMinutes().toString();
+ hours = date.getHours().toString();
return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes);
},
-
//does the argument only contain whitespace?
isBlank: function(text) {
- var blank = /^\s*$/;
- return (text.match(blank) !== null);
+ var blank;
+ blank = /^\s*$/;
+ return text.match(blank) !== null;
}
};
-
//used to keep the most recent messages visible
-function scrollDown () {
+scrollDown = function() {
window.scrollBy(0, 100000000000000000);
- $("#entry").focus();
-}
-
+ return $("#entry").focus();
+};
//inserts an event into the stream for display
//the event may be a msg, join or part type
//from is the user, text is the body and time is the timestamp, defaulting to now
//_class is a css class to apply to the message, usefull for system events
-function addMessage (from, text, time, _class) {
- if (text === null)
- return;
-
- if (time == null) {
+addMessage = function(from, text, time, _class) {
+ var content, messageElement, nick_re;
+ if (text === null) {
+ return null;
+ }
+ if (time === null) {
// if the time is null or undefined, use the current time.
time = new Date();
} else if ((time instanceof Date) === false) {
// if it's a timestamp, interpret it
time = new Date(time);
}
-
//every message you see is actually a table with 3 cols:
// the time,
// the person who caused the event,
// and the content
- var messageElement = $(document.createElement("table"));
-
+ messageElement = $(document.createElement("table"));
messageElement.addClass("message");
- if (_class)
- messageElement.addClass(_class);
-
+ _class ? messageElement.addClass(_class) : null;
// sanitize
text = util.toStaticHTML(text);
-
// If the current user said this, add a special css class
- var nick_re = new RegExp(CONFIG.nick);
- if (nick_re.exec(text))
- messageElement.addClass("personal");
-
+ nick_re = new RegExp(CONFIG.nick);
+ nick_re.exec(text) ? messageElement.addClass("personal") : null;
// replace URLs with links
text = text.replace(util.urlRE, '<a target="_blank" href="$&">$&</a>');
-
- var content = '<tr>'
- + ' <td class="date">' + util.timeString(time) + '</td>'
- + ' <td class="nick">' + util.toStaticHTML(from) + '</td>'
- + ' <td class="msg-text">' + text + '</td>'
- + '</tr>'
- ;
+ content = ("<tr>\n <td class=\"date\">" + (util.timeString(time)) + "</td>\n <td class=\"nick\">" + (util.toStaticHTML(from)) + "</td>\n <td class=\"msg-text\">" + text + "</td>\n</tr>");
messageElement.html(content);
-
//the log is the stream that we view
$("#log").append(messageElement);
-
//always view the most recent message when it is added
- scrollDown();
-}
-
-function updateRSS () {
- var bytes = parseInt(rss);
+ return scrollDown();
+};
+updateRSS = function() {
+ var bytes, megabytes;
+ bytes = parseInt(rss);
if (bytes) {
- var megabytes = bytes / (1024*1024);
- megabytes = Math.round(megabytes*10)/10;
- $("#rss").text(megabytes.toString());
+ megabytes = bytes / (1024 * 1024);
+ megabytes = Math.round(megabytes * 10) / 10;
+ return $("#rss").text(megabytes.toString());
}
-}
-
-function updateUptime () {
+};
+updateUptime = function() {
if (starttime) {
- $("#uptime").text(starttime.toRelativeTime());
+ return $("#uptime").text(starttime.toRelativeTime());
}
-}
-
-var transmission_errors = 0;
-var first_poll = true;
-
-
+};
+transmission_errors = 0;
+first_poll = true;
//process updates if we have any, request updates from the server,
// and call again with response. the last part is like recursion except the call
// is being made from the response handler, and not at some point during the
// function's execution.
-function longPoll (data) {
+longPoll = function(data) {
+ var _a, _b, _c, _d, message, rss;
if (transmission_errors > 2) {
showConnect();
- return;
+ return null;
}
-
if (data && data.rss) {
rss = data.rss;
updateRSS();
}
-
//process any updates we may have
//data will be null on the first call of longPoll
if (data && data.messages) {
- for (var i = 0; i < data.messages.length; i++) {
- var message = data.messages[i];
-
+ _b = data.messages;
+ for (_a = 0, _c = _b.length; _a < _c; _a++) {
+ message = _b[_a];
//track oldest message so we only request newer messages from server
- if (message.timestamp > CONFIG.last_message_time)
- CONFIG.last_message_time = message.timestamp;
-
+ message.timestamp > CONFIG.last_message_time ? (CONFIG.last_message_time = message.timestamp) : null;
//dispatch new messages to their appropriate handlers
- switch (message.type) {
- case "msg":
- if(!CONFIG.focus){
- CONFIG.unread++;
- }
- addMessage(message.nick, message.text, message.timestamp);
- break;
-
- case "join":
- userJoin(message.nick, message.timestamp);
- break;
-
- case "part":
- userPart(message.nick, message.timestamp);
- break;
+ if ((_d = message.type) === "msg") {
+ !CONFIG.focus ? CONFIG.unread++ : null;
+ addMessage(message.nick, message.text, message.timestamp);
+ } else if (_d === "join") {
+ userJoin(message.nick, message.timestamp);
+ } else if (_d === "part") {
+ userPart(message.nick, message.timestamp);
}
}
//update the document title to include unread message count if blurred
updateTitle();
-
//only after the first request for messages do we want to show who is here
if (first_poll) {
first_poll = false;
who();
}
}
-
//make another request
- $.ajax({ cache: false
- , type: "GET"
- , url: "/recv"
- , dataType: "json"
- , data: { since: CONFIG.last_message_time, id: CONFIG.id }
- , error: function () {
- addMessage("", "long poll error. trying again...", new Date(), "error");
- transmission_errors += 1;
- //don't flood the servers on error, wait 10 seconds before retrying
- setTimeout(longPoll, 10*1000);
- }
- , success: function (data) {
- transmission_errors = 0;
- //if everything went well, begin another request immediately
- //the server will take a long time to respond
- //how long? well, it will wait until there is another message
- //and then it will return it to us and close the connection.
- //since the connection is closed when we get data, we longPoll again
- longPoll(data);
- }
- });
-}
-
+ return $.ajax({
+ cache: false,
+ type: "GET",
+ url: "/recv",
+ dataType: "json",
+ data: {
+ since: CONFIG.last_message_time,
+ id: CONFIG.id
+ },
+ error: function() {
+ addMessage("", "long poll error. trying again...", new Date(), "error");
+ transmission_errors += 1;
+ //don't flood the servers on error, wait 10 seconds before retrying
+ return setTimeout(longPoll, 10 * 1000);
+ },
+ success: function(data) {
+ transmission_errors = 0;
+ //if everything went well, begin another request immediately
+ //the server will take a long time to respond
+ //how long? well, it will wait until there is another message
+ //and then it will return it to us and close the connection.
+ //since the connection is closed when we get data, we longPoll again
+ return longPoll(data);
+ }
+ });
+};
//submit a new message to the server
-function send(msg) {
+send = function(msg) {
if (CONFIG.debug === false) {
// XXX should be POST
// XXX should add to messages immediately
- jQuery.get("/send", {id: CONFIG.id, text: msg}, function (data) { }, "json");
+ return jQuery.get("/send", {
+ id: CONFIG.id,
+ text: msg
+ }, function(data) {
+ return true;
+ }, "json");
}
-}
-
+};
//Transition the page to the state that prompts the user for a nickname
-function showConnect () {
+showConnect = function() {
$("#connect").show();
$("#loading").hide();
$("#toolbar").hide();
- $("#nickInput").focus();
-}
-
+ return $("#nickInput").focus();
+};
//transition the page to the loading screen
-function showLoad () {
+showLoad = function() {
$("#connect").hide();
$("#loading").show();
- $("#toolbar").hide();
-}
-
+ return $("#toolbar").hide();
+};
//transition the page to the main chat view, putting the cursor in the textfield
-function showChat (nick) {
+showChat = function(nick) {
$("#toolbar").show();
$("#entry").focus();
-
$("#connect").hide();
$("#loading").hide();
-
- scrollDown();
-}
-
+ return scrollDown();
+};
//we want to show a count of unread messages when the window does not have focus
-function updateTitle(){
+updateTitle = function() {
if (CONFIG.unread) {
document.title = "(" + CONFIG.unread.toString() + ") node chat";
+ return document.title;
} else {
document.title = "node chat";
+ return document.title;
}
-}
-
+};
// daemon start time
-var starttime;
+starttime = null;
// daemon memory usage
-var rss;
-
+rss = null;
//handle the server's response to our nickname and join request
-function onConnect (session) {
+onConnect = function(session) {
if (session.error) {
alert("error connecting: " + session.error);
showConnect();
- return;
+ return null;
}
-
CONFIG.nick = session.nick;
- CONFIG.id = session.id;
- starttime = new Date(session.starttime);
- rss = session.rss;
+ CONFIG.id = session.id;
+ starttime = new Date(session.starttime);
+ rss = session.rss;
updateRSS();
updateUptime();
-
//update the UI to show the chat
showChat(CONFIG.nick);
-
//listen for browser events so we know to update the document title
$(window).bind("blur", function() {
CONFIG.focus = false;
- updateTitle();
+ return updateTitle();
});
-
- $(window).bind("focus", function() {
+ return $(window).bind("focus", function() {
CONFIG.focus = true;
CONFIG.unread = 0;
- updateTitle();
+ return updateTitle();
});
-}
-
+};
//add a list of present chat members to the stream
-function outputUsers () {
- var nick_string = nicks.length > 0 ? nicks.join(", ") : "(none)";
+outputUsers = function() {
+ var nick_string;
+ nick_string = nicks.length > 0 ? nicks.join(", ") : "(none)";
addMessage("users:", nick_string, new Date(), "notice");
return false;
-}
-
+};
//get a list of the users presently in the room, and add it to the stream
-function who () {
- jQuery.get("/who", {}, function (data, status) {
- if (status != "success") return;
+who = function() {
+ return jQuery.get("/who", {}, function(data, status) {
+ if (status !== "success") {
+ return null;
+ }
nicks = data.nicks;
- outputUsers();
+ return outputUsers();
}, "json");
-}
-
+};
$(document).ready(function() {
-
+ alert('hello world');
//submit new messages when the user hits enter if the message isnt blank
- $("#entry").keypress(function (e) {
- if (e.keyCode != 13 /* Return */) return;
- var msg = $("#entry").attr("value").replace("\n", "");
- if (!util.isBlank(msg)) send(msg);
- $("#entry").attr("value", ""); // clear the entry field.
+ $("#entry").keypress(function(e) {
+ var msg;
+ if (e.keyCode !== 13) {
+ return null;
+ }
+ msg = $("#entry").attr("value").replace("\n", "");
+ !util.isBlank(msg) ? send(msg) : null;
+ return $("#entry").attr("value", "");
});
-
$("#usersLink").click(outputUsers);
-
//try joining the chat when the user clicks the connect button
- $("#connectButton").click(function () {
+ $("#connectButton").click(function() {
+ var nick;
//lock the UI while waiting for a response
showLoad();
- var nick = $("#nickInput").attr("value");
-
+ nick = $("#nickInput").attr("value");
//dont bother the backend if we fail easy validations
if (nick.length > 50) {
alert("Nick too long. 50 character max.");
showConnect();
return false;
}
-
//more validations
if (/[^\w_\-^!]/.exec(nick)) {
alert("Bad character in nick. Can only have letters, numbers, and '_', '-', '^', '!'");
showConnect();
return false;
}
-
//make the actual join request to the server
- $.ajax({ cache: false
- , type: "GET" // XXX should be POST
- , dataType: "json"
- , url: "/join"
- , data: { nick: nick }
- , error: function () {
- alert("error connecting to server");
- showConnect();
- }
- , success: onConnect
- });
+ $.ajax({
+ cache: false,
+ type: "GET",
+ // XXX should be POST
+ dataType: "json",
+ url: "/join",
+ data: {
+ nick: nick
+ },
+ error: function() {
+ alert("error connecting to server");
+ return showConnect();
+ },
+ success: onConnect
+ });
return false;
});
-
// update the daemon uptime every 10 seconds
- setInterval(function () {
- updateUptime();
- }, 10*1000);
-
+ setInterval(function() {
+ return updateUptime();
+ }, 10 * 1000);
if (CONFIG.debug) {
$("#loading").hide();
$("#connect").hide();
scrollDown();
- return;
+ return null;
}
-
// remove fixtures
$("#log table").remove();
-
//begin listening for updates right away
//interestingly, we don't need to join a room to get its updates
//we just don't show the chat stream to the user until we create a session
longPoll();
-
- showConnect();
+ return showConnect();
});
-
//if we can, notify the server that we're going away.
-$(window).unload(function () {
- jQuery.get("/part", {id: CONFIG.id}, function (data) { }, "json");
-});
+$(window).unload(function() {
+ return jQuery.get("/part", {
+ id: CONFIG.id
+ }, function(data) {
+ return null;
+ }, "json");
+});
View
507 client_nocoffee.js
@@ -0,0 +1,507 @@
+var CONFIG = { debug: false
+ , nick: "#" // set in onConnect
+ , id: null // set in onConnect
+ , last_message_time: 1
+ , focus: true //event listeners bound in onConnect
+ , unread: 0 //updated in the message-processing loop
+ };
+
+var nicks = [];
+
+// CUT ///////////////////////////////////////////////////////////////////
+/* This license and copyright apply to all code until the next "CUT"
+http://github.com/jherdman/javascript-relative-time-helpers/
+
+The MIT License
+
+Copyright (c) 2009 James F. Herdman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+ * Returns a description of this past date in relative terms.
+ * Takes an optional parameter (default: 0) setting the threshold in ms which
+ * is considered "Just now".
+ *
+ * Examples, where new Date().toString() == "Mon Nov 23 2009 17:36:51 GMT-0500 (EST)":
+ *
+ * new Date().toRelativeTime()
+ * --> 'Just now'
+ *
+ * new Date("Nov 21, 2009").toRelativeTime()
+ * --> '2 days ago'
+ *
+ * // One second ago
+ * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime()
+ * --> '1 second ago'
+ *
+ * // One second ago, now setting a now_threshold to 5 seconds
+ * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime(5000)
+ * --> 'Just now'
+ *
+ */
+Date.prototype.toRelativeTime = function(now_threshold) {
+ var delta = new Date() - this;
+
+ now_threshold = parseInt(now_threshold, 10);
+
+ if (isNaN(now_threshold)) {
+ now_threshold = 0;
+ }
+
+ if (delta <= now_threshold) {
+ return 'Just now';
+ }
+
+ var units = null;
+ var conversions = {
+ millisecond: 1, // ms -> ms
+ second: 1000, // ms -> sec
+ minute: 60, // sec -> min
+ hour: 60, // min -> hour
+ day: 24, // hour -> day
+ month: 30, // day -> month (roughly)
+ year: 12 // month -> year
+ };
+
+ for (var key in conversions) {
+ if (delta < conversions[key]) {
+ break;
+ } else {
+ units = key; // keeps track of the selected key over the iteration
+ delta = delta / conversions[key];
+ }
+ }
+
+ // pluralize a unit when the difference is greater than 1.
+ delta = Math.floor(delta);
+ if (delta !== 1) { units += "s"; }
+ return [delta, units].join(" ");
+};
+
+/*
+ * Wraps up a common pattern used with this plugin whereby you take a String
+ * representation of a Date, and want back a date object.
+ */
+Date.fromString = function(str) {
+ return new Date(Date.parse(str));
+};
+
+// CUT ///////////////////////////////////////////////////////////////////
+
+
+
+//updates the users link to reflect the number of active users
+function updateUsersLink ( ) {
+ var t = nicks.length.toString() + " user";
+ if (nicks.length != 1) t += "s";
+ $("#usersLink").text(t);
+}
+
+//handles another person joining chat
+function userJoin(nick, timestamp) {
+ //put it in the stream
+ addMessage(nick, "joined", timestamp, "join");
+ //if we already know about this user, ignore it
+ for (var i = 0; i < nicks.length; i++)
+ if (nicks[i] == nick) return;
+ //otherwise, add the user to the list
+ nicks.push(nick);
+ //update the UI
+ updateUsersLink();
+}
+
+//handles someone leaving
+function userPart(nick, timestamp) {
+ //put it in the stream
+ addMessage(nick, "left", timestamp, "part");
+ //remove the user from the list
+ for (var i = 0; i < nicks.length; i++) {
+ if (nicks[i] == nick) {
+ nicks.splice(i,1)
+ break;
+ }
+ }
+ //update the UI
+ updateUsersLink();
+}
+
+// utility functions
+
+util = {
+ urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
+
+ // html sanitizer
+ toStaticHTML: function(inputHtml) {
+ inputHtml = inputHtml.toString();
+ return inputHtml.replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;");
+ },
+
+ //pads n with zeros on the left,
+ //digits is minimum length of output
+ //zeroPad(3, 5); returns "005"
+ //zeroPad(2, 500); returns "500"
+ zeroPad: function (digits, n) {
+ n = n.toString();
+ while (n.length < digits)
+ n = '0' + n;
+ return n;
+ },
+
+ //it is almost 8 o'clock PM here
+ //timeString(new Date); returns "19:49"
+ timeString: function (date) {
+ var minutes = date.getMinutes().toString();
+ var hours = date.getHours().toString();
+ return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes);
+ },
+
+ //does the argument only contain whitespace?
+ isBlank: function(text) {
+ var blank = /^\s*$/;
+ return (text.match(blank) !== null);
+ }
+};
+
+//used to keep the most recent messages visible
+function scrollDown () {
+ window.scrollBy(0, 100000000000000000);
+ $("#entry").focus();
+}
+
+//inserts an event into the stream for display
+//the event may be a msg, join or part type
+//from is the user, text is the body and time is the timestamp, defaulting to now
+//_class is a css class to apply to the message, usefull for system events
+function addMessage (from, text, time, _class) {
+ if (text === null)
+ return;
+
+ if (time == null) {
+ // if the time is null or undefined, use the current time.
+ time = new Date();
+ } else if ((time instanceof Date) === false) {
+ // if it's a timestamp, interpret it
+ time = new Date(time);
+ }
+
+ //every message you see is actually a table with 3 cols:
+ // the time,
+ // the person who caused the event,
+ // and the content
+ var messageElement = $(document.createElement("table"));
+
+ messageElement.addClass("message");
+ if (_class)
+ messageElement.addClass(_class);
+
+ // sanitize
+ text = util.toStaticHTML(text);
+
+ // If the current user said this, add a special css class
+ var nick_re = new RegExp(CONFIG.nick);
+ if (nick_re.exec(text))
+ messageElement.addClass("personal");
+
+ // replace URLs with links
+ text = text.replace(util.urlRE, '<a target="_blank" href="$&">$&</a>');
+
+ var content = '<tr>'
+ + ' <td class="date">' + util.timeString(time) + '</td>'
+ + ' <td class="nick">' + util.toStaticHTML(from) + '</td>'
+ + ' <td class="msg-text">' + text + '</td>'
+ + '</tr>'
+ ;
+ messageElement.html(content);
+
+ //the log is the stream that we view
+ $("#log").append(messageElement);
+
+ //always view the most recent message when it is added
+ scrollDown();
+}
+
+function updateRSS () {
+ var bytes = parseInt(rss);
+ if (bytes) {
+ var megabytes = bytes / (1024*1024);
+ megabytes = Math.round(megabytes*10)/10;
+ $("#rss").text(megabytes.toString());
+ }
+}
+
+function updateUptime () {
+ if (starttime) {
+ $("#uptime").text(starttime.toRelativeTime());
+ }
+}
+
+var transmission_errors = 0;
+var first_poll = true;
+
+
+//process updates if we have any, request updates from the server,
+// and call again with response. the last part is like recursion except the call
+// is being made from the response handler, and not at some point during the
+// function's execution.
+function longPoll (data) {
+ if (transmission_errors > 2) {
+ showConnect();
+ return;
+ }
+
+ if (data && data.rss) {
+ rss = data.rss;
+ updateRSS();
+ }
+
+ //process any updates we may have
+ //data will be null on the first call of longPoll
+ if (data && data.messages) {
+ for (var i = 0; i < data.messages.length; i++) {
+ var message = data.messages[i];
+
+ //track oldest message so we only request newer messages from server
+ if (message.timestamp > CONFIG.last_message_time)
+ CONFIG.last_message_time = message.timestamp;
+
+ //dispatch new messages to their appropriate handlers
+ switch (message.type) {
+ case "msg":
+ if(!CONFIG.focus){
+ CONFIG.unread++;
+ }
+ addMessage(message.nick, message.text, message.timestamp);
+ break;
+
+ case "join":
+ userJoin(message.nick, message.timestamp);
+ break;
+
+ case "part":
+ userPart(message.nick, message.timestamp);
+ break;
+ }
+ }
+ //update the document title to include unread message count if blurred
+ updateTitle();
+
+ //only after the first request for messages do we want to show who is here
+ if (first_poll) {
+ first_poll = false;
+ who();
+ }
+ }
+
+ //make another request
+ $.ajax({ cache: false
+ , type: "GET"
+ , url: "/recv"
+ , dataType: "json"
+ , data: { since: CONFIG.last_message_time, id: CONFIG.id }
+ , error: function () {
+ addMessage("", "long poll error. trying again...", new Date(), "error");
+ transmission_errors += 1;
+ //don't flood the servers on error, wait 10 seconds before retrying
+ setTimeout(longPoll, 10*1000);
+ }
+ , success: function (data) {
+ transmission_errors = 0;
+ //if everything went well, begin another request immediately
+ //the server will take a long time to respond
+ //how long? well, it will wait until there is another message
+ //and then it will return it to us and close the connection.
+ //since the connection is closed when we get data, we longPoll again
+ longPoll(data);
+ }
+ });
+}
+
+//submit a new message to the server
+function send(msg) {
+ if (CONFIG.debug === false) {
+ // XXX should be POST
+ // XXX should add to messages immediately
+ jQuery.get("/send", {id: CONFIG.id, text: msg}, function (data) { }, "json");
+ }
+}
+
+//Transition the page to the state that prompts the user for a nickname
+function showConnect () {
+ $("#connect").show();
+ $("#loading").hide();
+ $("#toolbar").hide();
+ $("#nickInput").focus();
+}
+
+//transition the page to the loading screen
+function showLoad () {
+ $("#connect").hide();
+ $("#loading").show();
+ $("#toolbar").hide();
+}
+
+//transition the page to the main chat view, putting the cursor in the textfield
+function showChat (nick) {
+ $("#toolbar").show();
+ $("#entry").focus();
+
+ $("#connect").hide();
+ $("#loading").hide();
+
+ scrollDown();
+}
+
+//we want to show a count of unread messages when the window does not have focus
+function updateTitle(){
+ if (CONFIG.unread) {
+ document.title = "(" + CONFIG.unread.toString() + ") node chat";
+ } else {
+ document.title = "node chat";
+ }
+}
+
+// daemon start time
+var starttime;
+// daemon memory usage
+var rss;
+
+//handle the server's response to our nickname and join request
+function onConnect (session) {
+ if (session.error) {
+ alert("error connecting: " + session.error);
+ showConnect();
+ return;
+ }
+
+ CONFIG.nick = session.nick;
+ CONFIG.id = session.id;
+ starttime = new Date(session.starttime);
+ rss = session.rss;
+ updateRSS();
+ updateUptime();
+
+ //update the UI to show the chat
+ showChat(CONFIG.nick);
+
+ //listen for browser events so we know to update the document title
+ $(window).bind("blur", function() {
+ CONFIG.focus = false;
+ updateTitle();
+ });
+
+ $(window).bind("focus", function() {
+ CONFIG.focus = true;
+ CONFIG.unread = 0;
+ updateTitle();
+ });
+}
+
+//add a list of present chat members to the stream
+function outputUsers () {
+ var nick_string = nicks.length > 0 ? nicks.join(", ") : "(none)";
+ addMessage("users:", nick_string, new Date(), "notice");
+ return false;
+}
+
+//get a list of the users presently in the room, and add it to the stream
+function who () {
+ jQuery.get("/who", {}, function (data, status) {
+ if (status != "success") return;
+ nicks = data.nicks;
+ outputUsers();
+ }, "json");
+}
+
+$(document).ready(function() {
+
+ //submit new messages when the user hits enter if the message isnt blank
+ $("#entry").keypress(function (e) {
+ if (e.keyCode != 13 /* Return */) return;
+ var msg = $("#entry").attr("value").replace("\n", "");
+ if (!util.isBlank(msg)) send(msg);
+ $("#entry").attr("value", ""); // clear the entry field.
+ });
+
+ $("#usersLink").click(outputUsers);
+
+ //try joining the chat when the user clicks the connect button
+ $("#connectButton").click(function () {
+ //lock the UI while waiting for a response
+ showLoad();
+ var nick = $("#nickInput").attr("value");
+
+ //dont bother the backend if we fail easy validations
+ if (nick.length > 50) {
+ alert("Nick too long. 50 character max.");
+ showConnect();
+ return false;
+ }
+
+ //more validations
+ if (/[^\w_\-^!]/.exec(nick)) {
+ alert("Bad character in nick. Can only have letters, numbers, and '_', '-', '^', '!'");
+ showConnect();
+ return false;
+ }
+
+ //make the actual join request to the server
+ $.ajax({ cache: false
+ , type: "GET" // XXX should be POST
+ , dataType: "json"
+ , url: "/join"
+ , data: { nick: nick }
+ , error: function () {
+ alert("error connecting to server");
+ showConnect();
+ }
+ , success: onConnect
+ });
+ return false;
+ });
+
+ // update the daemon uptime every 10 seconds
+ setInterval(function () {
+ updateUptime();
+ }, 10*1000);
+
+ if (CONFIG.debug) {
+ $("#loading").hide();
+ $("#connect").hide();
+ scrollDown();
+ return;
+ }
+
+ // remove fixtures
+ $("#log table").remove();
+
+ //begin listening for updates right away
+ //interestingly, we don't need to join a room to get its updates
+ //we just don't show the chat stream to the user until we create a session
+ longPoll();
+
+ showConnect();
+});
+
+//if we can, notify the server that we're going away.
+$(window).unload(function () {
+ jQuery.get("/part", {id: CONFIG.id}, function (data) { }, "json");
+});
View
24 example.js
@@ -1,14 +1,12 @@
-(function(){
- var Hello, h;
- Hello = function() {
- this.name = "world";
- return this;
- };
- Hello.prototype.m = function() {
- puts("Hello");
- return puts(this.name);
- };
+var Hello, h;
+Hello = function() {
+ this.name = "world";
+ return this;
+};
+Hello.prototype.m = function() {
+ puts("Hello");
+ return puts(this.name);
+};
- h = new Hello();
- h.m();
-})();
+h = new Hello();
+h.m();
View
518 fu.js
@@ -1,272 +1,270 @@
-(function(){
- var DEBUG, NOT_FOUND, createServer, extname, fu, getMap, notFound, readFile, server, sys, url;
- var __slice = Array.prototype.slice, __bind = function(func, obj, args) {
+var DEBUG, NOT_FOUND, createServer, extname, fu, getMap, notFound, readFile, server, sys, url;
+var __slice = Array.prototype.slice, __bind = function(func, obj, args) {
return function() {
return func.apply(obj || {}, args ? args.concat(__slice.call(arguments, 0)) : arguments);
};
};
- createServer = require("http").createServer;
- readFile = require("fs").readFile;
- sys = require("sys");
- url = require("url");
- DEBUG = false;
- fu = exports;
- NOT_FOUND = "Not Found\n";
- notFound = function(req, res) {
- res.writeHead(404, {
- "Content-Type": "text/plain",
- "Content-Length": NOT_FOUND.length
- });
- return res.end(NOT_FOUND);
- };
- getMap = {};
- fu.get = function(path, handler) {
- getMap[path] = handler;
- return getMap[path];
- };
- server = createServer(function(req, res) {
- var handler;
- if (req.method === "GET" || req.method === "HEAD") {
- handler = getMap[url.parse(req.url).pathname] || notFound;
- res.simpleText = __bind(function(code, body) {
- res.writeHead(code, {
- "Content-Type": "text/plain",
- "Content-Length": body.length
- });
- return res.end(body);
- }, this);
- res.simpleJSON = function(code, obj) {
- var body;
- body = JSON.stringify(obj);
+createServer = require("http").createServer;
+readFile = require("fs").readFile;
+sys = require("sys");
+url = require("url");
+DEBUG = false;
+fu = exports;
+NOT_FOUND = "Not Found\n";
+notFound = function(req, res) {
+ res.writeHead(404, {
+ "Content-Type": "text/plain",
+ "Content-Length": NOT_FOUND.length
+ });
+ return res.end(NOT_FOUND);
+};
+getMap = {};
+fu.get = function(path, handler) {
+ getMap[path] = handler;
+ return getMap[path];
+};
+server = createServer(function(req, res) {
+ var handler;
+ if (req.method === "GET" || req.method === "HEAD") {
+ handler = getMap[url.parse(req.url).pathname] || notFound;
+ res.simpleText = __bind(function(code, body) {
res.writeHead(code, {
- "Content-Type": "text/json",
+ "Content-Type": "text/plain",
"Content-Length": body.length
});
return res.end(body);
- };
- return handler(req, res);
- }
- });
- fu.listen = function(port, host) {
- return server.listen(parseInt(port), host);
- //sys.puts("Server at http://" + (host or "127.0.0.1") + ":" + port.toString() + "/")
- };
- fu.close = function() {
- return server.close();
- };
- extname = function(path) {
- var index;
- index = path.lastIndexOf(".");
- if (index < 0) {
- return "";
- } else {
- return path.substring(index);
- }
- };
- fu.staticHandler = function(filename) {
- var body, content_type, headers, loadResponseData;
- body = "";
- headers = "";
- content_type = fu.mime.lookupExtension(extname(filename));
- loadResponseData = __bind(function(callback) {
- if (body && headers && !DEBUG) {
- callback();
- return null;
- }
- //sys.puts("loading " + filename + "...")
- return readFile(filename, function(err, data) {
- if (err) {
- return sys.puts("Error loading " + filename);
- } else {
- body = data;
- headers = {
- "Content-Type": content_type,
- "Content-Length": body.length
- };
- !DEBUG ? (headers["Cache-Control"] = "public") : null;
- //sys.puts("static file " + filename + " loaded")
- return callback();
- }
- });
}, this);
- return function(req, res) {
- return loadResponseData(function() {
- res.writeHead(200, headers);
- return res.end(body);
+ res.simpleJSON = function(code, obj) {
+ var body;
+ body = JSON.stringify(obj);
+ res.writeHead(code, {
+ "Content-Type": "text/json",
+ "Content-Length": body.length
});
+ return res.end(body);
};
+ return handler(req, res);
+ }
+});
+fu.listen = function(port, host) {
+ return server.listen(parseInt(port), host);
+ //sys.puts("Server at http://" + (host or "127.0.0.1") + ":" + port.toString() + "/")
+};
+fu.close = function() {
+ return server.close();
+};
+extname = function(path) {
+ var index;
+ index = path.lastIndexOf(".");
+ if (index < 0) {
+ return "";
+ } else {
+ return path.substring(index);
+ }
+};
+fu.staticHandler = function(filename) {
+ var body, content_type, headers, loadResponseData;
+ body = "";
+ headers = "";
+ content_type = fu.mime.lookupExtension(extname(filename));
+ loadResponseData = __bind(function(callback) {
+ if (body && headers && !DEBUG) {
+ callback();
+ return null;
+ }
+ //sys.puts("loading " + filename + "...")
+ return readFile(filename, function(err, data) {
+ if (err) {
+ return sys.puts("Error loading " + filename);
+ } else {
+ body = data;
+ headers = {
+ "Content-Type": content_type,
+ "Content-Length": body.length
+ };
+ !DEBUG ? (headers["Cache-Control"] = "public") : null;
+ //sys.puts("static file " + filename + " loaded")
+ return callback();
+ }
+ });
+ }, this);
+ return function(req, res) {
+ return loadResponseData(function() {
+ res.writeHead(200, headers);
+ return res.end(body);
+ });
};
- fu.mime = {
- TYPES: {
- ".3gp": "video/3gpp",
- ".a": "application/octet-stream",
- ".ai": "application/postscript",
- ".aif": "audio/x-aiff",
- ".aiff": "audio/x-aiff",
- ".asc": "application/pgp-signature",
- ".asf": "video/x-ms-asf",
- ".asm": "text/x-asm",
- ".asx": "video/x-ms-asf",
- ".atom": "application/atom+xml",
- ".au": "audio/basic",
- ".avi": "video/x-msvideo",
- ".bat": "application/x-msdownload",
- ".bin": "application/octet-stream",
- ".bmp": "image/bmp",
- ".bz2": "application/x-bzip2",
- ".c": "text/x-c",
- ".cab": "application/vnd.ms-cab-compressed",
- ".cc": "text/x-c",
- ".chm": "application/vnd.ms-htmlhelp",
- ".class": "application/octet-stream",
- ".com": "application/x-msdownload",
- ".conf": "text/plain",
- ".cpp": "text/x-c",
- ".crt": "application/x-x509-ca-cert",
- ".css": "text/css",
- ".csv": "text/csv",
- ".cxx": "text/x-c",
- ".deb": "application/x-debian-package",
- ".der": "application/x-x509-ca-cert",
- ".diff": "text/x-diff",
- ".djv": "image/vnd.djvu",
- ".djvu": "image/vnd.djvu",
- ".dll": "application/x-msdownload",
- ".dmg": "application/octet-stream",
- ".doc": "application/msword",
- ".dot": "application/msword",
- ".dtd": "application/xml-dtd",
- ".dvi": "application/x-dvi",
- ".ear": "application/java-archive",
- ".eml": "message/rfc822",
- ".eps": "application/postscript",
- ".exe": "application/x-msdownload",
- ".f": "text/x-fortran",
- ".f77": "text/x-fortran",
- ".f90": "text/x-fortran",
- ".flv": "video/x-flv",
- ".for": "text/x-fortran",
- ".gem": "application/octet-stream",
- ".gemspec": "text/x-script.ruby",
- ".gif": "image/gif",
- ".gz": "application/x-gzip",
- ".h": "text/x-c",
- ".hh": "text/x-c",
- ".htm": "text/html",
- ".html": "text/html",
- ".ico": "image/vnd.microsoft.icon",
- ".ics": "text/calendar",
- ".ifb": "text/calendar",
- ".iso": "application/octet-stream",
- ".jar": "application/java-archive",
- ".java": "text/x-java-source",
- ".jnlp": "application/x-java-jnlp-file",
- ".jpeg": "image/jpeg",
- ".jpg": "image/jpeg",
- ".js": "application/javascript",
- ".json": "application/json",
- ".log": "text/plain",
- ".m3u": "audio/x-mpegurl",
- ".m4v": "video/mp4",
- ".man": "text/troff",
- ".mathml": "application/mathml+xml",
- ".mbox": "application/mbox",
- ".mdoc": "text/troff",
- ".me": "text/troff",
- ".mid": "audio/midi",
- ".midi": "audio/midi",
- ".mime": "message/rfc822",
- ".mml": "application/mathml+xml",
- ".mng": "video/x-mng",
- ".mov": "video/quicktime",
- ".mp3": "audio/mpeg",
- ".mp4": "video/mp4",
- ".mp4v": "video/mp4",
- ".mpeg": "video/mpeg",
- ".mpg": "video/mpeg",
- ".ms": "text/troff",
- ".msi": "application/x-msdownload",
- ".odp": "application/vnd.oasis.opendocument.presentation",
- ".ods": "application/vnd.oasis.opendocument.spreadsheet",
- ".odt": "application/vnd.oasis.opendocument.text",
- ".ogg": "application/ogg",
- ".p": "text/x-pascal",
- ".pas": "text/x-pascal",
- ".pbm": "image/x-portable-bitmap",
- ".pdf": "application/pdf",
- ".pem": "application/x-x509-ca-cert",
- ".pgm": "image/x-portable-graymap",
- ".pgp": "application/pgp-encrypted",
- ".pkg": "application/octet-stream",
- ".pl": "text/x-script.perl",
- ".pm": "text/x-script.perl-module",
- ".png": "image/png",
- ".pnm": "image/x-portable-anymap",
- ".ppm": "image/x-portable-pixmap",
- ".pps": "application/vnd.ms-powerpoint",
- ".ppt": "application/vnd.ms-powerpoint",
- ".ps": "application/postscript",
- ".psd": "image/vnd.adobe.photoshop",
- ".py": "text/x-script.python",
- ".qt": "video/quicktime",
- ".ra": "audio/x-pn-realaudio",
- ".rake": "text/x-script.ruby",
- ".ram": "audio/x-pn-realaudio",
- ".rar": "application/x-rar-compressed",
- ".rb": "text/x-script.ruby",
- ".rdf": "application/rdf+xml",
- ".roff": "text/troff",
- ".rpm": "application/x-redhat-package-manager",
- ".rss": "application/rss+xml",
- ".rtf": "application/rtf",
- ".ru": "text/x-script.ruby",
- ".s": "text/x-asm",
- ".sgm": "text/sgml",
- ".sgml": "text/sgml",
- ".sh": "application/x-sh",
- ".sig": "application/pgp-signature",
- ".snd": "audio/basic",
- ".so": "application/octet-stream",
- ".svg": "image/svg+xml",
- ".svgz": "image/svg+xml",
- ".swf": "application/x-shockwave-flash",
- ".t": "text/troff",
- ".tar": "application/x-tar",
- ".tbz": "application/x-bzip-compressed-tar",
- ".tcl": "application/x-tcl",
- ".tex": "application/x-tex",
- ".texi": "application/x-texinfo",
- ".texinfo": "application/x-texinfo",
- ".text": "text/plain",
- ".tif": "image/tiff",
- ".tiff": "image/tiff",
- ".torrent": "application/x-bittorrent",
- ".tr": "text/troff",
- ".txt": "text/plain",
- ".vcf": "text/x-vcard",
- ".vcs": "text/x-vcalendar",
- ".vrml": "model/vrml",
- ".war": "application/java-archive",
- ".wav": "audio/x-wav",
- ".wma": "audio/x-ms-wma",
- ".wmv": "video/x-ms-wmv",
- ".wmx": "video/x-ms-wmx",
- ".wrl": "model/vrml",
- ".wsdl": "application/wsdl+xml",
- ".xbm": "image/x-xbitmap",
- ".xhtml": "application/xhtml+xml",
- ".xls": "application/vnd.ms-excel",
- ".xml": "application/xml",
- ".xpm": "image/x-xpixmap",
- ".xsl": "application/xml",
- ".xslt": "application/xslt+xml",
- ".yaml": "text/yaml",
- ".yml": "text/yaml",
- ".zip": "application/zip"
- },
- lookupExtension: function(ext, fallback) {
- return this.TYPES[ext.toLowerCase()];
- //or fallback or 'application/octet-stream'
- }
- };
-})();
+};
+fu.mime = {
+ TYPES: {
+ ".3gp": "video/3gpp",
+ ".a": "application/octet-stream",
+ ".ai": "application/postscript",
+ ".aif": "audio/x-aiff",
+ ".aiff": "audio/x-aiff",
+ ".asc": "application/pgp-signature",
+ ".asf": "video/x-ms-asf",
+ ".asm": "text/x-asm",
+ ".asx": "video/x-ms-asf",
+ ".atom": "application/atom+xml",
+ ".au": "audio/basic",
+ ".avi": "video/x-msvideo",
+ ".bat": "application/x-msdownload",
+ ".bin": "application/octet-stream",
+ ".bmp": "image/bmp",
+ ".bz2": "application/x-bzip2",
+ ".c": "text/x-c",
+ ".cab": "application/vnd.ms-cab-compressed",
+ ".cc": "text/x-c",
+ ".chm": "application/vnd.ms-htmlhelp",
+ ".class": "application/octet-stream",
+ ".com": "application/x-msdownload",
+ ".conf": "text/plain",
+ ".cpp": "text/x-c",
+ ".crt": "application/x-x509-ca-cert",
+ ".css": "text/css",
+ ".csv": "text/csv",
+ ".cxx": "text/x-c",
+ ".deb": "application/x-debian-package",
+ ".der": "application/x-x509-ca-cert",
+ ".diff": "text/x-diff",
+ ".djv": "image/vnd.djvu",
+ ".djvu": "image/vnd.djvu",
+ ".dll": "application/x-msdownload",
+ ".dmg": "application/octet-stream",
+ ".doc": "application/msword",
+ ".dot": "application/msword",
+ ".dtd": "application/xml-dtd",
+ ".dvi": "application/x-dvi",
+ ".ear": "application/java-archive",
+ ".eml": "message/rfc822",
+ ".eps": "application/postscript",
+ ".exe": "application/x-msdownload",
+ ".f": "text/x-fortran",
+ ".f77": "text/x-fortran",
+ ".f90": "text/x-fortran",
+ ".flv": "video/x-flv",
+ ".for": "text/x-fortran",
+ ".gem": "application/octet-stream",
+ ".gemspec": "text/x-script.ruby",
+ ".gif": "image/gif",
+ ".gz": "application/x-gzip",
+ ".h": "text/x-c",
+ ".hh": "text/x-c",
+ ".htm": "text/html",
+ ".html": "text/html",
+ ".ico": "image/vnd.microsoft.icon",
+ ".ics": "text/calendar",
+ ".ifb": "text/calendar",
+ ".iso": "application/octet-stream",
+ ".jar": "application/java-archive",
+ ".java": "text/x-java-source",
+ ".jnlp": "application/x-java-jnlp-file",
+ ".jpeg": "image/jpeg",
+ ".jpg": "image/jpeg",
+ ".js": "application/javascript",
+ ".json": "application/json",
+ ".log": "text/plain",
+ ".m3u": "audio/x-mpegurl",
+ ".m4v": "video/mp4",
+ ".man": "text/troff",
+ ".mathml": "application/mathml+xml",
+ ".mbox": "application/mbox",
+ ".mdoc": "text/troff",
+ ".me": "text/troff",
+ ".mid": "audio/midi",
+ ".midi": "audio/midi",
+ ".mime": "message/rfc822",
+ ".mml": "application/mathml+xml",
+ ".mng": "video/x-mng",
+ ".mov": "video/quicktime",
+ ".mp3": "audio/mpeg",
+ ".mp4": "video/mp4",
+ ".mp4v": "video/mp4",
+ ".mpeg": "video/mpeg",
+ ".mpg": "video/mpeg",
+ ".ms": "text/troff",
+ ".msi": "application/x-msdownload",
+ ".odp": "application/vnd.oasis.opendocument.presentation",
+ ".ods": "application/vnd.oasis.opendocument.spreadsheet",
+ ".odt": "application/vnd.oasis.opendocument.text",
+ ".ogg": "application/ogg",
+ ".p": "text/x-pascal",
+ ".pas": "text/x-pascal",
+ ".pbm": "image/x-portable-bitmap",
+ ".pdf": "application/pdf",
+ ".pem": "application/x-x509-ca-cert",
+ ".pgm": "image/x-portable-graymap",
+ ".pgp": "application/pgp-encrypted",
+ ".pkg": "application/octet-stream",
+ ".pl": "text/x-script.perl",
+ ".pm": "text/x-script.perl-module",
+ ".png": "image/png",
+ ".pnm": "image/x-portable-anymap",
+ ".ppm": "image/x-portable-pixmap",
+ ".pps": "application/vnd.ms-powerpoint",
+ ".ppt": "application/vnd.ms-powerpoint",
+ ".ps": "application/postscript",
+ ".psd": "image/vnd.adobe.photoshop",
+ ".py": "text/x-script.python",
+ ".qt": "video/quicktime",
+ ".ra": "audio/x-pn-realaudio",
+ ".rake": "text/x-script.ruby",
+ ".ram": "audio/x-pn-realaudio",
+ ".rar": "application/x-rar-compressed",
+ ".rb": "text/x-script.ruby",
+ ".rdf": "application/rdf+xml",
+ ".roff": "text/troff",
+ ".rpm": "application/x-redhat-package-manager",
+ ".rss": "application/rss+xml",
+ ".rtf": "application/rtf",
+ ".ru": "text/x-script.ruby",
+ ".s": "text/x-asm",
+ ".sgm": "text/sgml",
+ ".sgml": "text/sgml",
+ ".sh": "application/x-sh",
+ ".sig": "application/pgp-signature",
+ ".snd": "audio/basic",
+ ".so": "application/octet-stream",
+ ".svg": "image/svg+xml",
+ ".svgz": "image/svg+xml",
+ ".swf": "application/x-shockwave-flash",
+ ".t": "text/troff",
+ ".tar": "application/x-tar",
+ ".tbz": "application/x-bzip-compressed-tar",
+ ".tcl": "application/x-tcl",
+ ".tex": "application/x-tex",
+ ".texi": "application/x-texinfo",
+ ".texinfo": "application/x-texinfo",
+ ".text": "text/plain",
+ ".tif": "image/tiff",
+ ".tiff": "image/tiff",
+ ".torrent": "application/x-bittorrent",
+ ".tr": "text/troff",
+ ".txt": "text/plain",
+ ".vcf": "text/x-vcard",
+ ".vcs": "text/x-vcalendar",
+ ".vrml": "model/vrml",
+ ".war": "application/java-archive",
+ ".wav": "audio/x-wav",
+ ".wma": "audio/x-ms-wma",
+ ".wmv": "video/x-ms-wmv",
+ ".wmx": "video/x-ms-wmx",
+ ".wrl": "model/vrml",
+ ".wsdl": "application/wsdl+xml",
+ ".xbm": "image/x-xbitmap",
+ ".xhtml": "application/xhtml+xml",
+ ".xls": "application/vnd.ms-excel",
+ ".xml": "application/xml",
+ ".xpm": "image/x-xpixmap",
+ ".xsl": "application/xml",
+ ".xslt": "application/xslt+xml",
+ ".yaml": "text/yaml",
+ ".yml"</