11 Commits

Autor SHA1 Mensagem Data
Joshua Gross 7fe6758c6d Added message sending, status setting capabilities; remove old im.js; reconnect
* Sending messages between users now works
* Setting status now works (status message not working yet)
* Reconnecting setup
* Temporarily disabled "not connected" tooltip
2011-04-19 11:18:21 -05:00
Joshua Gross d0508b4b7f Automatically start the IM on DOM Ready; setup scrollers; add right scroller 2011-03-14 18:54:22 -04:00
Joshua Gross 3f75e46a67 Use base64-encoded images in default theme; autogrow textarea fixed; chat store
* Convert pngs to base64-encoded files and embed them in the default theme.css
* Use autogrow.js library in place of home-grown textarea autogrow; new library
  is more reliable
* Fix chat storage such that it restores and saves from/to the proper key
2011-03-13 00:24:43 -05:00
Joshua Gross 5f158cc907 Add localization file; break templates down more; local storage working
* Localization file added (necessary for error/state message and date formats)
* Message templates broken down further into containers and individual messages
* Chat & friends list storage saving/restoration works
2011-03-12 14:23:11 -05:00
Joshua Gross 5a58becdea Some progress on the new client; fix server bugs; temp. disable auto-compress
* Client partially rewritten to support socket.io
* Fix some typos in the server code that would cause it to crash on sess purge
* Add xxtea library for client-side encryption of localStorage data
* Convert theme.html to templates.js to simplify IM loading procedure
* Uglifyjs crashes on xxtea library; temporarily disable until another solution
  is found
* Remove google-compliler and build dir (for now)
2011-03-06 00:22:35 -06:00
Joshua Gross 1be565833d Serve client files from the server (with optional automatic JS compression). 2011-03-03 10:56:16 -06:00
Joshua Gross bd0f2562df Implement socket.io and break apart message/session/auth libraries.
* Library modularization inspired heavily by pjriot
  (http://bitbucket.org/pjriot/nodeim)
* Remove dev server
* Remove daemon library (not working)
* Nix middleware scripts
* Nix express.js usage
2011-03-03 00:09:47 -06:00
Joshua Gross 5f67201e39 Merge latest changes from master. 2011-03-02 21:34:27 -06:00
Joshua Gross 1793897395 Working with the socket.io test script 2010-09-27 14:27:11 -05:00
Joshua Gross e070dd3b76 Begin socket.io server implementation; add test client script & view. 2010-09-23 13:55:40 -05:00
Joshua Gross 20a765be7f Replace message polling with socket.io acceptor. 2010-09-13 22:59:44 -05:00
39 arquivos alterados com 1792 adições e 2465 exclusões
+10 -46
Ver Arquivo
@@ -20,56 +20,19 @@ community experience.
## Installation
Install `Node.js`:
wget http://nodejs.org/dist/node-v0.2.5.tar.gz
tar xzf node-v0.2.5.tar.gz
cd node-v0.2.5
wget http://nodejs.org/dist/node-latest.tar.gz
tar xzf node-latest.tar.gz
cd node-latest
./configure
make
make install
Install Node Package Manager (`npm`):
See instructions at http://github.com/isaacs/npm.
Install Node Package Manager (`npm`): see instructions on the [npm](http://github.com/isaacs/npm) page.
Install `Express.js` and `Connect` (included automatically):
npm install express
Install `Socket.io`:
Compile the daemon add-on if you plan on letting the server daemonize itself:
cd server/libs/daemon
node-waf configure build
cp build/default/daemon.node .
rm -rf build
## Installation for Development
If you want to test Ajax IM as a standalone app for development, you will need
to install [`Jade`](http://github.com/visionmedia/jade) as well.
To install `Jade`:
npm install jade
## Starting up the server
Starting the server in _development_ mode is as simple as:
node server/app.js
To start the server in _production_ mode:
EXPRESS_ENV=production node server/app.js
To start the server as a _daemon_ in _production_ mode:
node server/app.js start production
## Testing it out
Once the server is up and running in _development_ mode, you can test it out
through the included development testing app. The below instructions are
assuming that you have left all default configuration options. If not, please
replace the host/port values with the correct ones.
To get started, first initialize a session cookie by going to:
http://localhost:8000/dev/cookie
Then head over to the development page that will initialize the client:
http://localhost:8000/dev/
npm install socket-io
That's it!
@@ -81,8 +44,9 @@ That's it!
## Node Compatibility
The `master` branch of Ajax IM is compatible with node --version:
v0.2.5
The `socket-io` branch of Ajax IM is compatible with node --version:
v0.4.6
## Contributing
Arquivo binário não exibido.
+52
Ver Arquivo
@@ -0,0 +1,52 @@
/*
* Adapted from
* https://github.com/jaz303/jquery-grab-bag/blob/master/javascripts/jquery.autogrow-textarea.js
*
* Auto-growing textareas; technique ripped from Facebook
*/
$.fn.autogrow = function(options) {
this.filter('textarea').each(function() {
var $this = $(this),
minHeight = $this.height(),
lineHeight = $this.css('lineHeight');
var shadow = $('<div></div>').css({
position: 'absolute',
top: -10000,
left: -10000,
width: $(this).width() - parseInt($this.css('paddingLeft')) - parseInt($this.css('paddingRight')),
fontSize: $this.css('fontSize'),
fontFamily: $this.css('fontFamily'),
lineHeight: $this.css('lineHeight'),
resize: 'none',
'word-wrap':'break-word'
}).appendTo(document.body);
var update = function() {
var times = function(string, number) {
for (var i = 0, r = ''; i < number; i ++) r += string;
return r;
};
var val = this.value.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/&/g, '&amp;')
.replace(/\n$/, '<br/>&nbsp;')
.replace(/\n/g, '<br/>')
.replace(/ {2,}/g, function(space) { return times('&nbsp;', space.length -1) + ' ' });
shadow.html(val);
$(this).css('height', Math.max(shadow.height(), minHeight));
}
$(this).change(update).keyup(update).keydown(update);
update.apply(this);
});
return this;
}
+813 -1367
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+20 -13
Ver Arquivo
@@ -1,22 +1,29 @@
/*!
* Ajax IM
* Ajax Instant Messeneger
* http://ajaxim.com/
*
* Copyright 2010, Joshua Gross
* Licensed under the MIT license.
* [License URL]
* Ajax IM (AJAX Instant Messenger)
* http://ajaxim.com/ | http://github.com/endtwist/AjaxIM
* (c) 2005-2011, Joshua Gross
* MIT license
*
* Includes:
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* v2.1, Copyright 2002, Paul Johnston
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info
* JavaScript implementation of the RSA Data Security, Inc. MD5 Message 2.1
* http://pajhome.org.uk/crypt/md5
* (c) 2002 Paul Johnston
* BSD License
*
* Date Format 1.2.3
* http://blog.stevenlevithan.com/archives/date-time-format
* Copyright 2009 Steven Levithan
* (c) 2009 Steven Levithan
* MIT license
*
* Block TEA (xxtea) Tiny Encryption Algorithm, Base64 class, Utf8 class
* www.movable-type.co.uk/tea-block.html
* (c) 2002-2010 Chris Veness
* CC Attribution
*
* store.js
* https://github.com/marcuswestin/store.js
* (c) 2010-2011 Marcus Westin
* MIT license
*
*/
(function($) {
+21
Ver Arquivo
@@ -0,0 +1,21 @@
AjaxIM.l10n = {
dayNames: [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
],
monthNames: [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"January", "February", "March", "April", "May", "June", "July", "August", "September",
"October", "November", "December"
],
chatOffline: '%s signed off.',
chatOnline: '%s became available.',
chatAway: '%s went away.',
notConnected: 'You are currently not connected or the server is not available. ' +
'Please ensure that you are signed in and try again.',
notConnectedTip: 'You are currently not connected.',
defaultAway: 'I\'m away.'
};
+1 -1
Ver Arquivo
@@ -1,2 +1,2 @@
$(function() { AjaxIM.init(); });
})(jQuery || $);
+85 -36
Ver Arquivo
@@ -1,74 +1,123 @@
// Storage API
/* Copyright (c) 2010-2011 Marcus Westin
*
* 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.
*/
var store = (function(){
var api = {},
win = window,
doc = win.document,
localStorageName = 'localStorage',
globalStorageName = 'globalStorage',
namespace = '__storejs__',
storage
api.disabled = false
api.set = function(key, value) {}
api.get = function(key) {}
api.remove = function(key) {}
api.clear = function() {}
api.transact = function(key, transactionFn) {
var val = api.get(key)
if (typeof val == 'undefined') { val = {} }
transactionFn(val)
api.set(key, val)
}
function serialize(value) {
api.serialize = function(value) {
return JSON.stringify(value)
}
function deserialize(value) {
api.deserialize = function(value) {
if (typeof value != 'string') { return undefined }
return JSON.parse(value)
}
if (localStorageName in win && win[localStorageName]) {
// Functions to encapsulate questionable FireFox 3.6.13 behavior
// when about.config::dom.storage.enabled === false
// See https://github.com/marcuswestin/store.js/issues#issue/13
function isLocalStorageNameSupported() {
try { return (localStorageName in win && win[localStorageName]) }
catch(err) { return false }
}
function isGlobalStorageNameSupported() {
try { return (globalStorageName in win && win[globalStorageName] && win[globalStorageName][win.location.hostname]) }
catch(err) { return false }
}
if (isLocalStorageNameSupported()) {
storage = win[localStorageName]
api.set = function(key, val) { storage[key] = serialize(val) }
api.get = function(key) { return deserialize(storage[key]) }
api.remove = function(key) { delete storage[key] }
api.set = function(key, val) { storage.setItem(key, api.serialize(val)) }
api.get = function(key) { return api.deserialize(storage.getItem(key)) }
api.remove = function(key) { storage.removeItem(key) }
api.clear = function() { storage.clear() }
} else if (globalStorageName in win && win[globalStorageName]) {
} else if (isGlobalStorageNameSupported()) {
storage = win[globalStorageName][win.location.hostname]
api.set = function(key, val) { storage[key] = serialize(val) }
api.get = function(key) { return deserialize(storage[key] && storage[key].value) }
api.set = function(key, val) { storage[key] = api.serialize(val) }
api.get = function(key) { return api.deserialize(storage[key] && storage[key].value) }
api.remove = function(key) { delete storage[key] }
api.clear = function() { for (var key in storage ) { delete storage[key] } }
} else if (doc.documentElement.addBehavior) {
function getStorage() {
if (storage) { return storage; }
storage = doc.body.appendChild(doc.createElement('div'))
storage.style.display = 'none'
// See http://msdn.microsoft.com/en-us/library/ms531081(v=VS.85).aspx
// and http://msdn.microsoft.com/en-us/library/ms531424(v=VS.85).aspx
storage.addBehavior('#default#userData')
storage.load(localStorageName)
return storage;
var storage = doc.createElement('div')
function withIEStorage(storeFunction) {
return function() {
var args = Array.prototype.slice.call(arguments, 0)
args.unshift(storage)
// See http://msdn.microsoft.com/en-us/library/ms531081(v=VS.85).aspx
// and http://msdn.microsoft.com/en-us/library/ms531424(v=VS.85).aspx
doc.body.appendChild(storage)
storage.addBehavior('#default#userData')
storage.load(localStorageName)
var result = storeFunction.apply(api, args)
doc.body.removeChild(storage)
return result
}
}
api.set = function(key, val) {
var storage = getStorage()
storage.setAttribute(key, serialize(val))
api.set = withIEStorage(function(storage, key, val) {
storage.setAttribute(key, api.serialize(val))
storage.save(localStorageName)
}
api.get = function(key) {
var storage = getStorage()
return deserialize(storage.getAttribute(key))
}
api.remove = function(key) {
var storage = getStorage()
})
api.get = withIEStorage(function(storage, key) {
return api.deserialize(storage.getAttribute(key))
})
api.remove = withIEStorage(function(storage, key) {
storage.removeAttribute(key)
storage.save(localStorageName)
}
api.clear = function() {
var storage = getStorage()
var attributes = storage.XMLDocument.documentElement.attributes;
})
api.clear = withIEStorage(function(storage) {
var attributes = storage.XMLDocument.documentElement.attributes
storage.load(localStorageName)
for (var i=0, attr; attr = attributes[i]; i++) {
storage.removeAttribute(attr.name)
}
storage.save(localStorageName)
}
})
}
try {
api.set(namespace, namespace)
if (api.get(namespace) != namespace) { api.disabled = true }
api.remove(namespace)
} catch(e) {
api.disabled = true
}
return api
})();
})();
+92
Ver Arquivo
@@ -0,0 +1,92 @@
var Template = {
bar:
'<ul id="imjs-bar">' +
'<li id="imjs-friends" class="imjs-not-connected">' +
'<span class="imjs-tab-text"><strong>&bull;</strong> Friends <span>(<span>0</span>)</span></span>' +
'<div id="imjs-friends-panel" class="imjs-chatbox">' +
'<div class="imjs-header">' +
'<span>{username}</span>' +
'<a href="#" class="imjs-minimize">_</a>' +
'</div>' +
'<div id="imjs-status-panel">' +
'<textarea id="imjs-away-message-text"></textarea>' +
'<div id="imjs-away-message-text-arrow"></div>' +
'<a href="#" id="imjs-button-online" class="imjs-button"><span>&bull;</span> Available</a>' +
'<a href="#" id="imjs-button-away" class="imjs-button"><span>&bull;</span> Away</a>' +
'<a href="#" id="imjs-button-offline" class="imjs-button"><span>&bull;</span> Offline</a>' +
'</div>' +
'<ul id="imjs-friends-list">' +
'</ul>' +
'<form>' +
'<p><input type="text" id="imjs-search-friends" value="Search" /></p>' +
'</form>' +
'</div>' +
'</li>' +
'<li id="imjs-reconnect"><span class="imjs-tab-text">&#8635;</span></li>' +
'<li id="imjs-scroll-left" class="imjs-scroll">{count}</li>' +
'<li id="imjs-scroll-right" class="imjs-scroll">{count}</li>' +
'</ul>',
tab:
'<li class="imjs-tab">' +
'<span class="imjs-tab-text"><strong>&bull;</strong> {label} <a href="#" class="imjs-close">x</a></span>' +
'<span class="imjs-notification">{count}</span>' +
'<form class="imjs-chatbox">' +
'<div>' +
'<div class="imjs-header">' +
'<span>{username}</span>' +
'<a href="#" class="imjs-close">x</a>' +
'<a href="#" class="imjs-minimize">_</a>' +
'</div>' +
'<ul class="imjs-msglog"></ul>' +
'<textarea class="imjs-input"></textarea>' +
'</div>' +
'</form>' +
'</li>',
datestamp:
'<li class="imjs-date">' +
'<ul>' +
'<li>' +
'<span class="imjs-msg-time">hh:MM TT &bull;</span>' +
'<p class="imjs-date-date">ddd, mmmm d, yyyy</p>' +
'</li>' +
'</ul>' +
'</li>',
message_error:
'<li class="imjs-error">' +
'<ul>' +
'<li>' +
'<span class="imjs-msg-time">hh:MM TT &bull;</span>' +
'<p class="imjs-error-error">ddd, mmmm d, yyyy</p>' +
'</li>' +
'</ul>' +
'</li>',
message_you_outer:
'<li class="imjs-msg-you">' +
'<span>{username}</span>' +
'<ul class="imjs-msg-you-container"></ul>' +
'</li>',
message_you:
'<li class="imjs-msg-you-msg">' +
'<span class="imjs-msg-time">hh:MM TT &bull;</span>' +
'<p>{message}</p>' +
'</li>',
message_them_outer:
'<li class="imjs-msg-them">' +
'<span>{username}</span>' +
'<ul class="imjs-msg-them-container"></ul>' +
'</li>',
message_them:
'<li class="imjs-msg-them-msg">' +
'<span class="imjs-msg-time">hh:MM TT &bull;</span>' +
'<p>{message}</p>' +
'</li>',
group:
'<li class="imjs-friend-group">' +
'<span class="imjs-friend-group-header">{group}</span>' +
'<ul></ul>' +
'</li>',
friend:
'<li class="imjs-friend"><strong>&bull;</strong><span>{username}</span><span class="imjs-friend-status">{status}</span></li>',
tooltip:
'<span class="imjs-tooltip"><p>{tip}</p></span>'
};
+267
Ver Arquivo
@@ -0,0 +1,267 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Block TEA (xxtea) Tiny Encryption Algorithm implementation in JavaScript */
/* (c) Chris Veness 2002-2010: www.movable-type.co.uk/tea-block.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Algorithm: David Wheeler & Roger Needham, Cambridge University Computer Lab */
/* http://www.cl.cam.ac.uk/ftp/papers/djw-rmn/djw-rmn-tea.html (1994) */
/* http://www.cl.cam.ac.uk/ftp/users/djw3/xtea.ps (1997) */
/* http://www.cl.cam.ac.uk/ftp/users/djw3/xxtea.ps (1998) */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Tea = {}; // Tea namespace
/*
* encrypt text using Corrected Block TEA (xxtea) algorithm
*
* @param {string} plaintext String to be encrypted (multi-byte safe)
* @param {string} password Password to be used for encryption (1st 16 chars)
* @returns {string} encrypted text
*/
Tea.encrypt = function(plaintext, password) {
if (plaintext.length == 0) return(''); // nothing to encrypt
// convert string to array of longs after converting any multi-byte chars to UTF-8
var v = Tea.strToLongs(Utf8.encode(plaintext));
if (v.length <= 1) v[1] = 0; // algorithm doesn't work for n<2 so fudge by adding a null
// simply convert first 16 chars of password as key
var k = Tea.strToLongs(Utf8.encode(password).slice(0,16));
var n = v.length;
// ---- <TEA coding> ----
var z = v[n-1], y = v[0], delta = 0x9E3779B9;
var mx, e, q = Math.floor(6 + 52/n), sum = 0;
while (q-- > 0) { // 6 + 52/n operations gives between 6 & 32 mixes on each word
sum += delta;
e = sum>>>2 & 3;
for (var p = 0; p < n; p++) {
y = v[(p+1)%n];
mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
z = v[p] += mx;
}
}
// ---- </TEA> ----
var ciphertext = Tea.longsToStr(v);
return Base64.encode(ciphertext);
}
/*
* decrypt text using Corrected Block TEA (xxtea) algorithm
*
* @param {string} ciphertext String to be decrypted
* @param {string} password Password to be used for decryption (1st 16 chars)
* @returns {string} decrypted text
*/
Tea.decrypt = function(ciphertext, password) {
if (ciphertext.length == 0) return('');
var v = Tea.strToLongs(Base64.decode(ciphertext));
var k = Tea.strToLongs(Utf8.encode(password).slice(0,16));
var n = v.length;
// ---- <TEA decoding> ----
var z = v[n-1], y = v[0], delta = 0x9E3779B9;
var mx, e, q = Math.floor(6 + 52/n), sum = q*delta;
while (sum != 0) {
e = sum>>>2 & 3;
for (var p = n-1; p >= 0; p--) {
z = v[p>0 ? p-1 : n-1];
mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
y = v[p] -= mx;
}
sum -= delta;
}
// ---- </TEA> ----
var plaintext = Tea.longsToStr(v);
// strip trailing null chars resulting from filling 4-char blocks:
plaintext = plaintext.replace(/\0+$/,'');
return Utf8.decode(plaintext);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// supporting functions
Tea.strToLongs = function(s) { // convert string to array of longs, each containing 4 chars
// note chars must be within ISO-8859-1 (with Unicode code-point < 256) to fit 4/long
var l = new Array(Math.ceil(s.length/4));
for (var i=0; i<l.length; i++) {
// note little-endian encoding - endianness is irrelevant as long as
// it is the same in longsToStr()
l[i] = s.charCodeAt(i*4) + (s.charCodeAt(i*4+1)<<8) +
(s.charCodeAt(i*4+2)<<16) + (s.charCodeAt(i*4+3)<<24);
}
return l; // note running off the end of the string generates nulls since
} // bitwise operators treat NaN as 0
Tea.longsToStr = function(l) { // convert array of longs back to string
var a = new Array(l.length);
for (var i=0; i<l.length; i++) {
a[i] = String.fromCharCode(l[i] & 0xFF, l[i]>>>8 & 0xFF,
l[i]>>>16 & 0xFF, l[i]>>>24 & 0xFF);
}
return a.join(''); // use Array.join() rather than repeated string appends for efficiency in IE
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Base64 class: Base 64 encoding / decoding (c) Chris Veness 2002-2010 */
/* note: depends on Utf8 class */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Base64 = {}; // Base64 namespace
Base64.code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
/**
* Encode string into Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
* (instance method extending String object). As per RFC 4648, no newlines are added.
*
* @param {String} str The string to be encoded as base-64
* @param {Boolean} [utf8encode=false] Flag to indicate whether str is Unicode string to be encoded
* to UTF8 before conversion to base64; otherwise string is assumed to be 8-bit characters
* @returns {String} Base64-encoded string
*/
Base64.encode = function(str, utf8encode) { // http://tools.ietf.org/html/rfc4648
utf8encode = (typeof utf8encode == 'undefined') ? false : utf8encode;
var o1, o2, o3, bits, h1, h2, h3, h4, e=[], pad = '', c, plain, coded;
var b64 = Base64.code;
plain = utf8encode ? Utf8.encode(str) : str;
c = plain.length % 3; // pad string to length of multiple of 3
if (c > 0) { while (c++ < 3) { pad += '='; plain += '\0'; } }
// note: doing padding here saves us doing special-case packing for trailing 1 or 2 chars
for (c=0; c<plain.length; c+=3) { // pack three octets into four hexets
o1 = plain.charCodeAt(c);
o2 = plain.charCodeAt(c+1);
o3 = plain.charCodeAt(c+2);
bits = o1<<16 | o2<<8 | o3;
h1 = bits>>18 & 0x3f;
h2 = bits>>12 & 0x3f;
h3 = bits>>6 & 0x3f;
h4 = bits & 0x3f;
// use hextets to index into code string
e[c/3] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
}
coded = e.join(''); // join() is far faster than repeated string concatenation in IE
// replace 'A's from padded nulls with '='s
coded = coded.slice(0, coded.length-pad.length) + pad;
return coded;
}
/**
* Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
* (instance method extending String object). As per RFC 4648, newlines are not catered for.
*
* @param {String} str The string to be decoded from base-64
* @param {Boolean} [utf8decode=false] Flag to indicate whether str is Unicode string to be decoded
* from UTF8 after conversion from base64
* @returns {String} decoded string
*/
Base64.decode = function(str, utf8decode) {
utf8decode = (typeof utf8decode == 'undefined') ? false : utf8decode;
var o1, o2, o3, h1, h2, h3, h4, bits, d=[], plain, coded;
var b64 = Base64.code;
coded = utf8decode ? Utf8.decode(str) : str;
for (var c=0; c<coded.length; c+=4) { // unpack four hexets into three octets
h1 = b64.indexOf(coded.charAt(c));
h2 = b64.indexOf(coded.charAt(c+1));
h3 = b64.indexOf(coded.charAt(c+2));
h4 = b64.indexOf(coded.charAt(c+3));
bits = h1<<18 | h2<<12 | h3<<6 | h4;
o1 = bits>>>16 & 0xff;
o2 = bits>>>8 & 0xff;
o3 = bits & 0xff;
d[c/4] = String.fromCharCode(o1, o2, o3);
// check for padding
if (h4 == 0x40) d[c/4] = String.fromCharCode(o1, o2);
if (h3 == 0x40) d[c/4] = String.fromCharCode(o1);
}
plain = d.join(''); // join() is far faster than repeated string concatenation in IE
return utf8decode ? Utf8.decode(plain) : plain;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple */
/* single-byte character encoding (c) Chris Veness 2002-2010 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Utf8 = {}; // Utf8 namespace
/**
* Encode multi-byte Unicode string into utf-8 multiple single-byte characters
* (BMP / basic multilingual plane only)
*
* Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
*
* @param {String} strUni Unicode string to be encoded as UTF-8
* @returns {String} encoded string
*/
Utf8.encode = function(strUni) {
// use regular expressions & String.replace callback function for better efficiency
// than procedural approaches
var strUtf = strUni.replace(
/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
function(c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
);
strUtf = strUtf.replace(
/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
function(c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
);
return strUtf;
}
/**
* Decode utf-8 encoded string back into multi-byte Unicode characters
*
* @param {String} strUtf UTF-8 string to be decoded back to Unicode
* @returns {String} decoded string
*/
Utf8.decode = function(strUtf) {
// note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
var strUni = strUtf.replace(
/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars
function(c) { // (note parentheses for precence)
var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f);
return String.fromCharCode(cc); }
);
strUni = strUni.replace(
/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars
function(c) { // (note parentheses for precence)
var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
return String.fromCharCode(cc); }
);
return strUni;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 124 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 176 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 552 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 847 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 212 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 706 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 142 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 141 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 234 B

Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 109 B

+62 -17
Ver Arquivo
@@ -1,4 +1,48 @@
.imjs-default { display: none; }
/*
Content-Type: mutipart/related; boundary="_FOR_IE_6_AND_7"
--_FOR_IE_6_AND_7
Content-Location:bottom
Content-Transfer-Encoding:base64
iVBORw0KGgoAAAANSUhEUgAAAW8AAAABCAIAAACe17jdAAAAF0lEQVQ4y2NwcXEdRaOIePRqFIwCHAAACPCDdjrG0LoAAAAASUVORK5CYII=
--_FOR_IE_6_AND_7
Content-Location:closemin
Content-Transfer-Encoding:base64
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAZCAMAAAA/rk+6AAAABlBMVEUHAQUAAACy3FD1AAAAAnRSTlP/AOW3MEoAAAAwSURBVBjTY2CEAwZGBgYwzQBmMjCCCQgF4TJC2IzoTIQCJG1IhiFZMciYDFCAzAQAZEoAw+FFzSEAAAAASUVORK5CYII=
--_FOR_IE_6_AND_7
Content-Location:error
Content-Transfer-Encoding:base64
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABpklEQVQ4y52RyUsCURzHhYJudegYBUV/REVFe7QdEtoOnVouXYKCDgZGJEEUeIqo6BB0qMAWPARWaoSHmjR1KltwurSIRTM6mjrBt/fGtKgw7fDhLfN+n/d981PsqdMVCUj74Mc3ADIJi/fHMkD5TZKMQMWutYFd76QCVaqCPJMmC4EnBgHvEeic7qUimOHMakjBS0iBM3CmUSrQJisoPZzORUS8RPBhCcG7WUg+FhZtIZXUJiNYubfOQfLbaYFMhD/Ao32eznV/CXqYxWK8iecQb8fjAv/1IEnBgFkoouv+RALmxb2NkFcH/83wp8DVh9DjMnhOF2/rbwIVu6qEJFjgc/XKRTGB76wLgrMVkZc9XGx2072J74IckyYT4oMBwdsJ+Nh2Qkd0dCohOFognNZDvBrAq2cX5slsKsn/Kphy7w4h/LxDDtZBsDcQGuMJBFs1eGs5+JMShJ+2wRlH5LbGBKRtOeTdBnJTMzlYAd5WSaiKjnRtLZOLKYKtBpFnI2lrAZU0Rdt2PE3MWwh7N5JkEx7HHBXo5QRUEoubAno5Qexv/pd3xf1YaSj1xLcAAAAASUVORK5CYII=
--_FOR_IE_6_AND_7
Content-Location:loading
Content-Transfer-Encoding:base64
R0lGODlhEAAQAPQAAP///5iYmPv7+66urs3NzZqamqenp+3t7dvb26CgoMfHx8DAwPPz89XV1efn57S0tLq6ugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAAFUCAgjmRpnqUwFGwhKoRgqq2YFMaRGjWA8AbZiIBbjQQ8AmmFUJEQhQGJhaKOrCksgEla+KIkYvC6SJKQOISoNSYdeIk1ayA8ExTyeR3F749CACH5BAAKAAEALAAAAAAQABAAAAVoICCKR9KMaCoaxeCoqEAkRX3AwMHWxQIIjJSAZWgUEgzBwCBAEQpMwIDwY1FHgwJCtOW2UDWYIDyqNVVkUbYr6CK+o2eUMKgWrqKhj0FrEM8jQQALPFA3MAc8CQSAMA5ZBjgqDQmHIyEAIfkEAAoAAgAsAAAAABAAEAAABWAgII4j85Ao2hRIKgrEUBQJLaSHMe8zgQo6Q8sxS7RIhILhBkgumCTZsXkACBC+0cwF2GoLLoFXREDcDlkAojBICRaFLDCOQtQKjmsQSubtDFU/NXcDBHwkaw1cKQ8MiyEAIfkEAAoAAwAsAAAAABAAEAAABVIgII5kaZ6AIJQCMRTFQKiDQx4GrBfGa4uCnAEhQuRgPwCBtwK+kCNFgjh6QlFYgGO7baJ2CxIioSDpwqNggWCGDVVGphly3BkOpXDrKfNm/4AhACH5BAAKAAQALAAAAAAQABAAAAVgICCOZGmeqEAMRTEQwskYbV0Yx7kYSIzQhtgoBxCKBDQCIOcoLBimRiFhSABYU5gIgW01pLUBYkRItAYAqrlhYiwKjiWAcDMWY8QjsCf4DewiBzQ2N1AmKlgvgCiMjSQhACH5BAAKAAUALAAAAAAQABAAAAVfICCOZGmeqEgUxUAIpkA0AMKyxkEiSZEIsJqhYAg+boUFSTAkiBiNHks3sg1ILAfBiS10gyqCg0UaFBCkwy3RYKiIYMAC+RAxiQgYsJdAjw5DN2gILzEEZgVcKYuMJiEAOwAAAAAAAAAAAA==
--_FOR_IE_6_AND_7
Content-Location:sl
Content-Transfer-Encoding:base64
iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQI12P4//8/AwizM7L+h7HhAiiCMAHCKrGZCQChyjk9tLLHDwAAAABJRU5ErkJggg==
--_FOR_IE_6_AND_7
Content-Location:sr
Content-Transfer-Encoding:base64
iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAIUlEQVQI12P4//8/AwizM7L+h7EZkAVhEhiCIExYJYwNAO9yOT1MGMFxAAAAAElFTkSuQmCC
--_FOR_IE_6_AND_7
Content-Location:tooltip
Content-Transfer-Encoding:base64
iVBORw0KGgoAAAANSUhEUgAAAA0AAAAICAYAAAAiJnXPAAAAiklEQVQY02PIzcrpZmdk/UoszkrP7Gf4//8/g4uD81agwH9CGKQOpB6s6fWrVzwq8krX8WkAyYPUwTWB8IXzF9QEuPheY9MAEgfJw9TCNYHwqhWr/KBuR9b0FSSOrA5FEwjX19RVIWn8CuKjq8HQBMIBPv4rQZpANDZ5rJo+fPjAFuwfuBxEY5MHACmH0K64j6daAAAAAElFTkSuQmCC
--_FOR_IE_6_AND_7
Content-Location:w
Content-Transfer-Encoding:base64
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC
--_FOR_IE_6_AND_7
*/
/* [begin] Chatbox */
.imjs-chatbox {
@@ -48,7 +92,7 @@
padding-top: 10px;
width: 10px;
overflow: hidden;
background: url(images/closemin.png) 0 0 no-repeat;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAZCAMAAAA/rk+6AAAABlBMVEUHAQUAAACy3FD1AAAAAnRSTlP/AOW3MEoAAAAwSURBVBjTY2CEAwZGBgYwzQBmMjCCCQgF4TJC2IzoTIQCJG1IhiFZMciYDFCAzAQAZEoAw+FFzSEAAAAASUVORK5CYII=) 0 0 no-repeat;
}
div.imjs-header a.imjs-minimize {
@@ -59,7 +103,7 @@
height: 0;
width: 207px;
overflow: hidden;
background: url(images/closemin.png) 100% -10px no-repeat;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAZCAMAAAA/rk+6AAAABlBMVEUHAQUAAACy3FD1AAAAAnRSTlP/AOW3MEoAAAAwSURBVBjTY2CEAwZGBgYwzQBmMjCCCQgF4TJC2IzoTIQCJG1IhiFZMciYDFCAzAQAZEoAw+FFzSEAAAAASUVORK5CYII=) 100% -10px no-repeat;
}
div.imjs-header a.imjs-minimize:active {
@@ -98,18 +142,18 @@
color: #ff0000;
}
li.imjs-msg-a, li.imjs-msg-b {
li.imjs-msg-you, li.imjs-msg-them {
display: block;
padding: 3px 5px 3px 8px;
border-top: 1px dotted #bfbfbf;
}
li.imjs-msg-b > span {
li.imjs-msg-them > span {
color: #ff0000;
font-weight: 700;
}
li.imjs-msg-a > span {
li.imjs-msg-you > span {
color: #0099ff;
font-weight: 700;
}
@@ -134,7 +178,7 @@
color: #bcbcbc;
}
li.imjs-msg-a ul p, li.imjs-msg-b ul p, li.imjs-date ul p {
li.imjs-msg-you ul p, li.imjs-msg-them ul p, li.imjs-date ul p {
margin: 0;
}
/* [end] Message Log Messages */
@@ -161,7 +205,7 @@ ul#imjs-bar {
position: fixed;
bottom: 0;
right: 5%;
background: #f0efed url(images/w.png) top left repeat-x;
background: #f0efed url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC) top left repeat-x;
height: 25px;
border: solid #cfceca;
border-width: 1px 1px 0 0;
@@ -233,7 +277,8 @@ ul#imjs-bar {
border-color: #444;
border-bottom: 0;
padding-bottom: 1px;
background-image: url(images/bottom.png);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAW8AAAABCAIAAACe17jdAAAAF0lEQVQ4y2NwcXEdRaOIePRqFIwCHAAACPCDdjrG0LoAAAAASUVORK5CYII=);
*background-image: url(mhtml:/theme.css!bottom);
background-position: -142px bottom;
background-repeat: no-repeat;
}
@@ -253,7 +298,7 @@ ul#imjs-bar {
padding-top: 10px;
overflow: hidden;
margin-top: 7.5px;
background: url(images/closemin.png) 0 0 no-repeat;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAZCAMAAAA/rk+6AAAABlBMVEUHAQUAAACy3FD1AAAAAnRSTlP/AOW3MEoAAAAwSURBVBjTY2CEAwZGBgYwzQBmMjCCCQgF4TJC2IzoTIQCJG1IhiFZMciYDFCAzAQAZEoAw+FFzSEAAAAASUVORK5CYII=) 0 0 no-repeat;
}
ul#imjs-bar > li:hover .imjs-tab-text a {
@@ -273,14 +318,14 @@ ul#imjs-bar {
ul#imjs-bar li#imjs-scroll-left {
padding: 1px 6px 0 4px;
background-image: url(images/sl.png);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQI12P4//8/AwizM7L+h7HhAiiCMAHCKrGZCQChyjk9tLLHDwAAAABJRU5ErkJggg==);
background-position: 5px center;
float: left;
}
ul#imjs-bar li#imjs-scroll-right {
padding: 1px 5px 0;
background-image: url(images/sr.png);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAIUlEQVQI12P4//8/AwizM7L+h7EZkAVhEhiCIExYJYwNAO9yOT1MGMFxAAAAAElFTkSuQmCC);
background-position: 20px center;
float: right;
}
@@ -314,7 +359,7 @@ ul#imjs-bar {
padding-bottom: 5px;
margin: 0 0 3px 6px;
left: -10000px;
background: url(images/tooltip.png) right bottom no-repeat;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAICAYAAAAiJnXPAAAAiklEQVQY02PIzcrpZmdk/UoszkrP7Gf4//8/g4uD81agwH9CGKQOpB6s6fWrVzwq8krX8WkAyYPUwTWB8IXzF9QEuPheY9MAEgfJw9TCNYHwqhWr/KBuR9b0FSSOrA5FEwjX19RVIWn8CuKjq8HQBMIBPv4rQZpANDZ5rJo+fPjAFuwfuBxEY5MHACmH0K64j6daAAAAAElFTkSuQmCC) right bottom no-repeat;
color: #fff;
text-shadow: none;
opacity: 0.9;
@@ -338,7 +383,7 @@ ul#imjs-bar {
ul#imjs-bar > li#imjs-friends.imjs-not-connected,
ul#imjs-bar > li#imjs-friends.imjs-not-connected:hover {
width: 10px;
background-image: url(images/error.png);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABpklEQVQ4y52RyUsCURzHhYJudegYBUV/REVFe7QdEtoOnVouXYKCDgZGJEEUeIqo6BB0qMAWPARWaoSHmjR1KltwurSIRTM6mjrBt/fGtKgw7fDhLfN+n/d981PsqdMVCUj74Mc3ADIJi/fHMkD5TZKMQMWutYFd76QCVaqCPJMmC4EnBgHvEeic7qUimOHMakjBS0iBM3CmUSrQJisoPZzORUS8RPBhCcG7WUg+FhZtIZXUJiNYubfOQfLbaYFMhD/Ao32eznV/CXqYxWK8iecQb8fjAv/1IEnBgFkoouv+RALmxb2NkFcH/83wp8DVh9DjMnhOF2/rbwIVu6qEJFjgc/XKRTGB76wLgrMVkZc9XGx2072J74IckyYT4oMBwdsJ+Nh2Qkd0dCohOFognNZDvBrAq2cX5slsKsn/Kphy7w4h/LxDDtZBsDcQGuMJBFs1eGs5+JMShJ+2wRlH5LbGBKRtOeTdBnJTMzlYAd5WSaiKjnRtLZOLKYKtBpFnI2lrAZU0Rdt2PE3MWwh7N5JkEx7HHBXo5QRUEoubAno5Qexv/pd3xf1YaSj1xLcAAAAASUVORK5CYII=);
background-position: center;
background-repeat: no-repeat;
}
@@ -388,7 +433,7 @@ textarea#imjs-away-message-text {
}
textarea#imjs-away-message-text.imjs-loading {
background: url(images/loading.gif) 161px 2px no-repeat;
background: url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///5iYmPv7+66urs3NzZqamqenp+3t7dvb26CgoMfHx8DAwPPz89XV1efn57S0tLq6ugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAAFUCAgjmRpnqUwFGwhKoRgqq2YFMaRGjWA8AbZiIBbjQQ8AmmFUJEQhQGJhaKOrCksgEla+KIkYvC6SJKQOISoNSYdeIk1ayA8ExTyeR3F749CACH5BAAKAAEALAAAAAAQABAAAAVoICCKR9KMaCoaxeCoqEAkRX3AwMHWxQIIjJSAZWgUEgzBwCBAEQpMwIDwY1FHgwJCtOW2UDWYIDyqNVVkUbYr6CK+o2eUMKgWrqKhj0FrEM8jQQALPFA3MAc8CQSAMA5ZBjgqDQmHIyEAIfkEAAoAAgAsAAAAABAAEAAABWAgII4j85Ao2hRIKgrEUBQJLaSHMe8zgQo6Q8sxS7RIhILhBkgumCTZsXkACBC+0cwF2GoLLoFXREDcDlkAojBICRaFLDCOQtQKjmsQSubtDFU/NXcDBHwkaw1cKQ8MiyEAIfkEAAoAAwAsAAAAABAAEAAABVIgII5kaZ6AIJQCMRTFQKiDQx4GrBfGa4uCnAEhQuRgPwCBtwK+kCNFgjh6QlFYgGO7baJ2CxIioSDpwqNggWCGDVVGphly3BkOpXDrKfNm/4AhACH5BAAKAAQALAAAAAAQABAAAAVgICCOZGmeqEAMRTEQwskYbV0Yx7kYSIzQhtgoBxCKBDQCIOcoLBimRiFhSABYU5gIgW01pLUBYkRItAYAqrlhYiwKjiWAcDMWY8QjsCf4DewiBzQ2N1AmKlgvgCiMjSQhACH5BAAKAAUALAAAAAAQABAAAAVfICCOZGmeqEgUxUAIpkA0AMKyxkEiSZEIsJqhYAg+boUFSTAkiBiNHks3sg1ILAfBiS10gyqCg0UaFBCkwy3RYKiIYMAC+RAxiQgYsJdAjw5DN2gILzEEZgVcKYuMJiEAOwAAAAAAAAAAAA==) 161px 2px no-repeat;
}
div#imjs-away-message-text-arrow {
@@ -442,11 +487,11 @@ div#imjs-status-panel > a.imjs-button {
color: #df9b00;
}
div#imjs-status-panel > a#imjs-button-available {
div#imjs-status-panel > a#imjs-button-online {
margin-left: 12px;
}
div#imjs-status-panel > a#imjs-button-available span {
div#imjs-status-panel > a#imjs-button-online span {
color: #00cc00;
}
-86
Ver Arquivo
@@ -1,86 +0,0 @@
<!-- Ajax IM, Footer Bar -->
<ul id="imjs-bar">
<li id="imjs-friends" class="imjs-not-connected">
<span class="imjs-tab-text"><strong>&bull;</strong> Friends <span>(<span>0</span>)</span></span>
<div id="imjs-friends-panel" class="imjs-chatbox">
<div class="imjs-header">
<span>{username}</span>
<a href="#" class="imjs-minimize">_</a>
</div>
<div id="imjs-status-panel">
<textarea id="imjs-away-message-text"></textarea>
<div id="imjs-away-message-text-arrow"></div>
<a href="#" id="imjs-button-available" class="imjs-button"><span>&bull;</span> Available</a>
<a href="#" id="imjs-button-away" class="imjs-button"><span>&bull;</span> Away</a>
<a href="#" id="imjs-button-offline" class="imjs-button"><span>&bull;</span> Offline</a>
</div>
<ul id="imjs-friends-list">
<li class="imjs-friend-group imjs-default">
<span class="imjs-friend-group-header">{group}</span>
<ul>
<li class="imjs-friend imjs-default"><strong>&bull;</strong><span>{username}</span><span class="imjs-friend-status">{status}</span></li>
</ul>
</li>
</ul>
<form>
<p><input type="text" id="imjs-search-friends" value="Search" /></p>
</form>
</div>
</li>
<li id="imjs-reconnect"><span class="imjs-tab-text">&#8635;</span></li>
<li id="imjs-scroll-left" class="imjs-scroll">{count}</li>
<li class="imjs-tab imjs-default">
<span class="imjs-tab-text"><strong>&bull;</strong> {label} <a href="#" class="imjs-close">x</a></span>
<span class="imjs-notification">{count}</span>
<form class="imjs-chatbox">
<div>
<div class="imjs-header">
<span>{username}</span>
<a href="#" class="imjs-close">x</a>
<a href="#" class="imjs-minimize">_</a>
</div>
<ul class="imjs-msglog">
<li class="imjs-date">
<ul>
<li>
<span class="imjs-msg-time">hh:MM TT &bull;</span>
<p class="imjs-date-date">ddd, mmmm d, yyyy</p>
</li>
</ul>
</li>
<li class="imjs-error">
<ul>
<li>
<span class="imjs-msg-time">hh:MM TT &bull;</span>
<p class="imjs-error-error">ddd, mmmm d, yyyy</p>
</li>
</ul>
</li>
<li class="imjs-msg-a">
<span>{username}</span>
<ul class="imjs-msg-a-container">
<li class="imjs-msg-a-msg">
<span class="imjs-msg-time">hh:MM TT &bull;</span>
<p>{message}</p>
</li>
</ul>
</li>
<li class="imjs-msg-b">
<span>{username}</span>
<ul class="imjs-msg-b-container">
<li class="imjs-msg-b-msg">
<span class="imjs-msg-time">hh:MM TT &bull;</span>
<p>{message}</p>
</li>
</ul>
</li>
</ul>
<textarea class="imjs-input"></textarea>
</div>
</form>
</li>
<li id="imjs-scroll-right" class="imjs-scroll">{count}</li>
</ul>
<span class="imjs-tooltip"><p>{tip}</p></span>
+88 -125
Ver Arquivo
@@ -1,135 +1,98 @@
#!/usr/bin/env node
var sys = require('sys'),
express = require('express'),
packages = require('./libs/packages'),
o_ = require('./libs/utils');
var http = require('http'),
url = require('url'),
fs = require('fs'),
io = require('socket.io'),
uglifyjs,
o_ = require('./libs/utils'),
client = {
'im.js': {
folder: 'js',
files: [
'intro.js',
'cookies.js', 'dateformat.js', 'json.js',
'autogrow.js', 'md5.js', 'store.js',
'xxtea.js', 'templates.js', 'im.js',
'l10n.js', 'outro.js'
],
},
'theme.css': {
folder: 'themes/default',
files: ['theme.css']
}
},
contentTypes = {
js: 'text/javascript',
css: 'text/css'
},
_clientFiles = {},
server, socket;
try { var uglifyjs = require('uglify-js'); } catch(e) {}
o_.merge(global, require('./settings'));
try { o_.merge(global, require('./settings.local')); } catch(e) {}
try {
var daemon = require('./libs/daemon/daemon'),
start = function() {
daemon.init({
lock: PID_FILE,
stdin: '/dev/null',
stdout: LOG_FILE,
stderr: LOG_FILE,
umask: 0,
chroot: null,
chdir: '.'
});
server = http.createServer(_serveClient);
server.listen(APP_PORT, APP_HOST);
var authHandler = require('./auth/' + AUTH_LIBRARY)(),
sessionStore = require('./session/' + SESSION_STORE)(),
msgHandler = require('./message/' + MESSAGE_HANDLER)(authHandler, sessionStore);
// setup socket.io
socket = io.listen(server);
socket.on('connection', function(client) {
client.on('message', function(message) {
msgHandler.message(client, message);
});
client.on('disconnect', function() {
msgHandler.disconnect(client, SESSION_TIMEOUT);
});
});
// compile client javascript
for(var file in client) {
var fileData = "";
for(var i = 0, fl = client[file].files.length; i < fl; i++)
fileData += fs.readFileSync(
__dirname + '/../client/'
+ client[file].folder + '/'
+ client[file].files[i],
'utf8'
);
var ext = file.split('.').pop();
/*
// breaks on xxtea library?
if(ext == 'js' && uglifyjs) {
// if uglify-js is installed, let's compress
fileData = uglifyjs.parser.parse(fileData);
fileData = uglifyjs.uglify.ast_mangle(fileData);
fileData = uglifyjs.uglify.ast_squeeze(fileData);
fileData = uglifyjs.uglify.gen_code(fileData);
}
*/
_clientFiles[file] = {
headers: {
'Content-Length': fileData.length,
'Content-Type': contentTypes[ext]
// Should use ETag
},
stop = function() {
process.kill(parseInt(require('fs').readFileSync(PID_FILE)));
};
content: fileData,
encoding: 'utf8'
};
};
switch(process.argv[2]) {
case 'stop':
stop();
process.exit(0);
break;
// serve client javascript
function _serveClient(req, res) {
var path = url.parse(req.url).pathname,
file = path.substr(1);
case 'start':
if(process.argv[3])
process.env.EXPRESS_ENV = process.argv[3];
start();
break;
case 'restart':
stop();
start();
process.exit(0);
break;
case 'help':
sys.puts('Usage: node app.js [start|stop|restart]');
process.exit(0);
break;
}
} catch(e) {
sys.puts('Daemon library not found! Please compile ' +
'./libs/daemon/daemon.node if you would like to use it.');
}
var app = express.createServer(
express.methodOverride(),
express.cookieDecoder(),
express.bodyDecoder(),
require('./middleware/im')({
maxAge: 15 * 60 * 1000,
reapInterval: 60 * 1000,
authentication: require('./libs/authentication/' + AUTH_LIBRARY)
})
);
app.set('root', __dirname);
app.configure('development', function() {
app.set('view engine', 'jade');
app.set('views', __dirname + '/dev/views');
app.stack.unshift({
route: '/dev',
handle: function(req, res, next) {
req.dev = true;
next();
}
});
app.use(express.logger());
app.use('/dev', express.router(require('./dev/app')));
app.use(express.staticProvider(
require('path').join(__dirname, '../client')));
app.use(express.errorHandler({dumpExceptions: true, showStack: true}));
});
app.listen(APP_PORT, APP_HOST);
// Listener endpoint; handled in middleware
app.get('/listen', function(){});
app.post('/message', function(req, res) {
res.find(req.body['to'], function(user) {
if(!user)
return res.send(new packages.Error('not online'));
res.message(user, new packages.Message(
req.session.data('username'),
req.body.body
));
});
});
app.post('/message/typing', function(req, res) {
if(~packages.TYPING_STATES.indexOf('typing' + req.body['state'])) {
res.find(req.body['to'], function(user) {
if(user) {
res.message(user, new packages.Status(
req.session.data('username'),
'typing' + req.body.state
));
}
// Typing updates do not receive confirmations,
// as they are not important enough.
res.send('');
});
if(req.method == 'GET' && file in _clientFiles) {
res.writeHead(200, _clientFiles[file].headers);
res.end(_clientFiles[file].content, _clientFiles[file].encoding);
} else {
res.send(new packages.Error('invalid state'));
res.writeHead(404);
res.end('404');
}
});
app.post('/status', function(req, res) {
if(~packages.STATUSES.indexOf(req.body['status'])) {
res.status(req.body.status, req.body.message);
res.send(new packages.Success('status updated'));
} else {
res.send(new packages.Error('invalid status'));
}
});
app.post('/signoff', function(req, res) {
res.signOff();
res.send(new packages.Success('goodbye'));
});
}
+17
Ver Arquivo
@@ -0,0 +1,17 @@
var ExampleAuth = function() {};
ExampleAuth.prototype.authenticate = function(client, msg, callback) {
callback({
authenticated: true,
username: 'username' + Math.round(Math.random() * 3)
});
};
ExampleAuth.prototype.friends = function(client, res, callback) {
callback(['username0', 'username1', 'username2', 'username3']);
};
var instance = new ExampleAuth();
module.exports = function getInstance() {
return instance;
};
-16
Ver Arquivo
@@ -1,16 +0,0 @@
var utils = require('connect/utils');
module.exports = function(app) {
app.get('/', function(req, res) {
res.render('chat', {
locals: {}
});
});
app.get('/cookie', function(req, res) {
res.send('cookie set', {
'Set-Cookie': utils.serializeCookie('sessionid', utils.uid(),
{path: '/'})
});
});
};
-1
Ver Arquivo
@@ -1 +0,0 @@
../../../client/js/
-1
Ver Arquivo
@@ -1 +0,0 @@
../../../client/themes/
-13
Ver Arquivo
@@ -1,13 +0,0 @@
!!! 5
html
head
title Ajax IM
script(src: '/js/jquery-1.4.1.js', type: 'text/javascript')
script(src: '/js/md5.js', type: 'text/javascript')
script(src: '/js/store.js', type: 'text/javascript')
script(src: '/js/cookies.js', type: 'text/javascript')
script(src: '/js/dateformat.js', type: 'text/javascript')
script(src: '/js/im.js', type: 'text/javascript')
script(type: 'text/javascript')
| $(function(){var im = AjaxIM.init({theme: "/themes/default"});});
body Hello.
-1
Ver Arquivo
@@ -1 +0,0 @@
!{body}
-24
Ver Arquivo
@@ -1,24 +0,0 @@
// Cookie that stores the session ID
// Will be set as request.sessionID in `authenticate` and `friends` functions
exports.cookie = 'sessionid';
exports.authenticate = function(request, callback) {
// Verify user based on request.
// On failure, redirect user to auth form
callback({
username: 'username' + Math.floor(Math.random() * 1000),
displayname: 'John Smith',
otherinfo: 'any other relevant key/values'
});
};
exports.friends = function(request, data, callback) {
// Create a friends list based on given user data
callback([
'username1',
'username2',
'username3'
]);
};
-301
Ver Arquivo
@@ -1,301 +0,0 @@
/*
* Daemon.node
*** A node.JS addon that allows creating Unix/Linux Daemons in pure Javascript.
*** Copyright 2010 (c) <arthur@norgic.com>
* Under MIT License. See LICENSE file.
*/
#include <node/node.h>
#include <v8.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define PID_MAXLEN 10
using namespace v8;
// Go through special routines to become a daemon.
// if successful, returns daemon's PID
Handle<Value> Start(const Arguments& args) {
pid_t pid;
pid = fork();
if(pid > 0) exit(0);
if(pid < 0) exit(1);
// Can be changed after with process.umaks
umask(0);
setsid();
// Can be changed with process.chdir
chdir("/");
return Integer::New(getpid());
}
// Close Standard IN/OUT/ERR Streams
Handle<Value> CloseIO(const Arguments& args) {
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
}
// File-lock to make sure that only one instance of daemon is running.. also for storing PID
/* lock ( filename )
*** filename: a path to a lock-file.
*** Note: if filename doesn't exist, it will be created when function is called.
*/
Handle<Value> LockD(const Arguments& args) {
if(!args[0]->IsString())
return Boolean::New(false);
String::Utf8Value data(args[0]->ToString());
char pid_str[PID_MAXLEN+1];
int lfp = open(*data, O_RDWR | O_CREAT, 0640);
if(lfp < 0) exit(1);
if(lockf(lfp, F_TLOCK, 0) < 0) exit(0);
int len = snprintf(pid_str, PID_MAXLEN, "%d", getpid());
write(lfp, pid_str, len);
return Boolean::New(true);
}
class StreamPtr : public node::ObjectWrap
{
public:
explicit StreamPtr(FILE** fpp, const char *pmode);
~StreamPtr();
static Handle<Value> Open(const Arguments& args);
static Handle<Value> Close(const Arguments& args);
static Handle<Value> Redirect(const Arguments& args);
static void Initialize(Handle<Object> target);
static Handle<Value> New(const Arguments& args);
FILE** stream;
const char *mode;
private:
static Persistent<FunctionTemplate> constructor_template;
};
Persistent<FunctionTemplate> StreamPtr::constructor_template;
StreamPtr::StreamPtr(FILE** fpp, const char *pmode)
: stream(fpp)
, mode( pmode )
{
}
StreamPtr::~StreamPtr()
{
fclose(*stream);
}
Handle<Value> StreamPtr::Open(const Arguments& args)
{
HandleScope scope;
StreamPtr *fp = ObjectWrap::Unwrap<StreamPtr>(args.This());
char *new_file = *String::Utf8Value(args[0]->ToString());
*fp->stream = fopen(new_file, fp->mode);
// return if the creation of the new FILE* was successful;
return Boolean::New( *fp->stream != NULL );
}
Handle<Value> StreamPtr::Close(const Arguments& args)
{
HandleScope handle_scope;
StreamPtr *fp = ObjectWrap::Unwrap<StreamPtr>(args.This());
assert( fp && "object had no InternalField" );
int ret = fclose(*fp->stream);
return Boolean::New(ret == 0);
}
Handle<Value> StreamPtr::Redirect(const Arguments& args)
{
HandleScope scope;
if (Close(args)->ToBoolean()->Value())
{
return Open(args);
} else {
return Boolean::New(false);
}
}
Persistent<Object> stdin_obj;
Persistent<Object> stdout_obj;
Persistent<Object> stderr_obj;
void StreamPtr::Initialize(Handle<Object> target)
{
HandleScope scope;
Local<FunctionTemplate> file_pointer = FunctionTemplate::New(StreamPtr::New);
constructor_template = Persistent<FunctionTemplate>::New(file_pointer);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "open", StreamPtr::Open);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", StreamPtr::Close);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "sendTo", StreamPtr::Redirect);
// Although we could expose the prototype directly, I see no reason to, as it's not
// very useful for anything other than the intended purpose.
//target->Set(String::NewSymbol("StreamPtr"), constructor_template->GetFunction());
stdin_obj = Persistent<Object>::New( constructor_template->GetFunction()->NewInstance() );
Handle<External> stdin_ptr = External::New( new StreamPtr( &stdin, "r" ) );
stdin_obj->SetInternalField(0, stdin_ptr);
target->Set(String::NewSymbol("stdin"), stdin_obj);
stdout_obj = Persistent<Object>::New( constructor_template->GetFunction()->NewInstance() );
Handle<External> stdout_ptr = External::New( new StreamPtr( &stdout, "w" ) );
stdout_obj->SetInternalField(0, stdout_ptr);
target->Set(String::NewSymbol("stdout"), stdout_obj);
stderr_obj = Persistent<Object>::New( constructor_template->GetFunction()->NewInstance() );
Handle<External> stderr_ptr = External::New( new StreamPtr( &stderr, "w" ) );
stderr_obj->SetInternalField(0, stderr_ptr);
target->Set(String::NewSymbol("stderr"), stderr_obj);
}
Handle<Value> StreamPtr::New(const Arguments& args)
{
HandleScope scope;
return scope.Close(args.This());
}
/* The default object:
* {
* "fork": true,
* "lock": "daemon.pid"
* "stdout": null,
* "stderr": null,
* "stdin": null,
* "umask": 0,
* "chroot": null,
* "chdir": ".",
* }
*/
#define SET_DEFAULT(obj, name, value) \
do { \
Local<String> str_##name = String::New(#name); \
if( !obj->Has( str_##name ) ) \
obj->Set( str_##name, (value) ); \
} while (0)
static inline void setDefaults(Handle<Object> &arg)
{
HandleScope scope;
SET_DEFAULT( arg, fork, Boolean::New(true) );
SET_DEFAULT( arg, lock, String::New("daemon.pid") );
SET_DEFAULT( arg, stdin, Null() );
SET_DEFAULT( arg, stdout, Null() );
SET_DEFAULT( arg, stderr, Null() );
SET_DEFAULT( arg, umask, Integer::New(0) );
SET_DEFAULT( arg, chroot, Null() );
SET_DEFAULT( arg, chdir, String::New(".") );
//SET_DEFAULT( arg, close_fds, Boolean::New(false) );
//SET_DEFAULT( arg, catch_signals, Boolean::New(false) );
}
#undef SET_DEFAULT
Handle<Value> Init(const Arguments& args)
{
HandleScope scope;
Local<Object> arg = args[0]->ToObject();
setDefaults( arg );
pid_t pid = 0;
if( arg->Get( String::New("fork") )->IsTrue() )
{
pid = fork();
if(pid > 0) exit(0);
if(pid < 0) exit(1);
}
if( !arg->Get( String::New("lock") )->IsNull() )
{
Local<String> file = arg->Get( String::New( "lock" ) )->ToString();
String::Utf8Value data(file);
char pid_str[PID_MAXLEN+1];
int lfp = open(*data, O_RDWR | O_CREAT, 0640);
if(lfp < 0) exit(1);
if(lockf(lfp, F_TLOCK, 0) < 0) exit(0);
int len = snprintf(pid_str, PID_MAXLEN, "%d", getpid());
write(lfp, pid_str, len);
}
if( arg->Get( String::New("stdin") )->IsNull() )
{
Local<Function> close = Function::Cast( *stdin_obj->Get( String::New("close") ) );
close->Call( stdin_obj, 0, NULL );
} else {
Local<Function> send_to = Function::Cast( *stdin_obj->Get( String::New("sendTo") ) );
Local<Value> string_arg = arg->Get( String::New("stdin") );
send_to->Call( stdin_obj, 1, &string_arg );
}
if( arg->Get( String::New("stdout") )->IsNull() )
{
Local<Function> close = Function::Cast( *stdout_obj->Get( String::New("close") ) );
close->Call( stdout_obj, 0, NULL );
} else {
Local<Function> send_to = Function::Cast( *stdout_obj->Get( String::New("sendTo") ) );
Local<Value> string_arg = arg->Get( String::New("stdout") );
send_to->Call( stdout_obj, 1, &string_arg );
}
if( arg->Get( String::New("stderr") )->IsNull() )
{
Local<Function> close = Function::Cast( *stderr_obj->Get( String::New("close") ) );
close->Call( stderr_obj, 0, NULL );
} else {
Local<Function> send_to = Function::Cast( *stderr_obj->Get( String::New("sendTo") ) );
Local<Value> string_arg = arg->Get( String::New("stderr") );
send_to->Call( stderr_obj, 1, &string_arg );
}
Local<Integer> umask_arg = arg->Get( String::New("umask") )->ToInteger();
umask( umask_arg->Value() );
if( !arg->Get( String::New("chroot") )->IsNull() )
{
char* dir = *String::AsciiValue( arg->Get( String::New("chroot") ) );
chroot( dir );
}
if( !arg->Get( String::New("chdir") )->IsNull() )
{
char* dir = *String::AsciiValue( arg->Get( String::New("chdir") ) );
chdir( dir );
}
return Integer::New(pid);
}
extern "C" void init(Handle<Object> target) {
HandleScope scope;
target->Set(String::New("start"), FunctionTemplate::New(Start)->GetFunction());
target->Set(String::New("lock"), FunctionTemplate::New(LockD)->GetFunction());
target->Set(String::New("closeIO"), FunctionTemplate::New(CloseIO)->GetFunction());
target->Set(String::New("init"), FunctionTemplate::New(Init)->GetFunction());
StreamPtr::Initialize(target);
}
-15
Ver Arquivo
@@ -1,15 +0,0 @@
srcdir = "."
blddir = "build"
VERSION = "0.0.1"
def set_options(opt):
opt.tool_options("compiler_cxx")
def configure(conf):
conf.check_tool("compiler_cxx")
conf.check_tool("node_addon")
def build(bld):
obj = bld.new_task_gen("cxx", "shlib", "node_addon")
obj.target = "daemon"
obj.source = "daemon.cc"
-88
Ver Arquivo
@@ -1,88 +0,0 @@
var sys = require('sys');
var Package = function() {};
Package.prototype._sanitize = function(content) {
// strip HTML
return content.replace(/<(.|\n)*?>/g, '');
};
var Error = exports.Error = function(error) {
this.error = error;
};
sys.inherits(Error, Package);
Error.prototype.toJSON = function() {
return {
type: 'error',
error: this.error
};
};
var Success = exports.Success = function(success) {
this.success = success;
};
sys.inherits(Success, Package);
Success.prototype.toJSON = function() {
return {
type: 'success',
success: this.success
};
};
var Message = exports.Message = function(from, body) {
this.from = from;
this.body = body;
};
sys.inherits(Message, Package);
Message.prototype.toJSON = function() {
return {
type: 'message',
user: this.from,
body: this._sanitize(this.body)
};
};
var Notice = exports.Notice = function(username, info) {
this.username = username;
this.info = info;
};
sys.inherits(Notice, Package);
Notice.prototype.toJSON = function() {
return {
type: 'notice',
user: this.username,
info: this.info
};
};
exports.TYPING_STATES = ['typing+', 'typing~', 'typing-'];
exports.STATUSES = ['available', 'away', 'idle'];
var Status = exports.Status = function(username, status, message) {
var statuses = exports.STATUSES + exports.TYPING_STATES;
this.username = username;
this.status = -~statuses.indexOf(status) ? status : statuses[0];
this.message = message;
};
sys.inherits(Status, Package);
Status.prototype.toJSON = function() {
return {
type: 'status',
user: this.username,
status: this.status,
message: this._sanitize(this.message || '')
};
};
var Offline = exports.Offline = function(username) {
this.username = username;
};
sys.inherits(Offline, Package);
Offline.prototype.toJSON = function() {
// A special type of status
return {
type: 'status',
user: this.username,
status: 'offline',
message: ''
};
};
+160
Ver Arquivo
@@ -0,0 +1,160 @@
var MessageHandler = function(auth_handler, session_store) {
this.auth_handler = auth_handler;
this.session_store = session_store;
};
MessageHandler.prototype._auth = function(client, message) {
var session = this.session_store.get('identifier', message.identifier);
if(session) {
this.session_store.touch(session, client);
client.send({
type: 'AUTH',
loggedin: true,
username: session.username
});
} else {
var auth_handler = this.auth_handler,
session_store = this.session_store;
// authenticate the new user
auth_handler.authenticate(client, message, function(res) {
// authentication succeeded, setup user
if(res.authenticated) {
// get user's friends
auth_handler.friends(client, res, function(friends_array) {
var friends_list = {},
sess,
indentifier;
for(var i = 0, fl = friends_array.length; i < fl; i++) {
sess = session_store.get('username', friends_array[i])
if(sess) {
// notify friends that user has logged on
sess.client.send({
type: 'STATUS',
username: res.username,
status: 'online'
});
friends_list[friends_array[i]] = sess.status;
} else if(friends_array[i] == res.username) {
friends_list[friends_array[i]] = 'online';
} else {
friends_list[friends_array[i]] = 'offline';
}
}
identifier = session_store.create(res.username,
client,
friends_array
);
// notify user that they're logged on
client.send({
type: 'AUTH',
loggedin: true,
username: res.username,
friends: friends_list,
identifier: identifier
});
});
} else {
// authentication failed
client.send({
type: 'AUTH',
loggedin: false
});
}
});
}
}
MessageHandler.prototype._im = function(client, to, message) {
var sender = this.session_store.get('client', client.sessionId),
recipient;
if(sender) {
recipient = this.session_store.get('username', to);
if(recipient) {
recipient.client.send({
type: 'IM',
from: sender.username,
message: message
});
} else {
client.send({
type: 'ERROR',
origin: 'IM',
to: to
});
}
}
};
MessageHandler.prototype._status = function(client, status, status_msg) {
var session = this.session_store.get('client', client.sessionId),
friend;
if(['online', 'away', 'offline'].indexOf(status) != -1) {
session.status = status;
this.session_store.set(session.username, session);
// let user's friends know about the new status
for(var i = 0, fl = session.friends.length; i < fl; i++) {
friend = this.session_store.get('username', session.friends[i]);
if(friend) {
friend.client.send({
type: 'STATUS',
username: session.username,
status: status
});
}
}
}
};
MessageHandler.prototype._disconnect = function(client, SESSION_TIMEOUT) {
var threshold = +new Date - SESSION_TIMEOUT,
session = this.session_store.get('client', client);
if(session && session.lastAccess < threshold) {
if(session.status != 'offline')
this._status(session.username, 'offline');
this.session_store.remove(session.username);
}
};
MessageHandler.prototype.message = function(client, message) {
console.log(message);
switch(message.type) {
case 'AUTH':
this._auth(client, message);
break;
case 'IM':
this._im(client, message.to, message.message);
break;
case 'STATUS':
this._status(client, message.status, message.status_msg);
break;
}
};
MessageHandler.prototype.disconnect = function(client, SESSION_TIMEOUT) {
var self = this;
setTimeout(function() {
self._disconnect(client, SESSION_TIMEOUT);
}, SESSION_TIMEOUT);
};
module.exports = function createInstance(auth_handler, session_store) {
return new MessageHandler(auth_handler, session_store);
};
-67
Ver Arquivo
@@ -1,67 +0,0 @@
var url = require('url'),
Hub = require('./im/hub');
module.exports = function setupHub(options) {
options = options || {};
store = new Hub(options);
return function session(req, res, next) {
req.sessionStore = store;
req.sessionID = req.cookies[options.authentication.cookie];
if(!req.cookies) {
next(new Error('session requires cookieDecoder to work properly'));
return;
}
if(req.dev) {
next();
return;
}
if(req.sessionID) {
store.get(req, function(err, sess) {
if(err) {
next(err);
return;
}
if(!sess) {
next(new Error(JSON.stringify({
type: 'error',
error: 'not authenticated'})));
return;
}
sess.touch();
if(url.parse(req.url).pathname === '/listen') {
req.connection.setTimeout(5 * 60 * 1000);
sess.listener(res);
store.set(req.sessionID, sess);
if(msg = sess.message_queue.shift())
sess._send.apply(sess, msg);
} else {
sess.connection = res;
}
req.session = sess;
res.find = store.find.bind(store);
res.message = function(to, package) {
store.message(req.session, to, package);
};
res.status = function(value, message) {
req.session.status(value, message);
};
res.signOff = function() { store.signOff(req.sessionID); };
next();
});
} else {
next(new Error(JSON.stringify({
type: 'error',
error: 'not authenticated'})));
}
};
};
-126
Ver Arquivo
@@ -1,126 +0,0 @@
var events = require('events'),
sys = require('sys'),
packages = require('../../libs/packages'),
o_ = require('../../libs/utils'),
User = require('./user');
var Hub = module.exports = function Hub(options) {
this.events = new events.EventEmitter();
this.auth = options.authentication;
this.sessions = {};
this.maxAge = options.maxAge || 4 * 60 * 60 * 1000;
this.reapInterval = options.reapInterval || 60 * 1000;
if(this.reapInterval !== -1) {
setInterval(function(self) {
self.reap(self.maxAge);
}, this.reapInterval, this);
}
this.events.addListener('update', o_.bind(function(package) {
var _package = package.toJSON();
if(package.type == 'status' && package.status == 'offline') {
var sids = Object.keys(this.sessions), sid, sess;
for(sid in this.sessions) {
sess = this.sessions[sid];
if(sess.data('username') == package.username) {
if(sess.listeners.length)
sess.send(200, {type: 'goodbye'});
delete this.sessions[sid];
break;
}
}
}
}, this));
};
Hub.prototype.destroy = function(sid, fn) {
this.set(sid, null, fn);
};
Hub.prototype.reap = function(ms) {
var threshold = +new Date - ms,
sids = Object.keys(this.sessions);
for(var i = 0, len = sids.length; i < len; ++i) {
var sid = sids[i], sess = this.sessions[sid];
if(sess.lastAccess < threshold) {
this.events.emit('update', new packages.Offline(sess.data('username')));
}
}
};
Hub.prototype.get = function(req, fn) {
if(this.sessions[req.sessionID]) {
fn(null, this.sessions[req.sessionID]);
} else {
this.auth.authenticate(req, o_.bind(function(data) {
if(data) {
var session = new User(req.sessionID, data);
this.set(req.sessionID, session);
this.auth.friends(req, data, o_.bind(function(friends) {
var friends_copy = friends.slice();
o_.values(this.sessions).filter(function(friend) {
return ~friends.indexOf(friend.data('username'));
}).forEach(function(friend) {
var username = friend.data('username');
friends_copy[friends_copy.indexOf(username)] =
[username, friend.status()];
}, this);
session._friends(friends_copy);
session.events.addListener('status',
o_.bind(function(value, message) {
this.events.emit(
'update',
new packages.Status(session.data('username'),
value,
message)
);
}, this));
this.events.addListener('update',
o_.bind(session.receivedUpdate, session));
this.set(req.sessionID, session);
fn(null, session);
}, this));
} else {
fn();
}
}, this));
}
};
Hub.prototype.set = function(sid, sess, fn) {
this.sessions[sid] = sess;
fn && fn();
};
Hub.prototype.find = function(username, fn) {
for(var sid in this.sessions) {
var session = this.sessions[sid],
sess_username = session.data('username');
if(sess_username == username) {
fn(session);
return;
}
}
fn(false);
};
Hub.prototype.message = function(from, to, package) {
try {
package.user = from;
to.send(package);
from.respond(new packages.Success('sent'));
} catch(e) {
from.respond(new packages.Error(e.description));
}
};
Hub.prototype.signOff = function(sid) {
if(sid in this.sessions)
this.events.emit('update',
new packages.Offline(
this.sessions[sid].data('username')));
};
-114
Ver Arquivo
@@ -1,114 +0,0 @@
var events = require('events'),
packages = require('../../libs/packages'),
o_ = require('../../libs/utils');
var User = module.exports = function(id, data) {
this.id = id;
this.connection = null;
this.listeners = [];
this.message_queue = [];
this.convos = {};
this._data = data;
this.events = new events.EventEmitter();
this.status(packages.STATUSES[0], '');
setInterval(o_.bind(this._expireConns, this), 500);
};
User.prototype.receivedUpdate = function(package) {
if(this.friends.indexOf(package.username))
this.send(package);
};
User.prototype._friends = function(friends) {
this.friends = friends;
this.send(JSON.stringify({
type: 'hello',
username: this.data('username'),
friends: friends
}));
};
User.prototype._expireConns = function() {
var conn,
noop = JSON.stringify({type: 'noop'}),
noop_headers = {
'Content-Type': 'application/json',
'Content-Length': noop.length
};
for(var i = 0; i < this.listeners.length; i++) {
conn = this.listeners[i].connection;
if((Date.now() - conn._idleStart) >= conn._idleTimeout - 2000) {
this.listeners[i].writeHead(200, noop_headers);
this.listeners[i].end(noop);
this.listeners.splice(i, 1);
i--;
}
}
};
User.prototype.listener = function(conn) {
this.listeners.push(conn);
};
User.prototype.respond = function(code, message, callback) {
this._send('connection', code, message, callback);
};
User.prototype.send = function(code, message, callback) {
this._send('listener', code, message, callback);
};
User.prototype._send = function(type, code, message, callback) {
if(!message && typeof code != 'number') {
callback = message;
message = code;
code = 200;
}
if(typeof message != 'string')
message = JSON.stringify(message);
if(type == 'connection' && this.connection) {
this.connection.writeHead(code || 200, {
'Content-Type': 'application/json',
'Content-Length': message.length
});
this.connection.end(message);
} else {
if(!this.listeners.length)
return this.message_queue.push(arguments);
var cx = this.listeners.slice(), conn;
this.listeners = [];
while(conn = cx.shift()) {
conn.writeHead(code || 200, {
'Content-Type': 'application/json',
'Content-Length': message.length
});
conn.end(message);
}
if(callback) callback();
}
};
User.prototype.data = function(key, def) {
if(key == 'id') return this.id;
return this._data[key] || this['_' + key] ||
(typeof this[key] != 'function' && this[key]) ||
def || false;
};
User.prototype.touch = function() {
this.lastAccess = +new Date;
};
User.prototype.status = function(value, message) {
if(!value)
return this._status;
this._status = value;
this._status_message = message;
this.events.emit('status', value, message);
};
+90
Ver Arquivo
@@ -0,0 +1,90 @@
var crypto = require('crypto');
var Session = function(username) {
this.username = username;
this.authenticated = false;
this.lastAccess = new Date().getTime();
this.client = -1;
this.identifier = -1;
this.friends = [];
this.status = 'online';
};
var MemorySessionStore = function() {
this.sessions = {};
this.sessionClientMap = {};
this.sessionIdentifierMap = {};
};
MemorySessionStore.prototype.create = function(username, client, friends) {
var session = new Session(username);
session.client = client;
if(friends) {
for(var i = 0, fl = friends.length; i < fl; i++)
session.friends.push(friends[i])
}
this.sessions[username] = session;
this.sessionClientMap[client.sessionId] = username;
// create a re-authentication identifier
var md5 = crypto.createHash('md5'),
identifier = md5.update(username + client.sessionId).digest('hex');
this.sessionIdentifierMap[identifier] = username;
session.identifier = identifier;
return identifier;
};
MemorySessionStore.prototype.set = function(username, session) {
this.sessions[username] = session;
this.sessionClientMap[session.client.sessionId] = username;
this.sessionIdentifierMap[session.identifier] = username;
};
MemorySessionStore.prototype.touch = function(session, client) {
var new_session = session;
new_session.lastAccess = new Date().getTime();
new_session.client = client;
this.remove(new_session.username);
this.set(new_session.username, new_session);
};
MemorySessionStore.prototype.get = function(key, val) {
switch(key) {
case 'client':
return this.sessions[this.sessionClientMap[val]];
break;
case 'username':
return this.sessions[val];
break;
case 'identifier':
return this.sessions[this.sessionIdentifierMap[val]];
break;
}
return null;
}
MemorySessionStore.prototype.all = function() {
return this.sessions;
};
MemorySessionStore.prototype.remove = function(username) {
if(this.sessions[username]) {
var clientid = this.sessions[username].client.sessionId,
identifier = this.sessions[username].identifier;
delete this.sessionClientMap[clientid];
delete this.sessionIdentifierMap[identifier];
delete this.sessions[username];
}
};
var instance = new MemorySessionStore();
module.exports = function getInstance() {
return instance;
};
+14 -7
Ver Arquivo
@@ -7,18 +7,25 @@
// === Host and Port ===
//
// Define the host and port that Ajax IM will run on.
//
// Note: Setting APP_HOST to null will run the server on port 8000 for any
// hostname!
APP_HOST = 'localhost';
APP_PORT = 8000;
// === Authentication Library ===
//
// This is the library (from libs/authenticate/) that we will use to
// authenticate a user signing in. The value should be the name of the file
// without the '.js' part. 'index' is the default library.
AUTH_LIBRARY = 'default';
// Document me!
AUTH_LIBRARY = 'example';
SESSION_STORE = 'memory';
MESSAGE_HANDLER = 'default';
// Document me!
SESSION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
// === Daemon ===
//
// Define where the PID and log files will be deposited when run as a daemon.
/*
// Broken! Removed for the time being.
PID_FILE = '/tmp/ajaxim.pid';
LOG_FILE = '/var/run/ajaxim.log';
LOG_FILE = '/var/run/ajaxim.log';
*/