UNPKG

telehash

Version:

A telehash library for node and browserify

145 lines (118 loc) 3.68 kB
var Duplex = require('stream').Duplex; var util = require("util"); var lob = require('lob-enc'); var log = require("../lib/util/log")("Stream") module.exports = ChannelStream /** ChannelStream impliments a Duplex stream API over Telehash channels. * for Duplex stream usage, consult core node docs. for an idea of how you might * expand upon streams within the Telehash ecosystem, see thtp * @class ChannelStream * @constructor * @param {Channel} channel - a Telehash channel (generated by e3x) * @param {stringa} encoding - 'binary' or 'json' * @return {Stream} */ function ChannelStream(chan, encoding){ if(!encoding) encoding = 'binary'; if(typeof chan != 'object' || !chan.isChannel) { log.warn('invalid channel passed to streamize'); return false; } var allowHalfOpen = (chan.type === "thtp") ? true : false; Duplex.call(this,{allowHalfOpen: allowHalfOpen, objectMode:true}) this.on('finish',function(){ chan.send({json:{end:true}}); }); this.on('error',function(err){ if(err == chan.err) return; // ignore our own generated errors chan.send({json:{err:err.toString()}}); }); var stream = this this.on('pipe', function(from){ from.on('end',function(){ stream.end() }) }) chan.receiving = chan_to_stream(this); this._chan = chan; this._encoding = encoding; return this; } util.inherits(ChannelStream, Duplex) ChannelStream.prototype._read = function(size){ if(this._getNextPacket) this._getNextPacket(); this._getNextPacket = false; }; ChannelStream.prototype._write = function(data,enc,cbWrite) { cbWrite = cbWrite || function(){}; if(this._chan.state == 'gone') return cbWrite('closed'); // switch to our default encoding syntax // dynamically detect object streams and change encoding if(!Buffer.isBuffer(data) && typeof data != 'string') { data = JSON.stringify(data); this._encoding = 'json'; } // fragment it while(data.length) { var frag = data.slice(0,1000); data = data.slice(1000); var packet = {json:{},body:frag}; // last packet gets continuation callback if(!data.length) { if(enc != 'binary') packet.json.enc = this._encoding; packet.callback = cbWrite; }else{ packet.json.frag = true; } this._chan.send(packet); } } function chan_to_stream (stream){ var data = new Buffer(0); return function receiving(err, packet, getNextPacket) { // was a wait writing, let it through if(err) stream.emit('error',err); if(packet.body.length || data.length) { data = Buffer.concat([data,packet.body]); if(!packet.json.frag) { var body = data; data = new Buffer(0); if(packet.json.enc == 'json') try{ body = JSON.parse(body) }catch(E){ low.warn('stream json frag parse error',E,body.toString()); err = E; } if(packet.json.enc == 'lob') { var packet = lob.decode(body); if(!packet) { log.warn('stream lob frag decode error',body.toString('hex')); err = 'lob decode failed'; }else{ body = packet; } } // stream consumer is not ready for another packet yet, so hold on // before getting more to send to readable... if(!err && !stream.push(body)) stream._getNextPacket = getNextPacket; } } //the packet has been read by stream consumer, so get the next one if(!stream._getNextPacket) getNextPacket(); //close the stream if this is the last packet if(packet.json.end) stream.push(null); }; }