Commit c90223a8 authored by Michał Woźniak's avatar Michał Woźniak
Browse files

Update dependencies (apart from Gun/SEA/WebRTC)

parent c2ab61c1
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Samizdat</title>
<script defer src="samizdat.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/gun/examples/jquery.js"></script>
<script src="./lib/gun.js"></script>
<script src="./lib/sea.js"></script>
<script src="./lib/webrtc.js"></script>
......@@ -12,15 +13,12 @@
<script src="./plugins/fetch.js"></script>
<script src="./plugins/cache.js"></script>
<script src="./plugins/gun-ipfs.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gun/lib/unset.js"></script>
<h1>Samizdat</h1>
<p>Censorship-resistant, browser-based content delivery <a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps">Progressive Web App</a>, using <a href="https://gun.eco/">Gun</a>, and <a href="https://github.com/ipfs/js-ipfs">JS-IPFS</a>.</p>
<p>This is a proof of concept.</p>
<div>
<!--p>Gun user is: <strong id="gun-user"></strong></p-->
<p>Gun user said:</p>
<ul id="gun-said"></ul>
<p>Gun user published:</p>
<ul id="gun-published"></ul>
</div>
......@@ -36,24 +34,23 @@
<ul id="tutorial"></ul>
<form id="said">
<input id="say">
<input id="speak" type="submit" value="speak">
</form>
<script>
var gun = Gun([/*'http://localhost:8765/gun', 'https://gunjs.herokuapp.com/gun'*/'https://samizdat.is/gun']);
/**
* just to prove Gun is active and getting content
*/
var testUser = gun.user('WUK5ylwqqgUorceQRa84qfbBFhk7eNRDUoPbGK05SyE.-yohFhTzWPpDT-UDMuKGgemOUrw_cMMYWpy6plILqrg')
testUser.get('said').map().once(function(say, id){
var li = $('#' + id).get(0) || $('<li>').attr('id', id).appendTo('ul#gun-said');
$(li).text(say);
})
testUser.get(window.location.host).map().once(function(data, id){
if (typeof data === 'string') {
var li = $('#' + id.replace(/\//g, '_')).get(0) || $('<li>').attr('id', id.replace(/\//g, '_')).appendTo('ul#gun-published');
$(li).text(id + " :: " + data);
console.log(`${id} -> ${data}`)
var li = document.getElementById(id.replace(/\//g, '_'))
if (li) {
li.innerHTPL = `<strong>${id}</strong><br/>${data}`
} else {
li = document.createElement('template')
li.innerHTML = `<li id="${id.replace(/\//g, '_')}"><strong>${id}</strong><br/>${data}</li>`
document.getElementById('gun-published').insertAdjacentElement('beforeend', li.content.firstChild.cloneNode(true))
}
}
})
......@@ -63,30 +60,17 @@
* actual code that handles actual things is in service-worker.js
*/
var user = gun.user();
$('#up').on('click', function(e){
user.create($('#alias').val(), $('#pass').val());
document.getElementById('up').addEventListener('click', function(e){
user.create(document.getElementById('alias').value, document.getElementById('pass').value);
});
$('#sign').on('submit', function(e){
document.getElementById('sign').addEventListener('submit', function(e){
e.preventDefault();
user.auth($('#alias').val(), $('#pass').val());
user.auth(document.getElementById('alias').value, document.getElementById('pass').value);
});
$('#said').on('submit', function(e){
e.preventDefault();
if(!user.is){ return }
user.get('said').set($('#say').val());
$('#say').val("");
});
function UI(say, id){
var li = $('#' + id).get(0) || $('<li>').attr('id', id).appendTo('ul#tutorial');
$(li).text(say);
};
gun.on('auth', function(){
$('#sign').hide();
user.get('said').map().once(UI);
document.getElementById('sign').style.visibility = "hidden";
});
</script>
</body>
......
This diff is collapsed.
This diff is collapsed.
;(function(){
/* UNBUILD */
var root;
if(typeof window !== "undefined"){ root = window }
if(typeof global !== "undefined"){ root = global }
root = root || {};
var console = root.console || {log: function(){}};
function USE(arg, req){
return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
arg(mod = {exports: {}});
......@@ -16,7 +10,7 @@
return p.split('/').slice(-1).toString().replace('.js','');
}
}
if(typeof module !== "undefined"){ var common = module }
if(typeof module !== "undefined"){ var MODULE = module }
/* UNBUILD */
;USE(function(module){
......@@ -32,7 +26,7 @@
if(SEA.window = module.window){ SEA.window.SEA = SEA }
try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
try{ if(typeof MODULE !== "undefined"){ MODULE.exports = SEA } }catch(e){}
module.exports = SEA;
})(USE, './root');
......@@ -50,10 +44,10 @@
;USE(function(module){
if(typeof btoa === "undefined"){
if(typeof Buffer === "undefined") {
root.Buffer = require("buffer").Buffer
global.Buffer = require("buffer").Buffer
}
root.btoa = function (data) { return Buffer.from(data, "binary").toString("base64"); };
root.atob = function (data) { return Buffer.from(data, "base64").toString("binary"); };
global.btoa = function (data) { return Buffer.from(data, "binary").toString("base64"); };
global.atob = function (data) { return Buffer.from(data, "base64").toString("binary"); };
}
})(USE, './base64');
......@@ -170,7 +164,7 @@
var o = {};
if(SEA.window){
api.crypto = navigator && navigator.product === 'ReactNative' ? require('isomorphic-webcrypto') : window.crypto || window.msCrypto || require('isomorphic-webcrypto');
api.crypto = window.crypto || window.msCrypto
api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
api.TextEncoder = window.TextEncoder;
api.TextDecoder = window.TextDecoder;
......@@ -182,17 +176,20 @@
api.TextDecoder = TextDecoder;
api.TextEncoder = TextEncoder;
}
if(!api.crypto){try{
if(!api.crypto)
{
try
{
var crypto = USE('crypto', 1);
Object.assign(api, {
crypto,
random: (len) => Buffer.from(crypto.randomBytes(len))
});
const isocrypto = require('isomorphic-webcrypto');
api.ossl = api.subtle = isocrypto.subtle;
}catch(e){
const { Crypto: WebCrypto } = USE('@peculiar/webcrypto', 1);
api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH
}
catch(e){
console.log("text-encoding and @peculiar/webcrypto may not be included by default, please add it to your package.json!");
TEXT_ENCODING_OR_PECULIAR_WEBCRYPTO_NOT_INSTALLED;
}}
module.exports = api
......@@ -275,13 +272,13 @@
cb = salt;
salt = u;
}
salt = salt || shim.random(9);
data = (typeof data == 'string')? data : JSON.stringify(data);
if('sha' === (opt.name||'').toLowerCase().slice(0,3)){
var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64')
if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
return rsha;
}
salt = salt || shim.random(9);
var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']);
var work = await (shim.ossl || shim.subtle).deriveBits({
name: opt.name || 'PBKDF2',
......@@ -439,7 +436,7 @@
opt = opt || {};
// SEA.I // verify is free! Requires no user permission.
var pub = pair.pub || pair;
var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']);
var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', S.jwk(pub), {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']);
var hash = await sha(json.m);
var buf, sig, check, tmp; try{
buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT!
......@@ -474,9 +471,11 @@
return knownKeys[pair];
};
var O = SEA.opt;
SEA.opt.fall_verify = async function(data, pair, cb, opt, f){
if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1;
var tmp = data||'';
data = SEA.opt.unpack(data) || data;
var json = S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub);
var hash = (f <= SEA.opt.fallback)? shim.Buffer.from(await shim.subtle.digest({name: 'SHA-256'}, new shim.TextEncoder().encode(S.parse(json.m)))) : await sha(json.m); // this line is old bad buggy code but necessary for old compatibility.
var buf; var sig; var check; try{
......@@ -491,6 +490,7 @@
if(!check){ throw "Signature did not match." }
}
var r = check? S.parse(json.m) : u;
O.fall_soul = tmp['#']; O.fall_key = tmp['.']; O.fall_val = data; O.fall_state = tmp['>'];
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
}
......@@ -652,14 +652,14 @@
;USE(function(module){
var shim = USE('./shim');
// Practical examples about usage found from ./test/common.js
// Practical examples about usage found in tests.
var SEA = USE('./root');
SEA.work = USE('./work');
SEA.sign = USE('./sign');
SEA.verify = USE('./verify');
SEA.encrypt = USE('./encrypt');
SEA.decrypt = USE('./decrypt');
SEA.opt.aeskey = USE('./aeskey'); // not official!
//SEA.opt.aeskey = USE('./aeskey'); // not official! // this causes problems in latest WebCrypto.
SEA.random = SEA.random || shim.random;
......@@ -701,7 +701,7 @@
// But all other behavior needs to be equally easy, like opinionated ways of
// Adding friends (trusted public keys), sending private messages, etc.
// Cheers! Tell me what you think.
var Gun = (SEA.window||{}).Gun || USE((typeof common == "undefined"?'.':'')+'./gun', 1);
var Gun = (SEA.window||{}).Gun || USE((typeof MODULE == "undefined"?'.':'')+'./gun', 1);
Gun.SEA = SEA;
SEA.GUN = SEA.Gun = Gun;
......@@ -710,9 +710,9 @@
;USE(function(module){
var Gun = USE('./sea').Gun;
Gun.chain.then = function(cb){
Gun.chain.then = function(cb, opt){
var gun = this, p = (new Promise(function(res, rej){
gun.once(res);
gun.once(res, opt);
}));
return cb? p.then(cb) : p;
}
......@@ -740,7 +740,7 @@
at.opt.uuid = function(cb){
var id = uuid(), pub = root.user;
if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
id = id + '~' + pub + '.';
id = id + '~' + pub + '/';
if(cb && cb.call){ cb(null, id) }
return id;
}
......@@ -1086,21 +1086,59 @@
}());
return gun;
}
/**
* returns the decrypted value, encrypted by secret
* @returns {Promise<any>}
// Mark needs to review 1st before officially supported
User.prototype.decrypt = function(cb) {
let gun = this,
path = ''
gun.back(function(at) {
if (at.is) {
return
}
path += at.get || ''
})
return gun
.then(async data => {
if (data == null) {
return
}
const user = gun.back(-1).user()
const pair = user.pair()
let sec = await user
.get('trust')
.get(pair.pub)
.get(path)
sec = await SEA.decrypt(sec, pair)
if (!sec) {
return data
}
let decrypted = await SEA.decrypt(data, sec)
return decrypted
})
.then(res => {
cb && cb(res)
return res
})
}
*/
module.exports = User
})(USE, './create');
;USE(function(module){
const SEA = USE('./sea')
const Gun = SEA.Gun;
var SEA = USE('./sea')
var Gun = SEA.Gun;
// After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
// We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
Gun.on('opt', function(at){
if(!at.sea){ // only add SEA once per instance, on the "at" context.
at.sea = {own: {}};
at.on('in', security, at); // now listen to all input data, acting as a firewall.
at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
at.on('node', each, at);
//at.on('in', security, at); // now listen to all input data, acting as a firewall.
//at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
at.on('put', check, at);
}
this.to.next(at); // make sure to call the "next" middleware adapter.
});
......@@ -1149,6 +1187,94 @@
security.call(this, msg);
}
var u;
function check(msg){ // REVISE / IMPROVE, NO NEED TO PASS MSG/EVE EACH SUB?
var eve = this, at = eve.as, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp;
if(!soul || !key){ return }
if((msg._||'').faith && (at.opt||'').faith && 'function' == typeof msg._){
SEA.verify(SEA.opt.pack(put), false, function(data){ // this is synchronous if false
put['='] = SEA.opt.unpack(data);
eve.to.next(msg);
});
return
}
var no = function(why){ at.on('in', {'@': id, err: why}) };
//var no = function(why){ msg.ack(why) };
(msg._||'').DBG && ((msg._||'').DBG.c = +new Date);
if(0 <= soul.indexOf('<?')){ // special case for "do not sync data X old"
// 'a~pub.key/b<?9'
tmp = parseFloat(soul.split('<?')[1]||'');
if(tmp && (state < (Gun.state() - (tmp * 1000)))){ // sec to ms
(tmp = msg._) && (tmp = tmp.lot) && (tmp.more--); // THIS IS BAD CODE! It assumes GUN internals do something that will probably change in future, but hacking in now.
return; // omit!
}
}
if('~@' === soul){ // special case for shared system data, the list of aliases.
check.alias(eve, msg, val, key, soul, at, no); return;
}
if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
check.pubs(eve, msg, val, key, soul, at, no); return;
}
//if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
if(tmp = SEA.opt.pub(soul)){ // special case, account data for a public key.
check.pub(eve, msg, val, key, soul, at, no, at.user||'', tmp); return;
}
if(0 <= soul.indexOf('#')){ // special case for content addressing immutable hashed data.
check.hash(eve, msg, val, key, soul, at, no); return;
}
check.any(eve, msg, val, key, soul, at, no, at.user||''); return;
eve.to.next(msg); // not handled
}
check.hash = function(eve, msg, val, key, soul, at, no){
SEA.work(val, null, function(data){
if(data && data === key.split('#').slice(-1)[0]){ return eve.to.next(msg) }
no("Data hash not same as hash!");
}, {name: 'SHA-256'});
}
check.alias = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@, ~@alice: {#~@alice}}
if(!val){ return no("Data must exist!") } // data MUST exist
if('~@'+key === link_is(val)){ return eve.to.next(msg) } // in fact, it must be EXACTLY equal to itself
no("Alias not same!"); // if it isn't, reject.
};
check.pubs = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
if(!val){ return no("Alias must exist!") } // data MUST exist
if(key === link_is(val)){ return eve.to.next(msg) } // and the ID must be EXACTLY equal to its property
no("Alias not same!"); // that way nobody can tamper with the list of public keys.
};
check.pub = function(eve, msg, val, key, soul, at, no, user, pub){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
if('pub' === key && '~'+pub === soul){
if(val === pub){ return eve.to.next(msg) } // the account MUST match `pub` property that equals the ID of the public key.
return no("Account not same!");
}
if((tmp = user.is) && pub === tmp.pub){
SEA.sign(SEA.opt.pack(msg.put), (user._).sea, function(data){
if(u === data){ return no(SEA.err || 'Signature fail.') }
if(tmp = link_is(val)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
msg.put[':'] = JSON.stringify({':': tmp = SEA.opt.unpack(data.m), '~': data.s});
msg.put['='] = tmp;
eve.to.next(msg);
}, {raw: 1});
return;
}
SEA.verify(SEA.opt.pack(msg.put), pub, function(data){ var tmp;
data = SEA.opt.unpack(data);
if(u === data){ return no("Unverified data.") } // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account.
if((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
msg.put['='] = data;
eve.to.next(msg);
});
};
check.any = function(eve, msg, val, key, soul, at, no, user){ var tmp, pub;
if(at.opt.secure){ return no("Soul missing public key at '" + key + "'.") }
// TODO: Ask community if should auto-sign non user-graph data.
at.on('secure', function(msg){ this.off();
if(!at.opt.secure){ return eve.to.next(msg) }
no("Data cannot be changed.");
}).on.on('secure', msg);
return;
}
var link_is = Gun.val.link.is, state_ify = Gun.state.ify;
// okay! The security function handles all the heavy lifting.
// It needs to deal read and write of input and output of system data, account/public key data, and regular data.
// This is broken down into some pretty clear edge cases, let's go over them:
......@@ -1174,6 +1300,11 @@
}
}
if(msg.put){
/*
NOTICE: THIS IS OLD AND GETTING DEPRECATED.
ANY SECURITY CHANGES SHOULD HAPPEN ABOVE FIRST
THEN PORTED TO HERE.
*/
// potentially parallel async operations!!!
var check = {}, each = {}, u;
each.node = function(node, soul){
......@@ -1298,12 +1429,14 @@
}
to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
}
var pubcut = /[^\w_-]/; // anything not alphanumeric or _ -
SEA.opt.pub = function(s){
if(!s){ return }
s = s.split('~');
if(!s || !(s = s[1])){ return }
s = s.split('.');
if(!s || 2 > s.length){ return }
s = s.split(pubcut).slice(0,2);
if(!s || 2 != s.length){ return }
if('@' === (s[0]||'')[0]){ return }
s = s.slice(0,2).join('.');
return s;
}
......@@ -1312,16 +1445,18 @@
}
SEA.opt.pack = function(d,k, n,s){ // pack for verifying
if(SEA.opt.check(d)){ return d }
var meta = (Gun.obj.ify(d)||noop), sig = meta['~'];
return sig? {m: {'#':s,'.':k,':':meta[':'],'>':Gun.state.is(n, k)}, s: sig} : d;
var meta = (Gun.obj.ify((d && d[':'])||d)||''), sig = meta['~'];
return sig? {m: {'#':s||d['#'],'.':k||d['.'],':':meta[':'],'>':d['>']||Gun.state.is(n, k)}, s: sig} : d;
}
var O = SEA.opt;
SEA.opt.unpack = function(d, k, n){ var tmp;
if(u === d){ return }
if(d && (u !== (tmp = d[':']))){ return tmp }
k = k || O.fall_key; if(!n && O.fall_val){ n = {}; n[k] = O.fall_val }
if(!k || !n){ return }
if(d === n[k]){ return d }
if(!SEA.opt.check(n[k])){ return d }
var soul = Gun.node.soul(n), s = Gun.state.is(n, k);
var soul = Gun.node.soul(n) || O.fall_soul, s = Gun.state.is(n, k) || O.fall_state;
if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
return d[2];
}
......@@ -1333,6 +1468,7 @@
var noop = function(){}, u;
var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
var rel_is = Gun.val.rel.is;
var obj_ify = Gun.obj.ify;
// TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.
})(USE, './index');
......
......@@ -21,13 +21,16 @@
opt.RTCSessionDescription = rtcsd;
opt.RTCIceCandidate = rtcic;
opt.rtc = opt.rtc || {'iceServers': [
{url: 'stun:stun.l.google.com:19302'},
{url: "stun:stun.sipgate.net:3478"},
{url: "stun:stun.stunprotocol.org"},
{url: "stun:stun.sipgate.net:10000"},
{url: "stun:217.10.68.152:10000"},
{url: 'stun:stun.services.mozilla.com'}
{urls: 'stun:stun.l.google.com:19302'},
{urls: "stun:stun.sipgate.net:3478"}/*,
{urls: "stun:stun.stunprotocol.org"},
{urls: "stun:stun.sipgate.net:10000"},
{urls: "stun:217.10.68.152:10000"},
{urls: 'stun:stun.services.mozilla.com'}*/
]};
// TODO: Select the most appropriate stuns.
// FIXME: Find the wire throwing ICE Failed
// The above change corrects at least firefox RTC Peer handler where it **throws** on over 6 ice servers, and updates url: to urls: removing deprecation warning
opt.rtc.dataChannel = opt.rtc.dataChannel || {ordered: false, maxRetransmits: 2};
opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}};
opt.announce = function(to){
......
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"binary": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
"integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
"requires": {
"buffers": "0.1.1",
"chainsaw": "0.1.0"
}
},
"bl": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
"requires": {
"buffer": "5.6.0",
"inherits": "2.0.4",
"readable-stream": "3.6.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "2.0.4",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
}
}
}
},
"browserify-zlib": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
"integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=",
"requires": {
"pako": "0.2.9"
}
},
"buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"requires": {
"base64-js": "1.3.1",
"ieee754": "1.1.13"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"buffers": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
"integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
},
"chainsaw": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
"integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
"requires": {
"traverse": "0.3.9"
}
},
"chownr": {
"version": "1.1.4",
"res