為你的瀏覽器下水晶塔

以 NodeJS 實作出一機制,
讓不同的機器上點選超連結,
都能在相同的機器上開啟
http://is.gy/SyncURL/slide.php

草兒 Grassboy
@nodeJS Taiwan Party 24
2013-01-09

關於我

Before Start: 民意調查

//TODO: 今天你可能會學到的事

今日情境

有圖有真相

遊戲規則

Core Code: Server

var ws = require('websocket.io');
var server = ws.listen(5400);
var clientPool = {};
server.on('connection', function(client) {
	try{
		var protocol = client.ws.protocol;
		var channel = (client.ws.upgradeReq.url).substr(1);
	} catch( e ){
		client.close();
		return;
	}
	switch(protocol){
	case "gSyncURL":
		//SKIP: 防止過多連線機制 
		if(!clientPool[channel]) clientPool[channel] = {};
		var clientId = "c"+((new Date()).getTime() % 1000000)+(Math.random()*1000).toFixed(0);
		clientPool[channel][clientId] = client;
		//SKIP: 心跳偵測機制
		client.on('close', function() {
			delete clientPool[channel][clientId];
			if(Object.keys(clientPool[channel]).length == 0){
				delete clientPool[channel];
			}
		});
		break;
	case "gSyncURL-Push":
		var sep = "/---/";
		var redir = decodeURIComponent(channel.substr(channel.indexOf(sep)+sep.length));
		channel = channel.substr(0, channel.indexOf(sep));
		client.close();
		if(clientPool[channel]){ //這個 channel 下有 client 存在
			for(key in clientPool[channel]){
				var c = clientPool[channel][key];
				c.send("gsURL.page.open::"+redir);
			}
		}
		break;
	default:
		client.close(); //未知的 Protocol 直接不理他
		break;
	}
});
				

Core Code: Client

var WebSocketClient = require('websocket').client;
var client = new WebSocketClient();
var timer;
var channel = process.argv[2];
var url = process.argv[3];
//SKIP: 沒有指定 channel name 的處理方式
client.on('connect', function(connection) {
    timer = setTimeout(function(){
    	connection.close();
    }, 100);
});
client.on('close', function(){
	process.exit();
});
client.connect(['ws://grassboy.tw:5400/',channel,'/---/',encodeURIComponent(url)].join(''), 'gSyncURL-Push');
			

Firefox Addon

Core Code: WebSocket Page

var host = "ws://grassboy.tw:5400";
var gWebSocket = window.MozWebSocket || window.WebSocket;
var CreateWebSocket = function(host, key){
	var webSocket = new gWebSocket(host+"/"+key, "gSyncURL"); 
	webSocket.onopen = function(){
		self.postMessage("gsURL.page.connected", "*");
		setConnected(true);
	};
	webSocket.onerror = function(){
		self.postMessage("gsURL.page.connectfailed", "*");
		setConnected(false);
	};
	webSocket.onmessage = function(e){
		var msg = e.data;
		var open_prefix = "gsURL.page.open::";
		if(msg.indexOf(open_prefix) === 0){
			$("#syncForm").attr("action", msg.replace(open_prefix, ""));
			$("#syncForm input").click();
		} else {
			self.postMessage(msg, "*");
		}
	};
	webSocket.onclose = function(){
		self.postMessage("gsURL.page.closed", "*");
		setConnected(false);
	}
	return webSocket;
};
				

Core Code: node.js to .exe??

/*
 * SyncURL : 讓你遠端想開啟的 URL 直接開啟到本機 
 *           (PCMan面對超連結的執行程式)
 * 檔名:jscript.js
 * Compiler 方式: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\jsc.exe
 */
import System;
// 取得 input 參數 
var args:String[] = System.Environment.GetCommandLineArgs();
var channel:String = "syncurl-newbie"; //您所要用的 channel name
var file:String = "webSocketClient.node.js";
// new 一個 shell 來執行 cmd 指令
var WSH = new ActiveXObject("WScript.Shell");
WSH.run("node.exe "+file+" "+channel+" "+args[1], 0);
				

進階情境一

相關應用

Vim Script

function! ToBrowser(...)
	let path = expand("%:p")
	let url_from = ''
	let url_to = ''
	if(a:0 >= 1)
		let url_from = a:1
	endif
	if (a:0 >= 2)
		let url_to = a:2
	endif
	exec("silent !node E:\\Homepage\\SyncURL\\webSocketClient.node.js grassboy-own-channel ".substitute(substitute(path, url_from, url_to, ""),"\\","/","g"))
endfunction

if !&cp && !exists(":ToBrowser") && has("user_commands")
  command! -range=% -nargs=* -complete=file  ToBrowser :call ToBrowser('E:\\Homepage\\', 'http://grassboy.tw/', 'SyncURL');
endif
				

機器多到受不了時的使用情境

Core Code: 7Headline Hack

javascript:(function(){
var openURL = function(url) {
	var host = "ws://grassboy.tw:5400";
	var channel = "grassboy-own-channel";
	var gWebSocket = window.MozWebSocket || window.WebSocket;
	var CreateWebSocket = function(host, channel) {
		var webSocket = new gWebSocket(['ws://grassboy.tw:5400/', channel, '/---/', encodeURIComponent(url)].join(''), 'gSyncURL-Push');
		return webSocket;
	};
	CreateWebSocket(host, channel);
};
//openURL("http://tw.yahoo.com");
jQuery("#content").on("click", ".article-link", function(e) {
	e.preventDefault();
	$.ajax({
		url: "http://query.yahooapis.com/v1/public/yql?q=select%20%20value%20from%20html%20where%20url%3D%22" + encodeURIComponent(this.href) + "%22%20and%20xpath%3D'%2F%2F*%2Fdiv%5B%40class%3D%22address-bar%22%5D%2Finput'&format=json",
		dataType: "jsonp",
		type: "get",
	}).success(function(result, state) {
		var real_url;
		if (state == "success") {
			if (result.query && result.query.results && result.query.results.input && (real_url = result.query.results.input.value)) {
				openURL(real_url+"?syncURL");
			}
		}
	});
});
})()

				

結語

參考資料