Whenever I'm away from home, my usual stream of random ideas tends to become more focused on projects involving sharing. Usually something about creating a connection between people where the interaction point is the Internet. This is what first inspired the now defunct MonkeyTV project back in 2007 or noodler.net in 2008 – both created while I was living in Tokyo as ways to connect to people back in Edinburgh.

Until the beginning of August, I'm in Berlin finding somewhere to live while Jenni and Oskar are in Edinburgh packing up our flat (although, I'm not entirely sure Oskar is doing much more than drooling over packing boxes). The result of this is that I started to wonder about how to best show Jenni some of the flats I'm looking at remotely. What I figured I wanted was a way for us both to be looking at the same web page and for each of us to be able to point out things to the other. I tidied up my idea and posted it to the Made By Ideas site hoping that someone else would run off and make it so I could focus on apartment hunting.

The inevitable happened, I couldn't let it lie:

Multi-user page (installable bookmarklet).

If you install that bookmarklet by draggin’ it to your bookmarks bar then launch it anywhere, your cursor position and how far you've scrolled the page will be sent to anyone else viewing the same page who has also launched it. If you launch it on this page just by clicking it just now, you'll see cursors from other people reading this post who've also clicked it.

Technical

This is built in node.js with socket.io.

It heavily reuses Jeff Kreeftmeijer's multiple user cursor experiment but updated to use socket.io v0.7. I also used the socket.io 'rooms' feature to contain clients to a given window.location.href so that it could be launched on any page and interactions would only appear to users on that same page. I also removed the 'speak' feature to simplify things. I'm planning on talking via Skype when I'm using it. In theory, mouse events other than cursor coordinates and scroll could be shared – keyboard input, clicks, selects.

The day after I built this, Christian Heilmann pointed out on twitter a different solution to the same problem. Browser Mirror uses the same technology (node + websockets) but instead of passing cursor positions, it passes the entire DOM of the page from the instigator's computer to their node relay and then out to any invited viewers. This approach gets round a lot of the problems and is probably a more robust solution, all in all. They also have an integrated chat feature.

Warning

The server side is running on a borrowed VPS. It's not even been daemonised using Forever so it might fall over and not come back up. Don't rely on this for anything, just think of it as a point of interest.

The Code

I'm not really going to do any further development with it but for interest, here's the node.js server, almost entirely Jeff Kreeftmeijer's work but updated for the latest socket.io

var sys = require('sys'),
    http = require('http'),
    io = require('socket.io').listen(8000),
    log = sys.puts;

io.sockets.on('connection', function (socket) {
	socket.on('set location', function (name) {
	    socket.set('location', name, function () { socket.emit('ready'); });
		socket.join(name);
	});
	socket.on('message', function (request) {
		socket.get('location', function (err, name) {
			if(request.action != 'close' && request.action != 'move' && request.action != 'scroll') {
				log('Invalid request:' + "\n" + request);
				return false;
			}
			request.id = socket.id;
			socket.broadcast.to(name).json.send(request);
		});
	});
	socket.on('disconnect', function () {
		socket.broadcast.json.send({'id': socket.id, 'action': 'close'});
	});
});

And here's a bit of the client-side JS that I modified to connect via socket.io v0.7 (again, modified from Jeff Kreeftmeijer's):


var socket = io.connect('http://yourserver.com', {port: 8000}),
socket.on('connect', function() {
	socket.emit('set location', window.location.href);
	socket.on('message', function(data){
    if(data['action'] == 'close'){
      $('#mouse_'+data['id']).remove();
    } else if(data['action'] == 'move'){
      move(data);
    } else if(data['action'] == 'scroll'){
			clearTimeout(noscrolltimeout);
			disabled = true;
      scroll(data);
			noscrolltimeout = setTimeout('disabled = false;',2000);
    };
  });
});

If you'd like to snoop around the code more, it's all available on the lab: Multi-user pages