<commit_before>import TelegramBot from 'node-telegram-bot-api';                // importing telegram bot node api
import https from 'https';                                      // importing https to make requests to travis json user data
import express from 'express';
import { version as packageInfo } from './package.json';
const token = '227706347:AAF-Iq5fV8L4JYdk3g5wcU-z1eK1dd4sKa0';  // authorization token
const travis = 'https://travis-ci.org';                         // using for getting json data and slicing strings
let bot = new TelegramBot(token, {polling: true});              // initializing new bot
const opts = {  // keyboard options
  reply_markup: {
    'keyboard': [
      ['Yes'],
      ['No']
    ],
    one_time_keyboard: true // keyboard will shown only once and when it's required
  }
};

let app = express();

app.get('/', (req, res) => {
  return res.json({ version: packageInfo });
});

let server = app.listen(process.env.PORT, () => {
  console.log(`Server at http://${server.address().address}:${server.address().port}`);
});

bot.on('text', msg => {     // when user sending message
  let chatID = msg.chat.id; // saving user chat id from who bot received message
  let msgText = msg.text;   // getting text content

  // variables to declare in global scope
  let userID;      // slice msg to get user ID
  let userRepo;    // slice msg to get user Repository name
  let options;     // options for http request json data
  let prevBuild;   // storing number of previous build
  let currBuild;   // storing number of current build
  let currLink;    // storing here name of current link
  let linkMessage; // text message on /link command
  let slicing;     // using this variables for slicing user msg link
  let slicedLink;  // using this variables for slicing user msg link

  // Send Message from bot function
  const botSendMsg = (text, response) => {  // Function takes two arguments, bot command, and bot response
    return msgText === text ? bot.sendMessage(chatID, response) : false;
  };

  // Function for getting JSON data file for user repository
  const getTravisData = () => {
    if (msgText.indexOf(' ') > -1) {
      if (msgText.indexOf('https') > -1) {
        slicing = msgText.slice(msgText.indexOf('https'), msgText.indexOf(' ', msgText.lastIndexOf('/')));
        slicedLink = slicing.replace(/\s/g, '');
      } else {
        slicing = msgText.slice(msgText.indexOf('travis'), msgText.indexOf(' ', msgText.lastIndexOf('/')));
        slicedLink = slicing.replace(/\s/g, '');
      }
    } else {
      slicedLink = msgText;
    }

    userID = slicedLink.slice(slicedLink.lastIndexOf('org') + 4, slicedLink.lastIndexOf('/')); // getting user id
    userRepo = slicedLink.slice(slicedLink.lastIndexOf('/'));                                  // getting user repository name

    // setting options for requested JSON file
    options = {
      host: 'api.travis-ci.org',
      path: `/repositories/${userID}${userRepo}.json`,
      method: 'GET',
      headers: {
        'User-Agent': userID
      }
    };

    // making request to user travis link to get current build status
    let request = https.request(options, response => {
      let str = '';

      response.on('data', data => {
        str += data;
      });
      response.on('end', () => {
        const parsed = JSON.parse(str);       // parsing received data
        prevBuild = parsed.last_build_number; // ssigning previous build number to prevBuild
        currBuild = prevBuild;                // assign it to prevBuild
        if (parsed.file) {                    // parsed.file is shown if reposotiry where request where made doesn't exist
          bot.sendMessage(chatID, `${commands.messages.invalidLink}`);
        } else {
          bot.sendMessage(chatID, `${commands.messages.validLink} ${slicedLink}`);
        }
      });
    });
    request.end();
  };

  let httpIntervalRequest = () => {         // creating function which will be called when user sends travis link
    setInterval(() => {                     // creating setInterval to make http request each 7 seconds
      https.request(options, response => {  // defining options
        let str = '';                       // creating string where all json will be stored
        response.on('data', data => {       // while getting data
          str += data;                      // pass data to string
        });
        response.on('end', () => {              // when request is done
          let parsed = JSON.parse(str);         // parsing JSON data
          currBuild = parsed.last_build_number; // assigning current build number
          if (prevBuild !== currBuild && parsed.last_build_finished_at) {  // if prevBuild !== currBuild and build done

            let buildText = parsed.last_build_status === 0 ? 'completed successfully' : 'failed'; // defining if build failed or passed
            let buildNumber = parsed.last_build_number;                     // geting build number
            let repoName = parsed.slug.slice(parsed.slug.indexOf('/') + 1); // name of repository
            let startedAt = parsed.last_build_started_at;                   // when build was started
            let finishedAt = parsed.last_build_finished_at;                 // when build was ended
            let buildStarted = startedAt.slice(startedAt.indexOf('T') + 1, startedAt.length - 1);     // getting pure date
            let buildFinished = finishedAt.slice(finishedAt.indexOf('T') + 1, finishedAt.length - 1); // getting pure date

            bot.sendMessage(chatID, `Hi, your build at ${repoName} repository just has ended. \nYour build ${buildText}. \nBuild number was ${buildNumber}. \nYour build started at ${buildStarted} and finished at ${buildFinished}`);

            currBuild = parsed.last_build_number;   // reassign new variables
            prevBuild = parsed.last_build_number;   // reassign new variables

          } else if (!parsed.last_build_finished_at) {  // if user send link during build
            prevBuild = parsed.last_build_number - 1;   // assign prevBuild number to currBuildNumber - 1
          }
        });
      }).end();
    }, 7000);
  };

  // Check if user send Travis Repository link
  const checkLink = msgText.indexOf(travis) > -1 || msgText.indexOf(travis.slice(8)) > -1;
  if (checkLink) {
    getTravisData();
    httpIntervalRequest();
  };

  if (slicedLink) {
    linkMessage = `Hi, your link is ${slicedLink}`;
    slicedLink = slicedLink;
  } else {
    linkMessage = 'Hi, you have no watched links. Send me your link and I will start watching for you changes and will notify you each time when your build is done.';
  }

  const commands = {
    how: {
      commandName: '/how',
      commandText: 'how does it work',
      msgText: 'You send me your Tavis CI repository link. Example: \nhttps://travis-ci.org/twbs/bootstrap \nThen I will watch for changes and will notify you each time when your build is done. \n\nI will also include some basic information about your build. \nCurrently i can watch only one repository from each user.'
    },
    link: {
      commandName: '/link',
      commandText: 'get the currently watched link',
      msgText: linkMessage
    },
    start: {
      commandName: '/start',
      commandText: 'get main description of what this bot can do',
      msgText: 'Hi, I\'m @TravisCI_Telegam_Bot. Just send me link to Travis CI repository and I will notify you each time when your build is done.'
    },
    messages: {
      invalidLink: 'It\'s look like you send invalid link. Please send valid link.',
      validLink: 'Ok, since now I will watch for changes in'
    }
  };

  botSendMsg(`${commands.how.commandName}`, `${commands.how.msgText}`);
  botSendMsg(`${commands.link.commandName}`, `${commands.link.msgText}`);
  botSendMsg(`${commands.start.commandName}`, `${commands.start.msgText}\n
    ${commands.how.commandName} - ${commands.how.commandText}
    ${commands.link.commandName} - ${commands.link.commandText}
    ${commands.start.commandName} - ${commands.start.commandText}
  `);

});

// TODO: Time displayed in message should be locally
// TODO: Fix '/link' command. It should send curently watching link
<commit_msg>replace older comments with new<commit_after>import TelegramBot from 'node-telegram-bot-api';                // importing telegram bot node api
import https from 'https';                                      // importing https to make requests to travis json user data
import express from 'express';
import { version as packageInfo } from './package.json';
const token = '227706347:AAF-Iq5fV8L4JYdk3g5wcU-z1eK1dd4sKa0';  // authorization token
const travis = 'https://travis-ci.org';                         // using for getting json data and slicing strings
let bot = new TelegramBot(token, {polling: true});              // initializing new bot
const opts = {  // keyboard options
  reply_markup: {
    'keyboard': [
      ['Yes'],
      ['No']
    ],
    one_time_keyboard: true // keyboard will shown only once and when it's required
  }
};

let app = express();

app.get('/', (req, res) => {
  return res.json({ version: packageInfo });
});

let server = app.listen(process.env.PORT, () => {
  console.log(`Server at http://${server.address().address}:${server.address().port}`);
});

bot.on('text', msg => {     // when user sending message
  let chatID = msg.chat.id; // saving user chat id from who bot received message
  let msgText = msg.text;   // getting text content

  // variables to declare in global scope
  let userID;      // slice msg to get user ID
  let userRepo;    // slice msg to get user Repository name
  let options;     // options for http request json data
  let prevBuild;   // storing number of previous build
  let currBuild;   // storing number of current build
  let currLink;    // storing here name of current link
  let linkMessage; // text message on /link command
  let slicing;     // using this variables for slicing user msg link
  let slicedLink;  // using this variables for slicing user msg link

  // Function to send Message to user
  // It takes two arguments, bot command(text), and bot response(response)
  // If msgText match text then send msg with response parameter
  const botSendMsg = (text, response) => {
    return msgText === text ? bot.sendMessage(chatID, response) : false;
  };

  // Function for getting JSON data file for user repository
  // This function will slice user msg if there any spaces, and other
  // then it will slice msg to get user ID and repository name
  const getTravisData = () => {
    if (msgText.indexOf(' ') > -1) {
      if (msgText.indexOf('https') > -1) {
        slicing = msgText.slice(msgText.indexOf('https'), msgText.indexOf(' ', msgText.lastIndexOf('/')));
        slicedLink = slicing.replace(/\s/g, '');
      } else {
        slicing = msgText.slice(msgText.indexOf('travis'), msgText.indexOf(' ', msgText.lastIndexOf('/')));
        slicedLink = slicing.replace(/\s/g, '');
      }
    } else {
      slicedLink = msgText;
    }

    userID = slicedLink.slice(slicedLink.lastIndexOf('org') + 4, slicedLink.lastIndexOf('/'));
    userRepo = slicedLink.slice(slicedLink.lastIndexOf('/'));

    // setting options for requested JSON file
    options = {
      host: 'api.travis-ci.org',
      path: `/repositories/${userID}${userRepo}.json`,
      method: 'GET',
      headers: {
        'User-Agent': userID
      }
    };

    // Function to make http request to users travis api json file
    // to get current build info
    let request = https.request(options, response => {
      let str = '';

      response.on('data', data => {
        str += data;
      });
      response.on('end', () => {
        const parsed = JSON.parse(str);       // parsing received data
        prevBuild = parsed.last_build_number; // ssigning previous build number to prevBuild
        currBuild = prevBuild;                // assign it to prevBuild
        if (parsed.file) {                    // parsed.file is shown if reposotiry where request where made doesn't exist
          bot.sendMessage(chatID, `${commands.messages.invalidLink}`);
        } else {
          bot.sendMessage(chatID, `${commands.messages.validLink} ${slicedLink}`);
        }
      });
    });
    request.end();
  };

  // Function to make http request to users travis api json file, which will be called each 7 seconds
  // getting current build status and build number and storing them
  // when will making next http request it will compare current build status locally stored and in json file remotelly
  // and check if last build has ended
  // will send message to user with basic info about his build, like:

  // Hi, your build at ember.js repository just has ended.
  // Your build completed successfully.
  // Build number was 19444.
  // Your build started at 09:56:47 and finished at 10:05:39
  let httpIntervalRequest = () => {
    setInterval(() => {                    // creating setInterval to make http request each 7 seconds
      https.request(options, response => { // defining options
        let str = '';                      // creating string where all json will be stored
        response.on('data', data => {      // while getting data
          str += data;                     // pass data to string
        });
        response.on('end', () => {              // when request is done
          let parsed = JSON.parse(str);         // parsing JSON data
          currBuild = parsed.last_build_number; // assigning current build number
          if (prevBuild !== currBuild && parsed.last_build_finished_at) {  // if prevBuild !== currBuild and build done

            let buildText = parsed.last_build_status === 0 ? 'completed successfully' : 'failed'; // defining if build failed or passed
            let buildNumber = parsed.last_build_number;                     // geting build number
            let repoName = parsed.slug.slice(parsed.slug.indexOf('/') + 1); // name of repository
            let startedAt = parsed.last_build_started_at;                   // when build was started
            let finishedAt = parsed.last_build_finished_at;                 // when build was ended
            let buildStarted = startedAt.slice(startedAt.indexOf('T') + 1, startedAt.length - 1);     // getting pure date
            let buildFinished = finishedAt.slice(finishedAt.indexOf('T') + 1, finishedAt.length - 1); // getting pure date

            bot.sendMessage(chatID, `Hi, your build at ${repoName} repository just has ended. \nYour build ${buildText}. \nBuild number was ${buildNumber}. \nYour build started at ${buildStarted} and finished at ${buildFinished}`);

            currBuild = parsed.last_build_number;   // reassign new variables
            prevBuild = parsed.last_build_number;   // reassign new variables

          } else if (!parsed.last_build_finished_at) {  // if user send link during build
            prevBuild = parsed.last_build_number - 1;   // assign prevBuild number to currBuildNumber - 1
          }
        });
      }).end();
    }, 7000);
  };

  // Check if user send Travis Repository link
  const checkLink = msgText.indexOf(travis) > -1 || msgText.indexOf(travis.slice(8)) > -1;
  if (checkLink) {
    getTravisData();
    httpIntervalRequest();
  };

  if (slicedLink) {
    linkMessage = `Hi, your link is ${slicedLink}`;
    slicedLink = slicedLink;
  } else {
    linkMessage = 'Hi, you have no watched links. Send me your link and I will start watching for you changes and will notify you each time when your build is done.';
  }

  const commands = {
    how: {
      commandName: '/how',
      commandText: 'how does it work',
      msgText: 'You send me your Tavis CI repository link. Example: \nhttps://travis-ci.org/twbs/bootstrap \nThen I will watch for changes and will notify you each time when your build is done. \n\nI will also include some basic information about your build. \nCurrently i can watch only one repository from each user.'
    },
    link: {
      commandName: '/link',
      commandText: 'get the currently watched link',
      msgText: linkMessage
    },
    start: {
      commandName: '/start',
      commandText: 'get main description of what this bot can do',
      msgText: 'Hi, I\'m @TravisCI_Telegam_Bot. Just send me link to Travis CI repository and I will notify you each time when your build is done.'
    },
    messages: {
      invalidLink: 'It\'s look like you send invalid link. Please send valid link.',
      validLink: 'Ok, since now I will watch for changes in'
    }
  };

  botSendMsg(`${commands.how.commandName}`, `${commands.how.msgText}`);
  botSendMsg(`${commands.link.commandName}`, `${commands.link.msgText}`);
  botSendMsg(`${commands.start.commandName}`, `${commands.start.msgText}\n
    ${commands.how.commandName} - ${commands.how.commandText}
    ${commands.link.commandName} - ${commands.link.commandText}
    ${commands.start.commandName} - ${commands.start.commandText}
  `);

});

// TODO: Time displayed in message should be locally
// TODO: Fix '/link' command. It should send curently watching link
<|endoftext|>


<commit_before>(function(global){
  var PP = {};

  // length of each pulse
  PP.pulseLength = 1000 / 7; // from pixelphones

  // pause between messages
  PP.pulseDelay  = 2000;

  // returns a function that will tell us if a pixel should be on/off at
  // a given timestamp

  // TODO - add padding, '0' indistinguishable from '1'
  PP.pulser = function(id){
    var pulses = manchester(padding(binary(id))),
        delay  = PP.pulseDelay,
        length = PP.pulseLength,
        span   = delay + (length * pulses.length);

    console.log(id, pulses.map(function(d){return d? '*' : '_'}).join(''))

    return function(t){
      var n = t % span;
      if(n < delay){
        return false
      } else {
        var i = Math.floor( (n - delay) / length );
        return pulses[i]
      }

    }
  }

  // todo - checksum

  // add a leading 1, so starts with a pulse
  function padding(digits){
    return [1].concat(digits)
  }

  function binary(i){
    return i.toString(2).split('').map(function(d){return parseInt(d)})
  }

  function manchester(digits){
    var encoded = new Array(digits.length*2)
    for(var i = 0; i < digits.length*2; i++){
      encoded[i] = ((i+1)%2) ^ digits[Math.floor(i/2)]
    }
    return encoded
  }

  function unpadding(digits){
    return digits.slice(1)
  }

  function unbinary(digits){
    var str = digits.map(function(d){return d ? '1': '0'}).join('');
    return parseInt(str, 2)
  }

  function unmanchester(digits){
    var decoded = new Array(digits.length/2)
    for(var i = 0; i < digits.length/2; i++){
      decoded[i] = !digits[i*2]
    }
    return decoded
  }

  // expose for testing
  PP.encodingHelpers = {
    padding : padding,
    binary : binary,
    manchester : manchester,
    unpadding : unpadding,
    unbinary : unbinary,
    unmanchester : unmanchester
  }

  // Detection functions

  PP.Transitions = function(threshold){
    this.prior = this.priorT = null;
    this.threshold = threshold;
  }

  PP.Transitions.prototype.add = function(value, timestamp){
    var state = value > this.threshold;
    if(this.prior !== state){
      if(this.prior === null){
        this.prior = state;
        this.priorT = timestamp;
      } else {
        var t = this.priorT + ((timestamp - this.priorT)/2);
        this.prior = state;
        this.priorT = timestamp;
        return [state ? 1: 0, ~~t]
      }
    }

    this.priorT = timestamp;
  }

  // deltas are either short, long, or weird,
  // would be much cooler if it used FFT
  PP.PhaseDetector = function(){
    this.period = null;
    this.lastTime = null;
    this.total = 0;
    this.count = 0;
  }

  // detects the period length of a manchester
  // encoded signal.  Not very robust or correct
  // deltas are either short, long, or weird
  PP.PhaseDetector.prototype.add = function(value, timestamp){
    if(this.lastTime === null){
      this.lastTime = timestamp;
      return;
    }

    var time = timestamp - this.lastTime;
    this.total += ~~(time + (time/2))
    this.count += 2;
    this.period = this.total / this.count;

    this.lastTime = timestamp;
  }

  PP.BinaryDecoder = function(period){
    this.period = period;
    this.lastTime = null;
    this.result = [];
  }

  PP.BinaryDecoder.prototype.add = function(value, timestamp){
    if(this.lastTime === null) {
      this.lastTime = timestamp;
      return
    }

    var time = timestamp - this.lastTime;
    var lastVal = value ? 0 : 1;

    var digits = Math.round(time/this.period);

    for(var i = 0; i < digits; i++){
      console.log(lastVal)
      this.result.push(lastVal)
    }

    this.lastTime = timestamp;
  }

  // full on decoder, takes the grey values and outputs
  // the id
  PP.Decoder = function(threshold, period){
    this.transitionDetector = new PP.Transitions(threshold);
    this.phaseDetector = new PP.PhaseDetector();
    this.binaryDecoder = new PP.BinaryDecoder(100);// TODO - dynamic
  }

  PP.Decoder.prototype.add = function(value, timestamp){
    var t = this.transitionDetector.add(value, timestamp)
    if(t){
      this.phaseDetector.add(t[0], t[1])
      console.log("detected phase", this.phaseDetector.period)

      this.binaryDecoder.add(t[0], t[1])
      console.log("binary code",this.binaryDecoder.result)
    }
  }

  global.PP = PP;
})(this)

// stats.js - http://github.com/mrdoob/stats.js
var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
"block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:12,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};"object"===typeof module&&(module.exports=Stats);

// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating

// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel

// MIT license

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());<commit_msg>less versbose<commit_after>(function(global){
  var PP = {};

  // length of each pulse
  PP.pulseLength = 1000 / 7; // from pixelphones

  // pause between messages
  PP.pulseDelay  = 2000;

  // returns a function that will tell us if a pixel should be on/off at
  // a given timestamp

  // TODO - add padding, '0' indistinguishable from '1'
  PP.pulser = function(id){
    var pulses = manchester(padding(binary(id))),
        delay  = PP.pulseDelay,
        length = PP.pulseLength,
        span   = delay + (length * pulses.length);

    console.log(id, pulses.map(function(d){return d? '*' : '_'}).join(''))

    return function(t){
      var n = t % span;
      if(n < delay){
        return false
      } else {
        var i = Math.floor( (n - delay) / length );
        return pulses[i]
      }

    }
  }

  // todo - checksum

  // add a leading 1, so starts with a pulse
  function padding(digits){
    return [1].concat(digits)
  }

  function binary(i){
    return i.toString(2).split('').map(function(d){return parseInt(d)})
  }

  function manchester(digits){
    var encoded = new Array(digits.length*2)
    for(var i = 0; i < digits.length*2; i++){
      encoded[i] = ((i+1)%2) ^ digits[Math.floor(i/2)]
    }
    return encoded
  }

  function unpadding(digits){
    return digits.slice(1)
  }

  function unbinary(digits){
    var str = digits.map(function(d){return d ? '1': '0'}).join('');
    return parseInt(str, 2)
  }

  function unmanchester(digits){
    var decoded = new Array(digits.length/2)
    for(var i = 0; i < digits.length/2; i++){
      decoded[i] = !digits[i*2]
    }
    return decoded
  }

  // expose for testing
  PP.encodingHelpers = {
    padding : padding,
    binary : binary,
    manchester : manchester,
    unpadding : unpadding,
    unbinary : unbinary,
    unmanchester : unmanchester
  }

  // Detection functions

  PP.Transitions = function(threshold){
    this.prior = this.priorT = null;
    this.threshold = threshold;
  }

  PP.Transitions.prototype.add = function(value, timestamp){
    var state = value > this.threshold;
    if(this.prior !== state){
      if(this.prior === null){
        this.prior = state;
        this.priorT = timestamp;
      } else {
        var t = this.priorT + ((timestamp - this.priorT)/2);
        this.prior = state;
        this.priorT = timestamp;
        return [state ? 1: 0, ~~t]
      }
    }

    this.priorT = timestamp;
  }

  // deltas are either short, long, or weird,
  // would be much cooler if it used FFT
  PP.PhaseDetector = function(){
    this.period = null;
    this.lastTime = null;
    this.total = 0;
    this.count = 0;
  }

  // detects the period length of a manchester
  // encoded signal.  Not very robust or correct
  // deltas are either short, long, or weird
  PP.PhaseDetector.prototype.add = function(value, timestamp){
    if(this.lastTime === null){
      this.lastTime = timestamp;
      return;
    }

    var time = timestamp - this.lastTime;
    this.total += ~~(time + (time/2))
    this.count += 2;
    this.period = this.total / this.count;

    this.lastTime = timestamp;
  }

  PP.BinaryDecoder = function(period){
    this.period = period;
    this.lastTime = null;
    this.result = [];
  }

  PP.BinaryDecoder.prototype.add = function(value, timestamp){
    if(this.lastTime === null) {
      this.lastTime = timestamp;
      return
    }

    var time = timestamp - this.lastTime;
    var lastVal = value ? 0 : 1;

    var digits = Math.round(time/this.period);

    for(var i = 0; i < digits; i++){
      this.result.push(lastVal)
    }

    this.lastTime = timestamp;
  }

  // full on decoder, takes the grey values and outputs
  // the id
  PP.Decoder = function(threshold, period){
    this.transitionDetector = new PP.Transitions(threshold);
    this.phaseDetector = new PP.PhaseDetector();
    this.binaryDecoder = new PP.BinaryDecoder(1000/7);// TODO - dynamic
  }

  PP.Decoder.prototype.add = function(value, timestamp){
    var t = this.transitionDetector.add(value, timestamp)
    if(t){
      this.phaseDetector.add(t[0], t[1])
      // console.log("detected phase", this.phaseDetector.period)

      this.binaryDecoder.add(t[0], t[1])
      // console.log("binary code",this.binaryDecoder.result)
    }
  }

  global.PP = PP;
})(this)

// stats.js - http://github.com/mrdoob/stats.js
var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
"block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:12,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};"object"===typeof module&&(module.exports=Stats);

// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating

// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel

// MIT license

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());<|endoftext|>


<commit_before>var router = require('express').Router();
var passport = require('passport');
var mongoose = require('mongoose');
var Article = mongoose.model('Article');
var User = mongoose.model('User');
var auth = require('../auth');

/*
CREATE ARTICLE
*/

router.post('/', auth.required, function(req, res, next){
	User.findById(req.payload.id).then(function(user){
		if(!user){ return res.sendStatus(401); }

		var article = new Article(req.body.article);

		article.author = user;

		return article.save().then(function(){
			console.log(article.author)
			return res.json({ article: article.toJSONFor(user)});
		});
	}).catch(next);
});

/*
router.param to intercept :articles
*/
router.param('article', function(req, res, next, slug){
	Article.findOne({ slug: slug })
	.populate('author')
	.then(function (article){
		if(!article){ return res.sendStatus(404); }

		req.article = article;

		return next();
	}).catch(next);
});

/*
READ article
*/
router.get('/:article', auth.optional, function(req, res, next){
	Promise.all([
		req.payload ? User.findById(req.payload.id) : null,
		req.article.populate('author').execPopulate()
		]).then(function(results){
			var user = results[0];

			return res.json({ article: req.article.toJSONFor(user)});
		}).catch(next);
});

module.exports = router;
<commit_msg>updated put for updating articles<commit_after>var router = require('express').Router();
var passport = require('passport');
var mongoose = require('mongoose');
var Article = mongoose.model('Article');
var User = mongoose.model('User');
var auth = require('../auth');

/*
CREATE ARTICLE
*/

router.post('/', auth.required, function(req, res, next){
	User.findById(req.payload.id).then(function(user){
		if(!user){ return res.sendStatus(401); }

		var article = new Article(req.body.article);

		article.author = user;

		return article.save().then(function(){
			console.log(article.author)
			return res.json({ article: article.toJSONFor(user)});
		});
	}).catch(next);
});

/*
router.param to intercept :articles
*/
router.param('article', function(req, res, next, slug){
	Article.findOne({ slug: slug })
	.populate('author')
	.then(function (article){
		if(!article){ return res.sendStatus(404); }

		req.article = article;

		return next();
	}).catch(next);
});

/*
READ article
*/
router.get('/:article', auth.optional, function(req, res, next){
	Promise.all([
		req.payload ? User.findById(req.payload.id) : null,
		req.article.populate('author').execPopulate()
		]).then(function(results){
			var user = results[0];

			return res.json({ article: req.article.toJSONFor(user)});
		}).catch(next);
});

router.put('/:article', auth.required, function(req, res, next){
	User.findById(req.payload.id).then(function(user){
		if (req.article.author._id.toString() === req.payload.id.toString()){
			if(typeof req.body.article.title !== 'undefined'){
				req.article.title = req.body.article.title;
			}

		if(typeof req.body.article.description !== 'undefined'){
			req.article.description = req.body.article.description;
		}

		if(typeof req.body.article.body !== 'undefined'){
			req.article.body = req.body.article.body;
		}

		req.article.save().then(function(article){
			return res.json({article: article.toJSONFor(user)});
		}).catch(next);
	} else {
		return res.sendStatus(403);
	}
	});
});

module.exports = router;
<|endoftext|>


<commit_before>
module.exports = function(RED) {
    "use strict";
    var exec = require('child_process').exec;
    var spawn = require('child_process').spawn;
    var fs = require('fs');

    var gpioCommand = __dirname+'/nrgpio';

    try {
        var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
        if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); }
    } catch(err) {
        throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
    }

    try {
        fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
        // /usr/lib/python2.7/dist-packages/RPi/GPIO
    } catch(err) {
        try {
            fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
        }
        catch(err) {
            try {
                fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
            }
            catch(err) {
                RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
                throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
            }
        }
    }

    if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
        RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
        throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
    }

    // the magic to make python print stuff immediately
    process.env.PYTHONUNBUFFERED = 1;

    var pinsInUse = {};
    var pinTypes = {"out":RED._("rpi-gpio.types.digout"), "tri":RED._("rpi-gpio.types.input"), "up":RED._("rpi-gpio.types.pullup"), "down":RED._("rpi-gpio.types.pulldown"), "pwm":RED._("rpi-gpio.types.pwmout")};

    function GPIOInNode(n) {
        RED.nodes.createNode(this,n);
        this.buttonState = -1;
        this.pin = n.pin;
        this.intype = n.intype;
        this.read = n.read || false;
        this.debounce = Number(n.debounce || 25);
        if (this.read) { this.buttonState = -2; }
        var node = this;
        if (!pinsInUse.hasOwnProperty(this.pin)) {
            pinsInUse[this.pin] = this.intype;
        }
        else {
            if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
                node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
            }
        }

        if (node.pin !== undefined) {
            node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
            node.running = true;
            node.status({fill:"green",shape:"dot",text:"common.status.ok"});

            node.child.stdout.on('data', function (data) {
                var d = data.toString().trim().split("\n");
                for (var i = 0; i < d.length; i++) {
                    if (d[i] === '') { return; }
                    if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
                        node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
                    }
                    node.buttonState = d[i];
                    node.status({fill:"green",shape:"dot",text:d[i]});
                    if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
                }
            });

            node.child.stderr.on('data', function (data) {
                if (RED.settings.verbose) { node.log("err: "+data+" :"); }
            });

            node.child.on('close', function (code) {
                node.running = false;
                node.child = null;
                if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
                if (node.done) {
                    node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
                    node.done();
                }
                else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
            });

            node.child.on('error', function (err) {
                if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
                else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
                else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
            });

        }
        else {
            node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
        }

        node.on("close", function(done) {
            node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
            delete pinsInUse[node.pin];
            if (node.child != null) {
                node.done = done;
                node.child.stdin.write("close "+node.pin);
                node.child.kill('SIGKILL');
            }
            else { done(); }
        });
    }
    RED.nodes.registerType("rpi-gpio in",GPIOInNode);

    function GPIOOutNode(n) {
        RED.nodes.createNode(this,n);
        this.pin = n.pin;
        this.set = n.set || false;
        this.level = n.level || 0;
        this.freq = n.freq || 100;
        this.out = n.out || "out";
        var node = this;
        if (!pinsInUse.hasOwnProperty(this.pin)) {
            pinsInUse[this.pin] = this.out;
        }
        else {
            if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
                node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
            }
        }

        function inputlistener(msg) {
            if (msg.payload === "true") { msg.payload = true; }
            if (msg.payload === "false") { msg.payload = false; }
            var out = Number(msg.payload);
            var limit = 1;
            if (node.out === "pwm") { limit = 100; }
            if ((out >= 0) && (out <= limit)) {
                if (RED.settings.verbose) { node.log("out: "+msg.payload); }
                if (node.child !== null) {
                    node.child.stdin.write(msg.payload+"\n");
                    node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
                }
                else {
                    node.error(RED._("rpi-gpio.errors.pythoncommandnotfound"),msg);
                    node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.not-running"});
                }
            }
            else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
        }

        if (node.pin !== undefined) {
            if (node.set && (node.out === "out")) {
                node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
                node.status({fill:"green",shape:"dot",text:node.level});
            } else {
                node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
                node.status({fill:"green",shape:"dot",text:"common.status.ok"});
            }
            node.running = true;

            node.on("input", inputlistener);

            node.child.stdout.on('data', function (data) {
                if (RED.settings.verbose) { node.log("out: "+data+" :"); }
            });

            node.child.stderr.on('data', function (data) {
                if (RED.settings.verbose) { node.log("err: "+data+" :"); }
            });

            node.child.on('close', function (code) {
                node.child = null;
                node.running = false;
                if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
                if (node.done) {
                    node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
                    node.done();
                }
                else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
            });

            node.child.on('error', function (err) {
                if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
                else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
                else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
            });

        }
        else {
            node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
        }

        node.on("close", function(done) {
            node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
            delete pinsInUse[node.pin];
            if (node.child != null) {
                node.done = done;
                node.child.stdin.write("close "+node.pin);
                node.child.kill('SIGKILL');
            }
            else { done(); }
        });

    }
    RED.nodes.registerType("rpi-gpio out",GPIOOutNode);

    function PiMouseNode(n) {
        RED.nodes.createNode(this,n);
        this.butt = n.butt || 7;
        var node = this;

        node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
        node.status({fill:"green",shape:"dot",text:"common.status.ok"});

        node.child.stdout.on('data', function (data) {
            data = Number(data);
            if (data === 1) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
            else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
        });

        node.child.stderr.on('data', function (data) {
            if (RED.settings.verbose) { node.log("err: "+data+" :"); }
        });

        node.child.on('close', function (code) {
            node.child = null;
            node.running = false;
            if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
            if (node.done) {
                node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
                node.done();
            }
            else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
        });

        node.child.on('error', function (err) {
            if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
            else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
            else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
        });

        node.on("close", function(done) {
            node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
            if (node.child != null) {
                node.done = done;
                node.child.kill('SIGINT');
                node.child = null;
            }
            else { done(); }
        });
    }
    RED.nodes.registerType("rpi-mouse",PiMouseNode);

    function PiKeyboardNode(n) {
        RED.nodes.createNode(this,n);
        var node = this;

        node.child = spawn(gpioCommand+".py", ["kbd","0"]);
        node.status({fill:"green",shape:"dot",text:"common.status.ok"});

        node.child.stdout.on('data', function (data) {
            var b = data.toString().trim().split(",");
            var act = "up";
            if (b[1] === "1") { act = "down"; }
            if (b[1] === "2") { act = "repeat"; }
            node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
        });

        node.child.stderr.on('data', function (data) {
            if (RED.settings.verbose) { node.log("err: "+data+" :"); }
        });

        node.child.on('close', function (code) {
            node.running = false;
            node.child = null;
            if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
            if (node.done) {
                node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
                node.done();
            }
            else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
        });

        node.child.on('error', function (err) {
            if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
            else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
            else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
        });

        node.on("close", function(done) {
            node.status({});
            if (node.child != null) {
                node.done = done;
                node.child.kill('SIGINT');
                node.child = null;
            }
            else { done(); }
        });
    }
    RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);

    var pitype = { type:"" };
    exec(gpioCommand+" info", function(err,stdout,stderr) {
        if (err) {
            RED.log.info(RED._("rpi-gpio.errors.version"));
        }
        else {
            try {
                var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
                pitype.type = info["TYPE"];
            }
            catch(e) {
                RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
            }
        }
    });

    RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
        res.json(pitype);
    });

    RED.httpAdmin.get('/rpi-pins/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
        res.json(pinsInUse);
    });
}
<commit_msg>Fix pi gpio output of boolean to actually send 1/0<commit_after>
module.exports = function(RED) {
    "use strict";
    var exec = require('child_process').exec;
    var spawn = require('child_process').spawn;
    var fs = require('fs');

    var gpioCommand = __dirname+'/nrgpio';

    try {
        var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
        if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); }
    } catch(err) {
        throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
    }

    try {
        fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
        // /usr/lib/python2.7/dist-packages/RPi/GPIO
    } catch(err) {
        try {
            fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
        }
        catch(err) {
            try {
                fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
            }
            catch(err) {
                RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
                throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
            }
        }
    }

    if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
        RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
        throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
    }

    // the magic to make python print stuff immediately
    process.env.PYTHONUNBUFFERED = 1;

    var pinsInUse = {};
    var pinTypes = {"out":RED._("rpi-gpio.types.digout"), "tri":RED._("rpi-gpio.types.input"), "up":RED._("rpi-gpio.types.pullup"), "down":RED._("rpi-gpio.types.pulldown"), "pwm":RED._("rpi-gpio.types.pwmout")};

    function GPIOInNode(n) {
        RED.nodes.createNode(this,n);
        this.buttonState = -1;
        this.pin = n.pin;
        this.intype = n.intype;
        this.read = n.read || false;
        this.debounce = Number(n.debounce || 25);
        if (this.read) { this.buttonState = -2; }
        var node = this;
        if (!pinsInUse.hasOwnProperty(this.pin)) {
            pinsInUse[this.pin] = this.intype;
        }
        else {
            if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
                node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
            }
        }

        if (node.pin !== undefined) {
            node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
            node.running = true;
            node.status({fill:"green",shape:"dot",text:"common.status.ok"});

            node.child.stdout.on('data', function (data) {
                var d = data.toString().trim().split("\n");
                for (var i = 0; i < d.length; i++) {
                    if (d[i] === '') { return; }
                    if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
                        node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
                    }
                    node.buttonState = d[i];
                    node.status({fill:"green",shape:"dot",text:d[i]});
                    if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
                }
            });

            node.child.stderr.on('data', function (data) {
                if (RED.settings.verbose) { node.log("err: "+data+" :"); }
            });

            node.child.on('close', function (code) {
                node.running = false;
                node.child = null;
                if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
                if (node.done) {
                    node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
                    node.done();
                }
                else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
            });

            node.child.on('error', function (err) {
                if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
                else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
                else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
            });

        }
        else {
            node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
        }

        node.on("close", function(done) {
            node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
            delete pinsInUse[node.pin];
            if (node.child != null) {
                node.done = done;
                node.child.stdin.write("close "+node.pin);
                node.child.kill('SIGKILL');
            }
            else { done(); }
        });
    }
    RED.nodes.registerType("rpi-gpio in",GPIOInNode);

    function GPIOOutNode(n) {
        RED.nodes.createNode(this,n);
        this.pin = n.pin;
        this.set = n.set || false;
        this.level = n.level || 0;
        this.freq = n.freq || 100;
        this.out = n.out || "out";
        var node = this;
        if (!pinsInUse.hasOwnProperty(this.pin)) {
            pinsInUse[this.pin] = this.out;
        }
        else {
            if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
                node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
            }
        }

        function inputlistener(msg) {
            if (msg.payload === "true") { msg.payload = true; }
            if (msg.payload === "false") { msg.payload = false; }
            var out = Number(msg.payload);
            var limit = 1;
            if (node.out === "pwm") { limit = 100; }
            if ((out >= 0) && (out <= limit)) {
                if (RED.settings.verbose) { node.log("out: "+out); }
                if (node.child !== null) {
                    node.child.stdin.write(out+"\n");
                    node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
                }
                else {
                    node.error(RED._("rpi-gpio.errors.pythoncommandnotfound"),msg);
                    node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.not-running"});
                }
            }
            else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
        }

        if (node.pin !== undefined) {
            if (node.set && (node.out === "out")) {
                node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
                node.status({fill:"green",shape:"dot",text:node.level});
            } else {
                node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
                node.status({fill:"green",shape:"dot",text:"common.status.ok"});
            }
            node.running = true;

            node.on("input", inputlistener);

            node.child.stdout.on('data', function (data) {
                if (RED.settings.verbose) { node.log("out: "+data+" :"); }
            });

            node.child.stderr.on('data', function (data) {
                if (RED.settings.verbose) { node.log("err: "+data+" :"); }
            });

            node.child.on('close', function (code) {
                node.child = null;
                node.running = false;
                if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
                if (node.done) {
                    node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
                    node.done();
                }
                else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
            });

            node.child.on('error', function (err) {
                if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
                else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
                else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
            });

        }
        else {
            node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
        }

        node.on("close", function(done) {
            node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
            delete pinsInUse[node.pin];
            if (node.child != null) {
                node.done = done;
                node.child.stdin.write("close "+node.pin);
                node.child.kill('SIGKILL');
            }
            else { done(); }
        });

    }
    RED.nodes.registerType("rpi-gpio out",GPIOOutNode);

    function PiMouseNode(n) {
        RED.nodes.createNode(this,n);
        this.butt = n.butt || 7;
        var node = this;

        node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
        node.status({fill:"green",shape:"dot",text:"common.status.ok"});

        node.child.stdout.on('data', function (data) {
            data = Number(data);
            if (data === 1) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
            else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
        });

        node.child.stderr.on('data', function (data) {
            if (RED.settings.verbose) { node.log("err: "+data+" :"); }
        });

        node.child.on('close', function (code) {
            node.child = null;
            node.running = false;
            if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
            if (node.done) {
                node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
                node.done();
            }
            else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
        });

        node.child.on('error', function (err) {
            if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
            else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
            else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
        });

        node.on("close", function(done) {
            node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
            if (node.child != null) {
                node.done = done;
                node.child.kill('SIGINT');
                node.child = null;
            }
            else { done(); }
        });
    }
    RED.nodes.registerType("rpi-mouse",PiMouseNode);

    function PiKeyboardNode(n) {
        RED.nodes.createNode(this,n);
        var node = this;

        node.child = spawn(gpioCommand+".py", ["kbd","0"]);
        node.status({fill:"green",shape:"dot",text:"common.status.ok"});

        node.child.stdout.on('data', function (data) {
            var b = data.toString().trim().split(",");
            var act = "up";
            if (b[1] === "1") { act = "down"; }
            if (b[1] === "2") { act = "repeat"; }
            node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
        });

        node.child.stderr.on('data', function (data) {
            if (RED.settings.verbose) { node.log("err: "+data+" :"); }
        });

        node.child.on('close', function (code) {
            node.running = false;
            node.child = null;
            if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
            if (node.done) {
                node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
                node.done();
            }
            else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
        });

        node.child.on('error', function (err) {
            if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
            else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
            else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
        });

        node.on("close", function(done) {
            node.status({});
            if (node.child != null) {
                node.done = done;
                node.child.kill('SIGINT');
                node.child = null;
            }
            else { done(); }
        });
    }
    RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);

    var pitype = { type:"" };
    exec(gpioCommand+" info", function(err,stdout,stderr) {
        if (err) {
            RED.log.info(RED._("rpi-gpio.errors.version"));
        }
        else {
            try {
                var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
                pitype.type = info["TYPE"];
            }
            catch(e) {
                RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
            }
        }
    });

    RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
        res.json(pitype);
    });

    RED.httpAdmin.get('/rpi-pins/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
        res.json(pinsInUse);
    });
}
<|endoftext|>


<commit_before>/*
 Copyright (c) <2009> <yvind Sean Kinsey, oyvind@kinsey.no>
 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 EasyXSS = {
    onReadyCallbacks: { //
        /// <summary>
        /// Hashtable for storing callbacks when using hashTransport
        /// </summary>
    },
    registerOnReady: function(channel, callback){
        /// <summary>
        /// Register a callback that can be called when hash.html is fully loaded.
        /// </summary>
        /// <param name="channeL" type="string">The name of the channel</param>
        /// <param name="callback" type="function">The function to call</param>
        this.onReadyCallbacks[channel] = callback;
    },
    onReady: function(channel){
        /// <summary>
        /// Call the onReady method associated with the channel
        /// </summary>
        /// <param name="channel" type="string">The name of the channel</param>
        var fn = this.onReadyCallbacks[channel];
        if (fn) {
            fn();
            delete this.onReadyCallbacks[channel];
        }
    },
    converters: {
        json2Converter: {//
            /// <summary>
            /// A converter that uses the JSON library for
            /// converting between JSON strings and objects.
            /// </summary>
            /// <remark>
            /// Requires an available JSON object, native or using JSON2
            /// http://json.org/json2.js
            /// </remark>
            convertToString: function(data){
                return JSON.stringify(data);
            },
            convertFromString: function(message){
                return JSON.parse(message);
            }
        },
        hashTableConverter: {//
            /// <summary>
            /// A converter that can convert to and from hashtables
            /// string values, or values having valid string representation.
            /// </summary>
            convertToString: function(data){
                var message = "";
                for (key in data) {
                    message += key + "=" + escape(data[key]) + "&";
                }
                return message.substring(0, message.length - 1);
            },
            convertFromString: function(message){
                var data = {};
                var d = message.split("&");
                var pair, key, value;
                for (var i = 0, len = d.length; i < len; i++) {
                    pair = d[i];
                    key = pair.substring(0, pair.indexOf("="));
                    value = pair.substring(key.length + 1);
                    data[key] = unescape(value);
                }
                return data;
            }
        }
    },
    Query: function(){
        /// <summary>
        /// Returns a hashtable containing the query parameteres
        /// </summary>
        /// <returns type="object">A hashtable</returns>
        var pair, key, value, hash = {}, search = location.search.substring(1).split("&");
        for (var i = 0, len = search.length; i < len; i++) {
            pair = search[i];
            key = pair.substring(0, pair.indexOf("="));
            value = pair.substring(key.length + 1);
            hash[key] = value;
        }
        return hash;
    },
    getDomainName: function(url){
        /// <summary>
        /// </summary>
        /// <param name="url" type="string">The url to extract the domain from</param>
        /// <returns type="string">The domain part of the url</returns>
        var domain = url.substring(url.indexOf("//") + 2);
        domain = domain.substring(0, domain.indexOf("/"));
        var _indexOf = domain.indexOf(":");
        if (_indexOf != -1) {
            domain = domain.substring(0, _indexOf);
        }
        return domain;
    },
    getLocation: function(uri){
        /// <summary>
        /// Returns a string containing the schema, domain and if present the port
        /// </summary
        /// <param name="url" type="string">The url to extract the location from</param>
        /// <returns type="string">The location part of the url</returns>
        var indexOf = uri.indexOf("//");
        var loc = uri.substring(indexOf + 2);
        loc = loc.substring(0, loc.indexOf("/"));
        return uri.substring(0, indexOf + 2) + loc;
    },
    createFrame: function(url, name, onLoad){
        /// <summary>
        /// Creates a frame and appends it to the DOM.
        /// </summary
        /// <param name="url" type="string">The url the frame should be set to</param>
        /// <param name="name" type="string">The id/name the frame should get</param>
        /// <param name="onLoad" type="function">
        /// A method that should be called with the frames contentWindow as argument
        /// when the frame is fully loaded.
        /// </param>
        /// <returns type="DOMElement">The frames DOMElement</returns>
        var frame;
        if (name && window.attachEvent) {
            // Internet Explorer does not support setting the
            // name om DOMElements created in Javascript.
            // A workaround is to insert HTML and have the browser parse
            // and instantiate the element.
            var span = document.createElement("span");
            document.body.appendChild(span);
            span.innerHTML = '<iframe src="' + url + '" id="' + name + '" name="' + name + '"></iframe>';
            frame = document.getElementById(name);
            if (onLoad) {
                this.addEventListener(frame, "load", function(){
                    onLoad(frame.contentWindow);
                })
            }
        }
        else {
            // When name is not needed, or in other browsers,
            // we use regular createElement.
            frame = document.createElement("IFRAME");
            frame.style.position = "absolute";
            frame.style.left = "-2000px";
            frame.name = name;
            frame.id = name;
            frame.src = url;
            if (onLoad) {
                this.addEventListener(frame, "load", function(){
                    onLoad(frame.contentWindow);
                })
            }
            document.body.appendChild(frame);
        }
        return frame;
    },
    addEventListener: function(element, event, handler){
        /// <summary>
        /// Gives a consistent interface for adding eventhandlers
        /// </summary
        /// <param name="element" type="DOMElement">The DOMElement to attach the handler to</param>
        /// <param name="event" type="string">The eventname</param>
        /// <param name="handler" type="function">The handler to attach</param>
        if (window.addEventListener) {
            element.addEventListener(event, handler, false);
        }
        else {
            element.attachEvent("on" + event, handler);
        }
    },
    removeEventListener: function(element, event, handler){
        /// <summary>
        /// Gives a consistent interface for removing eventhandlers
        /// </summary
        /// <param name="element" type="DOMElement">The DOMElement to remote the handler from</param>
        /// <param name="event" type="string">The eventname</param>
        /// <param name="handler" type="function">The handler to remove</param>
        if (window.removeEventListener) {
            element.removeEventListener(event, handler, false);
        }
        else {
            element.detachEvent("on" + event, handler);
        }
    },
    createTransport: function(config){
        /// <summary>
        /// Creates a transport channel using the available parameters.
        /// Parameters are collected both from the supplied config,
        /// but also from the querystring if not present in the config.
        /// </summary
        /// <param name="config" type="object"></param>
        /// <returns type="object">An object able to send and receive messages</returns>
        if (!config.local) {
            // We assume that this is the remote part of the channel
            var query = this.Query();
            config.channel = query["channel"];
            config.remote = query["endpoint"];
        }
        if (window.postMessage) {
            // We need to check here if it 'really' supports postMessage
            // Safari is unable to post messages to an iframe
            return this.createPostMessageTransport(config);
        }
        else {
            return this.createHashTransport(config);
        }
    },
    createChannel: function(config){
        /// <summary>
        /// Creates wrapper around the available transport mechanism that
        /// also enables you to insert a converter for the messages transmitted.
        /// </summary
        /// <param name="config" type="object"></param>
        /// <returns type="object">An object able to send and receive arbitrary data</returns>
        /// <remark>The supported data types depends on the converter used</remark>
        // If a converter is present then we wrap the callback in a call to this
        config.onMessage = (config.converter) ? (function(message, origin){
            this.onData(this.converter.convertFromString(message), origin);
        }) : config.onData;
        var transport = this.createTransport(config);
        return {
            transport: transport,
            // If a converter is present then we wrap postMessage in a call to this
            sendData: (config.converter) ? (function(data){
                this.transport.postMessage(config.converter.convertToString(data));
            }) : transport.sendMessage,
            start: transport.start,
            stop: transport.stop
        };
    },
    createPostMessageTransport: function(config){
        /// <summary>
        /// Sets up the infrastructure for sending and receiving
        /// messages using the window.postMessage interface.
        /// http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx
        /// https://developer.mozilla.org/en/DOM/window.postMessage
        /// </summary
        /// <param name="config" type="object"></param>
        /// <returns type="object">An object able to send and receive strings</returns>
        var xss = this;
        var _targetOrigin = xss.getLocation(config.remote);
        var _windowPostMessage;
        var _isStarted, _isReady;

        function _getOrigin(event){
            /// <summary>
            /// The origin property should be valid, but some clients
            /// still uses the uri or domain property.
            /// </summary
            /// <param name="event" type="MessageEvent">The eventobject from the browser</param>
            /// <returns type="string">The origin of the event</returns>
            if (event.origin) {
                return event.origin;
            }
            if (event.uri) {
                return xss.getLocation(event.uri);
            }
            if (event.domain) {
                // This will fail if the origin is not using the same
                // schema as we are
                return location.protocol + "//" + event.domain;
            }
            throw "Unable to retrieve the origin of the event"
        }

        function _window_onMessage(event){
            /// <summary>
            /// The handler for the "message" event.
            /// If the origin is allowed then we relay the message to the receiver
            /// </summary
            /// <param name="event" type="MessageEvent">The eventobject from the browser</param>
            var origin = _getOrigin(event);
            if (origin == _targetOrigin) {
                config.onMessage(event.data, origin);
            }
        }
        function _onReady(){
            /// <summary>
            /// Calls the supplied onReady method
            /// </summary
            /// <remark>
            /// We delay this so that the the call to createChannel or
            /// createTransport will have completed
            /// </remark>
            if (config.onReady) {
                window.setTimeout(config.onReady, 10);
            }
        }

        if (config.local) {
            _listeningWindow = xss.createFrame(config.remote + "?endpoint=" + config.local + "&channel=" + config.channel, "", function(win){
                _windowPostMessage = win.postMessage;
                _onReady();
            });
        }
        else {
            _windowPostMessage = window.parent.postMessage;
            _onReady();
        }
        xss.addEventListener(window, "message", _window_onMessage);
        return {
            postMessage: function(message){
                /// <summary>
                /// Sends the message using the bound window object
                /// </summary
                _windowPostMessage(message, _targetOrigin);
            },
            start: function(){
                //No-op
            },
            stop: function(){
                //No-op
            }
        };
    },
    createHashTransport: function(config){
        /// <summary>
        /// Sets up the infrastructure for sending and receiving
        /// messages using the the IFrame URI Technique.
        /// </summary
        /// <param name="config" type="object"></param>
        /// <returns type="object">An object able to send and receive strings</returns>
        var xss = this;
        var _timer = null;
        var _lastMsg = "#" + config.channel, _msgNr = 0;
        var _listenerWindow = (!config.local) ? window : null, _callerWindow;
        var _remoteUrl = config.remote + ((config.local) ? "?endpoint=" + config.local + "&channel=" + config.channel : "#" + config.channel);
        var _remoteOrigin = this.getLocation(config.remote);
        var _pollInterval = (config.interval) ? config.interval : 300;

        function _checkForMessage(){
            /// <summary>
            /// Checks location.hash for a new message and relays this to the receiver.
            /// </summary
            /// <remark>
            /// We have no way of knowing if messages have passed in between the checks.
            /// We could possibly device a way of reporting back the message number read
            /// so that the sender can concatenate multiple messages if previous message
            /// are unread.
            /// </remark>
            if (!_listenerWindow) {
                // If _listenerWindow is not already set (to window) then we try to attach the
                // frame located on [remote].
                // We need to include the hash part of the url to avoid reloading the frame.
                _listenerWindow = window.open(config.local + "#" + config.channel, "xss_" + config.channel);
            }
            if (_listenerWindow.location.hash && _listenerWindow.location.hash != _lastMsg) {
                _lastMsg = _listenerWindow.location.hash
                config.onMessage(decodeURIComponent(_lastMsg.substring(_lastMsg.indexOf("_") + 1)), _remoteOrigin);
            }
        }

        function _postMessage(message){
            /// <summary>
            /// Sends a message by encoding and placing it in the hash part of _callerWindows url.
            /// </summary
            /// <param name="message" type="string">The message to send</param>
            /// <remark>
            /// We include a message number so that identical messages will be read as separate messages.
            /// </remark>
            _callerWindow.src = _remoteUrl + "#" + (_msgNr++) + "_" + encodeURIComponent(message);
        }

        _callerWindow = xss.createFrame(_remoteUrl, ((config.local) ? "" : "xss_" + config.channel), function(){
            if (config.onReady) {
                if (config.local) {
                    // Register onReady callback in the library so that
                    // it can be called when hash.html has loaded.
                    xss.registerOnReady(config.channel, config.onReady);
                }
                else {
                    config.onReady();
                }
            }
        });

        return {
            postMessage: _postMessage,
            start: function(){
                _timer = window.setInterval(function(){
                    _checkForMessage();
                }, _pollInterval);
            },
            stop: function(){
                window.clearInterval(_timer);
                _listenerWindow = null;
                _callerWindow.parentNode.removeChild(_callerWindow);
            }
        };
    }
}
<commit_msg>Done some minor tweaks.  It seems some browsers don't like it when you use references to postMessage instead of calling the interface directly. The library now works with postMessage in Firefox 3, Opera 9.64 and Safari 4.0.<commit_after>/*
 Copyright (c) <2009> <yvind Sean Kinsey, oyvind@kinsey.no>
 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 EasyXSS = {
    onReadyCallbacks: { //
        /// <summary>
        /// Hashtable for storing callbacks when using hashTransport
        /// </summary>
    },
    registerOnReady: function(channel, callback){
        /// <summary>
        /// Register a callback that can be called when hash.html is fully loaded.
        /// </summary>
        /// <param name="channeL" type="string">The name of the channel</param>
        /// <param name="callback" type="function">The function to call</param>
        this.onReadyCallbacks[channel] = callback;
    },
    onReady: function(channel){
        /// <summary>
        /// Call the onReady method associated with the channel
        /// </summary>
        /// <param name="channel" type="string">The name of the channel</param>
        var fn = this.onReadyCallbacks[channel];
        if (fn) {
            fn();
            delete this.onReadyCallbacks[channel];
        }
    },
    converters: {
        json2Converter: {//
            /// <summary>
            /// A converter that uses the JSON library for
            /// converting between JSON strings and objects.
            /// </summary>
            /// <remark>
            /// Requires an available JSON object, native or using JSON2
            /// http://json.org/json2.js
            /// </remark>
            convertToString: function(data){
                return JSON.stringify(data);
            },
            convertFromString: function(message){
                return JSON.parse(message);
            }
        },
        hashTableConverter: {//
            /// <summary>
            /// A converter that can convert to and from hashtables
            /// string values, or values having valid string representation.
            /// </summary>
            convertToString: function(data){
                var message = "";
                for (key in data) {
                    message += key + "=" + escape(data[key]) + "&";
                }
                return message.substring(0, message.length - 1);
            },
            convertFromString: function(message){
                var data = {};
                var d = message.split("&");
                var pair, key, value;
                for (var i = 0, len = d.length; i < len; i++) {
                    pair = d[i];
                    key = pair.substring(0, pair.indexOf("="));
                    value = pair.substring(key.length + 1);
                    data[key] = unescape(value);
                }
                return data;
            }
        }
    },
    Query: function(){
        /// <summary>
        /// Returns a hashtable containing the query parameteres
        /// </summary>
        /// <returns type="object">A hashtable</returns>
        var pair, key, value, hash = {}, search = location.search.substring(1).split("&");
        for (var i = 0, len = search.length; i < len; i++) {
            pair = search[i];
            key = pair.substring(0, pair.indexOf("="));
            value = pair.substring(key.length + 1);
            hash[key] = value;
        }
        return hash;
    },
    getDomainName: function(url){
        /// <summary>
        /// </summary>
        /// <param name="url" type="string">The url to extract the domain from</param>
        /// <returns type="string">The domain part of the url</returns>
        var domain = url.substring(url.indexOf("//") + 2);
        domain = domain.substring(0, domain.indexOf("/"));
        var _indexOf = domain.indexOf(":");
        if (_indexOf != -1) {
            domain = domain.substring(0, _indexOf);
        }
        return domain;
    },
    getLocation: function(uri){
        /// <summary>
        /// Returns a string containing the schema, domain and if present the port
        /// </summary
        /// <param name="url" type="string">The url to extract the location from</param>
        /// <returns type="string">The location part of the url</returns>
        var indexOf = uri.indexOf("//");
        var loc = uri.substring(indexOf + 2);
        loc = loc.substring(0, loc.indexOf("/"));
        return uri.substring(0, indexOf + 2) + loc;
    },
    createFrame: function(url, name, onLoad){
        /// <summary>
        /// Creates a frame and appends it to the DOM.
        /// </summary
        /// <param name="url" type="string">The url the frame should be set to</param>
        /// <param name="name" type="string">The id/name the frame should get</param>
        /// <param name="onLoad" type="function">
        /// A method that should be called with the frames contentWindow as argument
        /// when the frame is fully loaded.
        /// </param>
        /// <returns type="DOMElement">The frames DOMElement</returns>
        var frame;
        if (name && window.attachEvent) {
            // Internet Explorer does not support setting the
            // name om DOMElements created in Javascript.
            // A workaround is to insert HTML and have the browser parse
            // and instantiate the element.
            var span = document.createElement("span");
            document.body.appendChild(span);
            span.innerHTML = '<iframe src="' + url + '" id="' + name + '" name="' + name + '"></iframe>';
            frame = document.getElementById(name);
            if (onLoad) {
                this.addEventListener(frame, "load", function(){
                    onLoad(frame.contentWindow);
                })
            }
        }
        else {
            // When name is not needed, or in other browsers,
            // we use regular createElement.
            frame = document.createElement("IFRAME");
            frame.style.position = "absolute";
            frame.style.left = "-2000px";
            frame.name = name;
            frame.id = name;
            frame.src = url;
            if (onLoad) {
                this.addEventListener(frame, "load", function(){
                    onLoad(frame.contentWindow);
                })
            }
            document.body.appendChild(frame);
        }
        return frame;
    },
    addEventListener: function(element, event, handler){
        /// <summary>
        /// Gives a consistent interface for adding eventhandlers
        /// </summary
        /// <param name="element" type="DOMElement">The DOMElement to attach the handler to</param>
        /// <param name="event" type="string">The eventname</param>
        /// <param name="handler" type="function">The handler to attach</param>
        if (window.addEventListener) {
			element.addEventListener(event, handler, false);
        }
        else {
            element.attachEvent("on" + event, handler);
        }
    },
    removeEventListener: function(element, event, handler){
        /// <summary>
        /// Gives a consistent interface for removing eventhandlers
        /// </summary
        /// <param name="element" type="DOMElement">The DOMElement to remote the handler from</param>
        /// <param name="event" type="string">The eventname</param>
        /// <param name="handler" type="function">The handler to remove</param>
        if (window.removeEventListener) {
            element.removeEventListener(event, handler, false);
        }
        else {
            element.detachEvent("on" + event, handler);
        }
    },
    createTransport: function(config){
        /// <summary>
        /// Creates a transport channel using the available parameters.
        /// Parameters are collected both from the supplied config,
        /// but also from the querystring if not present in the config.
        /// </summary
        /// <param name="config" type="object"></param>
        /// <returns type="object">An object able to send and receive messages</returns>
        if (!config.local) {
            // We assume that this is the remote part of the channel
            var query = this.Query();
            config.channel = query["channel"];
            config.remote = query["endpoint"];
        }
        if (window.postMessage) {
            return this.createPostMessageTransport(config);
        }
        else {
            return this.createHashTransport(config);
        }
    },
    createChannel: function(config){
        /// <summary>
        /// Creates wrapper around the available transport mechanism that
        /// also enables you to insert a converter for the messages transmitted.
        /// </summary
        /// <param name="config" type="object"></param>
        /// <returns type="object">An object able to send and receive arbitrary data</returns>
        /// <remark>The supported data types depends on the converter used</remark>
        // If a converter is present then we wrap the callback in a call to this
        config.onMessage = (config.converter) ? (function(message, origin){
            this.onData(this.converter.convertFromString(message), origin);
        }) : config.onData;
        var transport = this.createTransport(config);
        return {
            transport: transport,
            // If a converter is present then we wrap postMessage in a call to this
            sendData: (config.converter) ? (function(data){
                this.transport.postMessage(config.converter.convertToString(data));
            }) : transport.sendMessage,
            start: transport.start,
            stop: transport.stop
        };
    },
    createPostMessageTransport: function(config){
        /// <summary>
        /// Sets up the infrastructure for sending and receiving
        /// messages using the window.postMessage interface.
        /// http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx
        /// https://developer.mozilla.org/en/DOM/window.postMessage
        /// </summary
        /// <param name="config" type="object"></param>
        /// <returns type="object">An object able to send and receive strings</returns>
        var xss = this;
        var _targetOrigin = xss.getLocation(config.remote);
        var _windowPostMessage;
        var _isStarted, _isReady;
        var _callerWindow;
        function _getOrigin(event){
            /// <summary>
            /// The origin property should be valid, but some clients
            /// still uses the uri or domain property.
            /// </summary
            /// <param name="event" type="MessageEvent">The eventobject from the browser</param>
            /// <returns type="string">The origin of the event</returns>
            if (event.origin) {
                return event.origin;
            }
            if (event.uri) {
                return xss.getLocation(event.uri);
            }
            if (event.domain) {
                // This will fail if the origin is not using the same
                // schema as we are
                return location.protocol + "//" + event.domain;
            }
            throw "Unable to retrieve the origin of the event"
        }

        function _window_onMessage(event){
            /// <summary>
            /// The handler for the "message" event.
            /// If the origin is allowed then we relay the message to the receiver
            /// </summary
            /// <param name="event" type="MessageEvent">The eventobject from the browser</param>
            var origin = _getOrigin(event);
			//alert(location.host +","+origin)
            if (origin == _targetOrigin) {
                config.onMessage(event.data, origin);
            }
        }
        function _onReady(){
            /// <summary>
            /// Calls the supplied onReady method
            /// </summary
            /// <remark>
            /// We delay this so that the the call to createChannel or
            /// createTransport will have completed
            /// </remark>
            if (config.onReady) {
                window.setTimeout(config.onReady, 10);
            }
        }

        if (config.local) {
            _callerWindow = xss.createFrame(config.remote + "?endpoint=" + config.local + "&channel=" + config.channel, "", function(win){
                _onReady();
            });
        }
        else {
            _onReady();
        }
		xss.addEventListener(window, "message", _window_onMessage);
        return {
            postMessage: function(message){
                /// <summary>
                /// Sends the message using the bound window object
                /// </summary
				if (config.local){
					_callerWindow.contentWindow.postMessage(message,_targetOrigin);
				}else{
					window.parent.postMessage(message,_targetOrigin);
				}
            },
            start: function(){
                //No-op
            },
            stop: function(){
                //No-op
            }
        };
    },
    createHashTransport: function(config){
        /// <summary>
        /// Sets up the infrastructure for sending and receiving
        /// messages using the the IFrame URI Technique.
        /// </summary
        /// <param name="config" type="object"></param>
        /// <returns type="object">An object able to send and receive strings</returns>
        var xss = this;
        var _timer = null;
        var _lastMsg = "#" + config.channel, _msgNr = 0;
        var _listenerWindow = (!config.local) ? window : null, _callerWindow;
        var _remoteUrl = config.remote + ((config.local) ? "?endpoint=" + config.local + "&channel=" + config.channel : "#" + config.channel);
        var _remoteOrigin = this.getLocation(config.remote);
        var _pollInterval = (config.interval) ? config.interval : 300;

        function _checkForMessage(){
            /// <summary>
            /// Checks location.hash for a new message and relays this to the receiver.
            /// </summary
            /// <remark>
            /// We have no way of knowing if messages have passed in between the checks.
            /// We could possibly device a way of reporting back the message number read
            /// so that the sender can concatenate multiple messages if previous message
            /// are unread.
            /// </remark>
            if (!_listenerWindow) {
                // If _listenerWindow is not already set (to window) then we try to attach the
                // frame located on [remote].
                // We need to include the hash part of the url to avoid reloading the frame.
                _listenerWindow = window.open(config.local + "#" + config.channel, "xss_" + config.channel);
            }
            if (_listenerWindow.location.hash && _listenerWindow.location.hash != _lastMsg) {
                _lastMsg = _listenerWindow.location.hash
                config.onMessage(decodeURIComponent(_lastMsg.substring(_lastMsg.indexOf("_") + 1)), _remoteOrigin);
            }
        }

        function _postMessage(message){
            /// <summary>
            /// Sends a message by encoding and placing it in the hash part of _callerWindows url.
            /// </summary
            /// <param name="message" type="string">The message to send</param>
            /// <remark>
            /// We include a message number so that identical messages will be read as separate messages.
            /// </remark>
            _callerWindow.src = _remoteUrl + "#" + (_msgNr++) + "_" + encodeURIComponent(message);
        }

        _callerWindow = xss.createFrame(_remoteUrl, ((config.local) ? "" : "xss_" + config.channel), function(){
            if (config.onReady) {
                if (config.local) {
                    // Register onReady callback in the library so that
                    // it can be called when hash.html has loaded.
                    xss.registerOnReady(config.channel, config.onReady);
                }
                else {
                    config.onReady();
                }
            }
        });

        return {
            postMessage: _postMessage,
            start: function(){
                _timer = window.setInterval(function(){
                    _checkForMessage();
                }, _pollInterval);
            },
            stop: function(){
                window.clearInterval(_timer);
                _listenerWindow = null;
                _callerWindow.parentNode.removeChild(_callerWindow);
            }
        };
    }
}
<|endoftext|>