Comparar commits
11 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 7fe6758c6d | |||
| d0508b4b7f | |||
| 3f75e46a67 | |||
| 5f158cc907 | |||
| 5a58becdea | |||
| 1be565833d | |||
| bd0f2562df | |||
| 5f67201e39 | |||
| 1793897395 | |||
| e070dd3b76 | |||
| 20a765be7f |
@@ -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
|
||||
|
||||
|
||||
@@ -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, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/\n$/, '<br/> ')
|
||||
.replace(/\n/g, '<br/>')
|
||||
.replace(/ {2,}/g, function(space) { return times(' ', 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;
|
||||
|
||||
}
|
||||
@@ -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($) {
|
||||
|
||||
@@ -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,2 +1,2 @@
|
||||
|
||||
$(function() { AjaxIM.init(); });
|
||||
})(jQuery || $);
|
||||
@@ -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
|
||||
})();
|
||||
})();
|
||||
@@ -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>•</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>•</span> Available</a>' +
|
||||
'<a href="#" id="imjs-button-away" class="imjs-button"><span>•</span> Away</a>' +
|
||||
'<a href="#" id="imjs-button-offline" class="imjs-button"><span>•</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">↻</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>•</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 •</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 •</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 •</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 •</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>•</strong><span>{username}</span><span class="imjs-friend-status">{status}</span></li>',
|
||||
tooltip:
|
||||
'<span class="imjs-tooltip"><p>{tip}</p></span>'
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
Antes Largura: | Altura: | Tamanho: 124 B |
|
Antes Largura: | Altura: | Tamanho: 176 B |
|
Antes Largura: | Altura: | Tamanho: 552 B |
|
Antes Largura: | Altura: | Tamanho: 847 B |
|
Antes Largura: | Altura: | Tamanho: 212 B |
|
Antes Largura: | Altura: | Tamanho: 706 B |
|
Antes Largura: | Altura: | Tamanho: 142 B |
|
Antes Largura: | Altura: | Tamanho: 141 B |
|
Antes Largura: | Altura: | Tamanho: 234 B |
|
Antes Largura: | Altura: | Tamanho: 109 B |
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>•</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>•</span> Available</a>
|
||||
<a href="#" id="imjs-button-away" class="imjs-button"><span>•</span> Away</a>
|
||||
<a href="#" id="imjs-button-offline" class="imjs-button"><span>•</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>•</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">↻</span></li>
|
||||
<li id="imjs-scroll-left" class="imjs-scroll">{count}</li>
|
||||
<li class="imjs-tab imjs-default">
|
||||
<span class="imjs-tab-text"><strong>•</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 •</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 •</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 •</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 •</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>
|
||||
@@ -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'));
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 +0,0 @@
|
||||
../../../client/js/
|
||||
@@ -1 +0,0 @@
|
||||
../../../client/themes/
|
||||
@@ -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 +0,0 @@
|
||||
!{body}
|
||||
@@ -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'
|
||||
]);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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: ''
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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'})));
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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')));
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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';
|
||||
*/
|
||||