Browse files

readying for initial release

  • Loading branch information...
0 parents commit fcce72f38ed3e1a39113bdecf95fae649e789f52 Douglas Hunter committed Mar 9, 2012
60 README.md
@@ -0,0 +1,60 @@
+# Li'l Brother
+
+Li'l Brother tracks clicks on web pages, without blocking any interaction.
+
+## Client
+
+ <script type="text/javascript" src="http://server/lilbro.js"></script>
+ <script type="text/javascript">
+ // listen for click and change events on the wrapper element
+ var lilBro = new LilBro({
+ element: document.getElementById('lil_bro'),
+ server: 'server:8080',
+ ssl_server: 'server:8443',
+ track_focus: true
+ });
+
+ // register a click handler that snakes some data from the DOM,
+ // and specifies the the event type.
+ // This wont bubble to the wrapper element being watched.
+ lilBro.watch({
+ element: document.getElementById('search_button'),
+ callback: function(e) {
+ e.set(
+ 'element_value',
+ document.getElementById('search_term').value
+ );
+ e.set('event_type', 'search');
+ }
+ });
+
+ // fire an event right now.
+ lilBro.write({
+ 'event_type': 'page_load'
+ });
+ </script>
+
+## Server
+
+cd lil_brother/server && ./bin/lilbro
+
+## Authors
+
+This library was developed by Douglas Hunter, Dave Kozma and Eric Smiling at [Shutterstock](http://www.shutterstock.com)
+
+## License
+
+Copyright (C) 2012 by Shutterstock Images, LLC
+
+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.
+
+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.
+
55 bin/lilbro
@@ -0,0 +1,55 @@
+#!/usr/bin/env node
+
+var lilbro = require('../lib/lilbro')
+ , nomnom = require('nomnom');
+
+var opts = nomnom.opts({
+ 'png-bug': {
+ string: '--png-bug',
+ default: './data/b.png'
+ },
+ 'https-port': {
+ string: '--https-port',
+ default: false
+ },
+ 'https-key': {
+ string: '--https-key',
+ default: './data/test-cert/privatekey.pem'
+ },
+ 'https-cert': {
+ string: '--https-cert',
+ default: './data/test-cert/certificate.pem'
+ },
+ 'http-port': {
+ string: '--http-port',
+ default: 8000
+ },
+ 'devent-host': {
+ string: '--devent-host',
+ default: 'prod-deventforwarder01.nyc01'
+ },
+ 'devent-port': {
+ string: '--devent-port',
+ default: 7664
+ },
+ 'devent-topic': {
+ string: '--devent-topic',
+ default: 'lilbro'
+ },
+ 'client-js-path': {
+ string: '--client-js-path',
+ default: './client/src/'
+ }
+}).parseArgs();
+
+lilbro.setOpts(opts);
+
+var http_port = process.env.HTTP_PORT || opts['http-port'];
+lilbro.server.listen(http_port);
+console.log("listening on http port %d", http_port);
+
+var https_port = process.env.HTTPS_PORT || opts['https-port'];
+if (https_port) {
+ lilbro.secure_server.listen(https_port);
+ console.log("listening on https port %d", https_port);
+}
110 client/sample.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Devent - 'Lil Bro'</title>
+ </head>
+ <body>
+ <div class="container" id="lil_bro">
+ <header>
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+ <script type="text/javascript">
+ // these could come from a server page load, ensuring everyone
+ // agrees on the same order of events
+ var keyMap = {
+ event_type: 0,
+ version: 1,
+ timestamp: 2,
+ visitor_id: 3,
+ visit_id: 4,
+ element_id: 5,
+ test_variants: 6,
+ member_since: 7
+ };
+
+ var typeMap = {
+ click: 1,
+ change: 2,
+ search: 3
+ };
+
+ // and we can set up a data structure that contains information
+ // that the server retrieved from long term storage to send
+ // along with our clicks
+ var eventBase = {
+ version: '0.1a',
+ test_variants: '1,2,3',
+ member_since: '2008-12-31'
+ };
+ </script>
+ <nav>
+ <ul><li>here</li><li>there</li></ul>
+ </nav>
+ </header>
+ <article id="one">
+ <h1 class="one two">Aricle One</h1>
+ </article>
+ <article>
+ <h1 id="two"><a href="sample.html">Aricle Two</a></h1>
+ </article>
+ <article id="three">
+ <h1>Aricle Three</h1>
+ <form>
+ 1:<input type="checkbox" name="check" value="one">
+ 2:<input type="checkbox" name="check" value="two">
+ <br>
+ 1:<input type="radio" name="radio" value="one">
+ 2:<input type="radio" name="radio" value="two">
+ <br>
+ <select>
+ <option name="select" value="one">one</option>
+ <option name="select" value="two">two</option>
+ <option name="select" value="three">three</option>
+ </select>
+ <br>
+ username: <input id="username" type="text" name="username"
+ password: <input type="password" name="password">
+ <input type="submit" name="submit">
+ <br>
+ search: <input id="search_term" type="text" name="search">
+ <span id="search_button" name="search">search</span>
+ <br>
+ </form>
+ </article>
+ <footer>
+ <script type="text/javascript" src="src/LilBro.js"></script>
+ <script type="text/javascript" src="src/LilBro.BrowserDetect.js"></script>
+ <script type="text/javascript" src="src/LilBro.Schema.js"></script>
+ <script type="text/javascript">
+
+ var lilBro = new LilBro({
+ element: document.getElementById('lil_bro'),
+ server: '127.0.0.1:8000',
+ server_ssl: '127.0.0.1:8443',
+ //server: '10.0.2.2:8000',
+ //server_ssl: '10.0.2.2:8443',
+ watch_focus: true
+
+ // use the default key and type maps from the library,
+ // without any event pre-filling (event_base)
+ // key_map: keyMap,
+ // type_map: typeMap,
+ // event_base: eventBase
+ });
+
+ lilBro.watch({
+ element: document.getElementById('search_button'),
+ callback: function(e) {
+ e.set('element_value', document.getElementById('search_term').value || '');
+ e.set('event_type', 'search');
+ }
+ });
+
+ lilBro.write({
+ 'event_type': 'page_load'
+ });
+ </script>
+ </footer>
+ </body>
+ </div>
+</html>
122 client/src/LilBro.BrowserDetect.js
@@ -0,0 +1,122 @@
+LilBro = window.LilBro || {};
+
+// browser detection lifted from quirksmode
+LilBro.BrowserDetect = {
+ init: function () {
+ this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
+ this.version = this.searchVersion(navigator.userAgent)
+ || this.searchVersion(navigator.appVersion)
+ || "an unknown version";
+ this.OS = this.searchString(this.dataOS) || "an unknown OS";
+ },
+ searchString: function (data) {
+ for (var i=0;i<data.length;i++){
+ var dataString = data[i].string;
+ var dataProp = data[i].prop;
+ this.versionSearchString = data[i].versionSearch || data[i].identity;
+ if (dataString) {
+ if (dataString.indexOf(data[i].subString) != -1)
+ return data[i].identity;
+ }
+ else if (dataProp)
+ return data[i].identity;
+ }
+ },
+ searchVersion: function (dataString) {
+ var index = dataString.indexOf(this.versionSearchString);
+ if (index == -1) return;
+ return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
+ },
+ dataBrowser: [
+ {
+ string: navigator.userAgent,
+ subString: "Chrome",
+ identity: "Chrome"
+ },
+ { string: navigator.userAgent,
+ subString: "OmniWeb",
+ versionSearch: "OmniWeb/",
+ identity: "OmniWeb"
+ },
+ {
+ string: navigator.vendor,
+ subString: "Apple",
+ identity: "Safari",
+ versionSearch: "Version"
+ },
+ {
+ prop: window.opera,
+ identity: "Opera",
+ versionSearch: "Version"
+ },
+ {
+ string: navigator.vendor,
+ subString: "iCab",
+ identity: "iCab"
+ },
+ {
+ string: navigator.vendor,
+ subString: "KDE",
+ identity: "Konqueror"
+ },
+ {
+ string: navigator.userAgent,
+ subString: "Firefox",
+ identity: "Firefox"
+ },
+ {
+ string: navigator.vendor,
+ subString: "Camino",
+ identity: "Camino"
+ },
+ {// for newer Netscapes (6+)
+ string: navigator.userAgent,
+ subString: "Netscape",
+ identity: "Netscape"
+ },
+ {
+ string: navigator.userAgent,
+ subString: "MSIE",
+ identity: "Explorer",
+ versionSearch: "MSIE"
+ },
+ {
+ string: navigator.userAgent,
+ subString: "Gecko",
+ identity: "Mozilla",
+ versionSearch: "rv"
+ },
+ { // for older Netscapes (4-)
+ string: navigator.userAgent,
+ subString: "Mozilla",
+ identity: "Netscape",
+ versionSearch: "Mozilla"
+ }
+ ],
+ dataOS : [
+ {
+ string: navigator.platform,
+ subString: "Win",
+ identity: "Windows"
+ },
+ {
+ string: navigator.platform,
+ subString: "Mac",
+ identity: "Mac"
+ },
+ {
+ string: navigator.userAgent,
+ subString: "iPhone",
+ identity: "iPhone/iPod"
+ },
+ {
+ string: navigator.platform,
+ subString: "Linux",
+ identity: "Linux"
+ }
+ ]
+
+};
+
+try { LilBro.BrowserDetect.init() } catch (e) {}
+
48 client/src/LilBro.Schema.js
@@ -0,0 +1,48 @@
+(function(){
+
+ var root = this;
+
+ root.LilBro = root.LilBro || {};
+ root.LilBro.Schema = {};
+
+ root.LilBro.Schema.version = "default";
+
+ root.LilBro.Schema.key_map = {
+ // leave slot 0 for the server timestamp
+ version: 1,
+ timestamp: 2,
+ event_type: 3,
+ visitor_id: 4,
+ visit_id: 5,
+ mouse_x: 6,
+ mouse_y: 7,
+ viewport_width: 8,
+ viewport_height: 9,
+ scroll_x: 10,
+ scroll_y: 11,
+ element_id: 12,
+ element_id_from: 13,
+ element_class: 14,
+ element_class_from: 15,
+ element_name: 16,
+ element_tag: 17,
+ element_type: 18,
+ element_checked: 19,
+ element_value: 20,
+ element_x: 21,
+ element_y: 22,
+ browser: 23,
+ browser_version: 24,
+ operating_system: 25,
+ request_path: 26
+ };
+
+ root.LilBro.Schema.type_map = {
+ click: 1,
+ page_load: 2,
+ focusin: 3,
+ focusout: 4
+ };
+
+}).call(this)
+
364 client/src/LilBro.js
@@ -0,0 +1,364 @@
+LilBro = window.LilBro || {};
+
+LilBro = function(args) {
+
+ var self = this;
+
+ this.initialize = function (args) {
+ if (args) {
+ if (!args.server) { return }
+ if (!args.element) { return }
+
+ this.watch_container(args.element, args.watch_focus);
+
+ this.freshEvent = function () {
+ return new LilBro.Event({
+ base: args.event_base || {},
+ key_map: args.key_map || LilBro.Schema.key_map,
+ type_map: args.type_map || LilBro.Schema.type_map,
+ server: args.server,
+ ssl_server: args.ssl_server,
+ visit_id_cookie: args.visit_id_cookie || 'visit_id',
+ visitor_id_cookie: args.visitor_id_cookie || 'visitor_id'
+ });
+ }
+ } else {
+ return;
+ }
+
+ if (sessionStorage && sessionStorage.getItem('lilbrobug' + window.location.protocol)) {
+ var src = decodeURIComponent(sessionStorage.getItem('lilbrobug' + window.location.protocol));
+ var bug = new Image();
+ bug.onload = function () {
+ sessionStorage.removeItem('lilbrobug' + window.location.protocol);
+ };
+ bug.src = src;
+ }
+
+ this.event = this.freshEvent();
+ }
+
+ this.watch_container = function (el, focus) {
+ if (!el) { return }
+ if (el.addEventListener) {
+ el.addEventListener('click', _doer_maker('click'), false);
+ if (focus) {
+ el.addEventListener('focusin', _doer_maker('focusin'), false);
+ el.addEventListener('focusout', _doer_maker('focusout'), false);
+ }
+ } else {
+ el.attachEvent('onclick', _doer_maker('click'), false);
+ if (focus) {
+ el.attachEvent('onfocusin', _doer_maker('focusin'), false);
+ el.attachEvent('onfocusout', _doer_maker('focusout'), false);
+ }
+ }
+ }
+
+ this.watch = function (args) {
+ if (!args) { return }
+ if (!args.element) { return }
+
+ if (args.element.addEventListener) {
+ args.element.addEventListener(
+ 'click',
+ _doer_maker('click', args.callback, args.bubble),
+ false
+ );
+ } else {
+ args.element.attachEvent(
+ 'onclick',
+ _doer_maker('click', args.callback, args.bubble),
+ false
+ );
+ }
+ }
+
+ function _doer_maker (type, callback, bubble) {
+ return function(ev) {
+ if (!ev) { ev = window.event }
+ var targ = self._findTarget(ev);
+ self.event.fill({
+ type: type,
+ event: ev,
+ target: targ
+ });
+ if (callback) {
+ try { callback(self.event) } catch (e) {}
+ }
+ if (!bubble) {
+ ev.cancelBubble = true;
+ if (ev.stopPropagation) { ev.stopPropagation() }
+ }
+ self.event.write();
+ self.event = self.freshEvent();
+ };
+ }
+
+ this.write = function (obj) {
+ self.event.fill();
+ for (var key in obj) {
+ self.event.set(key, obj[key]);
+ }
+ self.event.write();
+ self.event = self.freshEvent();
+ }
+
+ // event target lifted from quirksmode
+ this._findTarget = function (ev) {
+ var targ;
+ if (ev.target) {
+ targ = ev.target;
+ } else if (ev.srcElement) {
+ targ = ev.srcElement;
+ }
+ // defeat Safari bug
+ if (targ.nodeType == 3) {
+ targ = targ.parentNode;
+ }
+ return targ;
+ }
+
+ this.initialize(args);
+}
+
+LilBro.Event = function (args) {
+
+ this.initialize = function(args) {
+ this._event = args.base;
+ this._key_map = args.key_map;
+ this._type_map = args.type_map;
+ this.server = args.server;
+ this.ssl_server = args.ssl_server;
+ this.visit_id_cookie = args.visit_id_cookie;
+ this.visitor_id_cookie = args.visitor_id_cookie;
+ }
+
+ this.set = function(prop, val) {
+ return this._event[prop] = val;
+ }
+
+ this.get = function(prop) {
+ return this._event[prop];
+ }
+
+ this.write = function() {
+ var event = [];
+ for (var key in this._key_map) {
+ if (key === "event_type") {
+ event[this._key_map[key]] = this._type_map[this.get(key)] || 0;
+ } else {
+ event[this._key_map[key]] = this.get(key) || "";
+ }
+ }
+ var protocol = window.location.protocol;
+ var src = protocol + '//'
+ + ((protocol === 'https:') ? this.ssl_server || this.server : this.server)
+ + '/'
+ + event.join('\x01')
+ + '.png?'
+ + this.randomHexBlocks(1);
+ if (sessionStorage) {
+ sessionStorage.setItem(
+ 'lilbrobug' + protocol,
+ encodeURIComponent(src)
+ );
+ }
+ var bug = new Image();
+ bug.onload = function () {
+ sessionStorage.removeItem('lilbrobug' + protocol);
+ };
+ bug.src = src;
+ }
+
+ this.fill = function (args) {
+
+ //version
+ this.set('version', LilBro.Schema.version);
+
+ if (args && args.type) {
+ // event type
+ this.set('event_type', args.type);
+ }
+
+ if (args && args.event) {
+ // mouse coordinates
+ var mouse_x = '';
+ var mouse_y = '';
+ if (args.event.pageX || args.event.pageY) {
+ mouse_x = args.event.pageX;
+ mouse_y = args.event.pageY;
+ }
+ else if (args.event.clientX || args.event.clientY) {
+ mouse_x = args.event.clientX + document.body.scrollLeft
+ + document.documentElement.scrollLeft;
+ mouse_y = args.event.clientY + document.body.scrollTop
+ + document.documentElement.scrollTop;
+ }
+ this.set('mouse_x', mouse_x);
+ this.set('mouse_y', mouse_y);
+ }
+
+ // viewport
+ this.set('viewport_width', document.documentElement.clientWidth);
+ this.set('viewport_height', document.documentElement.clientHeight);
+
+ // scroll, snaked from http://webcodingeasy.com/Javascript/Get-scroll-position-of-webpage--crossbrowser
+ var scroll_x = 0, scroll_y = 0;
+ if(typeof(window.pageYOffset) == 'number') {
+ scroll_x = window.pageXOffset;
+ scroll_y = window.pageYOffset;
+ } else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
+ scroll_x = document.body.scrollLeft;
+ scroll_y = document.body.scrollTop;
+ } else if (document.documentElement && (document.documentElement.scrollLeft
+ || document.documentElement.scrollTop)) {
+ scroll_x = document.documentElement.scrollLeft;
+ scroll_y = document.documentElement.scrollTop;
+ }
+ this.set('scroll_x', scroll_x || 0);
+ this.set('scroll_y', scroll_y || 0);
+
+ // element goodies
+ if (args && args.target) {
+ // element id and class, or their closest ancestors
+ var el_id = args.target.id;
+ var el_class = args.target.className;
+ var id_from_ancestor = !el_id;
+ var class_from_ancestor = !el_class;
+ var id_path, class_path;
+ if (!el_id || !el_class) {
+ var targ_orig = args.target;
+ id_path = args.target.tagName;
+ class_path = args.target.tagName;
+ do {
+ args.target = args.target.parentNode;
+ if (!el_id) {
+ id_path = args.target.tagName + '/' + id_path;
+ el_id = args.target.id;
+ }
+ if (!el_class) {
+ class_path = args.target.tagName + '/' + class_path;
+ el_class = args.target.className;
+ }
+ } while ((!el_id || !el_class) && args.target.parentNode);
+ args.target = targ_orig;
+ }
+ this.set('element_id', el_id);
+ this.set('element_class', el_class);
+ if (el_id && id_from_ancestor) {
+ this.set('element_id_from', id_path);
+ }
+ if (el_class && class_from_ancestor) {
+ this.set('element_class_from', class_path);
+ }
+
+ // element sundry
+ this.set('element_name', args.target.name || '');
+ this.set('element_tag', args.target.tagName || '');
+ this.set('element_type', args.target.type || '');
+ this.set('element_checked', args.target.checked ? 1 : '');
+ // by default, ignore typed input
+ if (args.target.type && args.target.type.toLowerCase() !== 'text'
+ && args.target.type.toLowerCase() !== 'password') {
+ this.set('element_value', args.target.value || '');
+ }
+
+ // including the position best effort (http://stackoverflow.com/a/442474)
+ var element_x = 0;
+ var element_y = 0;
+ var targ_orig = args.target;
+ while( args.target && !isNaN( args.target.offsetLeft ) && !isNaN( args.target.offsetTop ) ) {
+ element_x += args.target.offsetLeft - args.target.scrollLeft;
+ element_y += args.target.offsetTop - args.target.scrollTop;
+ args.target = args.target.offsetParent;
+ }
+ args.target = targ_orig;
+ this.set('element_x', element_x);
+ this.set('element_y', element_y);
+ }
+
+ // browser
+ if (LilBro.BrowserDetect) {
+ this.set('browser', LilBro.BrowserDetect.browser);
+ this.set('browser_version', LilBro.BrowserDetect.version);
+ this.set('operating_system', LilBro.BrowserDetect.OS);
+ }
+
+ // path part of url
+ this.set('request_path', window.location.pathname);
+
+ // other client bits
+ var d = new Date();
+ this.set('timestamp', d.getTime());
+ this.set('visitor_id', this.getVisitorId());
+ this.set('visit_id', this.getVisitId());
+ }
+
+ this.getVisitorId = function () {
+ var visitor_id = this.getCookie(this.visitor_id_cookie);
+ if (!visitor_id) {
+ visitor_id = this.randomHexBlocks(4);
+ }
+ this.setCookie(this.visitor_id_cookie, visitor_id, this.getVisitorExpiry());
+ return visitor_id;
+ }
+
+ this.getVisitId = function () {
+ var visit_id = this.getCookie(this.visit_id_cookie);
+ if (!visit_id) {
+ visit_id = this.randomHexBlocks(4);
+ }
+ this.setCookie(this.visit_id_cookie, visit_id, this.getVisitExpiry());
+ return visit_id;
+ }
+
+ this.getVisitorExpiry = function () {
+ var d = new Date();
+ // 90 days
+ d.setTime(d.getTime() + (90 * 24 * 60 * 60 * 1000));
+ return d.toGMTString();
+ }
+
+ this.getVisitExpiry = function () {
+ var d = new Date();
+ // 30 minutes
+ d.setTime(d.getTime() + (30 * 60 * 1000));
+ return d.toGMTString();
+
+ }
+
+ this.randomHexBlocks = function (blocks) {
+ if (!blocks) { blocks = 4 }
+ var hex = '';
+ for (var i = 0; i < blocks; i++) {
+ hex += parseInt(Math.random() * (Math.pow(2, 32))).toString(16);
+ }
+ return hex;
+ }
+
+ // cookies borrowed from quirksmode
+ this.setCookie = function (name,value,expiry) {
+ var expires;
+ if (expiry) {
+ expires = "; expires=" + expiry;
+ } else {
+ expires = "";
+ }
+ document.cookie = name+ "=" + value + expires + "; path=/";
+ }
+
+ this.getCookie = function (name) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for(var i=0;i < ca.length;i++) {
+ var c = ca[i];
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+ }
+ return null;
+ }
+
+ this.initialize(args);
+}
+
BIN data/Rockwell-somebodyswatchingme-1984.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN data/b.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 data/test-cert/certificate.pem
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB+zCCAWQCCQCKgk1jjfk1PzANBgkqhkiG9w0BAQUFADBCMQswCQYDVQQGEwJY
+WDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBh
+bnkgTHRkMB4XDTEyMDIxNjE2MjUyOFoXDTEyMDMxNzE2MjUyOFowQjELMAkGA1UE
+BhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBD
+b21wYW55IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1pvP6cw/UJ80
+KYcyvUugGAJl2AMgHyDMWgRma5xd34M3SWcFcJCehChFTIRMVglLrkFfulvLH45n
+65s0z841OBkUWufylSbSGZeQAHiUkSM/3BtaJ7tc5XKGc898S3qqndf7aey15y1I
+qqcA2eexfep8A64EiXGNff9wscis60MCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAH
+m5Ic+vFP7NxLt+zXObrC+Oens/JJeLn/XY8vnzc4Nj9h2TnLI87jgPqrvlDPAqyw
+7eykVDL166e88lcPOYIyDc4u2Mr7uOUrkWPi1KRvA9GClqv0dVr3BOWTm/Uxb2nA
+pg5fKbsGt6QPvmvDpuHRjvi5LpE2yLcZiYK/io1JAw==
+-----END CERTIFICATE-----
11 data/test-cert/certrequest.csr
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBgTCB6wIBADBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5
+MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDWm8/pzD9QnzQphzK9S6AYAmXYAyAfIMxaBGZrnF3fgzdJZwVw
+kJ6EKEVMhExWCUuuQV+6W8sfjmfrmzTPzjU4GRRa5/KVJtIZl5AAeJSRIz/cG1on
+u1zlcoZzz3xLeqqd1/tp7LXnLUiqpwDZ57F96nwDrgSJcY19/3CxyKzrQwIDAQAB
+oAAwDQYJKoZIhvcNAQEFBQADgYEAHKICv89cvHlpnER96Gsz+V8VvqyRx6ptq4Dj
+o0Rp97ShLFGzB5+dTltRiHhgTrxltK7vSH61NX8uq1rnrIPTLBKiEDwPHQHbKFvS
+cMlzM+EkILZky2Ha0qdY1IG2exDWlWSTt0lVKMA71mZOnA2pirWfaPDqwAWIxqJk
+ZyL9n4M=
+-----END CERTIFICATE REQUEST-----
15 data/test-cert/privatekey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXwIBAAKBgQDWm8/pzD9QnzQphzK9S6AYAmXYAyAfIMxaBGZrnF3fgzdJZwVw
+kJ6EKEVMhExWCUuuQV+6W8sfjmfrmzTPzjU4GRRa5/KVJtIZl5AAeJSRIz/cG1on
+u1zlcoZzz3xLeqqd1/tp7LXnLUiqpwDZ57F96nwDrgSJcY19/3CxyKzrQwIDAQAB
+AoGBAMssdfkVCfgs0Phj67B0kVtJoduQke+GYJD0S3ajcefyVPwkEW1RLRFXAsx/
+D7oAUOrnL1bCIpp8env+00/NHGmEvxJZwY17/WzCDdc+CwP1n/9kZIrvuML5mlBs
+ldkQ8KFb8ISM0iw5QXpVPXiRJ/1yoR+DIFMBaEL/MlmMJABBAkEA78wZgb4MfTc4
+rzs0oK6agubiJPvIrYb4kx/pa3B/kdy75/1iXti64nkfWw2etXitm/N9e+uLqoCE
+YEMwYNqQoQJBAOUcAvXrbHAs0Dt0v0BMYT+v4x+GsIiwjYn/pkRIYF6FdgwoOoWO
+JZb7lDKc6URZdNejoKeqZX6u2anqmUXM3WMCQQC2luR9JCcCWT/yPdSu3QSN1Bwy
+KP3/E5zG+i+UPY8Fk9wIo6l5qvFybOPxQwesCn23F8pex1YLt9wUKFT4CrTBAkEA
+vIhif0/t+hlGecbGovi3hhsNAaJqnvD6aDbx8XrFosKcs8Be+AxTLn10JHovEsmy
+dsj1qUN1JdxozYR6AJBnFQJBAJhmJCKogyYZe5dsAQq0eHlYkPU618kIym8P+hwe
+BtQSMObVsWbn0sWChBzmBfq6KN3nEdCbwx+L/Hb5AYzhwJA=
+-----END RSA PRIVATE KEY-----
188 lib/lilbro.js
@@ -0,0 +1,188 @@
+var fs = require("fs")
+ , http = require("http")
+ , https = require("https")
+ , path = require("path")
+ , url = require("url")
+ , util = require("util");
+
+var loaded_schemas = {};
+var bad_schemas = {};
+
+function load_schema(vstring) {
+ if (bad_schemas[vstring]) {
+ return;
+ }
+ if (loaded_schemas[vstring]) {
+ return loaded_schemas[vstring];
+ }
+
+ try {
+ var rev_key_map = {};
+ var vlib = vstring === "default" ? "LilBro.Schema" : "LilBro.Schema." + vstring;
+ var lbs = require(path.resolve(client_js_path, vlib));
+ for (var key in lbs.LilBro.Schema.key_map) {
+ rev_key_map[lbs.LilBro.Schema.key_map[key]] = key;
+ }
+ loaded_schemas[vstring] = rev_key_map;
+ return loaded_schemas[vstring];
+ } catch (e) {
+ bad_schemas[vstring] = true;
+ return;
+ }
+}
+
+var post_opts = https_opts = {};
+var bug,
+ client_js_path,
+ post_opts,
+ server,
+ secure_server;
+
+var js_bundles = {};
+
+module.exports.setOpts = function setOpts(o) {
+ bug = fs.readFileSync(o["png-bug"]);
+ client_js_path = o["client-js-path"];
+ https_opts.key = fs.readFileSync(o["https-key"]),
+ https_opts.cert = fs.readFileSync(o["https-cert"]);
+
+ js_bundles["shared"] = fs.readFileSync(path.join(client_js_path, "LilBro.js"));
+ js_bundles["shared"] += fs.readFileSync(path.join(client_js_path, "LilBro.BrowserDetect.js"));
+
+ js_bundles["default"] = fs.readFileSync(path.join(client_js_path, "LilBro.Schema.js"));
+
+ var version = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
+ var agent;
+ if (version[1] > 0 || version[2] > 4) {
+ agent = new http.Agent({host: o["devent-host"], port: o["devent-port"]});
+ } else {
+ agent = http.getAgent(o["devent-host"], o["devent-port"]);
+ }
+ agent.maxSockets = 512;
+
+ post_opts = {
+ host: o["devent-host"],
+ port: o["devent-port"],
+ path: "/" + o["devent-topic"],
+ method: "POST",
+ agent: agent,
+ headers: {
+ "content-type": "application/json"
+ }
+ };
+
+ module.exports.server = server = http.createServer(listener);
+ module.exports.secure_server = secure_server = https.createServer(https_opts, listener);
+
+ server.on("clientError", function (e) {
+ util.log("client error: " + e.toString());
+ });
+
+ secure_server.on("clientError", function (e) {
+ util.log("client error: " + e.toString());
+ });
+}
+
+var listener = function(req, res) {
+ if (req.method !== "GET") {
+ respond_with(res, 400);
+ return;
+ }
+ var parsed = url.parse(req.url, true);
+ if (parsed.pathname === "/favicon.ico") {
+ respond_with(res, 404);
+ return;
+ }
+ if (parsed.pathname === "/lilbro.js" || parsed.pathname === "/lilbro.min.js") {
+ serve_client_javascript(parsed, res);
+ return;
+ }
+ if (parsed.pathname === "/healthcheck.html") {
+ respond_with(res, 200, "YESOK");
+ }
+
+ res.writeHead(200, {
+ "Content-Length": bug.length,
+ "Content-Type": "image/png",
+ "Pragma": "no-cache",
+ "Cache-Control": "must-revalidate"
+ });
+ res.end(bug);
+
+ var event = parsed.pathname.substring(1, parsed.pathname.length - 4);
+ log_event(event);
+ return;
+}
+
+function serve_client_javascript(parsed_url, res) {
+ var version = parsed_url.query.v || "default";
+ if (!js_bundles[version]) {
+ fs.readFile(path.join(client_js_path, "LilBro.Schema." + version + ".js"), function(err, data) {
+ // TODO cache bad versions to avoid stat
+ if (err) {
+ res.writeHead(404);
+ res.end("404 Not Found");
+ return;
+ }
+ js_bundles[version] = data;
+ var expires = new Date;
+ expires.setTime(expires.getTime() + (90 * 24 * 60 * 60 * 1000));
+ res.writeHead(200, {
+ "Content-Type": "application/javascript",
+ "Expires": expires.toGMTString()
+ });
+ res.end(js_bundles["shared"] + js_bundles[version]);
+ return;
+ });
+ } else {
+ var expires = new Date;
+ expires.setTime(expires.getTime() + (90 * 24 * 60 * 60 * 1000));
+ res.writeHead(200, {
+ "Content-Type": "application/javascript",
+ "Expires": expires.toGMTString()
+ });
+ res.end(js_bundles["shared"] + js_bundles[version]);
+ }
+}
+
+function log_event(e) {
+ try {
+ var items = decodeURIComponent(e).split("\x01");
+ } catch (err) {
+ if (err) {
+ util.log("could not decode event string: " + err.toString());
+ return;
+ }
+ }
+ var schema = load_schema(items[1]);
+ if (!schema) {
+ return;
+ }
+ var event = {};
+ for (var i = 1; i < items.length; i++) {
+ event[schema[i]] = items[i];
+ }
+ var json = JSON.stringify(event);
+ var req = http.request(post_opts, function (res) {
+ res.on("end", function () {
+ if (res.statusCode !== 200) {
+ util.log("bad response: " + res.statusCode);
+ }
+ return;
+ });
+ });
+ req.on("error", function (e) {
+ util.log("error posting to devent-forwarder" + e.toString());
+ return;
+ });
+ req.end(json);
+
+ return;
+}
+
+function respond_with(res, code, mess) {
+ res.statusCode = code;
+ res.write(mess || code.toString());
+ res.end();
+ return;
+}
14 package.json
@@ -0,0 +1,14 @@
+{
+ "name": "lil-brother"
+ , "version": "0.0.1"
+ , "private": true
+ , "dependencies": {
+ "nomnom": "> 1.x.x",
+ "uglify-js": "> 1.x.x"
+ }
+ , "engine": {
+ "node": "= 0.4.x"
+ }
+ , "bin": { "lilbro": "bin/lilbro" }
+ , "main": "lib/lilbro"
+}

0 comments on commit fcce72f

Please sign in to comment.