diff --git a/examples/nodeclient_example/chat.js b/examples/nodeclient_example/chat.js
new file mode 100644
index 0000000..4f8125d
--- /dev/null
+++ b/examples/nodeclient_example/chat.js
@@ -0,0 +1,16 @@
+var nowjs = require('../../lib/nodeclient/now.js');
+var now = nowjs.nowInitialize('http://localhost:8080');
+var readline = require('readline');
+var rl = readline.createInterface(process.stdin, process.stdout);
+rl.on('line', function(line){
+ now.distributeMessage(line);
+});
+now.ready(function(){
+ console.log("Chat server running!");
+ rl.question("What's your name? ",function(answer){
+ now.name = answer;
+ });
+});
+now.receiveMessage = function(message,name){
+ console.log("----"+name+": "+message);
+}
diff --git a/examples/nodeclient_example/index.html b/examples/nodeclient_example/index.html
new file mode 100644
index 0000000..48b94d1
--- /dev/null
+++ b/examples/nodeclient_example/index.html
@@ -0,0 +1,32 @@
+
+
+
+nowjs test
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/nodeclient_example/server.js b/examples/nodeclient_example/server.js
new file mode 100644
index 0000000..ce94b0c
--- /dev/null
+++ b/examples/nodeclient_example/server.js
@@ -0,0 +1,25 @@
+var http = require('http');
+var sys = require('util');
+var nowjs = require('now');
+var fs = require('fs');
+var server = http.createServer(function (req,res){
+ fs.readFile('./index.html', function(error, content) {
+ if (error) {
+ res.writeHead(500);
+ res.end();
+ }
+ else {
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(content, 'utf-8');
+ }
+ });
+});
+everyone = nowjs.initialize(server,{socketio: {"log level": 3}});
+everyone.now.log = function(str){
+ console.log(str);
+}
+everyone.now.distributeMessage = function(str){
+ everyone.now.receiveMessage(str,this.now.name);
+}
+server.listen(8080);
+console.log("Listening on 8080");
diff --git a/lib/nodeclient/now.js b/lib/nodeclient/now.js
new file mode 100644
index 0000000..e659ea7
--- /dev/null
+++ b/lib/nodeclient/now.js
@@ -0,0 +1,564 @@
+ var nowObjects = {};
+ var io = require('socket.io-client');
+ var noConflict = function (uri, options) {
+ uri = uri || '';
+ if (nowObjects[uri]) {
+ return nowObjects[uri];
+ }
+ options = options || {};
+
+ var socket;
+ var closures = {};
+ var nowReady = false;
+ var readied = 0;
+ var lastTimeout;
+
+ var util, lib;
+ //uses the defineProperty function to test if browser isIE (this doesn't matter now)
+ var isIE = false;
+
+ var fqnMap = {
+ data: {},
+ arrays: {},
+ get: function (fqn) {
+ return fqnMap.data[fqn];
+ },
+ //processes the input fqn for . notation, thereby signifying JS objects; if so, uses addParent and mutual recursion to set the children
+ set: function (fqn, val) {
+ if (fqnMap.data[fqn] !== undefined) {
+ fqnMap.deleteChildren(fqn, val);
+ } else {
+ var lastIndex = fqn.lastIndexOf('.');
+ var parent = fqn.substring(0, lastIndex);
+ fqnMap.addParent(parent, fqn.substring(lastIndex + 1));
+ }
+ return (fqnMap.data[fqn] = val);
+ },
+ //checks if parent is not ''; if not, if an array has been created for parent, then it will add the key to that array
+ addParent: function (parent, key) {
+ if (parent) {
+ if (!util.isArray(fqnMap.data[parent])) {
+ fqnMap.set(parent, []); // Handle changing a non-object to an object.
+ }
+ fqnMap.data[parent].push(key);
+ }
+ },
+ deleteChildren: function (fqn) {
+ var keys = this.data[fqn];
+ var children = [];
+ if (util.isArray(this.data[fqn])) {
+ // Deleting a child will remove it via splice.
+ for (var i = 0; keys.length;) {
+ // Recursive delete all children.
+ var arr = this.deleteVar(fqn + '.' + keys[i]);
+ for (var j = 0; j < arr.length; j++) {
+ children.push(arr[j]);
+ }
+ }
+ }
+ return children;
+ },
+ deleteVar: function (fqn) {
+ var lastIndex = fqn.lastIndexOf('.');
+ var parent = fqn.substring(0, lastIndex);
+ if (util.hasProperty(this.data, parent)) {
+ var index = util.indexOf(this.data[parent], fqn.substring(lastIndex + 1));
+ if (index > -1) {
+ this.data[parent].splice(index, 1);
+ }
+ }
+ var children = this.deleteChildren(fqn);
+ children.push(fqn);
+ delete this.data[fqn];
+ this.unflagAsArray(fqn);
+ return children;
+ },
+ flagAsArray: function (val) {
+ return (this.arrays[val] = true);
+ },
+ unflagAsArray: function (val) {
+ delete this.arrays[val];
+ }
+ };
+ util = {
+ _events: {},
+ // Event code from socket.io
+ on: function (name, fn) {
+ if (!(util.hasProperty(util._events, name))) {
+ util._events[name] = [];
+ }
+ util._events[name].push(fn);
+ return util;
+ },
+
+ indexOf: function (arr, val) {
+ for (var i = 0, ii = arr.length; i < ii; i++) {
+ if ("" + arr[i] === val) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ emit: function (name, args) {
+ if (util.hasProperty(util._events, name)) {
+ var events = util._events[name].slice(0);
+ for (var i = 0, ii = events.length; i < ii; i++) {
+ events[i].apply(util, args === undefined ? [] : args);
+ }
+ }
+ return util;
+ },
+ removeEvent: function (name, fn) {
+ if (util.hasProperty(util._events, name)) {
+ for (var a = 0, l = util._events[name].length; a < l; a++) {
+ if (util._events[name][a] === fn) {
+ util._events[name].splice(a, 1);
+ }
+ }
+ }
+ return util;
+ },
+
+ hasProperty: function (obj, prop) {
+ return Object.prototype.hasOwnProperty.call(Object(obj), prop);
+ },
+ isArray: Array.isArray || function (obj) {
+ return Object.prototype.toString.call(obj) === '[object Array]';
+ },
+
+ createVarAtFqn: function (scope, fqn, value) {
+ var path = fqn.split('.');
+ var currVar = util.forceGetParentVarAtFqn(scope, fqn);
+ var key = path.pop();
+ fqnMap.set(fqn, (value && typeof value === 'object') ? [] : value);
+ if (util.isArray(value)) {
+ fqnMap.flagAsArray(fqn);
+ }
+ currVar[key] = value;
+ if (!(isIE || util.isArray(currVar))) {
+ util.watch(currVar, key, fqn);
+ }
+ },
+
+ forceGetParentVarAtFqn: function (scope, fqn) {
+ var path = fqn.split('.');
+ path.shift();
+ var currVar = scope;
+ while (path.length > 1) {
+ var prop = path.shift();
+ if (!util.hasProperty(currVar, prop)) {
+ if (!isNaN(path[0])) {
+ currVar[prop] = [];
+ } else {
+ currVar[prop] = {};
+ }
+ }
+ if(!(currVar[prop] && typeof currVar[prop] === "object")) {
+ currVar[prop] = {};
+ }
+ currVar = currVar[prop];
+ }
+ return currVar;
+ },
+
+ getVarFromFqn: function (scope, fqn) {
+ var path = fqn.split('.');
+ path.shift();
+ var currVar = scope;
+ while (path.length > 0) {
+ var prop = path.shift();
+ if (util.hasProperty(currVar, prop)) {
+ currVar = currVar[prop];
+ } else {
+ return false;
+ }
+ }
+ return currVar;
+ },
+
+ generateRandomString: function () {
+ return Math.random().toString().substr(2);
+ },
+
+ getValOrFqn: function (val, fqn) {
+ if (typeof val === 'function') {
+ if (val.remote) {
+ return undefined;
+ }
+ return {fqn: fqn};
+ } else {
+ return val;
+ }
+ },
+
+ watch: function (obj, label, fqn) {
+ var val = obj[label];
+
+ function getter() {
+ return val;
+ }
+ function setter(newVal) {
+ if (val !== newVal && newVal !== fqnMap.get(fqn)) {
+ // trigger some sort of change.
+ if (val && typeof val === 'object') {
+ fqnMap.deleteVar(fqn);
+ socket.emit('del', [fqn]);
+ val = newVal;
+ lib.processScope(obj, fqn.substring(0, fqn.lastIndexOf('.')));
+ return newVal;
+ }
+ if (newVal && typeof newVal === 'object') {
+ fqnMap.deleteVar(fqn);
+ socket.emit('del', [fqn]);
+ val = newVal;
+ lib.processScope(obj, fqn.substring(0, fqn.lastIndexOf('.')));
+ return newVal;
+ }
+ fqnMap.set(fqn, newVal);
+ val = newVal;
+ if (typeof newVal === 'function') {
+ newVal = {fqn: fqn};
+ }
+ var toReplace = {};
+ toReplace[fqn] = newVal;
+ socket.emit('rv', toReplace);
+ }
+ return newVal;
+ }
+
+ if (Object.defineProperty) {
+ Object.defineProperty(obj, label, {get: getter, set: setter});
+ } else {
+ if (obj.__defineSetter__) {
+ obj.__defineSetter__(label, setter);
+ }
+ if (obj.__defineGetter__) {
+ obj.__defineGetter__(label, getter);
+ }
+ }
+ },
+
+ unwatch: function (obj, label) {
+ if (Object.defineProperty) {
+ Object.defineProperty(obj, label, {get: undefined, set: undefined});
+ } else {
+ if (obj.__defineSetter__) {
+ obj.__defineSetter__(label, undefined);
+ }
+ if (obj.__defineGetter__) {
+ obj.__defineGetter__(label, undefined);
+ }
+ }
+ }
+ };
+
+ var now = {
+ ready: function (func) {
+ if (arguments.length === 0) {
+ util.emit('ready');
+ } else {
+ if (nowReady) {
+ func();
+ }
+ util.on('ready', func);
+ }
+ },
+ core: {
+ on: util.on,
+ options: options,
+ removeEvent: util.removeEvent,
+ clientId: undefined,
+ noConflict: noConflict
+ }
+ };
+
+ lib = {
+ deleteVar: function (fqn) {
+ var path, currVar, parent, key;
+ path = fqn.split('.');
+ currVar = now;
+ for (var i = 1; i < path.length; i++) {
+ key = path[i];
+ if (currVar === undefined) {
+ // delete from fqnMap, just to be safe.
+ fqnMap.deleteVar(fqn);
+ return;
+ }
+ if (i === path.length - 1) {
+ delete currVar[path.pop()];
+ fqnMap.deleteVar(fqn);
+ return;
+ }
+ currVar = currVar[key];
+ }
+ },
+
+ replaceVar: function (data) {
+ for (var fqn in data) {
+ if (util.hasProperty(data[fqn], 'fqn')) {
+ data[fqn] = lib.constructRemoteFunction(fqn);
+ }
+ util.createVarAtFqn(now, fqn, data[fqn]);
+ }
+ },
+
+ remoteCall: function (data) {
+ var func;
+ // Retrieve the function, either from closures hash or from the now scope
+ if (data.fqn.split('_')[0] === 'closure') {
+ func = closures[data.fqn];
+ } else {
+ func = util.getVarFromFqn(now, data.fqn);
+ }
+ var i, ii, args = data.args;
+
+ if (typeof args === 'object' && !util.isArray(args)) {
+ var newargs = [];
+ // Enumeration order is not defined so this might be useless,
+ // but there will be cases when it works
+ for (i in args) {
+ newargs.push(args[i]);
+ }
+ args = newargs;
+ }
+
+ // Search (only at top level) of args for functions parameters,
+ // and replace with wrapper remote call function
+ for (i = 0, ii = args.length; i < ii; i++) {
+ if (util.hasProperty(args[i], 'fqn')) {
+ args[i] = lib.constructRemoteFunction(args[i].fqn);
+ }
+ }
+ func.apply({now: now}, args);
+ },
+
+ // Handle the ready message from the server
+ serverReady: function () {
+ nowReady = true;
+ lib.processNowScope();
+ util.emit('ready');
+ },
+
+ constructRemoteFunction: function (fqn) {
+ var remoteFn = function () {
+
+ lib.processNowScope();
+
+ var args = [];
+ for (var i = 0, ii = arguments.length; i < ii; i++) {
+ args[i] = arguments[i];
+ }
+ for (i = 0, ii = args.length; i < ii; i++) {
+ if (typeof args[i] === 'function') {
+ var closureId = 'closure_' + args[i].name + '_' + util.generateRandomString();
+ closures[closureId] = args[i];
+ args[i] = {fqn: closureId};
+ }
+ }
+ socket.emit('rfc', {fqn: fqn, args: args});
+ };
+ remoteFn.remote = true;
+ return remoteFn;
+ },
+ handleNewConnection: function (socket) {
+ if (socket.handled) {
+ return;
+ }
+ socket.handled = true;
+
+ socket.on('rfc', function (data) {
+ lib.remoteCall(data);
+ util.emit('rfc', data);
+ });
+ socket.on('rv', function (data) {
+ lib.replaceVar(data);
+ util.emit('rv', data);
+ });
+ socket.on('del', function (data) {
+ lib.deleteVar(data);
+ util.emit('del', data);
+ });
+
+ // Handle the ready message from the server
+ socket.on('rd', function (data) {
+ if (++readied === 2) {
+ lib.serverReady();
+ }
+ });
+
+ socket.on('disconnect', function () {
+ readied = 0;
+ util.emit('disconnect');
+ });
+ // Forward planning for socket io 0.7
+ socket.on('error', function () {
+ util.emit('error');
+ });
+ socket.on('retry', function () {
+ util.emit('retry');
+ });
+ socket.on('reconnect', function () {
+ util.emit('reconnect');
+ });
+ socket.on('reconnect_failed', function () {
+ util.emit('reconnect_failed');
+ });
+ socket.on('connect_failed', function () {
+ util.emit('connect_failed');
+ });
+ },
+ processNowScope: function () {
+ lib.processScope(now, 'now');
+ clearTimeout(lastTimeout);
+ if (socket.socket.connected) {
+ lastTimeout = setTimeout(lib.processNowScope, 1000);
+ }
+ },
+ processScope: function (obj, path) {
+ var data = {};
+ lib.traverseScope(obj, path, data);
+ // Send only for non-empty object
+ for (var i in data) {
+ if (util.hasProperty(data, i) && data[i] !== undefined) {
+ socket.emit('rv', data);
+ break;
+ }
+ }
+ },
+ traverseScope: function (obj, path, data) {
+
+ if (obj && typeof obj === 'object') {
+ var objIsArray = util.isArray(obj);
+ var keys = fqnMap.get(path);
+ for (var key in obj) {
+ var fqn = path + '.' + key;
+
+ if (fqn === 'now.core' || fqn === 'now.ready') {
+ continue;
+ }
+
+ if (util.hasProperty(obj, key)) {
+
+ var val = obj[key];
+ var mapVal = fqnMap.get(fqn);
+ var wasArray = fqnMap.arrays[fqn];
+ var valIsArray = util.isArray(val);
+ var valIsObj = val && typeof val === 'object';
+ var wasObject = util.isArray(mapVal) && !wasArray;
+
+ if (objIsArray || isIE) {
+ if (valIsObj) {
+ if (valIsArray) {
+ // Value is an array
+ if (!wasArray) {
+ fqnMap.set(fqn, []);
+ fqnMap.flagAsArray(fqn);
+ data[fqn] = [];
+ }
+ } else {
+ // Value is object
+ if (!wasObject) {
+ fqnMap.set(fqn, []);
+ fqnMap.unflagAsArray(fqn);
+ data[fqn] = {};
+ }
+ }
+ } else {
+ // Value is primitive / func
+ if (val !== mapVal) {
+ fqnMap.set(fqn, val);
+ fqnMap.unflagAsArray(fqn);
+ data[fqn] = util.getValOrFqn(val, fqn);
+ }
+ }
+ } else if (mapVal === undefined) {
+ util.watch(obj, key, fqn);
+
+ if (valIsObj) {
+ if (valIsArray) {
+ // Value is array
+ fqnMap.set(fqn, []);
+ fqnMap.flagAsArray(fqn);
+ data[fqn] = [];
+ } else {
+ // Value is object
+ fqnMap.set(fqn, []);
+ data[fqn] = {};
+ }
+ } else {
+ // Value is primitive / func
+ fqnMap.set(fqn, val);
+ data[fqn] = util.getValOrFqn(val, fqn);
+ }
+ }
+ if (valIsObj) {
+ lib.traverseScope(val, fqn, data);
+ }
+ }
+ }
+
+ if (keys && typeof keys === 'object') {
+ var toDelete = [];
+ // Scan for deleted keys.
+ for (var i = 0; i < keys.length; i++) {
+ if (keys[i] !== undefined && obj[keys[i]] === undefined) {
+ toDelete.push(path + '.' + keys[i]);
+ fqnMap.deleteVar(path + '.' + keys[i]);
+ --i;
+ }
+ }
+ // Send message to server to delete from its database.
+ if (toDelete.length > 0) {
+ socket.emit('del', toDelete);
+ }
+ }
+ }
+
+ },
+
+ };
+
+ var scriptLoaded = function () {
+ socket = io.connect(uri + '/', now.core.options.socketio || {});
+ now.core.socketio = socket;
+ socket.on('connect', function () {
+ now.core.clientId = socket.socket.sessionid;
+ lib.handleNewConnection(socket);
+ // Begin intermittent scope traversal
+
+ setTimeout(function () {
+ lib.processNowScope();
+ socket.emit('rd');
+ if (++readied === 2) {
+ nowReady = true;
+ util.emit('ready');
+ }
+ }, 100);
+
+ util.emit('connect');
+ });
+ socket.on('disconnect', function () {
+ // y-combinator trick
+ (function (y) {
+ y(y, now);
+ }(function (fn, obj) {
+ for (var i in obj) {
+ if (obj[i] && typeof obj[i] === 'object' &&
+ obj[i] !== document && obj[i] !== now.core) {
+ fn(fn, obj[i]);
+ }
+ else if (typeof obj[i] === 'function' && obj[i].remote) {
+ delete obj[i];
+ }
+ }
+ }));
+ // Clear all sorts of stuff in preparation for reconnecting.
+ fqnMap.data = {};
+ });
+ };
+ scriptLoaded();
+ return (nowObjects[uri] = now);
+ };
+
+ exports.nowInitialize = noConflict;