Hey @oskaremilsson and @Emil, great work on getting this together.  I have built on top of this with the following:
- timeout when recording an IR signal
- optional logging of TCP requests/responses
- ignore favicon.ico requests when using a browser
- the server now has 4 methods using a semantic url path
The four server methods are:
- GET /putIRarray/<IR_NAME>:<IR_ARRAY>to store IR arrays crowd-sourced from others
- GET /getIRarray/<IR_NAME>to share your IR arrays with others
- GET /recordIR/<IR_NAME>to record an IR array, with timeout
- GET /playIR/<IR_SEQUENCE>to play IR sequences
<IR_NAME> (string) is the name you choose for your IR code(s)
<IR_ARRAY> (string) is, without spaces, in the form from the Flic Hub SDK docs, eg: [<CARRIER_FREQ>,<ON_us>,<OFF_us>,<ON_us>,...]
<IR_SEQUENCE> (string) is a comma-separated list of <IR_NAME>, eg: /playIR/tvOn or playIR/tvOn,lightsOff
To stagger when the IR codes start use <IR_NAME>:<SECONDS_DELAY> (0.1 second precision), eg: playIR/tvOn:2.0,lightsOff
Here's the code:
// main.js
const tcpServer = require('./tcpServer');
// tcpServer.js
// Based on work by Oskar Emilsson https://community.flic.io/topic/18043/irtcp-v1-0-0-hub-server-for-using-ir-module-via-http
const net = require('net');
const irUtils = require('./irUtils');
// Set true to log TCP (HTTP) requests/responses
const logRequestResponse = false;
const respondToClient = function(c, statusCode, message, log) {
  
  var content = 'HTTP/1.1 '+statusCode+'\r\n'+'\r\n'+message;
  if(typeof log === 'undefined') log = logRequestResponse;
  if(log) {
    console.log('\n# HTTP RESPONSE');
    console.log(content);
    console.log('# END HTTP RESPONSE\n');
  }
  c.write(content);
  c.end();
}
var server = net.createServer(function(c) {
  console.log('Server connected');
  c.on('end', function() {
    console.log('Server disconnected\n');
  });
  c.on('data', function(data) {
    // Convert TCP content byte array to string
    var content = data.toString();
    
    // Ignore favicon requests from a browser
    if(content.indexOf('GET /favicon.ico') !== -1) {
      console.log('# ignoring favicon.ico request');
      return respondToClient(c, '200 OK', '', false);
    }
    if(logRequestResponse) {
      console.log('\n# HTTP REQUEST');
      console.log(content);
      console.log('# END HTTP REQUEST\n');      
    }
    // The first line of the raw TCP will look something like this "GET playIR/tvOn:2.0,lightsOff HTTP/1.1"
    // Check for URL paths /recordIR/<IR_NAME>, /playIR/<IR_SEQUENCE>, /putIRarray/<IR_NAME>:<IR_ARRAY> or /getIRarray/<IR_NAME>
    // <IR_ARRAY> is, without spaces, in the form from the docs [<CARRIER_FREQ>,<ON_us>,<OFF_us>,<ON_us>,...]
    
    // <IR_SEQUENCE> is a comma-separated list of <IR_NAME>, eg: playIR/tvOn or playIR/tvOn,lightsOff
    // To stagger when the IR codes start use <IR_NAME>:<SECONDS_DELAY> (0.1 second precision), eg: playIR/tvOn:2.0,lightsOff
    // From the Hub SDK documentation "If another play is started before a previous one has completed, it gets enqueued and starts as soon as the previous completes (max 100 enqueued signals)"
    var recordIRmatch = content.match(/GET \/recordIR\/(.[^ ]*) HTTP/);
    var playIRmatch = content.match(/GET \/playIR\/(.[^ ]*) HTTP/);
    var putIRarraymatch = content.match(/GET \/putIRarray\/(.[^ ]*) HTTP/);
    var getIRarraymatch = content.match(/GET \/getIRarray\/(.[^ ]*) HTTP/);
    
    if(recordIRmatch && recordIRmatch[1]) {
      // Start recording an IR signal
      irUtils.record(c, recordIRmatch[1]);
    }
    else if(playIRmatch && playIRmatch[1]) {
      // Play an IR signal or IR signal sequence
      var items = playIRmatch[1].split(',');
      irUtils.play(c, items);
    }
    else if(putIRarraymatch && putIRarraymatch[1]) {
      // Store an IR signal
      var splitPath = putIRarraymatch[1].split(':');
      if(splitPath.length == 2) {
        var irArray = JSON.parse(splitPath[1]);
        if(Array.isArray(irArray) && irArray.length % 2 === 0) {
          irUtils.put(c, splitPath[0], splitPath[1]);
        }
        else {
          respondToClient(c, '400 Bad Request', 'Use the form /putIRarray/<IR_NAME>:<IR_ARRAY>\r\n\r\n<IR_ARRAY> is, without spaces, in the form from the Flic Hub SDK docs [<CARRIER_FREQ>,<ON_us>,<OFF_us>,<ON_us>,...] and must have an even number of items (finishing with an <ON_us> item)');
        }
      }
      else {
        respondToClient(c, '400 Bad Request', 'Use the form /putIRarray/<IR_NAME>:<IR_ARRAY>\r\n\r\n<IR_ARRAY> is, without spaces, in the form from the Flic Hub SDK docs [<CARRIER_FREQ>,<ON_us>,<OFF_us>,<ON_us>,...] and must have an even number of items (finishing with an <ON_us> item)');
      }
    }
    else if(getIRarraymatch && getIRarraymatch[1]) {
      // Retrieve an IR signal
      irUtils.get(c, getIRarraymatch[1]);
    }
    else {
      respondToClient(c, '400 Bad Request', 'Valid url paths are recordIR/<IR_NAME> and playIR/<IR_SEQUENCE> \r\n\r\nWhere <IR_SEQUENCE> is a comma-separated list of <IR_NAME>, eg: playIR/tvOn or playIR/tvOn,lightsOff \r\n\r\nTo stagger when the IR codes start use <IR_NAME>:<SECONDS_DELAY> (0.1 second precision), eg: playIR/tvOn:2.0,lightsOff');
    }
  }); // on.data
}); // net.createServer
server.listen(1338, function() {
  console.log('Server bound', server.address().port);
});
exports.respondToClient = respondToClient;
// irUtils.js
const ir = require('ir');
const datastore = require('datastore');
const server = require('./tcpServer');
// Set true to respond before playing IR signals (and after checking all signals have been recorded)
// You may want a faster response for the system requesting the IR signal(s) playback, although you will not know if ir.play() fails
const respondBeforePlaying = false;
const _irSignal2str = function(signal) {
  // Instead of using signal.buffer use the JS object (undocumented, this might not always be available)
  // The object keys are "0", "1", etc and the values are the integers that need to be in the array
  // Convert the JS object to an array of integers
  var items = [];
  for(var i=0; i<Object.keys(signal).length;i++) {
    items.push(signal[i]);
  }
  return JSON.stringify(items);
}
const _str2irSignal = function(str) {
  return new Uint32Array(JSON.parse(str));
}
const put = function(c, name, data) {
  console.log('@ irUtils.put',name,data);
  
  datastore.put(name, data, function(err) {
    console.log('@ datastore.put callback');
    if (!err) {
      console.log('IR signal '+name+' stored');
      server.respondToClient(c, '200 OK', 'IR signal '+name+' stored');
    } else {
      console.error('# error: ', error);
      server.respondToClient(c, '500 Internal Server Error', 'Could not store IR signal');
    }
  }); // datastore.put
}
const get = function(c, name) {
  console.log('@ irUtils.get '+name);
  datastore.get(name, function(err, str) {
    console.log('@ datastore.get callback');
    if(!err && typeof str === 'string' && str.length > 0) {
      server.respondToClient(c, '200 OK', str);
    }
    else {
      server.respondToClient(c, '404 Not Found', 'Could not find IR signal '+name);
    }    
  }); // datastore.get
}
const record = function(c, name) {
  console.log('@ irUtils.record '+name);
  
  // Start recording
  ir.record();
  // Set up a timeout timer for 5 seconds
  var timeoutTimer = setTimeout(function(){
    ir.cancelRecord();
    console.log('Recording IR signal '+name+' TIMEOUT');
    clearTimeout(timeoutTimer);
    server.respondToClient(c, '408 Request Timeout', 'Recording IR signal '+name+' TIMEOUT');
    return;
  },5000);
  // Wait for recordComplete event
  ir.on('recordComplete', function(signal) {  
    console.log('@ ir.on.recordComplete');
    // Stop the timeout timer
    clearTimeout(timeoutTimer);
    
    // Convert the signal to a string
    var data = _irSignal2str(signal);
    console.log(data);
    // Store the data
    put(c, name, data);
  }); // ir.on.recordComplete
}
const play = function(c, items) {
  console.log('@ irUtils.play '+items);
  // Check all the IR codes exist
  const retrievalMs = 20;
  var index = 0;
  var irCodes = {};
  var errors = '';
  // datastore is async, so give each item time to be retrieved
  var fetchingTimer = setInterval(function(){
    var item = items[index].split(':')[0];
    if(typeof irCodes[item] !== 'undefined') {
      console.log('# '+item+' already retrieved');
      if(++index === items.length) clearTimeout(fetchingTimer);      
    }
    else {
      console.log('# getting '+item+' from datastore')
      datastore.get(item, function(err, str) {
        console.log('@ datastore.get callback');
        if(!err && typeof str === 'string' && str.length > 0) {
          irCodes[item] = str;
        }
        else {
          console.error('Cannot find IR code '+item+' in datastore.');
          errors += 'Cannot find IR code '+item+' in datastore. ';
        }    
        if(++index === items.length) clearTimeout(fetchingTimer);
      }); // datastore.get
    }
  },retrievalMs); // setInterval
  // Wait for datastore to finish
  setTimeout(function(){
    if(errors !== '') {
      server.respondToClient(c, '400 Bad Request', errors);
      return;
    }
    console.log(JSON.stringify(irCodes,null,2));
    
    if(respondBeforePlaying) server.respondToClient(c, '200 OK', 'Sending IR signal(s)');
    
    // Set up a timer to process the queue and pauses
    var pausingTenths = 0;
    var sendingTimer = setInterval(function(){
      if(pausingTenths > 0) {
        // Keep pausing
        pausingTenths--;
      }
      else {
        if(items.length > 0) {
          var itemSplit = items.shift().split(':');
          // Play the IR code
          console.log('# Sending IR code '+itemSplit[0]);
          var signal = _str2irSignal(irCodes[itemSplit[0]]);
          ir.play(signal, function(err) {
            if(err) {
              clearTimeout(sendingTimer);
              if(!respondBeforePlaying) server.respondToClient(c, '500 Internal Server Error', 'Could not send IR signal '+itemSplit[0]);
              return;
            }
          });
          // Add a pause if requested
          if(itemSplit[1] && typeof parseFloat(itemSplit[1]) === 'number') {
            var pause = parseFloat(itemSplit[1]);
            console.log('# Adding '+pause+' seconds pause');
            pausingTenths = parseInt(pause*10);
          }
        }
        else {
          // Finish up
          console.log('# Finished IR send');
          clearTimeout(sendingTimer);
          if(!respondBeforePlaying) server.respondToClient(c, '200 OK', 'Sent IR signal(s)');
        }
      }
    },100); // setInterval
    
  },retrievalMs*(items.length+1)); // setTimeout
}
exports.put = put;
exports.get = get;
exports.record = record;
exports.play = play;