diff --git a/.travis.yml b/.travis.yml index 93f7a625..4cf6f163 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ branches: except: - debug node_js: - - 4 - - 6 - 8 - 10 cache: diff --git a/README.md b/README.md index 81bb0b3b..c2438c08 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ -

- - -

+

[![npm](https://img.shields.io/npm/dm/gun.svg)](https://www.npmjs.com/package/gun) [![Travis](https://img.shields.io/travis/amark/gun/master.svg)](https://travis-ci.org/amark/gun) @@ -9,7 +6,9 @@ [![Gitter](https://img.shields.io/gitter/room/amark/gun.js.svg)](https://gitter.im/amark/gun) [![](https://data.jsdelivr.com/v1/package/npm/gun/badge?style=rounded)](https://www.jsdelivr.com/package/npm/gun) -GUN is a realtime, distributed, offline-first, graph database engine. Doing **[20M+](https://github.com/amark/gun/wiki/100000-ops-sec-in-IE6-on-2GB-Atom-CPU) ops/sec** in just **~9KB** gzipped. +**GUN** is an _ecosystem_ of tools that let you build tomorrow's dApps, today. + +Decentralized alternatives to [Reddit](https://notabug.io/t/whatever/comments/36588a16b9008da4e3f15663c2225e949eca4a15/gpu-bot-test), [YouTube](https://d.tube/), [Wikipedia](https://news.ycombinator.com/item?id=17685682), etc. are already pushing terabytes of daily P2P traffic on GUN. We are a [friendly community](https://gitter.im/amark/gun) creating a free fun future for freedom: @@ -22,22 +21,16 @@ GUN is a realtime, distributed, offline-first, graph database engine. Doing **[2
- - - - -
+The ecosystem is one nice stack of technologies that looks like this: -## Why? +
+
- - **Realtime** - You might use Socket.IO for realtime updates, but what happens if you reload the page? GUN solves *state synchronization* for you, no matter what, on reloads, across all your users, and even on conflicting updates. - - **Distributed** - GUN is peer-to-peer by design, meaning you have no centralized database server to maintain or that could crash. This lets you sleep through the night without worrying about database DevOps - we call it "NoDB". From there, you can build decentralized, federated, or centralized apps. - - **Offline-first** - GUN works even if your internet or cell reception doesn't. Users can still plug away and save data as normal, and then when the network comes back online GUN will automatically synchronize all the changes and handle any conflicts for you. - - **Graph** - Most databases force you to bend over backwards to match their storage constraints. But graphs are different, they let you have any data structure you want. Whether that be traditional tables with relations, document oriented trees, or tons of circular references. You choose. +For now, it is best to start with GUN and _just use it_ to learn the basics, since it is _**so easy**_: (**or** want to read more? Skip ahead to the "[What is GUN?](#what-is-gun)" section.) ## Quickstart - - Try the [interactive tutorial](https://gun.eco/think.html) in the browser (**5min** ~ average developer). + - Try the [interactive tutorial](https://gun.eco/docs/Todo-Dapp) in the browser (**5min** ~ average developer). - Or `npm install gun` and run the examples with `cd node_modules/gun && npm start` (**5min** ~ average developer). > **Note:** If you don't have [node](http://nodejs.org/) or [npm](https://www.npmjs.com/), read [this](https://github.com/amark/gun/blob/master/examples/install.sh) first. @@ -62,7 +55,7 @@ gun.get('mark').on(function(data, key){ }); ``` -- Or try something mind blowing, like saving circular references to a table of documents! ([play](http://jsbin.com/wefozepume/edit?js,console)) +- Or try something **mind blowing**, like saving circular references to a table of documents! ([play](http://jsbin.com/wefozepume/edit?js,console)) ```javascript var cat = {name: "Fluffy", species: "kitty"}; var mark = {boss: cat}; @@ -72,7 +65,7 @@ cat.slave = mark; gun.get('mark').put(mark); // access the data as if it is a document. -gun.get('mark').get('boss').get('name').val(function(data, key){ +gun.get('mark').get('boss').get('name').once(function(data, key){ // `val` grabs the data once, no subscriptions. console.log("Mark's boss is", data); }); @@ -95,20 +88,32 @@ gun.get('list').map().once(function(data, key){ gun.get('list').set({type: "cucumber", goal: "scare cat"}); ``` -## Support +Want to keep building more? **Jump to [THE DOCUMENTATION](#documentation)!** + +# What is GUN? + +First & foremost, GUN is **a community of the nicest and most helpful people** out there. So [I want to invite you](https://gitter.im/amark/gun) to come tell us about what **you** are working on & wanting to build (new or old school alike! Just be nice as well.) and ask us your questions directly. :) + +On that note, let's get some official shout outs covered first: + +### Support

Thanks to:
-Lorenzo Mangani, -Sam Liu, -Daniel Dombrowsky, -Vincent Woo, -AJ ONeal, +Robert Heessels, +Lorenzo Mangani, +NLnet Foundation, +Sam Liu, +Daniel Dombrowsky, +Vincent Woo, +AJ ONeal, Bill Ottman, -Sean Matheson, +Mike Lange, +Sean Matheson, Alan Mimms, Dário Freire, -John Williamson +John Williamson, +Robin Bron

- Join others in sponsoring code: https://www.patreon.com/gunDB ! @@ -116,13 +121,26 @@ Thanks to:
- Found a bug? Report at: https://github.com/amark/gun/issues ; - **Need help**? Chat with us: https://gitter.im/amark/gun . +### History + +[GUN](https://gun.eco) was created by [Mark Nadal](https://twitter.com/marknadal) in 2014 after he had spent 4 years trying to get his collaborative web app to scale up with traditional databases. + + After he realized [Master-Slave database architecture causes one big bottleneck](https://gun.eco/distributed/matters.html), he (as a complete newbie outsider) naively decided **to question the status quo** and shake things up with controversial, heretical, and contrarian experiments: + +**The NoDB** - no master, no servers, no "single source of truth", not built with a real programming language or real hardware, no DevOps, no locking, not *just* SQL or NoSQL but both (**all** - graphs, documents, tables, key/value). + +The goal was to build a P2P database that could survive living inside **any** browser, and could correctly sync data between **any** device after assuming **any** offline-first activity. + + + +Technically, **GUN is a graph synchronization protocol** with a *lightweight embedded engine*, capable of doing *[20M+ API ops/sec](https://gun.eco/docs/Performance)* in **just ~9KB gzipped size**. ## Documentation - - + + @@ -136,23 +154,52 @@ Thanks to:
- + - + - - - + + +

API reference

Tutorials

API reference

Tutorials

Examples

Webcomponents

CAP Theorem Tradeoffs

CAP Theorem Tradeoffs

How Data Sync Works

How GUN is Built

How GUN is Built

Crypto Auth

Modules

Roadmap

Crypto Auth

Modules

Roadmap

This would not be possible without **community contributors**, big shout out to: -**[anywhichway](https://github.com/anywhichway) ([Block Storage](https://github.com/anywhichway/gun-block))**; **[beebase](https://github.com/beebase) ([Quasar](https://github.com/beebase/gun-vuex-quasar))**; **[BrockAtkinson](https://github.com/BrockAtkinson) ([brunch config](https://github.com/BrockAtkinson/brunch-gun))**; **[Brysgo](https://github.com/brysgo) ([GraphQL](https://github.com/brysgo/graphql-gun))**; **[d3x0r](https://github.com/d3x0r) ([SQLite](https://github.com/d3x0r/gun-db))**; **[forrestjt](https://github.com/forrestjt) ([file.js](https://github.com/amark/gun/blob/master/lib/file.js))**; **[hillct](https://github.com/hillct) (Docker)**; **[JosePedroDias](https://github.com/josepedrodias) ([graph visualizer](http://acor.sl.pt:9966))**; **[JuniperChicago](https://github.com/JuniperChicago) ([cycle.js bindings](https://github.com/JuniperChicago/cycle-gun))**; **[jveres](https://github.com/jveres) ([todoMVC](https://github.com/jveres/todomvc))**; **[kristianmandrup](https://github.com/kristianmandrup) ([edge](https://github.com/kristianmandrup/gun-edge))**; **[lmangani](https://github.com/lmangani) ([Cytoscape Visualizer](https://github.com/lmangani/gun-scape), [Cassandra](https://github.com/lmangani/gun-cassandra), [Fastify](https://github.com/lmangani/fastify-gundb), [LetsEncrypt](https://github.com/lmangani/polyGun-letsencrypt))**; **[mhelander](https://github.com/mhelander) ([SEA](https://github.com/amark/gun/blob/master/sea.js))**; [omarzion](https://github.com/omarzion) ([Sticky Note App](https://github.com/omarzion/stickies)); [PsychoLlama](https://github.com/PsychoLlama) ([LevelDB](https://github.com/PsychoLlama/gun-level)); **[RangerMauve](https://github.com/RangerMauve) ([schema](https://github.com/gundb/gun-schema))**; **[robertheessels](https://github.com/swifty) ([gun-p2p-auth](https://github.com/swifty/gun-p2p-auth))**; [sbeleidy](https://github.com/sbeleidy); **[Sean Matheson](https://github.com/ctrlplusb) ([Observable/RxJS/Most.js bindings](https://github.com/ctrlplusb/gun-most))**; **[Stefdv](https://github.com/stefdv) (Polymer/web components)**; **[sjones6](https://github.com/sjones6) ([Flint](https://github.com/sjones6/gun-flint))**; **[zrrrzzt](https://github.com/zrrrzzt) ([JWT Auth](https://gist.github.com/zrrrzzt/6f88dc3cedee4ee18588236756d2cfce))**; **[88dev](https://github.com/88dev) ([Database Viewer](https://github.com/88dev/gun-show))**; +**[ajmeyghani](https://github.com/ajmeyghani) ([Learn GUN Basics with Diagrams](https://medium.com/@ajmeyghani/gundb-a-graph-database-in-javascript-3860a08d873c))**; **[anywhichway](https://github.com/anywhichway) ([Block Storage](https://github.com/anywhichway/gun-block))**; **[beebase](https://github.com/beebase) ([Quasar](https://github.com/beebase/gun-vuex-quasar))**; **[BrockAtkinson](https://github.com/BrockAtkinson) ([brunch config](https://github.com/BrockAtkinson/brunch-gun))**; **[Brysgo](https://github.com/brysgo) ([GraphQL](https://github.com/brysgo/graphql-gun))**; **[d3x0r](https://github.com/d3x0r) ([SQLite](https://github.com/d3x0r/gun-db))**; **[forrestjt](https://github.com/forrestjt) ([file.js](https://github.com/amark/gun/blob/master/lib/file.js))**; **[hillct](https://github.com/hillct) (Docker)**; **[JosePedroDias](https://github.com/josepedrodias) ([graph visualizer](http://acor.sl.pt:9966))**; **[JuniperChicago](https://github.com/JuniperChicago) ([cycle.js bindings](https://github.com/JuniperChicago/cycle-gun))**; **[jveres](https://github.com/jveres) ([todoMVC](https://github.com/jveres/todomvc))**; **[kristianmandrup](https://github.com/kristianmandrup) ([edge](https://github.com/kristianmandrup/gun-edge))**; **[Lightnet](https://github.com/Lightnet)** ([Awesome Vue User Examples](https://glitch.com/edit/#!/jsvuegunui?path=README.md:1:0) & [User Kitchen Sink Playground](https://gdb-auth-vue-node.glitch.me/)); **[lmangani](https://github.com/lmangani) ([Cytoscape Visualizer](https://github.com/lmangani/gun-scape), [Cassandra](https://github.com/lmangani/gun-cassandra), [Fastify](https://github.com/lmangani/fastify-gundb), [LetsEncrypt](https://github.com/lmangani/polyGun-letsencrypt))**; **[mhelander](https://github.com/mhelander) ([SEA](https://github.com/amark/gun/blob/master/sea.js))**; [omarzion](https://github.com/omarzion) ([Sticky Note App](https://github.com/omarzion/stickies)); [PsychoLlama](https://github.com/PsychoLlama) ([LevelDB](https://github.com/PsychoLlama/gun-level)); **[RangerMauve](https://github.com/RangerMauve) ([schema](https://github.com/gundb/gun-schema))**; **[robertheessels](https://github.com/swifty) ([gun-p2p-auth](https://github.com/swifty/gun-p2p-auth))**; **[rogowski](https://github.com/rogowski) (AXE)**; [sbeleidy](https://github.com/sbeleidy); **[sbiaudet](https://github.com/sbiaudet) ([C# Port](https://github.com/sbiaudet/cs-gun))**; **[Sean Matheson](https://github.com/ctrlplusb) ([Observable/RxJS/Most.js bindings](https://github.com/ctrlplusb/gun-most))**; **[Shadyzpop](https://github.com/Shadyzpop) ([React Native example](https://github.com/amark/gun/tree/master/examples/react-native))**; **[sjones6](https://github.com/sjones6) ([Flint](https://github.com/sjones6/gun-flint))**; **[Stefdv](https://github.com/stefdv) (Polymer/web components)**; **[zrrrzzt](https://github.com/zrrrzzt) ([JWT Auth](https://gist.github.com/zrrrzzt/6f88dc3cedee4ee18588236756d2cfce))**; **[xmonader](https://github.com/xmonader) ([Python Port](https://github.com/xmonader/pygundb))**; **[88dev](https://github.com/88dev) ([Database Viewer](https://github.com/88dev/gun-show))**; I am missing many others, apologies, will be adding them soon! +## Testing + +Tests may be run with `npm test`. Tests will trigger persistent writes to the DB, so subsequent runs of the test will fail. You must clear the DB before running the tests again. This can be done by running the following command in the project directory. + +```bash +rm -rf *data* +``` + +### Additional Cryptography Libraries + +To install with npm, first install `npm install gun -S`. +For just the networking layer, import Gun: + +```javascript +var Gun = require('gun/gun'); +``` + +If you also need to install SEA for user auth and crypto, also install some of its dependencies like this: + +`npm install @trust/crypto text-encoding node-webcrypto-ossl --save` + +You will need to require it too (it will be automatically added to the Gun object): + +```javascript +var Gun = require('gun/gun'); +var Sea = require('gun/sea'); +``` + + ## Deploy To quickly spin up a Gun test server for your development team, utilize either [Heroku](http://heroku.com) or [Docker](http://docker.com) or any variant thereof [Dokku](http://dokku.viewdocs.io/dokku/), [Flynn.io](http://flynn.io), [now.sh](https://zeit.co/now), etc. ! diff --git a/as.js b/as.js index 94505c64..f6ac4f37 100644 --- a/as.js +++ b/as.js @@ -80,12 +80,11 @@ }, wait || 200); } } - as.sort = function(sort, el) { - var vs = $(el).find('.sort'); - vs = (vs[0] && u === vs[0].value)? vs.text() : vs.val(); - var id = sort; - var test = id >= vs; - return test ? el : as.sort(sort, el.prev()); + as.sort = function sort(id, li){ + var num = parseFloat(id); + var id = $(li).find('.sort').text() || -Infinity; + var at = num >= parseFloat(id); + return at ? li : sort(id, li.prev()); } $(document).on('keyup', 'input, textarea, [contenteditable]', as.wait(function(){ var el = $(this); diff --git a/axe.js b/axe.js new file mode 100644 index 00000000..a950e363 --- /dev/null +++ b/axe.js @@ -0,0 +1,99 @@ +;(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: {}}); + USE[R(path)] = mod.exports; + } + function R(p){ + return p.split('/').slice(-1).toString().replace('.js',''); + } + } + if(typeof module !== "undefined"){ var common = module } + /* UNBUILD */ + + ;USE(function(module){ + if(typeof window !== "undefined"){ module.window = window } + var tmp = module.window || module; + var AXE = tmp.AXE || function(){}; + + if(AXE.window = module.window){ try{ + AXE.window.AXE = AXE; + tmp = document.createEvent('CustomEvent'); + tmp.initCustomEvent('extension', false, false, {type: "AXE"}); + (window.dispatchEvent || window.fireEvent)(tmp); + window.postMessage({type: "AXE"}, '*'); + } catch(e){} } + + try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){} + module.exports = AXE; + })(USE, './root'); + + ;USE(function(module){ + + var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1); + (Gun.AXE = AXE).GUN = AXE.Gun = Gun; + + Gun.on('opt', function(at){ + if(!at.axe){ + at.axe = {}; + var p = at.opt.peers, tmp; + // 1. If any remembered peers or from last cache or extension + // 2. Fallback to use hard coded peers from dApp + // 3. Or any offered peers. + //if(Gun.obj.empty(p)){ + // Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){ + // p[url] = {url: url, axe: {}}; + // }); + //} + // Our current hypothesis is that it is most optimal + // to take peers in a common network, and align + // them in a line, where you only have left and right + // peers, so messages propagate left and right in + // a linear manner with reduced overlap, and + // with one common superpeer (with ready failovers) + // in case the p2p linear latency is high. + // Or there could be plenty of other better options. + console.log("axe", at.opt); + if(at.opt.super){ + function verify(msg, send, at) { + var peers = Object.keys(p), puts = Object.keys(msg.put), i, j, peer; + var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls? + for (i=0; i < peers.length; ++i) { + peer = p[peers[i]]; + //if (peer.url) {console.log('AXE do not reject superpeers'); send(msg, peer); continue;} /// always send to superpeers? + if (!peer.id) {console.log('AXE peer without id: ', peer); continue;} + if (!Gun.subscribe[soul] || !Gun.subscribe[soul][peer.id]) { console.log('AXE SAY reject msg to peer: %s, soul: %s', peer.id, soul); continue; } + send(msg, peer); + } + } + AXE.say = function(msg, send, at) { + if (!msg.put) { send(msg); return; } + console.log('AXE HOOK!! ', msg); + verify(msg, send, at); + }; + /// TODO: remove peer from all Gun.subscribe. On `mesh.bye` event? + } + if(at.opt.super){ + at.on('in', USE('./lib/super', 1), at); + } else { + //at.on('in', input, at); + } + } + this.to.next(at); // make sure to call the "next" middleware adapter. + }); + + function input(msg){ + var at = this.as, to = this.to; + } + + module.exports = AXE; + })(USE, './axe'); + +}()); diff --git a/examples/axe.html b/examples/axe.html new file mode 100644 index 00000000..40dade3b --- /dev/null +++ b/examples/axe.html @@ -0,0 +1,40 @@ + + + + + + + Testing AXE + + + + + + + + + diff --git a/test/tmp/li.html b/examples/basic/private.html similarity index 96% rename from test/tmp/li.html rename to examples/basic/private.html index df801459..24325c8c 100644 --- a/test/tmp/li.html +++ b/examples/basic/private.html @@ -73,7 +73,6 @@ - + + + + + \ No newline at end of file diff --git a/test/tmp/tables.html b/examples/basic/tables.html similarity index 100% rename from test/tmp/tables.html rename to examples/basic/tables.html diff --git a/test/tmp/user.html b/examples/basic/user.html similarity index 52% rename from test/tmp/user.html rename to examples/basic/user.html index a7f52dfb..873d517c 100644 --- a/test/tmp/user.html +++ b/examples/basic/user.html @@ -5,40 +5,42 @@ +
- +
- - - + + + - - + + + + + + + + + \ No newline at end of file diff --git a/examples/social.html b/examples/party.html similarity index 83% rename from examples/social.html rename to examples/party.html index 9ff941d8..e466da70 100644 --- a/examples/social.html +++ b/examples/party.html @@ -1,6 +1,7 @@ + Party by Neon ERA @@ -37,10 +38,12 @@ ;(() => { function S(){}; window.S = S; + try{localStorage.clear();//sessionStorage.clear(); + }catch(e){} S.gun = Gun(location.host? location.origin+'/gun' : 'http://localhost:8765/gun'); //S.gun = Gun('http://localhost:8765/gun'); //S.gun = Gun(); - S.app = S.gun.get('examples/social/1'); + S.app = S.gun.get('examples/social/2'); S.user = S.gun.user(); S.tell = (what, n) => { var e = $('#tell').find('p'); @@ -68,7 +71,7 @@ width: 3em; } -
A social network you own & control.
+
Party by NEON ERA.
@@ -508,6 +511,8 @@ } #draft .what { } + #draft .spoke { + }

Welcome!

-
+
  • -
    +
  • @@ -544,26 +549,40 @@ }); window.user = S.user; $('#speak').on('submit', (e) => { - var say = $('#speak .draft').text(); + var say = $('#speak .draft').text(); //.text(); // NO NO NO NO NO if(!say){ return } - var ref = S.user.get('who').get('all').set({what: say}); - ref.get('by').put(S.user.get('who')); - S.user.get('who').get('said').time(ref); - S.gun.get('@').time(ref); + console.log('save!', say); + var ref = S.user.get('who').get('all').set({what: say, when: Gun.state()}); + //ref.get('by').put(S.user.get('who')); + //S.user.get('who').get('said').time(ref); + S.user.get('who').get('said').set(ref); + //S.gun.get('@').time(ref); $('#speak .draft').text(''); }); - S.gun.get('@').time(async (data, key, time) => { - var ref = S.gun.get(data); - var said = await ref.then(); - var $li = $($('#'+key)[0] || $('#draft .model .spoke').clone(true,true).attr('id', key).prependTo('#draft ul')); - $li.find('.what').text(said.what); - var by = ref.get('by'); + //S.gun.get('@').time(async (data, key, time) => { + S.user.get('who').get('said').map().once(async (data, key, time) => { + //var ref = S.gun.get(data), tmp; + //var said = await ref.then(); + key = key.replace(/[^A-Za-z]/ig,''); + var tmp, said = data, time = said.when; + var $li = $($('#'+key)[0] || $('#draft .model .spoke').clone(true,true).attr('id', key)[(tmp = $.as.sort(time, $('#draft ul').children('li').first()))[0]?'insertBefore':'appendTo'](tmp[0] || '#draft ul')); + tmp = said.what; + if(tmp && tmp.ct){ + tmp = JSON.stringify(tmp); + setTimeout(async function(){ + tmp = await SEA.decrypt(said.what, S.user._.sea); + $li.find('.what').text(tmp); + }, 750); + } + $li.find('.what').text(tmp); // NORMALIAZE!!! + var by = S.user.get('who');// ref.get('by'); by.get('face').get('small').on(data => { $li.find('.face').attr('src', data).removeClass('none'); }); by.get('name').on(data => { data && $li.find('b').text(data); }); + $li.find('.sort').text(time); var time = new Date(time); $li.find('i').text(time.toDateString() + ', ' + time.toLocaleTimeString()); return; @@ -572,7 +591,7 @@ if(face){ $li.find('.face').attr('src', face).removeClass('none'); } - }, 10); + }); $(document).on('click', '#speak .act.face', (eve) => { }); @@ -580,61 +599,80 @@
    -
    +
    - + -
    @@ -666,28 +704,9 @@

    Hello world!

    - - + + + + + + \ No newline at end of file diff --git a/examples/react-native/.babelrc b/examples/react-native/.babelrc new file mode 100644 index 00000000..d4b74b5b --- /dev/null +++ b/examples/react-native/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["module:metro-react-native-babel-preset"] +} diff --git a/examples/react-native/.buckconfig b/examples/react-native/.buckconfig new file mode 100644 index 00000000..934256cb --- /dev/null +++ b/examples/react-native/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/examples/react-native/.flowconfig b/examples/react-native/.flowconfig new file mode 100644 index 00000000..1043c82d --- /dev/null +++ b/examples/react-native/.flowconfig @@ -0,0 +1,70 @@ +[ignore] +; We fork some components by platform +.*/*[.]android.js + +; Ignore "BUCK" generated dirs +/\.buckd/ + +; Ignore unexpected extra "@providesModule" +.*/node_modules/.*/node_modules/fbjs/.* + +; Ignore duplicate module providers +; For RN Apps installed via npm, "Libraries" folder is inside +; "node_modules/react-native" but in the source repo it is in the root +.*/Libraries/react-native/React.js + +; Ignore polyfills +.*/Libraries/polyfills/.* + +; Ignore metro +.*/node_modules/metro/.* + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow/ +node_modules/react-native/flow-github/ + +[options] +emoji=true + +esproposal.optional_chaining=enable +esproposal.nullish_coalescing=enable + +module.system=haste +module.system.haste.use_name_reducers=true +# get basename +module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' +# strip .js or .js.flow suffix +module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' +# strip .ios suffix +module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' +module.system.haste.paths.blacklist=.*/__tests__/.* +module.system.haste.paths.blacklist=.*/__mocks__/.* +module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* +module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* + +munge_underscores=true + +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +module.file_ext=.js +module.file_ext=.jsx +module.file_ext=.json +module.file_ext=.native.js + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FlowFixMeProps +suppress_type=$FlowFixMeState + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy +suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError + +[version] +^0.78.0 diff --git a/examples/react-native/.gitattributes b/examples/react-native/.gitattributes new file mode 100644 index 00000000..d42ff183 --- /dev/null +++ b/examples/react-native/.gitattributes @@ -0,0 +1 @@ +*.pbxproj -text diff --git a/examples/react-native/.gitignore b/examples/react-native/.gitignore new file mode 100644 index 00000000..5d647565 --- /dev/null +++ b/examples/react-native/.gitignore @@ -0,0 +1,56 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots + +# Bundle artifact +*.jsbundle diff --git a/examples/react-native/.watchmanconfig b/examples/react-native/.watchmanconfig new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/examples/react-native/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/examples/react-native/README.md b/examples/react-native/README.md new file mode 100644 index 00000000..7331c712 --- /dev/null +++ b/examples/react-native/README.md @@ -0,0 +1,18 @@ +# Gun on react-native! +--- +### running the demo +1. do `yarn install` on the directory of the demo `examples/react-native` +2. run the demo with `react-native run-ios` or `react-native run-android` + +### debugging +i would recommend using [react-native-debugger](https://github.com/facebook/react-devtools/tree/master/packages/react-devtools) but you can use chrome's debugger as well + +- ios: `cmd+D` then `Debug JS Remotely` +- android: `cmd+M` then `Debug JS Remotely` + +now you have access to the gun globals on the console which are +`gun` -> the root gun +`user` -> the gun user +--- +# how it all of this is done +since react-native doesnt provide the crypto module that we desire the most and all of the packages are incompatible with react-native/sea, and so to get `sea.js` working we use a webview(react-native browser) and bridge the crypto module from that browser to the global `window` and thats exactly what `webview-crypto` does, thanks to [webview-crypto repo](https://github.com/saulshanabrook/webview-crypto), the webview-crypto provided in this repo is somewhat the same but modified to get it working and mostly compatible with sea/react-native (even though there is a polyfiller for that but it just doesnt work ;/). \ No newline at end of file diff --git a/examples/react-native/android/app/BUCK b/examples/react-native/android/app/BUCK new file mode 100644 index 00000000..5d9a8b3f --- /dev/null +++ b/examples/react-native/android/app/BUCK @@ -0,0 +1,65 @@ +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +lib_deps = [] + +for jarfile in glob(['libs/*.jar']): + name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')] + lib_deps.append(':' + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) + +for aarfile in glob(['libs/*.aar']): + name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] + lib_deps.append(':' + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +android_library( + name = "all-libs", + exported_deps = lib_deps, +) + +android_library( + name = "app-code", + srcs = glob([ + "src/main/java/**/*.java", + ]), + deps = [ + ":all-libs", + ":build_config", + ":res", + ], +) + +android_build_config( + name = "build_config", + package = "com.gundemo", +) + +android_resource( + name = "res", + package = "com.gundemo", + res = "src/main/res", +) + +android_binary( + name = "app", + keystore = "//android/keystores:debug", + manifest = "src/main/AndroidManifest.xml", + package_type = "debug", + deps = [ + ":app-code", + ], +) diff --git a/examples/react-native/android/app/build.gradle b/examples/react-native/android/app/build.gradle new file mode 100644 index 00000000..134d4fd1 --- /dev/null +++ b/examples/react-native/android/app/build.gradle @@ -0,0 +1,151 @@ +apply plugin: "com.android.application" + +import com.android.build.OutputFile + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "../../node_modules/react-native/react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation + * entryFile: "index.android.js", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // whether to disable dev mode in custom build variants (by default only disabled in release) + * // for example: to disable dev mode in the staging build type (if configured) + * devDisabledInStaging: true, + * // The configuration property can be in the following formats + * // 'devDisabledIn${productFlavor}${buildType}' + * // 'devDisabledIn${buildType}' + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // date; if you have any other folders that you want to ignore for performance reasons (gradle + * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ + * // for example, you might want to remove it from here. + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"], + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] + * ] + */ + +project.ext.react = [ + entryFile: "index.js" +] + +apply from: "../../node_modules/react-native/react.gradle" + +/** + * Set this to true to create two separate APKs instead of one: + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = false + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + applicationId "com.gundemo" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + ndk { + abiFilters "armeabi-v7a", "x86" + } + } + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86" + } + } + buildTypes { + release { + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a":1, "x86":2] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + } + } +} + +dependencies { + compile project(':react-native-webview-bridge') + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" + implementation "com.facebook.react:react-native:+" // From node_modules +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} diff --git a/examples/react-native/android/app/proguard-rules.pro b/examples/react-native/android/app/proguard-rules.pro new file mode 100644 index 00000000..a92fa177 --- /dev/null +++ b/examples/react-native/android/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/examples/react-native/android/app/src/main/AndroidManifest.xml b/examples/react-native/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..5fde7ad3 --- /dev/null +++ b/examples/react-native/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/examples/react-native/android/app/src/main/assets/html/blank.html b/examples/react-native/android/app/src/main/assets/html/blank.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/react-native/android/app/src/main/java/com/gundemo/MainActivity.java b/examples/react-native/android/app/src/main/java/com/gundemo/MainActivity.java new file mode 100644 index 00000000..de2d8c2f --- /dev/null +++ b/examples/react-native/android/app/src/main/java/com/gundemo/MainActivity.java @@ -0,0 +1,15 @@ +package com.gundemo; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "GunDemo"; + } +} diff --git a/examples/react-native/android/app/src/main/java/com/gundemo/MainApplication.java b/examples/react-native/android/app/src/main/java/com/gundemo/MainApplication.java new file mode 100644 index 00000000..cd09d780 --- /dev/null +++ b/examples/react-native/android/app/src/main/java/com/gundemo/MainApplication.java @@ -0,0 +1,47 @@ +package com.gundemo; + +import android.app.Application; + +import com.facebook.react.ReactApplication; +import com.github.alinz.reactnativewebviewbridge.WebViewBridgePackage; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new WebViewBridgePackage() + ); + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + } +} diff --git a/examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..a2f59082 Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..1b523998 Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..ff10afd6 Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..115a4c76 Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..dcd3cd80 Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..459ca609 Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..8ca12fe0 Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..8e19b410 Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..b824ebdd Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..4c19a13c Binary files /dev/null and b/examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/examples/react-native/android/app/src/main/res/values/strings.xml b/examples/react-native/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..532e430a --- /dev/null +++ b/examples/react-native/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + GunDemo + diff --git a/examples/react-native/android/app/src/main/res/values/styles.xml b/examples/react-native/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..319eb0ca --- /dev/null +++ b/examples/react-native/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/examples/react-native/android/build.gradle b/examples/react-native/android/build.gradle new file mode 100644 index 00000000..a1e80854 --- /dev/null +++ b/examples/react-native/android/build.gradle @@ -0,0 +1,39 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext { + buildToolsVersion = "27.0.3" + minSdkVersion = 16 + compileSdkVersion = 27 + targetSdkVersion = 26 + supportLibVersion = "27.1.1" + } + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.4' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + mavenLocal() + google() + jcenter() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } + } +} + + +task wrapper(type: Wrapper) { + gradleVersion = '4.4' + distributionUrl = distributionUrl.replace("bin", "all") +} diff --git a/examples/react-native/android/gradle.properties b/examples/react-native/android/gradle.properties new file mode 100644 index 00000000..89e0d99e --- /dev/null +++ b/examples/react-native/android/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/examples/react-native/android/gradle/wrapper/gradle-wrapper.jar b/examples/react-native/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..01b8bf6b Binary files /dev/null and b/examples/react-native/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/react-native/android/gradle/wrapper/gradle-wrapper.properties b/examples/react-native/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..b6517bb1 --- /dev/null +++ b/examples/react-native/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/examples/react-native/android/gradlew b/examples/react-native/android/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/examples/react-native/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/examples/react-native/android/gradlew.bat b/examples/react-native/android/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/examples/react-native/android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/react-native/android/keystores/BUCK b/examples/react-native/android/keystores/BUCK new file mode 100644 index 00000000..88e4c31b --- /dev/null +++ b/examples/react-native/android/keystores/BUCK @@ -0,0 +1,8 @@ +keystore( + name = "debug", + properties = "debug.keystore.properties", + store = "debug.keystore", + visibility = [ + "PUBLIC", + ], +) diff --git a/examples/react-native/android/keystores/debug.keystore.properties b/examples/react-native/android/keystores/debug.keystore.properties new file mode 100644 index 00000000..121bfb49 --- /dev/null +++ b/examples/react-native/android/keystores/debug.keystore.properties @@ -0,0 +1,4 @@ +key.store=debug.keystore +key.alias=androiddebugkey +key.store.password=android +key.alias.password=android diff --git a/examples/react-native/android/settings.gradle b/examples/react-native/android/settings.gradle new file mode 100644 index 00000000..78376f3d --- /dev/null +++ b/examples/react-native/android/settings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'GunDemo' +include ':react-native-webview-bridge' +project(':react-native-webview-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview-bridge/android') + +include ':app' diff --git a/examples/react-native/app.json b/examples/react-native/app.json new file mode 100644 index 00000000..0cb2cb60 --- /dev/null +++ b/examples/react-native/app.json @@ -0,0 +1,4 @@ +{ + "name": "GunDemo", + "displayName": "GunDemo" +} \ No newline at end of file diff --git a/examples/react-native/index.js b/examples/react-native/index.js new file mode 100644 index 00000000..9e370d8c --- /dev/null +++ b/examples/react-native/index.js @@ -0,0 +1,8 @@ +/** @format */ +import './shim'; +import {AppRegistry} from 'react-native'; +import App from './src/App'; +import {name as appName} from './app.json'; + +AppRegistry.registerComponent(appName, () => App); +console.disableYellowBox = true; diff --git a/examples/react-native/ios/GunDemo-tvOS/Info.plist b/examples/react-native/ios/GunDemo-tvOS/Info.plist new file mode 100644 index 00000000..2fb6a11c --- /dev/null +++ b/examples/react-native/ios/GunDemo-tvOS/Info.plist @@ -0,0 +1,54 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSLocationWhenInUseUsageDescription + + NSAppTransportSecurity + + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + + diff --git a/examples/react-native/ios/GunDemo-tvOSTests/Info.plist b/examples/react-native/ios/GunDemo-tvOSTests/Info.plist new file mode 100644 index 00000000..886825cc --- /dev/null +++ b/examples/react-native/ios/GunDemo-tvOSTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/examples/react-native/ios/GunDemo.xcodeproj/project.pbxproj b/examples/react-native/ios/GunDemo.xcodeproj/project.pbxproj new file mode 100644 index 00000000..b07184e6 --- /dev/null +++ b/examples/react-native/ios/GunDemo.xcodeproj/project.pbxproj @@ -0,0 +1,1590 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; + 00E356F31AD99517003FC87E /* GunDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* GunDemoTests.m */; }; + 11D1A2F320CAFA9E000508D9 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; }; + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 2D02E4C21E0B4AEC006451C7 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */; }; + 2D02E4C31E0B4AEC006451C7 /* libRCTImage-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E841DF850E9000B6D8A /* libRCTImage-tvOS.a */; }; + 2D02E4C41E0B4AEC006451C7 /* libRCTLinking-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E881DF850E9000B6D8A /* libRCTLinking-tvOS.a */; }; + 2D02E4C51E0B4AEC006451C7 /* libRCTNetwork-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E8C1DF850E9000B6D8A /* libRCTNetwork-tvOS.a */; }; + 2D02E4C61E0B4AEC006451C7 /* libRCTSettings-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E901DF850E9000B6D8A /* libRCTSettings-tvOS.a */; }; + 2D02E4C71E0B4AEC006451C7 /* libRCTText-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E941DF850E9000B6D8A /* libRCTText-tvOS.a */; }; + 2D02E4C81E0B4AEC006451C7 /* libRCTWebSocket-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E991DF850E9000B6D8A /* libRCTWebSocket-tvOS.a */; }; + 2D16E6881FA4F8E400B85C8A /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D16E6891FA4F8E400B85C8A /* libReact.a */; }; + 2DCD954D1E0B4F2C00145EB5 /* GunDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* GunDemoTests.m */; }; + 2DF0FFEE2056DD460020B375 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3EA31DF850E9000B6D8A /* libReact.a */; }; + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; + 99082B67837B4425B90A62F2 /* libReact-Native-Webview-Bridge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 929405EFCB05436C99DFC544 /* libReact-Native-Webview-Bridge.a */; }; + ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTActionSheet; + }; + 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTGeolocation; + }; + 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5115D1A9E6B3D00147676; + remoteInfo = RCTImage; + }; + 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B511DB1A9E6C8500147676; + remoteInfo = RCTNetwork; + }; + 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; + remoteInfo = RCTVibration; + }; + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = GunDemo; + }; + 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTSettings; + }; + 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; + 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; + remoteInfo = React; + }; + 2344B40F21A4F53000763E72 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5188B081163B45E68CD4AE92 /* React-Native-Webview-Bridge.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 4114DC4C1C187C3A003CD988; + remoteInfo = "React-Native-Webview-Bridge"; + }; + 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2D02E47A1E0B4A5D006451C7; + remoteInfo = "GunDemo-tvOS"; + }; + 2D16E6711FA4F8DC00B85C8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = ADD01A681E09402E00F6D226; + remoteInfo = "RCTBlob-tvOS"; + }; + 2D16E6831FA4F8DC00B85C8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3DBE0D001F3B181A0099AA32; + remoteInfo = fishhook; + }; + 2D16E6851FA4F8DC00B85C8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3DBE0D0D1F3B181C0099AA32; + remoteInfo = "fishhook-tvOS"; + }; + 2DF0FFDE2056DD460020B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EBF21BDC1FC498900052F4D5; + remoteInfo = jsinspector; + }; + 2DF0FFE02056DD460020B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EBF21BFA1FC4989A0052F4D5; + remoteInfo = "jsinspector-tvOS"; + }; + 2DF0FFE22056DD460020B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 139D7ECE1E25DB7D00323FB7; + remoteInfo = "third-party"; + }; + 2DF0FFE42056DD460020B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D383D3C1EBD27B6005632C8; + remoteInfo = "third-party-tvOS"; + }; + 2DF0FFE62056DD460020B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 139D7E881E25C6D100323FB7; + remoteInfo = "double-conversion"; + }; + 2DF0FFE82056DD460020B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D383D621EBD27B9005632C8; + remoteInfo = "double-conversion-tvOS"; + }; + 2DF0FFEA2056DD460020B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9936F3131F5F2E4B0010BF04; + remoteInfo = privatedata; + }; + 2DF0FFEC2056DD460020B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9936F32F1F5F2E5B0010BF04; + remoteInfo = "privatedata-tvOS"; + }; + 3DAD3E831DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A283A1D9B042B00D4039D; + remoteInfo = "RCTImage-tvOS"; + }; + 3DAD3E871DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28471D9B043800D4039D; + remoteInfo = "RCTLinking-tvOS"; + }; + 3DAD3E8B1DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28541D9B044C00D4039D; + remoteInfo = "RCTNetwork-tvOS"; + }; + 3DAD3E8F1DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28611D9B046600D4039D; + remoteInfo = "RCTSettings-tvOS"; + }; + 3DAD3E931DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A287B1D9B048500D4039D; + remoteInfo = "RCTText-tvOS"; + }; + 3DAD3E981DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28881D9B049200D4039D; + remoteInfo = "RCTWebSocket-tvOS"; + }; + 3DAD3EA21DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28131D9B038B00D4039D; + remoteInfo = "React-tvOS"; + }; + 3DAD3EA41DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3C059A1DE3340900C268FA; + remoteInfo = yoga; + }; + 3DAD3EA61DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3C06751DE3340C00C268FA; + remoteInfo = "yoga-tvOS"; + }; + 3DAD3EA81DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3CD9251DE5FBEC00167DC4; + remoteInfo = cxxreact; + }; + 3DAD3EAA1DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3CD9321DE5FBEE00167DC4; + remoteInfo = "cxxreact-tvOS"; + }; + 3DAD3EAC1DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3CD90B1DE5FBD600167DC4; + remoteInfo = jschelpers; + }; + 3DAD3EAE1DF850E9000B6D8A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3D3CD9181DE5FBD800167DC4; + remoteInfo = "jschelpers-tvOS"; + }; + 5E9157321DD0AC6500FF2AA8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTAnimation; + }; + 5E9157341DD0AC6500FF2AA8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28201D9B03D100D4039D; + remoteInfo = "RCTAnimation-tvOS"; + }; + 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTLinking; + }; + 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5119B1A9E6C1200147676; + remoteInfo = RCTText; + }; + ADBDB9261DFEBF0700ED6528 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 358F4ED71D1E81A9004DF814; + remoteInfo = RCTBlob; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; + 00E356EE1AD99517003FC87E /* GunDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GunDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* GunDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GunDemoTests.m; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* GunDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GunDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = GunDemo/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = GunDemo/AppDelegate.m; sourceTree = ""; }; + 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = GunDemo/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = GunDemo/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = GunDemo/main.m; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 2D02E47B1E0B4A5D006451C7 /* GunDemo-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "GunDemo-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2D02E4901E0B4A5D006451C7 /* GunDemo-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "GunDemo-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5188B081163B45E68CD4AE92 /* React-Native-Webview-Bridge.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = "React-Native-Webview-Bridge.xcodeproj"; path = "../node_modules/react-native-webview-bridge/ios/React-Native-Webview-Bridge.xcodeproj"; sourceTree = ""; }; + 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; + 929405EFCB05436C99DFC544 /* libReact-Native-Webview-Bridge.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libReact-Native-Webview-Bridge.a"; sourceTree = ""; }; + ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTBlob.xcodeproj; path = "../node_modules/react-native/Libraries/Blob/RCTBlob.xcodeproj"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */, + 11D1A2F320CAFA9E000508D9 /* libRCTAnimation.a in Frameworks */, + 146834051AC3E58100842450 /* libReact.a in Frameworks */, + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, + 99082B67837B4425B90A62F2 /* libReact-Native-Webview-Bridge.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D02E4781E0B4A5D006451C7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D16E6881FA4F8E400B85C8A /* libReact.a in Frameworks */, + 2D02E4C21E0B4AEC006451C7 /* libRCTAnimation.a in Frameworks */, + 2D02E4C31E0B4AEC006451C7 /* libRCTImage-tvOS.a in Frameworks */, + 2D02E4C41E0B4AEC006451C7 /* libRCTLinking-tvOS.a in Frameworks */, + 2D02E4C51E0B4AEC006451C7 /* libRCTNetwork-tvOS.a in Frameworks */, + 2D02E4C61E0B4AEC006451C7 /* libRCTSettings-tvOS.a in Frameworks */, + 2D02E4C71E0B4AEC006451C7 /* libRCTText-tvOS.a in Frameworks */, + 2D02E4C81E0B4AEC006451C7 /* libRCTWebSocket-tvOS.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D02E48D1E0B4A5D006451C7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DF0FFEE2056DD460020B375 /* libReact.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00C302A81ABCB8CE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302B61ABCB90400DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302BC1ABCB91800DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, + 3DAD3E841DF850E9000B6D8A /* libRCTImage-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302D41ABCB9D200DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, + 3DAD3E8C1DF850E9000B6D8A /* libRCTNetwork-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302E01ABCB9EE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, + ); + name = Products; + sourceTree = ""; + }; + 00E356EF1AD99517003FC87E /* GunDemoTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* GunDemoTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = GunDemoTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 139105B71AF99BAD00B5F7CC /* Products */ = { + isa = PBXGroup; + children = ( + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, + 3DAD3E901DF850E9000B6D8A /* libRCTSettings-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 139FDEE71B06529A00C62182 /* Products */ = { + isa = PBXGroup; + children = ( + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, + 3DAD3E991DF850E9000B6D8A /* libRCTWebSocket-tvOS.a */, + 2D16E6841FA4F8DC00B85C8A /* libfishhook.a */, + 2D16E6861FA4F8DC00B85C8A /* libfishhook-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* GunDemo */ = { + isa = PBXGroup; + children = ( + 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = GunDemo; + sourceTree = ""; + }; + 146834001AC3E56700842450 /* Products */ = { + isa = PBXGroup; + children = ( + 146834041AC3E56700842450 /* libReact.a */, + 3DAD3EA31DF850E9000B6D8A /* libReact.a */, + 3DAD3EA51DF850E9000B6D8A /* libyoga.a */, + 3DAD3EA71DF850E9000B6D8A /* libyoga.a */, + 3DAD3EA91DF850E9000B6D8A /* libcxxreact.a */, + 3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */, + 3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */, + 3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */, + 2DF0FFDF2056DD460020B375 /* libjsinspector.a */, + 2DF0FFE12056DD460020B375 /* libjsinspector-tvOS.a */, + 2DF0FFE32056DD460020B375 /* libthird-party.a */, + 2DF0FFE52056DD460020B375 /* libthird-party.a */, + 2DF0FFE72056DD460020B375 /* libdouble-conversion.a */, + 2DF0FFE92056DD460020B375 /* libdouble-conversion.a */, + 2DF0FFEB2056DD460020B375 /* libprivatedata.a */, + 2DF0FFED2056DD460020B375 /* libprivatedata-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 2344B3E621A4F52C00763E72 /* Recovered References */ = { + isa = PBXGroup; + children = ( + 929405EFCB05436C99DFC544 /* libReact-Native-Webview-Bridge.a */, + ); + name = "Recovered References"; + sourceTree = ""; + }; + 2344B40C21A4F53000763E72 /* Products */ = { + isa = PBXGroup; + children = ( + 2344B41021A4F53000763E72 /* libReact-Native-Webview-Bridge.a */, + ); + name = Products; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 2D16E6891FA4F8E400B85C8A /* libReact.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5E91572E1DD0AC6500FF2AA8 /* Products */ = { + isa = PBXGroup; + children = ( + 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */, + 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */, + ); + name = Products; + sourceTree = ""; + }; + 78C398B11ACF4ADC00677621 /* Products */ = { + isa = PBXGroup; + children = ( + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, + 3DAD3E881DF850E9000B6D8A /* libRCTLinking-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */, + 146833FF1AC3E56700842450 /* React.xcodeproj */, + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, + ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */, + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + 5188B081163B45E68CD4AE92 /* React-Native-Webview-Bridge.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; + 832341B11AAA6A8300B99B32 /* Products */ = { + isa = PBXGroup; + children = ( + 832341B51AAA6A8300B99B32 /* libRCTText.a */, + 3DAD3E941DF850E9000B6D8A /* libRCTText-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* GunDemo */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* GunDemoTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + 2344B3E621A4F52C00763E72 /* Recovered References */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* GunDemo.app */, + 00E356EE1AD99517003FC87E /* GunDemoTests.xctest */, + 2D02E47B1E0B4A5D006451C7 /* GunDemo-tvOS.app */, + 2D02E4901E0B4A5D006451C7 /* GunDemo-tvOSTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + ADBDB9201DFEBF0600ED6528 /* Products */ = { + isa = PBXGroup; + children = ( + ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */, + 2D16E6721FA4F8DC00B85C8A /* libRCTBlob-tvOS.a */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* GunDemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "GunDemoTests" */; + buildPhases = ( + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = GunDemoTests; + productName = GunDemoTests; + productReference = 00E356EE1AD99517003FC87E /* GunDemoTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* GunDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "GunDemo" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GunDemo; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* GunDemo.app */; + productType = "com.apple.product-type.application"; + }; + 2D02E47A1E0B4A5D006451C7 /* GunDemo-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "GunDemo-tvOS" */; + buildPhases = ( + 2D02E4771E0B4A5D006451C7 /* Sources */, + 2D02E4781E0B4A5D006451C7 /* Frameworks */, + 2D02E4791E0B4A5D006451C7 /* Resources */, + 2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "GunDemo-tvOS"; + productName = "GunDemo-tvOS"; + productReference = 2D02E47B1E0B4A5D006451C7 /* GunDemo-tvOS.app */; + productType = "com.apple.product-type.application"; + }; + 2D02E48F1E0B4A5D006451C7 /* GunDemo-tvOSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "GunDemo-tvOSTests" */; + buildPhases = ( + 2D02E48C1E0B4A5D006451C7 /* Sources */, + 2D02E48D1E0B4A5D006451C7 /* Frameworks */, + 2D02E48E1E0B4A5D006451C7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */, + ); + name = "GunDemo-tvOSTests"; + productName = "GunDemo-tvOSTests"; + productReference = 2D02E4901E0B4A5D006451C7 /* GunDemo-tvOSTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 940; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + 2D02E47A1E0B4A5D006451C7 = { + CreatedOnToolsVersion = 8.2.1; + ProvisioningStyle = Automatic; + }; + 2D02E48F1E0B4A5D006451C7 = { + CreatedOnToolsVersion = 8.2.1; + ProvisioningStyle = Automatic; + TestTargetID = 2D02E47A1E0B4A5D006451C7; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "GunDemo" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; + ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + }, + { + ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */; + ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */; + }, + { + ProductGroup = ADBDB9201DFEBF0600ED6528 /* Products */; + ProjectRef = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */; + }, + { + ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; + ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + }, + { + ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; + ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + }, + { + ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; + ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + }, + { + ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; + ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + }, + { + ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; + ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + }, + { + ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; + ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + }, + { + ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; + ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + }, + { + ProductGroup = 139FDEE71B06529A00C62182 /* Products */; + ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + }, + { + ProductGroup = 2344B40C21A4F53000763E72 /* Products */; + ProjectRef = 5188B081163B45E68CD4AE92 /* React-Native-Webview-Bridge.xcodeproj */; + }, + { + ProductGroup = 146834001AC3E56700842450 /* Products */; + ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* GunDemo */, + 00E356ED1AD99517003FC87E /* GunDemoTests */, + 2D02E47A1E0B4A5D006451C7 /* GunDemo-tvOS */, + 2D02E48F1E0B4A5D006451C7 /* GunDemo-tvOSTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTActionSheet.a; + remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTGeolocation.a; + remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTImage.a; + remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTNetwork.a; + remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTVibration.a; + remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTSettings.a; + remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 146834041AC3E56700842450 /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2344B41021A4F53000763E72 /* libReact-Native-Webview-Bridge.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libReact-Native-Webview-Bridge.a"; + remoteRef = 2344B40F21A4F53000763E72 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2D16E6721FA4F8DC00B85C8A /* libRCTBlob-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTBlob-tvOS.a"; + remoteRef = 2D16E6711FA4F8DC00B85C8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2D16E6841FA4F8DC00B85C8A /* libfishhook.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libfishhook.a; + remoteRef = 2D16E6831FA4F8DC00B85C8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2D16E6861FA4F8DC00B85C8A /* libfishhook-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libfishhook-tvOS.a"; + remoteRef = 2D16E6851FA4F8DC00B85C8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2DF0FFDF2056DD460020B375 /* libjsinspector.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjsinspector.a; + remoteRef = 2DF0FFDE2056DD460020B375 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2DF0FFE12056DD460020B375 /* libjsinspector-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libjsinspector-tvOS.a"; + remoteRef = 2DF0FFE02056DD460020B375 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2DF0FFE32056DD460020B375 /* libthird-party.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libthird-party.a"; + remoteRef = 2DF0FFE22056DD460020B375 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2DF0FFE52056DD460020B375 /* libthird-party.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libthird-party.a"; + remoteRef = 2DF0FFE42056DD460020B375 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2DF0FFE72056DD460020B375 /* libdouble-conversion.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libdouble-conversion.a"; + remoteRef = 2DF0FFE62056DD460020B375 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2DF0FFE92056DD460020B375 /* libdouble-conversion.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libdouble-conversion.a"; + remoteRef = 2DF0FFE82056DD460020B375 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2DF0FFEB2056DD460020B375 /* libprivatedata.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libprivatedata.a; + remoteRef = 2DF0FFEA2056DD460020B375 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2DF0FFED2056DD460020B375 /* libprivatedata-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libprivatedata-tvOS.a"; + remoteRef = 2DF0FFEC2056DD460020B375 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3E841DF850E9000B6D8A /* libRCTImage-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTImage-tvOS.a"; + remoteRef = 3DAD3E831DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3E881DF850E9000B6D8A /* libRCTLinking-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTLinking-tvOS.a"; + remoteRef = 3DAD3E871DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3E8C1DF850E9000B6D8A /* libRCTNetwork-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTNetwork-tvOS.a"; + remoteRef = 3DAD3E8B1DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3E901DF850E9000B6D8A /* libRCTSettings-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTSettings-tvOS.a"; + remoteRef = 3DAD3E8F1DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3E941DF850E9000B6D8A /* libRCTText-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTText-tvOS.a"; + remoteRef = 3DAD3E931DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3E991DF850E9000B6D8A /* libRCTWebSocket-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRCTWebSocket-tvOS.a"; + remoteRef = 3DAD3E981DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3EA31DF850E9000B6D8A /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 3DAD3EA21DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3EA51DF850E9000B6D8A /* libyoga.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libyoga.a; + remoteRef = 3DAD3EA41DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3EA71DF850E9000B6D8A /* libyoga.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libyoga.a; + remoteRef = 3DAD3EA61DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3EA91DF850E9000B6D8A /* libcxxreact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libcxxreact.a; + remoteRef = 3DAD3EA81DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libcxxreact.a; + remoteRef = 3DAD3EAA1DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjschelpers.a; + remoteRef = 3DAD3EAC1DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjschelpers.a; + remoteRef = 3DAD3EAE1DF850E9000B6D8A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTAnimation.a; + remoteRef = 5E9157321DD0AC6500FF2AA8 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTAnimation.a; + remoteRef = 5E9157341DD0AC6500FF2AA8 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTLinking.a; + remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTText.a; + remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTBlob.a; + remoteRef = ADBDB9261DFEBF0700ED6528 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D02E4791E0B4A5D006451C7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D02E48E1E0B4A5D006451C7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; + }; + 2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native Code And Images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00E356EA1AD99517003FC87E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 00E356F31AD99517003FC87E /* GunDemoTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D02E4771E0B4A5D006451C7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */, + 2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2D02E48C1E0B4A5D006451C7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DCD954D1E0B4F2C00145EB5 /* GunDemoTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* GunDemo */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; + 2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2D02E47A1E0B4A5D006451C7 /* GunDemo-tvOS */; + targetProxy = 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + path = GunDemo; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 00E356F61AD99517003FC87E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-webview-bridge/ios", + ); + INFOPLIST_FILE = GunDemoTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GunDemo.app/GunDemo"; + }; + name = Debug; + }; + 00E356F71AD99517003FC87E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COPY_PHASE_STRIP = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-webview-bridge/ios", + ); + INFOPLIST_FILE = GunDemoTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GunDemo.app/GunDemo"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-webview-bridge/ios", + ); + INFOPLIST_FILE = GunDemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = GunDemo; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = 1; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-webview-bridge/ios", + ); + INFOPLIST_FILE = GunDemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = GunDemo; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 2D02E4971E0B4A5E006451C7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-webview-bridge/ios", + ); + INFOPLIST_FILE = "GunDemo-tvOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.GunDemo-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 9.2; + }; + name = Debug; + }; + 2D02E4981E0B4A5E006451C7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_NO_COMMON_BLOCKS = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-webview-bridge/ios", + ); + INFOPLIST_FILE = "GunDemo-tvOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.GunDemo-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 9.2; + }; + name = Release; + }; + 2D02E4991E0B4A5E006451C7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-webview-bridge/ios", + ); + INFOPLIST_FILE = "GunDemo-tvOSTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.GunDemo-tvOSTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GunDemo-tvOS.app/GunDemo-tvOS"; + TVOS_DEPLOYMENT_TARGET = 10.1; + }; + name = Debug; + }; + 2D02E49A1E0B4A5E006451C7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_NO_COMMON_BLOCKS = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-webview-bridge/ios", + ); + INFOPLIST_FILE = "GunDemo-tvOSTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.GunDemo-tvOSTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GunDemo-tvOS.app/GunDemo-tvOS"; + TVOS_DEPLOYMENT_TARGET = 10.1; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "GunDemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "GunDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "GunDemo-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2D02E4971E0B4A5E006451C7 /* Debug */, + 2D02E4981E0B4A5E006451C7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "GunDemo-tvOSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2D02E4991E0B4A5E006451C7 /* Debug */, + 2D02E49A1E0B4A5E006451C7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "GunDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/examples/react-native/ios/GunDemo/AppDelegate.h b/examples/react-native/ios/GunDemo/AppDelegate.h new file mode 100644 index 00000000..d4f2580b --- /dev/null +++ b/examples/react-native/ios/GunDemo/AppDelegate.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@interface AppDelegate : UIResponder + +@property (nonatomic, strong) UIWindow *window; + +@end diff --git a/examples/react-native/ios/GunDemo/AppDelegate.m b/examples/react-native/ios/GunDemo/AppDelegate.m new file mode 100644 index 00000000..df255a6d --- /dev/null +++ b/examples/react-native/ios/GunDemo/AppDelegate.m @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "AppDelegate.h" + +#import +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + NSURL *jsCodeLocation; + + jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; + + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation + moduleName:@"GunDemo" + initialProperties:nil + launchOptions:launchOptions]; + rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/react-native/ios/GunDemo/Base.lproj/LaunchScreen.xib b/examples/react-native/ios/GunDemo/Base.lproj/LaunchScreen.xib new file mode 100644 index 00000000..3c66dc85 --- /dev/null +++ b/examples/react-native/ios/GunDemo/Base.lproj/LaunchScreen.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/react-native/ios/GunDemo/Images.xcassets/AppIcon.appiconset/Contents.json b/examples/react-native/ios/GunDemo/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..118c98f7 --- /dev/null +++ b/examples/react-native/ios/GunDemo/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/react-native/ios/GunDemo/Images.xcassets/Contents.json b/examples/react-native/ios/GunDemo/Images.xcassets/Contents.json new file mode 100644 index 00000000..2d92bd53 --- /dev/null +++ b/examples/react-native/ios/GunDemo/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/react-native/ios/GunDemo/Info.plist b/examples/react-native/ios/GunDemo/Info.plist new file mode 100644 index 00000000..7fd5d8ea --- /dev/null +++ b/examples/react-native/ios/GunDemo/Info.plist @@ -0,0 +1,60 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + GunDemo + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSLocationWhenInUseUsageDescription + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSLocationWhenInUseUsageDescription + + NSAppTransportSecurity + + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + + diff --git a/examples/react-native/ios/GunDemo/main.m b/examples/react-native/ios/GunDemo/main.m new file mode 100644 index 00000000..c73e0062 --- /dev/null +++ b/examples/react-native/ios/GunDemo/main.m @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/react-native/ios/GunDemoTests/GunDemoTests.m b/examples/react-native/ios/GunDemoTests/GunDemoTests.m new file mode 100644 index 00000000..ee464a0f --- /dev/null +++ b/examples/react-native/ios/GunDemoTests/GunDemoTests.m @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import + +#import +#import + +#define TIMEOUT_SECONDS 600 +#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" + +@interface GunDemoTests : XCTestCase + +@end + +@implementation GunDemoTests + +- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test +{ + if (test(view)) { + return YES; + } + for (UIView *subview in [view subviews]) { + if ([self findSubviewInView:subview matching:test]) { + return YES; + } + } + return NO; +} + +- (void)testRendersWelcomeScreen +{ + UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; + BOOL foundElement = NO; + + __block NSString *redboxError = nil; + RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { + if (level >= RCTLogLevelError) { + redboxError = message; + } + }); + + while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + + foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { + if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { + return YES; + } + return NO; + }]; + } + + RCTSetLogFunction(RCTDefaultLogFunction); + + XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); + XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); +} + + +@end diff --git a/examples/react-native/ios/GunDemoTests/Info.plist b/examples/react-native/ios/GunDemoTests/Info.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/examples/react-native/ios/GunDemoTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/examples/react-native/package.json b/examples/react-native/package.json new file mode 100644 index 00000000..0d42b8ba --- /dev/null +++ b/examples/react-native/package.json @@ -0,0 +1,30 @@ +{ + "name": "GunDemo", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/react-native/local-cli/cli.js start", + "test": "jest" + }, + "dependencies": { + "buffer": "^5.2.1", + "encode-utf8": "^1.0.2", + "fast-base64-encode": "^1.0.0", + "gun": "^0.9.99999", + "lodash": "^4.17.11", + "react": "16.6.1", + "react-native": "0.57.5", + "react-native-webview-bridge": "^0.40.1", + "serialize-error": "^3.0.0", + "text-encoding": "^0.7.0" + }, + "devDependencies": { + "babel-jest": "23.6.0", + "jest": "23.6.0", + "metro-react-native-babel-preset": "0.49.2", + "react-test-renderer": "16.6.1" + }, + "jest": { + "preset": "react-native" + } +} diff --git a/examples/react-native/shim.js b/examples/react-native/shim.js new file mode 100644 index 00000000..0e3e7e26 --- /dev/null +++ b/examples/react-native/shim.js @@ -0,0 +1,20 @@ +if (typeof __dirname === 'undefined') global.__dirname = '/' +if (typeof __filename === 'undefined') global.__filename = '' + +if (typeof Buffer === 'undefined') global.Buffer = require('buffer').Buffer + +const isDev = typeof __DEV__ === 'boolean' && __DEV__ +process.env['NODE_ENV'] = isDev ? 'development' : 'production' +if (typeof localStorage !== 'undefined') { + localStorage.debug = isDev ? '*' : '' +} + +global.location = { + protocol: 'file:', + host: '', +}; + +const { TextEncoder, TextDecoder } = require('text-encoding'); + +global.TextDecoder = TextDecoder; +global.TextEncoder = TextEncoder; \ No newline at end of file diff --git a/examples/react-native/src/App/Demo.js b/examples/react-native/src/App/Demo.js new file mode 100644 index 00000000..d86f1174 --- /dev/null +++ b/examples/react-native/src/App/Demo.js @@ -0,0 +1,152 @@ +import * as React from 'react'; +import {View, StyleSheet, TextInput, Text, TouchableOpacity, AsyncStorage} from 'react-native'; + +import Gun from 'gun/gun'; +import 'gun/lib/open'; +import '../extensions/sea'; + +import adapter from '../extensions/asyncStorageAdapter'; + +Gun.on('create', function(db) { + this.to.next(db); + const pluginInterop = function(middleware) { + return function(request) { + this.to.next(request); + return middleware(request, db); + }; + } + + // Register the adapter + db.on('get', pluginInterop(adapter.read)); + db.on('put', pluginInterop(adapter.write)); +}); + +export class Demo extends React.Component { + constructor() { + super(); + + this.gun = new Gun(); + this.user = this.gun.user(); + + window.gun = this.gun; + window.user = this.user; + + this.state = { + authenticated: false, + list: [], + listText: '', + username: '', + password: '', + } + } + + hookUserList = () => { + this.user.get('list').open((list) => { + const userList = Object.keys(list).reduce((newList, key) => { + if (!!Object.keys(list[key]).length) { + return [...newList, {text: list[key].text, key}]; + }; + }, []); + this.setState({ + list: userList || [], + }); + }); + } + + addToList = () => { + this.user.get('list').set({text: this.state.listText}); + } + + doSignin = () => { + this.user.auth(this.state.username, this.state.password, (d) => { + if (d.err) { + console.log('err', d.err); + return; + } + + this.setState({authenticated: true}); + this.hookUserList(); + }); + } + + doSignup = () => { + this.user.create(this.state.username, this.state.password, () => { + this.doSignin(); + }); + } + + loginScreen = () => { + return ( + + this.setState({username})} value={this.state.username} style={styles.input} /> + this.setState({password})} value={this.state.password} style={styles.input} /> + + Sign in + + + Sign up + + + ) + } + + userListScreen = () => { + return ( + + { + !!this.state.list.length && this.state.list.map((item) => * {item.text}) + } + + this.setState({listText})} value={this.state.listText} style={styles.input} /> + + Add to list + + + ) + } + + render() { + return ( + + { + this.state.authenticated ? + this.userListScreen() : this.loginScreen() + } + + ) + } +} + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: '100%', + }, + sub: { + height: '50%', + width: '60%', + justifyContent: 'space-between', + padding: 4, + }, + button: { + backgroundColor: 'rgba(0, 0, 0, 0.3)', + borderWidth: 1, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: 50, + }, + input: { + borderColor: 'black', + borderWidth: 1, + borderRadius: 5, + width: '100%', + height: 50, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: '32%', + }, +}); diff --git a/examples/react-native/src/App/PolyFillCrypto.js b/examples/react-native/src/App/PolyFillCrypto.js new file mode 100644 index 00000000..4ac624c7 --- /dev/null +++ b/examples/react-native/src/App/PolyFillCrypto.js @@ -0,0 +1,92 @@ +import * as React from 'react'; +import { Platform, StyleSheet, View } from 'react-native'; + +import WebViewBridge from 'react-native-webview-bridge'; + +import { MainWorker, webViewWorkerString } from '../webview-crypto'; + +import encodeUtf8 from 'encode-utf8'; +import encodeBase64 from 'fast-base64-encode'; + +const base64EncodeString = (input) => { + return encodeBase64(new Uint8Array(encodeUtf8(input))); +}; + +const internalLibIOS = ` +${webViewWorkerString} +(function () { + var wvw = new WebViewWorker(WebViewBridge.send.bind(WebViewBridge)); + WebViewBridge.onMessage = wvw.onMainMessage.bind(wvw); +}()); +`; + +const intermediateLib = ` +${webViewWorkerString} +(function () { + var wvw = new WebViewWorker(WebViewBridge.send.bind(WebViewBridge)); + WebViewBridge.onMessage = wvw.onMainMessage.bind(wvw); +}()); +`; + +const internalLibAndroid = `eval(window.atob('${base64EncodeString(intermediateLib)}'))`; + +export default class PolyfillCrypto extends React.Component { + shouldComponentUpdate() { + return false; + } + + render() { + let worker; + const uri = 'file:///android_asset/html/blank.html'; + return ( + + { + if (c && !worker) { + worker = new MainWorker(c.sendToBridge, this.props.debug); + + if (window.crypto) { + // we are in chrome debugger + // this means overridng the crypto object itself won't + // work, so we have to override all of it's methods + for (const name in worker.crypto.subtle) { + window.crypto.subtle[name] = worker.crypto.subtle[name]; + } + window.crypto.fake = true; + } else { + window.crypto = worker.crypto; + } + window.crypto.loaded = true; + console.log('*** poly injected', window.crypto); + } + }} + onBridgeMessage={ + // can't refer to this.state.onBridgeMessage directly + // because it is not defined when this component is first + // started, only set in `ref` + (message) => { + worker.onWebViewMessage(message); + } + } + injectedJavaScript={ + Platform.OS === 'android' ? internalLibAndroid : internalLibIOS + } + onError={(error) => { + console.warn('Error creating webview: ', error); + }} + javaScriptEnabled={true} + source={{ + uri: Platform.OS === 'android' ? uri : 'about:blank', + }} + /> + + ); + } +} + +const styles = StyleSheet.create({ + hidden: { + height: 0, + opacity: 0, + }, +}); diff --git a/examples/react-native/src/App/app.js b/examples/react-native/src/App/app.js new file mode 100644 index 00000000..440e9ba1 --- /dev/null +++ b/examples/react-native/src/App/app.js @@ -0,0 +1,26 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + * + * @format + * @flow + */ + +import React, {Component} from 'react'; +import {View} from 'react-native'; + +import {Demo} from './Demo'; + +import PolyFillCrypto from './PolyFillCrypto'; + + +export class App extends Component { + render() { + return ( + + + + + ); + } +} diff --git a/examples/react-native/src/App/index.js b/examples/react-native/src/App/index.js new file mode 100644 index 00000000..5cb8af0c --- /dev/null +++ b/examples/react-native/src/App/index.js @@ -0,0 +1 @@ +export {App as default} from './app'; diff --git a/examples/react-native/src/extensions/asyncStorageAdapter.js b/examples/react-native/src/extensions/asyncStorageAdapter.js new file mode 100644 index 00000000..8c9740e3 --- /dev/null +++ b/examples/react-native/src/extensions/asyncStorageAdapter.js @@ -0,0 +1,75 @@ +import * as Gun from 'gun'; +import { AsyncStorage } from 'react-native'; + +const readNode = (key, cb) => { + AsyncStorage.getItem(key || '', cb); +}; + +const read = (request, db) => { + const { get } = request; + + const dedupid = request['#']; + const key = get['#']; + const field = get['.']; + + const done = (err, data) => { + if (!data && !err) { + db.on('in', { + '@': dedupid, + put: null, + err: null, + }); + } else { + db.on('in', { + '@': dedupid, + put: Gun.graph.node(data), + err, + }); + } + }; + + const acknowledgeRet = (err, result) => { + if (err) { + done(err); + } else if (result === null) { + // Nothing found + done(null); + } else { + const temp = JSON.parse(result); + if (field) { + done(null, temp[field] || null); + } else { + done(null, temp); + } + } + }; + + readNode(key || '', acknowledgeRet); +}; + +const write = (request, db) => { + const { put: graph } = request; + const keys = Object.keys(graph); + const dedupid = graph['#']; + + const instructions = keys.map((key) => { + return [key, JSON.stringify(graph[key] || {})]; + }); + + AsyncStorage.multiMerge(instructions, (err) => { + db.on('in', { + '#': dedupid, + ok: !err || err.length === 0, + err, + }); + }); +}; + +// This returns a promise, it can be awaited! +const reset = () => AsyncStorage.clear(); + +export default { + read, + write, + reset, +}; diff --git a/examples/react-native/src/extensions/sea.js b/examples/react-native/src/extensions/sea.js new file mode 100644 index 00000000..b99397d9 --- /dev/null +++ b/examples/react-native/src/extensions/sea.js @@ -0,0 +1,1215 @@ +;(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? () => {} : arg.slice? USE[R(arg)] : function(mod, path){ + arg(mod = {exports: {}}); + USE[R(path)] = mod.exports; + } + function R(p){ + return p.split('/').slice(-1).toString().replace('.js',''); + } + } + if(typeof module !== "undefined"){ var common = module } + /* UNBUILD */ + + ;USE(function(module){ + // Security, Encryption, and Authorization: SEA.js + // MANDATORY READING: https://gun.eco/explainers/data/security.html + // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH. + // THIS IS AN EARLY ALPHA! + + if(typeof window !== "undefined"){ module.window = window } + + var tmp = module.window || module; + var SEA = tmp.SEA || {}; + + if(SEA.window = module.window){ SEA.window.SEA = SEA } + + try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){} + module.exports = SEA; + })(USE, './root'); + + ;USE(function(module){ + var SEA = USE('./root'); + if(SEA.window){ + if(location.protocol.indexOf('s') < 0 + && location.host.indexOf('localhost') < 0 + && location.protocol.indexOf('file:') < 0){ + location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS! + } + } + })(USE, './https'); + + ;USE(function(module){ + // This is Array extended to have .toString(['utf8'|'hex'|'base64']) + function SeaArray() {} + Object.assign(SeaArray, { from: Array.from }) + SeaArray.prototype = Object.create(Array.prototype) + SeaArray.prototype.toString = function(enc, start, end) { enc = enc || 'utf8'; start = start || 0; + const length = this.length + if (enc === 'hex') { + const buf = new Uint8Array(this) + return [ ...Array(((end && (end + 1)) || length) - start).keys()] + .map((i) => buf[ i + start ].toString(16).padStart(2, '0')).join('') + } + if (enc === 'utf8') { + return Array.from( + { length: (end || length) - start }, + (_, i) => String.fromCharCode(this[ i + start]) + ).join('') + } + if (enc === 'base64') { + return btoa(this) + } + } + module.exports = SeaArray; + })(USE, './array'); + + ;USE(function(module){ + // This is Buffer implementation used in SEA. Functionality is mostly + // compatible with NodeJS 'safe-buffer' and is used for encoding conversions + // between binary and 'hex' | 'utf8' | 'base64' + // See documentation and validation for safe implementation in: + // https://github.com/feross/safe-buffer#update + var SeaArray = USE('./array'); + function SafeBuffer(...props) { + console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()') + return SafeBuffer.from(...props) + } + SafeBuffer.prototype = Object.create(Array.prototype) + Object.assign(SafeBuffer, { + // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64' + from() { + if (!Object.keys(arguments).length) { + throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') + } + const input = arguments[0] + let buf + if (typeof input === 'string') { + const enc = arguments[1] || 'utf8' + if (enc === 'hex') { + const bytes = input.match(/([\da-fA-F]{2})/g) + .map((byte) => parseInt(byte, 16)) + if (!bytes || !bytes.length) { + throw new TypeError('Invalid first argument for type \'hex\'.') + } + buf = SeaArray.from(bytes) + } else if (enc === 'utf8') { + const length = input.length + const words = new Uint16Array(length) + Array.from({ length: length }, (_, i) => words[i] = input.charCodeAt(i)) + buf = SeaArray.from(words) + } else if (enc === 'base64') { + const dec = atob(input) + const length = dec.length + const bytes = new Uint8Array(length) + Array.from({ length: length }, (_, i) => bytes[i] = dec.charCodeAt(i)) + buf = SeaArray.from(bytes) + } else if (enc === 'binary') { + buf = SeaArray.from(input) + } else { + console.info('SafeBuffer.from unknown encoding: '+enc) + } + return buf + } + const byteLength = input.byteLength // what is going on here? FOR MARTTI + const length = input.byteLength ? input.byteLength : input.length + if (length) { + let buf + if (input instanceof ArrayBuffer) { + buf = new Uint8Array(input) + } + return SeaArray.from(buf || input) + } + }, + // This is 'safe-buffer.alloc' sans encoding support + alloc(length, fill = 0 /*, enc*/ ) { + return SeaArray.from(new Uint8Array(Array.from({ length: length }, () => fill))) + }, + // This is normal UNSAFE 'buffer.alloc' or 'new Buffer(length)' - don't use! + allocUnsafe(length) { + return SeaArray.from(new Uint8Array(Array.from({ length : length }))) + }, + // This puts together array of array like members + concat(arr) { // octet array + if (!Array.isArray(arr)) { + throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.') + } + return SeaArray.from(arr.reduce((ret, item) => ret.concat(Array.from(item)), [])) + } + }) + SafeBuffer.prototype.from = SafeBuffer.from + SafeBuffer.prototype.toString = SeaArray.prototype.toString + + module.exports = SafeBuffer; + })(USE, './buffer'); + + ;USE(function(module){ + const SEA = USE('./root') + const Buffer = USE('./buffer') + const api = {Buffer: Buffer} + var o = {}; + + const interval = setInterval(() => { + if (window.crypto.loaded) { + api.crypto = window.crypto; + api.subtle = api.crypto.subtle; + api.TextEncoder = window.TextEncoder; + api.TextDecoder = window.TextDecoder; + api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len)))) + console.log('*** gun sea crypto set', api); + clearInterval(interval); + } + }, 500); + + module.exports = api + })(USE, './shim'); + + ;USE(function(module){ + const SEA = USE('./root'); + const Buffer = USE('./buffer') + const settings = {} + // Encryption parameters + const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 } + + const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } } + const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' } + const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' } + + const _initial_authsettings = { + validity: 12 * 60 * 60, // internally in seconds : 12 hours + hook: (props) => props // { iat, exp, alias, remember } + // or return new Promise((resolve, reject) => resolve(props) + } + // These are used to persist user's authentication "session" + const authsettings = Object.assign({}, _initial_authsettings) + // This creates Web Cryptography API compliant JWK for sign/verify purposes + const keysToEcdsaJwk = (pub, d) => { // d === priv + //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old + const [ x, y ] = pub.split('.') // new + var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true } + jwk.key_ops = d ? ['sign'] : ['verify']; + if(d){ jwk.d = d } + return jwk; + } + + Object.assign(settings, { + pbkdf2: pbkdf2, + ecdsa: { + pair: ecdsaKeyProps, + sign: ecdsaSignProps + }, + ecdh: ecdhKeyProps, + jwk: keysToEcdsaJwk, + recall: authsettings + }) + SEA.opt = settings; + module.exports = settings + })(USE, './settings'); + + ;USE(function(module){ + module.exports = (props) => { + try { + if(props.slice && 'SEA{' === props.slice(0,4)){ + props = props.slice(3); + } + return props.slice ? JSON.parse(props) : props + } catch (e) {} //eslint-disable-line no-empty + return props + } + })(USE, './parse'); + + ;USE(function(module){ + const shim = USE('./shim'); + const Buffer = USE('./buffer') + const parse = USE('./parse') + const { pbkdf2 } = USE('./settings') + // This internal func returns SHA-256 hashed data for signing + const sha256hash = async (mm) => { + const m = parse(mm) + const hash = await shim.subtle.digest({name: pbkdf2.hash}, new shim.TextEncoder().encode(m)) + return Buffer.from(hash) + } + module.exports = sha256hash + })(USE, './sha256'); + + ;USE(function(module){ + // This internal func returns SHA-1 hashed data for KeyID generation + const __shim = USE('./shim') + const subtle = __shim.subtle + const ossl = __shim.ossl ? __shim.ossl : subtle + const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b)) + module.exports = sha1hash + })(USE, './sha1'); + + ;USE(function(module){ + var SEA = USE('./root'); + var shim = USE('./shim'); + var S = USE('./settings'); + var sha = USE('./sha256'); + var u; + + SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof` + var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random! + var opt = opt || {}; + if(salt instanceof Function){ + cb = salt; + salt = u; + } + salt = salt || shim.random(9); + if('SHA-256' === opt.name){ + var rsha = shim.Buffer.from(await sha(data), 'binary').toString('utf8') + if(cb){ try{ cb(rsha) }catch(e){console.log(e)} } + return rsha; + } + const key = await (shim.ossl || shim.subtle).importKey( + 'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits'] + ) + const result = await (shim.ossl || shim.subtle).deriveBits({ + name: opt.name || 'PBKDF2', + iterations: opt.iterations || S.pbkdf2.iter, + salt: new shim.TextEncoder().encode(opt.salt || salt), + hash: opt.hash || S.pbkdf2.hash, + }, key, opt.length || (S.pbkdf2.ks * 8)) + data = shim.random(data.length) // Erase data in case of passphrase + const r = shim.Buffer.from(result, 'binary').toString('utf8') + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }}); + + module.exports = SEA.work; + })(USE, './work'); + + ;USE(function(module){ + var SEA = USE('./root'); + var shim = USE('./shim'); + var S = USE('./settings'); + var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer; + + SEA.name = SEA.name || (async (cb, opt) => { try { + if(cb){ try{ cb() }catch(e){console.log(e)} } + return; + } catch(e) { + console.log(e); + SEA.err = e; + if(cb){ cb() } + return; + }}); + + //SEA.pair = async (data, proof, cb) => { try { + SEA.pair = SEA.pair || (async (cb, opt) => { try { + + const ecdhSubtle = shim.ossl || shim.subtle + // First: ECDSA keys for signing/verifying... + var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) + .then(async (keys) => { + // privateKey scope doesn't leak out from here! + //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) + const key = {}; + key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; + const pub = await shim.subtle.exportKey('jwk', keys.publicKey) + //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old + key.pub = pub.x+'.'+pub.y // new + // x and y are already base64 + // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) + // but split on a non-base64 letter. + return key; + }) + + // To include PGPv4 kind of keyId: + // const pubId = await SEA.keyid(keys.pub) + // Next: ECDH keys for encryption/decryption... + + try{ + var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) + .then(async (keys) => { + // privateKey scope doesn't leak out from here! + const key = {}; + key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; + const pub = await ecdhSubtle.exportKey('jwk', keys.publicKey) + //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old + key.epub = pub.x+'.'+pub.y // new + // ex and ey are already base64 + // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) + // but split on a non-base64 letter. + return key; + }) + }catch(e){ + if(SEA.window){ throw e } + if(e == 'Error: ECDH is not a supported algorithm'){ console.log('Ignoring ECDH...') } + else { throw e } + } dh = dh || {}; + + const r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } catch(e) { + console.log(e); + SEA.err = e; + if(cb){ cb() } + return; + }}); + + module.exports = SEA.pair; + })(USE, './pair'); + + ;USE(function(module){ + var SEA = USE('./root'); + var shim = USE('./shim'); + var S = USE('./settings'); + var sha256hash = USE('./sha256'); + + SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { + if(data && data.slice + && 'SEA{' === data.slice(0,4) + && '"m":' === data.slice(4,8)){ + // TODO: This would prevent pair2 signing pair1's signature. + // So we may want to change this in the future. + // but for now, we want to prevent duplicate double signature. + if(cb){ try{ cb(data) }catch(e){console.log(e)} } + return data; + } + opt = opt || {}; + if(!(pair||opt).priv){ + pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); + } + const pub = pair.pub + const priv = pair.priv + const jwk = S.jwk(pub, priv) + const msg = JSON.stringify(data) + const hash = await sha256hash(msg) + const sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) + .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! + const r = 'SEA'+JSON.stringify({m: msg, s: shim.Buffer.from(sig, 'binary').toString('utf8')}); + + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } catch(e) { + console.log(e); + SEA.err = e; + if(cb){ cb() } + return; + }}); + + module.exports = SEA.sign; + })(USE, './sign'); + + ;USE(function(module){ + var SEA = USE('./root'); + var shim = USE('./shim'); + var S = USE('./settings'); + var sha256hash = USE('./sha256'); + var parse = USE('./parse'); + var u; + + SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { + const json = parse(data) + if(false === pair){ // don't verify! + const raw = (json !== data)? + (json.s && json.m)? parse(json.m) : data + : json; + if(cb){ try{ cb(raw) }catch(e){console.log(e)} } + return raw; + } + opt = opt || {}; + // SEA.I // verify is free! Requires no user permission. + if(json === data){ throw "No signature on data." } + const pub = pair.pub || pair + const jwk = S.jwk(pub) + const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) + const hash = await sha256hash(json.m) + const sig = new Uint8Array(shim.Buffer.from(json.s, 'utf8')) + const check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + const r = check? parse(json.m) : u; + + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } catch(e) { + console.log(e); // mismatched owner FOR MARTTI + SEA.err = e; + if(cb){ cb() } + return; + }}); + + module.exports = SEA.verify; + })(USE, './verify'); + + ;USE(function(module){ + var shim = USE('./shim'); + var sha256hash = USE('./sha256'); + + const importGen = async (key, salt, opt) => { + //const combo = shim.Buffer.concat([shim.Buffer.from(key, 'utf8'), salt || shim.random(8)]).toString('utf8') // old + var opt = opt || {}; + const combo = key + (salt || shim.random(8)).toString('utf8'); // new + const hash = shim.Buffer.from(await sha256hash(combo), 'binary') + return await shim.subtle.importKey('raw', new Uint8Array(hash), opt.name || 'AES-GCM', false, ['encrypt', 'decrypt']) + } + module.exports = importGen; + })(USE, './aeskey'); + + ;USE(function(module){ + var SEA = USE('./root'); + var shim = USE('./shim'); + var S = USE('./settings'); + var aeskey = USE('./aeskey'); + + SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try { + opt = opt || {}; + var key = (pair||opt).epriv || pair; + if(!key){ + pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why}); + key = pair.epriv || pair; + } + const msg = JSON.stringify(data) + const rand = {s: shim.random(8), iv: shim.random(16)}; + const ct = await aeskey(key, rand.s, opt) + .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... + name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) + }, aes, new shim.TextEncoder().encode(msg))) + const r = 'SEA'+JSON.stringify({ + ct: shim.Buffer.from(ct, 'binary').toString('utf8'), + iv: rand.iv.toString('utf8'), + s: rand.s.toString('utf8') + }); + + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }}); + + module.exports = SEA.encrypt; + })(USE, './encrypt'); + + ;USE(function(module){ + var SEA = USE('./root'); + var shim = USE('./shim'); + var S = USE('./settings'); + var aeskey = USE('./aeskey'); + var parse = USE('./parse'); + + SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try { + opt = opt || {}; + var key = (pair||opt).epriv || pair; + if(!key){ + pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why}); + key = pair.epriv || pair; + } + const json = parse(data) + const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt) + .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... + name: opt.name || 'AES-GCM', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8')) + }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8')))) + const r = parse(new shim.TextDecoder('utf8').decode(ct)) + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }}); + + module.exports = SEA.decrypt; + })(USE, './decrypt'); + + ;USE(function(module){ + var SEA = USE('./root'); + var shim = USE('./shim'); + var S = USE('./settings'); + // Derive shared secret from other's pub and my epub/epriv + SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try { + opt = opt || {}; + if(!pair || !pair.epriv || !pair.epub){ + pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why}); + } + const pub = key.epub || key + const epub = pair.epub + const epriv = pair.epriv + const ecdhSubtle = shim.ossl || shim.subtle + const pubKeyData = keysToEcdhJwk(pub) + const props = Object.assign( + S.ecdh, + { public: await ecdhSubtle.importKey(...pubKeyData, true, []) } + ) + const privKeyData = keysToEcdhJwk(epub, epriv) + const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']) + .then(async (privKey) => { + // privateKey scope doesn't leak out from here! + const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]) + return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k) + }) + const r = derived; + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }}); + + const keysToEcdhJwk = (pub, d) => { // d === priv + //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old + const [ x, y ] = pub.split('.') // new + const jwk = d ? { d: d } : {} + return [ // Use with spread returned value... + 'jwk', + Object.assign( + jwk, + { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true } + ), // ??? refactor + S.ecdh + ] + } + + module.exports = SEA.secret; + })(USE, './secret'); + + ;USE(function(module){ + var shim = USE('./shim'); + // Practical examples about usage found from ./test/common.js + 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.random = SEA.random || shim.random; + + // This is Buffer used in SEA and usable from Gun/SEA application also. + // For documentation see https://nodejs.org/api/buffer.html + SEA.Buffer = SEA.Buffer || USE('./buffer'); + + // These SEA functions support now ony Promises or + // async/await (compatible) code, use those like Promises. + // + // Creates a wrapper library around Web Crypto API + // for various AES, ECDSA, PBKDF2 functions we called above. + // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string) + SEA.keyid = SEA.keyid || (async (pub) => { + try { + // base64('base64(x):base64(y)') => Buffer(xy) + const pb = Buffer.concat( + pub.replace(/-/g, '+').replace(/_/g, '/').split('.') + .map((t) => Buffer.from(t, 'base64')) + ) + // id is PGPv4 compliant raw key + const id = Buffer.concat([ + Buffer.from([0x99, pb.length / 0x100, pb.length % 0x100]), pb + ]) + const sha1 = await sha1hash(id) + const hash = Buffer.from(sha1, 'binary') + return hash.toString('hex', hash.length - 8) // 16-bit ID as hex + } catch (e) { + console.log(e) + throw e + } + }); + // all done! + // Obviously it is missing MANY necessary features. This is only an alpha release. + // Please experiment with it, audit what I've done so far, and complain about what needs to be added. + // SEA should be a full suite that is easy and seamless to use. + // Again, scroll naer the top, where I provide an EXAMPLE of how to create a user and sign in. + // Once logged in, the rest of the code you just read handled automatically signing/validating data. + // 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('./gun', 1); + Gun.SEA = SEA; + SEA.GUN = SEA.Gun = Gun; + + module.exports = SEA + })(USE, './sea'); + + ;USE(function(module){ + var Gun = USE('./sea').Gun; + Gun.chain.then = function(cb){ + var gun = this, p = (new Promise(function(res, rej){ + gun.once(res); + })); + return cb? p.then(cb) : p; + } + })(USE, './then'); + + ;USE(function(module){ + var SEA = USE('./sea'); + var Gun = SEA.Gun; + var then = USE('./then'); + + function User(root){ + this._ = {$: this}; + } + User.prototype = (function(){ function F(){}; F.prototype = Gun.chain; return new F() }()) // Object.create polyfill + User.prototype.constructor = User; + + // let's extend the gun chain with a `user` function. + // only one user can be logged in at a time, per gun instance. + Gun.chain.user = function(pub){ + var gun = this, root = gun.back(-1), user; + if(pub){ return root.get('~'+pub) } + if(user = root.back('user')){ return user } + var root = (root._), at = root, uuid = at.opt.uuid || Gun.state.lex; + (at = (user = at.user = gun.chain(new User))._).opt = {}; + at.opt.uuid = function(cb){ + var id = uuid(), pub = root.user; + if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id } + id = id + '~' + pub + '.'; + if(cb && cb.call){ cb(null, id) } + return id; + } + return user; + } + Gun.User = User; + module.exports = User; + })(USE, './user'); + + ;USE(function(module){ + // TODO: This needs to be split into all separate functions. + // Not just everything thrown into 'create'. + + var SEA = USE('./sea'); + var User = USE('./user'); + var authsettings = USE('./settings'); + var Gun = SEA.Gun; + + var noop = function(){}; + + // Well first we have to actually create a user. That is what this function does. + User.prototype.create = function(alias, pass, cb, opt){ + var gun = this, cat = (gun._), root = gun.back(-1); + cb = cb || noop; + if(cat.ing){ + cb({err: Gun.log("User is already being created or authenticated!"), wait: true}); + return gun; + } + cat.ing = true; + opt = opt || {}; + var act = {}, u; + act.a = function(pubs){ + act.pubs = pubs; + if(pubs && !opt.already){ + // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed. + var ack = {err: Gun.log('User already created!')}; + cat.ing = false; + cb(ack); + gun.leave(); + return; + } + act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it. + SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks. + } + act.b = function(proof){ + act.proof = proof; + SEA.pair(act.c); // now we have generated a brand new ECDSA key pair for the user account. + } + act.c = function(pair){ + act.pair = pair || {}; + // the user's public key doesn't need to be signed. But everything else needs to be signed with it! + act.data = {pub: pair.pub}; + SEA.sign(alias, pair, act.d); + } + act.d = function(alias){ + act.data.alias = alias; + SEA.sign(act.pair.epub, act.pair, act.e); + } + act.e = function(epub){ + act.data.epub = epub; + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f); // to keep the private key safe, we AES encrypt it with the proof of work! + } + act.f = function(auth){ + act.data.auth = auth; + SEA.sign({ek: auth, s: act.salt}, act.pair, act.g); + } + act.g = function(auth){ var tmp; + act.data.auth = auth; + root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID. + root.get('~@'+alias).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))); // next up, we want to associate the alias with the public key. So we add it to the alias list. + setTimeout(function(){ // we should be able to delete this now, right? + cat.ing = false; + cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) + if(noop === cb){ gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up. + },10); + } + root.get('~@'+alias).once(act.a); + return gun; + } + // now that we have created a user, we want to authenticate them! + User.prototype.auth = function(alias, pass, cb, opt){ + var gun = this, cat = (gun._), root = gun.back(-1); + cb = cb || function(){}; + if(cat.ing){ + cb({err: Gun.log("User is already being created or authenticated!"), wait: true}); + return gun; + } + cat.ing = true; + opt = opt || {}; + var pair = (alias.pub || alias.epub)? alias : (pass.pub || pass.epub)? pass : null; + var act = {}, u; + act.a = function(data){ + if(!data){ return act.b() } + if(!data.pub){ + var tmp = []; + Gun.node.is(data, function(v){ tmp.push(v) }) + return act.b(tmp); + } + if(act.name){ return act.f(data) } + act.c((act.data = data).auth); + } + act.b = function(list){ + var get = (act.list = (act.list||[]).concat(list||[])).shift(); + if(u === get){ + if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') } + return act.err('Wrong user or password.') + } + root.get(get).once(act.a); + } + act.c = function(auth){ + if(u === auth){ return act.b() } + SEA.work(pass, (act.auth = auth).s, act.d); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. + } + act.d = function(proof){ + if(u === proof){ return act.b() } + SEA.decrypt(act.auth.ek, proof, act.e); + } + act.e = function(half){ + if(u === half){ return act.b() } + act.half = half; + act.f(act.data); + } + act.f = function(data){ + if(!data || !data.pub){ return act.b() } + var tmp = act.half || {}; + act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv}); + } + act.g = function(pair){ + act.pair = pair; + var user = (root._).user, at = (user._); + var tmp = at.tag; + var upt = at.opt; + at = user._ = root.get('~'+pair.pub)._; + at.opt = upt; + // add our credentials in-memory only to our root user instance + user.is = {pub: pair.pub, epub: pair.epub, alias: alias}; + at.sea = act.pair; + cat.ing = false; + opt.change? act.z() : cb(at); + if(SEA.window && ((gun.back('user')._).opt||opt).remember){ + // TODO: this needs to be modular. + var sS = {}; try{sS = window.sessionStorage}catch(e){} + sS.recall = true; + sS.alias = alias; + sS.tmp = pass; + } + try{ + (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do. + //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data. + }catch(e){ + Gun.log("Your 'auth' callback crashed with:", e); + } + } + act.z = function(){ + // password update so encrypt private key using new pwd + salt + act.salt = Gun.text.random(64); // pseudo-random + SEA.work(opt.change, act.salt, act.y); + } + act.y = function(proof){ + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x); + } + act.x = function(auth){ + SEA.sign({ek: auth, s: act.salt}, act.pair, act.w); + } + act.w = function(auth){ + root.get('~'+act.pair.pub).get('auth').put(auth, cb); + } + act.err = function(e){ + var ack = {err: Gun.log(e || 'User cannot be found!')}; + cat.ing = false; + cb(ack); + } + act.plugin = function(name){ + if(!(act.name = name)){ return act.err() } + var tmp = [name]; + if('~' !== name[0]){ + tmp[1] = '~'+name; + tmp[2] = '~@'+name; + } + act.b(tmp); + } + if(pair){ + act.g(pair); + } else + if(alias){ + root.get('~@'+alias).once(act.a); + } else + if(!alias && !pass){ + SEA.name(act.plugin); + } + return gun; + } + User.prototype.pair = function(){ + console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!"); + var user = this; + if(!user.is){ return false } + return user._.sea; + } + User.prototype.leave = function(opt, cb){ + var gun = this, user = (gun.back(-1)._).user; + if(user){ + delete user.is; + delete user._.is; + delete user._.sea; + } + if(SEA.window){ + var sS = {}; try{sS = window.sessionStorage}catch(e){}; + delete sS.alias; + delete sS.tmp; + delete sS.recall; + } + return gun; + } + // If authenticated user wants to delete his/her account, let's support it! + User.prototype.delete = async function(alias, pass, cb){ + var gun = this, root = gun.back(-1), user = gun.back('user'); + try { + user.auth(alias, pass, function(ack){ + var pub = (user.is||{}).pub; + // Delete user data + user.map().once(function(){ this.put(null) }); + // Wipe user data from memory + user.leave(); + (cb || noop)({ok: 0}); + }); + } catch (e) { + Gun.log('User.delete failed! Error:', e); + } + return gun; + } + User.prototype.recall = function(opt, cb){ + var gun = this, root = gun.back(-1), tmp; + opt = opt || {}; + if(opt && opt.sessionStorage){ + if(SEA.window){ + var sS = {}; try{sS = window.sessionStorage}catch(e){} + if(sS){ + (root._).opt.remember = true; + ((gun.back('user')._).opt||opt).remember = true; + if(sS.recall || (sS.alias && sS.tmp)){ + root.user().auth(sS.alias, sS.tmp, cb); + } + } + } + return gun; + } + /* + TODO: copy mhelander's expiry code back in. + Although, we should check with community, + should expiry be core or a plugin? + */ + return gun; + } + User.prototype.alive = async function(){ + const gunRoot = this.back(-1) + try { + // All is good. Should we do something more with actual recalled data? + await authRecall(gunRoot) + return gunRoot._.user._ + } catch (e) { + const err = 'No session!' + Gun.log(err) + throw { err } + } + } + User.prototype.trust = async function(user){ + // TODO: BUG!!! SEA `node` read listener needs to be async, which means core needs to be async too. + //gun.get('alice').get('age').trust(bob); + if (Gun.is(user)) { + user.get('pub').get((ctx, ev) => { + console.log(ctx, ev) + }) + } + } + User.prototype.grant = function(to, cb){ + console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); + var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; + gun.back(function(at){ if(at.is){ return } path += (at.get||'') }); + (async function(){ + var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); + sec = await SEA.decrypt(sec, pair); + if(!sec){ + sec = SEA.random(16).toString(); + enc = await SEA.encrypt(sec, pair); + user.get('trust').get(pair.pub).get(path).put(enc); + } + var pub = to.get('pub').then(); + var epub = to.get('epub').then(); + pub = await pub; epub = await epub; + var dh = await SEA.secret(epub, pair); + enc = await SEA.encrypt(sec, dh); + user.get('trust').get(pub).get(path).put(enc, cb); + }()); + return gun; + } + User.prototype.secret = function(data, cb){ + console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); + var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; + gun.back(function(at){ if(at.is){ return } path += (at.get||'') }); + (async function(){ + var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); + sec = await SEA.decrypt(sec, pair); + if(!sec){ + sec = SEA.random(16).toString(); + enc = await SEA.encrypt(sec, pair); + user.get('trust').get(pair.pub).get(path).put(enc); + } + enc = await SEA.encrypt(data, sec); + gun.put(enc, cb); + }()); + return gun; + } + module.exports = User + })(USE, './create'); + + ;USE(function(module){ + const SEA = USE('./sea') + const 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); + } + this.to.next(at); // make sure to call the "next" middleware adapter. + }); + + // Alright, this next adapter gets run at the per node level in the graph database. + // This will let us verify that every property on a node has a value signed by a public key we trust. + // If the signature does not match, the data is just `undefined` so it doesn't get passed on. + // If it does match, then we transform the in-memory "view" of the data into its plain value (without the signature). + // Now NOTE! Some data is "system" data, not user data. Example: List of public keys, aliases, etc. + // This data is self-enforced (the value can only match its ID), but that is handled in the `security` function. + // From the self-enforced data, we can see all the edges in the graph that belong to a public key. + // Example: ~ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and + // its encrypted private key, but it might also have other signed values on it like `profile = ` edge. + // Using that directed edge's ID, we can then track (in memory) which IDs belong to which keys. + // Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous! + // This means we should ONLY trust our "friends" (our key ring) public keys, not any ones. + // I have not yet added that to SEA yet in this alpha release. That is coming soon, but beware in the meanwhile! + function each(msg){ // TODO: Warning: Need to switch to `gun.on('node')`! Do not use `Gun.on('node'` in your apps! + // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!! + // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT. + var to = this.to, vertex = (msg.$._).put, c = 0, d; + Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node + // TODO: consider async/await use here... + SEA.verify(val, false, function(data){ c--; // false just extracts the plain data. + node[key] = val = data; // transform to plain value. + if(d && !c && (c = -1)){ to.next(msg) } + }); + }); + d = true; + if(d && !c){ to.next(msg) } + return; + } + + // signature handles data output, it is a proxy to the security function. + function signature(msg){ + if(msg.user){ + return this.to.next(msg); + } + var ctx = this.as; + msg.user = ctx.user; + security.call(this, msg); + } + + // 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: + function security(msg){ + var at = this.as, sea = at.sea, to = this.to; + if(msg.get){ + // if there is a request to read data from us, then... + var soul = msg.get['#']; + if(soul){ // for now, only allow direct IDs to be read. + if(soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors. + if('alias' === soul){ // Allow reading the list of usernames/aliases in the system? + return to.next(msg); // yes. + } else + if('~@' === soul.slice(0,2)){ // Allow reading the list of public keys associated with an alias? + return to.next(msg); // yes. + } else { // Allow reading everything? + return to.next(msg); // yes // TODO: No! Make this a callback/event that people can filter on. + } + } + } + if(msg.put){ + // potentially parallel async operations!!! + var check = {}, each = {}, u; + each.node = function(node, soul){ + if(Gun.obj.empty(node, '_')){ return check['node'+soul] = 0 } // ignore empty updates, don't reject them. + Gun.obj.map(node, each.way, {soul: soul, node: node}); + }; + each.way = function(val, key){ + var soul = this.soul, node = this.node, tmp; + if('_' === key){ return } // ignore meta data + if('~@' === soul){ // special case for shared system data, the list of aliases. + each.alias(val, key, node, soul); return; + } + if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias. + each.pubs(val, key, node, soul); return; + } + if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key. + each.pub(val, key, node, soul, tmp, msg.user); return; + } + each.any(val, key, node, soul, msg.user); return; + return each.end({err: "No other data allowed!"}); + }; + each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}} + if(!val){ return each.end({err: "Data must exist!"}) } // data MUST exist + if('~@'+key === Gun.val.link.is(val)){ return check['alias'+key] = 0 } // in fact, it must be EXACTLY equal to itself + each.end({err: "Mismatching alias."}); // if it isn't, reject. + }; + each.pubs = function(val, key, node, soul){ // Example: {_:#~@alice, ~asdf: {#~asdf}} + if(!val){ return each.end({err: "Alias must exist!"}) } // data MUST exist + if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property + each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys. + }; + each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}} + if('pub' === key){ + if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key. + return each.end({err: "Account must match!"}); + } + check['user'+soul+key] = 1; + if(user && user.is && pub === user.is.pub){ + //var id = Gun.text.random(3); + SEA.sign(val, (user._).sea, function(data){ var rel; + if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) } + if(rel = Gun.val.link.is(val)){ + (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; + } + node[key] = data; + check['user'+soul+key] = 0; + each.end({ok: 1}); + }); + // TODO: Handle error!!!! + return; + } + SEA.verify(val, pub, function(data){ var rel, tmp; + if(u === data){ // make sure the signature matches the account it claims to be on. + return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account. + } + if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ + (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; + } + check['user'+soul+key] = 0; + each.end({ok: 1}); + }); + }; + function relpub(s){ + if(!s){ return } + s = s.split('~'); + if(!s || !(s = s[1])){ return } + s = s.split('.'); + if(!s || 2 > s.length){ return } + s = s.slice(0,2).join('.'); + return s; + } + each.any = function(val, key, node, soul, user){ var tmp, pub; + if(!user || !user.is){ + if(tmp = relpub(soul)){ + check['any'+soul+key] = 1; + SEA.verify(val, pub = tmp, function(data){ var rel; + if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski ! + if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ + (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; + } + check['any'+soul+key] = 0; + each.end({ok: 1}); + }); + return; + } + check['any'+soul+key] = 1; + at.on('secure', function(msg){ this.off(); + check['any'+soul+key] = 0; + if(at.opt.secure){ msg = null } + each.end(msg || {err: "Data cannot be modified."}); + }).on.on('secure', msg); + //each.end({err: "Data cannot be modified."}); + return; + } + if(!(tmp = relpub(soul))){ + if(at.opt.secure){ + each.end({err: "Soul is missing public key at '" + key + "'."}); + return; + } + if(val && val.slice && 'SEA{' === (val).slice(0,4)){ + check['any'+soul+key] = 0; + each.end({ok: 1}); + return; + } + //check['any'+soul+key] = 1; + //SEA.sign(val, user, function(data){ + // if(u === data){ return each.end({err: 'Any signature failed.'}) } + // node[key] = data; + check['any'+soul+key] = 0; + each.end({ok: 1}); + //}); + return; + } + if((pub = tmp) !== (user.is||noop).pub){ + each.any(val, key, node, soul); + return; + } + /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ + if((user.is||{}).pub !== p){ return p } + }); + if(other){ + each.any(val, key, node, soul); + return; + }*/ + check['any'+soul+key] = 1; + SEA.sign(val, (user._).sea, function(data){ + if(u === data){ return each.end({err: 'My signature fail.'}) } + node[key] = data; + check['any'+soul+key] = 0; + each.end({ok: 1}); + }); + } + each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb? + if(each.err){ return } + if((each.err = ctx.err) || ctx.no){ + console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI + return; + } + if(!each.end.ed){ return } + if(Gun.obj.map(check, function(no){ + if(no){ return true } + })){ return } + to.next(msg); + }; + Gun.obj.map(msg.put, each.node); + each.end({end: each.end.ed = true}); + return; // need to manually call next after async. + } + to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols). + } + var noop = {}; + + })(USE, './index'); +}()); \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/MainWorker.d.ts b/examples/react-native/src/webview-crypto/MainWorker.d.ts new file mode 100644 index 00000000..dff4ae82 --- /dev/null +++ b/examples/react-native/src/webview-crypto/MainWorker.d.ts @@ -0,0 +1,14 @@ +export default class MainWorker { + private sendToWebView; + private debug; + readonly crypto: Crypto; + private readonly subtle; + private static uuid; + private toSend; + private readyToSend; + private messages; + constructor(sendToWebView: (message: string) => void, debug?: boolean); + onWebViewMessage(message: string): void; + private getRandomValues; + private callMethod; +} diff --git a/examples/react-native/src/webview-crypto/MainWorker.js b/examples/react-native/src/webview-crypto/MainWorker.js new file mode 100644 index 00000000..fe13bc54 --- /dev/null +++ b/examples/react-native/src/webview-crypto/MainWorker.js @@ -0,0 +1,170 @@ +import serializeError from 'serialize-error'; +import { parse, stringify } from './serializeBinary'; +const SUBTLE_METHODS = [ + 'encrypt', + 'decrypt', + 'sign', + 'verify', + 'digest', + 'generateKey', + 'deriveKey', + 'deriveBits', + 'importKey', + 'exportKey', + 'wrapKey', + 'unwrapKey', +]; +/* +MainWorker provides a `crypto` attribute that proxies method calls +to the webview. + +It sends strings to the webview in the format: + + { + id: , + method: getRandomValues | subtle., + args: [] + } + +When the webview succeeds in completeing that method, it gets backs: + + { + id: , + value: + } + +And when it fails: + + { + id: , + reason: , + } + +*/ +export default class MainWorker { + // sendToWebView should take a string and send that message to the webview + constructor(sendToWebView, debug = false) { + this.sendToWebView = sendToWebView; + this.debug = debug; + // hold a queue of messages to send, in case someone calls crypto + // before the webview is initialized + this.toSend = []; + this.readyToSend = false; + // Holds the `resolve` and `reject` function for all the promises + // we are working on + this.messages = {}; + } + get crypto() { + const callMethod = this.callMethod; + return { + subtle: this.subtle, + getRandomValues: this.getRandomValues.bind(this), + fake: true, + }; + } + get subtle() { + const s = {}; + for (const m of SUBTLE_METHODS) { + s[m] = (...args) => { + return this.callMethod(`subtle.${m}`, args, true); + }; + } + return s; + } + // http://stackoverflow.com/a/105074/907060 + static uuid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return `${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}`; + } + onWebViewMessage(message) { + // first message just tells us the webview is ready + if (!this.readyToSend) { + if (this.debug) { + console.log('[webview-crypto] Got first message; ready to send'); + } + this.readyToSend = true; + for (const m of this.toSend) { + this.sendToWebView(m); + } + return; + } + parse(message) + .then(({ id, value, reason }) => { + if (this.debug) { + console.log('[webview-crypto] Received message:', JSON.stringify({ + id, + value, + reason, + })); + } + if (!id) { + console.warn('[webview-crypto] no ID passed back from message:', JSON.stringify(serializeError(reason))); + return; + } + const { resolve, reject } = this.messages[id]; + if (value) { + resolve(value); + } + else { + reject(reason); + } + delete this.messages[id]; + }) + .catch((reason) => { + console.warn('[webview-crypto] error in `parse` of message:', JSON.stringify(message), 'reason:', JSON.stringify(serializeError(reason))); + }); + } + getRandomValues(array) { + const promise = this.callMethod('getRandomValues', [array], false); + // make the _promise not enumerable so it isn't JSON stringified, + // which could lead to an infinite loop with Angular's zone promises + Object.defineProperty(array, '_promise', { + value: promise, + configurable: true, + enumerable: false, + writable: true, + }); + promise.then((updatedArray) => { + array.set(updatedArray); + }); + return array; + } + callMethod(method, args, waitForArrayBufferView) { + const id = MainWorker.uuid(); + // store this promise, so we can resolve it when we get a message + // back from the web view + const promise = new Promise((resolve, reject) => { + this.messages[id] = { resolve, reject }; + }); + const payloadObject = { method, id, args }; + if (this.debug) { + console.log('[webview-crypto] Sending message:', JSON.stringify({ + method, + args, + payloadObject, + })); + } + stringify(payloadObject, waitForArrayBufferView) + .then((message) => { + if (this.readyToSend) { + this.sendToWebView(message); + } + else { + this.toSend.push(message); + } + }) + .catch((reason) => { + this.messages[id].reject({ + message: `exception in stringify-ing message: ${method} ${id}`, + reason, + }); + delete this.messages[id]; + }); + return promise; + } +} +//# sourceMappingURL=MainWorker.js.map \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/MainWorker.js.map b/examples/react-native/src/webview-crypto/MainWorker.js.map new file mode 100644 index 00000000..0de8d063 --- /dev/null +++ b/examples/react-native/src/webview-crypto/MainWorker.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MainWorker.js","sourceRoot":"","sources":["../src/MainWorker.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAA+B,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAElF,MAAM,cAAc,GAAG;IACtB,SAAS;IACT,SAAS;IACT,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,aAAa;IACb,WAAW;IACX,YAAY;IACZ,WAAW;IACX,WAAW;IACX,SAAS;IACT,WAAW;CACX,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BE;AACF,MAAM,CAAC,OAAO,OAAO,UAAU;IA2C9B,0EAA0E;IAC1E,YAAoB,aAAwC,EAAU,QAAQ,KAAK;QAA/D,kBAAa,GAAb,aAAa,CAA2B;QAAU,UAAK,GAAL,KAAK,CAAQ;QAfnF,iEAAiE;QACjE,oCAAoC;QAC5B,WAAM,GAAa,EAAE,CAAC;QACtB,gBAAW,GAAG,KAAK,CAAC;QAE5B,iEAAiE;QACjE,oBAAoB;QACZ,aAAQ,GAKZ,EAAE,CAAC;IAG+E,CAAC;IA3CvF,IAAI,MAAM;QACT,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACnC,OAAO;YACN,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,eAAe,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;YAChD,IAAI,EAAE,IAAI;SACH,CAAC;IACV,CAAC;IAED,IAAY,MAAM;QACjB,MAAM,CAAC,GAAQ,EAAE,CAAC;QAClB,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE;YAC/B,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE;gBACzB,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YACnD,CAAC,CAAC;SACF;QACD,OAAO,CAAiB,CAAC;IAC1B,CAAC;IAED,2CAA2C;IACnC,MAAM,CAAC,IAAI;QAClB,SAAS,EAAE;YACV,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC;iBAC9C,QAAQ,CAAC,EAAE,CAAC;iBACZ,SAAS,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAC1E,CAAC;IAkBD,gBAAgB,CAAC,OAAe;QAC/B,mDAAmD;QACnD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YACtB,IAAI,IAAI,CAAC,KAAK,EAAE;gBACf,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;aACjE;YACD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE;gBAC5B,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;aACtB;YACD,OAAO;SACP;QACD,KAAK,CAAC,OAAO,CAAC;aACZ,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;YAC/B,IAAI,IAAI,CAAC,KAAK,EAAE;gBACf,OAAO,CAAC,GAAG,CACV,oCAAoC,EACpC,IAAI,CAAC,SAAS,CAAC;oBACd,EAAE;oBACF,KAAK;oBACL,MAAM;iBACN,CAAC,CACF,CAAC;aACF;YACD,IAAI,CAAC,EAAE,EAAE;gBACR,OAAO,CAAC,IAAI,CACX,kDAAkD,EAClD,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CACtC,CAAC;gBACF,OAAO;aACP;YACD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC9C,IAAI,KAAK,EAAE;gBACV,OAAO,CAAC,KAAK,CAAC,CAAC;aACf;iBAAM;gBACN,MAAM,CAAC,MAAM,CAAC,CAAC;aACf;YACD,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE;YACjB,OAAO,CAAC,IAAI,CACX,+CAA+C,EAC/C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EACvB,SAAS,EACT,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CACtC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,eAAe,CAAC,KAAkC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;QAEnE,iEAAiE;QACjE,oEAAoE;QACpE,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE;YACxC,KAAK,EAAE,OAAO;YACd,YAAY,EAAE,IAAI;YAClB,UAAU,EAAE,KAAK;YACjB,QAAQ,EAAE,IAAI;SACd,CAAC,CAAC;QAEH,OAAO,CAAC,IAAI,CAAC,CAAC,YAA6B,EAAE,EAAE;YAC7C,KAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACd,CAAC;IAEO,UAAU,CAAC,MAAc,EAAE,IAAW,EAAE,sBAA+B;QAC9E,MAAM,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;QAC7B,iEAAiE;QACjE,yBAAyB;QACzB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC/C,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,MAAM,aAAa,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QAC3C,IAAI,IAAI,CAAC,KAAK,EAAE;YACf,OAAO,CAAC,GAAG,CACV,mCAAmC,EACnC,IAAI,CAAC,SAAS,CAAC;gBACd,MAAM;gBACN,IAAI;gBACJ,aAAa;aACb,CAAC,CACF,CAAC;SACF;QACD,SAAS,CAAC,aAAa,EAAE,sBAAsB,CAAC;aAC9C,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;YACjB,IAAI,IAAI,CAAC,WAAW,EAAE;gBACrB,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;aAC5B;iBAAM;gBACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;aAC1B;QACF,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE;YACjB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;gBACxB,OAAO,EAAE,uCAAuC,MAAM,IAAI,EAAE,EAAE;gBAC9D,MAAM;aACN,CAAC,CAAC;YACH,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QACJ,OAAO,OAAO,CAAC;IAChB,CAAC;CACD"} \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/WebViewWorker.d.ts b/examples/react-native/src/webview-crypto/WebViewWorker.d.ts new file mode 100644 index 00000000..ed3c250f --- /dev/null +++ b/examples/react-native/src/webview-crypto/WebViewWorker.d.ts @@ -0,0 +1,6 @@ +export declare class WebViewWorker { + private sendToMain; + constructor(sendToMain: (message: string) => void); + onMainMessage(message: string): Promise; + send(data: any): Promise; +} diff --git a/examples/react-native/src/webview-crypto/WebViewWorker.js b/examples/react-native/src/webview-crypto/WebViewWorker.js new file mode 100644 index 00000000..f2c4846c --- /dev/null +++ b/examples/react-native/src/webview-crypto/WebViewWorker.js @@ -0,0 +1,75 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +import serializeError from 'serialize-error'; +import { subtle } from './compat'; +import { parse, stringify } from './serializeBinary'; +export class WebViewWorker { + constructor(sendToMain) { + this.sendToMain = sendToMain; + sendToMain('We are ready!'); + } + onMainMessage(message) { + return __awaiter(this, void 0, void 0, function* () { + let id; + let method; + let args; + try { + ({ id, method, args } = yield parse(message)); + } + catch (e) { + yield this.send({ + reason: `Couldn't parse data: ${e}`, + }); + return; + } + let value; + try { + if (method === 'getRandomValues') { + value = crypto.getRandomValues(args[0]); + } + else { + const methodName = method.split('.')[1]; + console.log(methodName, args); + value = yield subtle()[methodName].apply(subtle(), args); + // if we import a crypto key, we want to save how we imported it + // so we can send that back and re-create the key later + if (methodName === 'importKey') { + value._import = { + format: args[0], + keyData: args[1], + }; + } + } + } + catch (e) { + yield this.send({ id, reason: serializeError(e) }); + return; + } + yield this.send({ id, value }); + }); + } + send(data) { + return __awaiter(this, void 0, void 0, function* () { + let message; + try { + message = yield stringify(data); + } + catch (e) { + const newData = { + id: data.id, + reason: `stringify error ${e}`, + }; + this.sendToMain(JSON.stringify(newData)); + return; + } + this.sendToMain(message); + }); + } +} +//# sourceMappingURL=WebViewWorker.js.map \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/WebViewWorker.js.map b/examples/react-native/src/webview-crypto/WebViewWorker.js.map new file mode 100644 index 00000000..192a46db --- /dev/null +++ b/examples/react-native/src/webview-crypto/WebViewWorker.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WebViewWorker.js","sourceRoot":"","sources":["../src/WebViewWorker.ts"],"names":[],"mappings":";;;;;;;;AAAA,OAAO,cAAc,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,OAAO,aAAa;IACzB,YAAoB,UAAqC;QAArC,eAAU,GAAV,UAAU,CAA2B;QACxD,UAAU,CAAC,eAAe,CAAC,CAAC;IAC7B,CAAC;IAEK,aAAa,CAAC,OAAe;;YAClC,IAAI,EAAU,CAAC;YACf,IAAI,MAAc,CAAC;YACnB,IAAI,IAAW,CAAC;YAChB,IAAI;gBACH,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;aAC9C;YAAC,OAAO,CAAC,EAAE;gBACX,MAAM,IAAI,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,wBAAwB,CAAC,EAAE;iBACnC,CAAC,CAAC;gBACH,OAAO;aACP;YACD,IAAI,KAAK,CAAC;YAEV,IAAI;gBACH,IAAI,MAAM,KAAK,iBAAiB,EAAE;oBACjC,KAAK,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;iBACxC;qBAAM;oBACN,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;oBACxC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;oBAC9B,KAAK,GAAG,MAAO,MAAM,EAAU,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;oBAElE,gEAAgE;oBAChE,uDAAuD;oBACvD,IAAI,UAAU,KAAK,WAAW,EAAE;wBAC/B,KAAK,CAAC,OAAO,GAAG;4BACf,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;4BACf,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;yBAChB,CAAC;qBACF;iBACD;aACD;YAAC,OAAO,CAAC,EAAE;gBACX,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAG,cAAsB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC5D,OAAO;aACP;YACD,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAChC,CAAC;KAAA;IAEK,IAAI,CAAC,IAAS;;YACnB,IAAI,OAAe,CAAC;YACpB,IAAI;gBACH,OAAO,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;aAChC;YAAC,OAAO,CAAC,EAAE;gBACX,MAAM,OAAO,GAAG;oBACf,EAAE,EAAE,IAAI,CAAC,EAAE;oBACX,MAAM,EAAE,mBAAmB,CAAC,EAAE;iBAC9B,CAAC;gBACF,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;gBACzC,OAAO;aACP;YACD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;KAAA;CACD"} \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/asyncSerialize.d.ts b/examples/react-native/src/webview-crypto/asyncSerialize.d.ts new file mode 100644 index 00000000..da6d9c29 --- /dev/null +++ b/examples/react-native/src/webview-crypto/asyncSerialize.d.ts @@ -0,0 +1,8 @@ +export interface ISerializer { + id: string; + isType: (o: any) => boolean; + toObject?: (t: T) => Promise; + fromObject?: (o: S) => Promise; +} +export declare function toObjects(serializers: Array>, o: any): Promise; +export declare function fromObjects(serializers: Array>, o: any): Promise; diff --git a/examples/react-native/src/webview-crypto/asyncSerialize.js b/examples/react-native/src/webview-crypto/asyncSerialize.js new file mode 100644 index 00000000..1ba93371 --- /dev/null +++ b/examples/react-native/src/webview-crypto/asyncSerialize.js @@ -0,0 +1,56 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +// tslint:disable +import find from 'lodash/find'; +class Serialized { +} +function isSerialized(object) { + return object.hasOwnProperty('__serializer_id'); +} +export function toObjects(serializers, o) { + return __awaiter(this, void 0, void 0, function* () { + if (typeof o !== 'object') { + return o; + } + const serializer = find(serializers, (s) => s.isType(o)); + if (serializer) { + const value = serializer.toObject ? yield serializer.toObject(o) : o; + return { + __serializer_id: serializer.id, + value: yield toObjects(serializers, value), + }; + } + const newO = o instanceof Array ? [] : {}; + for (const atr in o) { + newO[atr] = yield toObjects(serializers, o[atr]); + } + return newO; + }); +} +export function fromObjects(serializers, o) { + return __awaiter(this, void 0, void 0, function* () { + if (typeof o !== 'object') { + return o; + } + if (isSerialized(o)) { + const value = yield fromObjects(serializers, o.value); + const serializer = find(serializers, ['id', o.__serializer_id]) || {}; + if (serializer.fromObject) { + return serializer.fromObject(value); + } + return value; + } + const newO = o instanceof Array ? [] : {}; + for (const atr in o) { + newO[atr] = yield fromObjects(serializers, o[atr]); + } + return newO; + }); +} +//# sourceMappingURL=asyncSerialize.js.map \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/asyncSerialize.js.map b/examples/react-native/src/webview-crypto/asyncSerialize.js.map new file mode 100644 index 00000000..02ca803d --- /dev/null +++ b/examples/react-native/src/webview-crypto/asyncSerialize.js.map @@ -0,0 +1 @@ +{"version":3,"file":"asyncSerialize.js","sourceRoot":"","sources":["../src/asyncSerialize.ts"],"names":[],"mappings":";;;;;;;;AAAA,iBAAiB;AACjB,OAAO,IAAI,MAAM,aAAa,CAAC;AAS/B,MAAM,UAAU;CAIf;AAED,SAAS,YAAY,CAAC,MAAW;IAChC,OAAO,MAAM,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAgB,SAAS,CAC9B,WAAyC,EACzC,CAAM;;QAEN,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;YAC1B,OAAO,CAAC,CAAC;SACT;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,UAAU,EAAE;YACf,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrE,OAAO;gBACN,eAAe,EAAE,UAAU,CAAC,EAAE;gBAC9B,KAAK,EAAE,MAAM,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC;aAC5B,CAAC;SAChB;QAED,MAAM,IAAI,GAAQ,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE;YACpB,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;SACjD;QACD,OAAO,IAAI,CAAC;IACb,CAAC;CAAA;AAED,MAAM,UAAgB,WAAW,CAChC,WAAyC,EACzC,CAAM;;QAEN,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;YAC1B,OAAO,CAAC,CAAC;SACT;QAED,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE;YACpB,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;YACtD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,IAAI,EAAS,CAAC;YAC7E,IAAI,UAAU,CAAC,UAAU,EAAE;gBAC1B,OAAO,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;aACpC;YACD,OAAO,KAAK,CAAC;SACb;QAED,MAAM,IAAI,GAAQ,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE;YACpB,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;SACnD;QACD,OAAO,IAAI,CAAC;IACb,CAAC;CAAA"} \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/compat.d.ts b/examples/react-native/src/webview-crypto/compat.d.ts new file mode 100644 index 00000000..6d8b364b --- /dev/null +++ b/examples/react-native/src/webview-crypto/compat.d.ts @@ -0,0 +1 @@ +export declare function subtle(): SubtleCrypto; diff --git a/examples/react-native/src/webview-crypto/compat.js b/examples/react-native/src/webview-crypto/compat.js new file mode 100644 index 00000000..4d6a856c --- /dev/null +++ b/examples/react-native/src/webview-crypto/compat.js @@ -0,0 +1,4 @@ +export function subtle() { + return window.crypto.subtle || window.crypto.webkitSubtle; +} +//# sourceMappingURL=compat.js.map \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/compat.js.map b/examples/react-native/src/webview-crypto/compat.js.map new file mode 100644 index 00000000..ae7444e6 --- /dev/null +++ b/examples/react-native/src/webview-crypto/compat.js.map @@ -0,0 +1 @@ +{"version":3,"file":"compat.js","sourceRoot":"","sources":["../src/compat.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,MAAM;IACrB,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,IAAK,MAAM,CAAC,MAAc,CAAC,YAAY,CAAC;AACpE,CAAC"} \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/index.d.ts b/examples/react-native/src/webview-crypto/index.d.ts new file mode 100644 index 00000000..e69657f9 --- /dev/null +++ b/examples/react-native/src/webview-crypto/index.d.ts @@ -0,0 +1,3 @@ +import MainWorker from './MainWorker'; +import webViewWorkerString from './webViewWorkerString'; +export { MainWorker, webViewWorkerString }; diff --git a/examples/react-native/src/webview-crypto/index.js b/examples/react-native/src/webview-crypto/index.js new file mode 100644 index 00000000..3de63b0a --- /dev/null +++ b/examples/react-native/src/webview-crypto/index.js @@ -0,0 +1,4 @@ +import MainWorker from './MainWorker'; +import webViewWorkerString from './webViewWorkerString'; +export { MainWorker, webViewWorkerString }; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/index.js.map b/examples/react-native/src/webview-crypto/index.js.map new file mode 100644 index 00000000..71f89487 --- /dev/null +++ b/examples/react-native/src/webview-crypto/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,cAAc,CAAC;AACtC,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,CAAC"} \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/serializeBinary.d.ts b/examples/react-native/src/webview-crypto/serializeBinary.d.ts new file mode 100644 index 00000000..53e09cfc --- /dev/null +++ b/examples/react-native/src/webview-crypto/serializeBinary.d.ts @@ -0,0 +1,5 @@ +export declare function parse(text: string): Promise; +export declare function stringify(value: any, waitForArrayBufferView?: boolean): Promise; +export interface IArrayBufferViewWithPromise extends ArrayBufferView { + _promise?: Promise; +} diff --git a/examples/react-native/src/webview-crypto/serializeBinary.js b/examples/react-native/src/webview-crypto/serializeBinary.js new file mode 100644 index 00000000..2af3a41d --- /dev/null +++ b/examples/react-native/src/webview-crypto/serializeBinary.js @@ -0,0 +1,160 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +import { fromObjects, toObjects } from './asyncSerialize'; +import { subtle } from './compat'; +export function parse(text) { + return __awaiter(this, void 0, void 0, function* () { + // need decodeURIComponent so binary strings are transfered properly + const deocodedText = unescape(text); + const objects = JSON.parse(deocodedText); + return fromObjects(serializers(true), objects); + }); +} +export function stringify(value, waitForArrayBufferView = true) { + return __awaiter(this, void 0, void 0, function* () { + const serialized = yield toObjects(serializers(waitForArrayBufferView), value); + // need encodeURIComponent so binary strings are transfered properly + const message = JSON.stringify(serialized); + return escape(message); + }); +} +function serializers(waitForArrayBufferView) { + return [ + ArrayBufferSerializer, + ArrayBufferViewSerializer(waitForArrayBufferView), + CryptoKeySerializer, + ]; +} +const ArrayBufferSerializer = { + id: 'ArrayBuffer', + isType: (o) => o instanceof ArrayBuffer, + // from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String + // modified to use Int8Array so that we can hold odd number of bytes + toObject: (ab) => __awaiter(this, void 0, void 0, function* () { + return String.fromCharCode.apply(null, new Int8Array(ab)); + }), + fromObject: (data) => __awaiter(this, void 0, void 0, function* () { + const buf = new ArrayBuffer(data.length); + const bufView = new Int8Array(buf); + for (let i = 0, strLen = data.length; i < strLen; i++) { + bufView[i] = data.charCodeAt(i); + } + return buf; + }), +}; +function isArrayBufferViewWithPromise(obj) { + return obj.hasOwnProperty('_promise'); +} +// Normally we could just do `abv.constructor.name`, but in +// JavaScriptCore, this wont work for some weird reason. +// list from https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView +function arrayBufferViewName(abv) { + if (abv instanceof Int8Array) { + return 'Int8Array'; + } + if (abv instanceof Uint8Array) { + return 'Uint8Array'; + } + if (abv instanceof Uint8ClampedArray) { + return 'Uint8ClampedArray'; + } + if (abv instanceof Int16Array) { + return 'Int16Array'; + } + if (abv instanceof Uint16Array) { + return 'Uint16Array'; + } + if (abv instanceof Int32Array) { + return 'Int32Array'; + } + if (abv instanceof Uint32Array) { + return 'Uint32Array'; + } + if (abv instanceof Float32Array) { + return 'Float32Array'; + } + if (abv instanceof Float64Array) { + return 'Float64Array'; + } + if (abv instanceof DataView) { + return 'DataView'; + } + return ''; +} +function ArrayBufferViewSerializer(waitForPromise) { + return { + id: 'ArrayBufferView', + isType: ArrayBuffer.isView, + toObject: (abv) => __awaiter(this, void 0, void 0, function* () { + if (waitForPromise) { + // wait for promise to resolve if the abv was returned from getRandomValues + if (isArrayBufferViewWithPromise(abv)) { + yield abv._promise; + } + } + return { + name: arrayBufferViewName(abv), + buffer: abv.buffer, + }; + }), + fromObject: (abvs) => __awaiter(this, void 0, void 0, function* () { + // tslint:disable-next-line + return eval(`new ${abvs.name}(abvs.buffer)`); + }), + }; +} +function hasData(ck) { + return ck._import !== undefined; +} +const CryptoKeySerializer = { + id: 'CryptoKey', + isType: (o) => { + const localStr = o.toLocaleString(); + // can't use CryptoKey or constructor on WebView iOS + const isCryptoKey = localStr === '[object CryptoKey]' || localStr === '[object Key]'; + const isCryptoKeyWithData = o._import && !o.serialized; + return isCryptoKey || isCryptoKeyWithData; + }, + toObject: (ck) => __awaiter(this, void 0, void 0, function* () { + // if we already have the import serialized, just return that + if (hasData(ck)) { + return { + serialized: true, + _import: ck._import, + type: ck.type, + extractable: ck.extractable, + algorithm: ck.algorithm, + usages: ck.usages, + }; + } + const jwk = yield subtle().exportKey('jwk', ck); + return { + _import: { + format: 'jwk', + keyData: jwk, + }, + serialized: true, + algorithm: ck.algorithm, + extractable: ck.extractable, + usages: ck.usages, + type: ck.type, + }; + }), + fromObject: (cks) => __awaiter(this, void 0, void 0, function* () { + // if we don't have access to to a real crypto implementation, just return + // the serialized crypto key + if (crypto.fake) { + const newCks = Object.assign({}, cks); + delete newCks.serialized; + return newCks; + } + return subtle().importKey(cks._import.format, cks._import.keyData, cks.algorithm, cks.extractable, cks.usages); + }), +}; +//# sourceMappingURL=serializeBinary.js.map \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/serializeBinary.js.map b/examples/react-native/src/webview-crypto/serializeBinary.js.map new file mode 100644 index 00000000..90f8642f --- /dev/null +++ b/examples/react-native/src/webview-crypto/serializeBinary.js.map @@ -0,0 +1 @@ +{"version":3,"file":"serializeBinary.js","sourceRoot":"","sources":["../src/serializeBinary.ts"],"names":[],"mappings":";;;;;;;;AAAA,OAAO,EAAE,WAAW,EAAe,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAIlC,MAAM,UAAgB,KAAK,CAAC,IAAY;;QACvC,oEAAoE;QACpE,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACzC,OAAO,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;CAAA;AACD,MAAM,UAAgB,SAAS,CAAC,KAAU,EAAE,sBAAsB,GAAG,IAAI;;QACxE,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/E,oEAAoE;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC3C,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;CAAA;AAED,SAAS,WAAW,CAAC,sBAA+B;IACnD,OAAO;QACN,qBAAqB;QACrB,yBAAyB,CAAC,sBAAsB,CAAC;QACjD,mBAAmB;KACnB,CAAC;AACH,CAAC;AAED,MAAM,qBAAqB,GAAqC;IAC/D,EAAE,EAAE,aAAa;IACjB,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,YAAY,WAAW;IAE5C,uGAAuG;IACvG,oEAAoE;IACpE,QAAQ,EAAE,CAAO,EAAe,EAAE,EAAE;QACnC,OAAO,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAA;IACD,UAAU,EAAE,CAAO,IAAY,EAAE,EAAE;QAClC,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;QACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE;YACtD,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;SAChC;QACD,OAAO,GAAG,CAAC;IACZ,CAAC,CAAA;CACD,CAAC;AAUF,SAAS,4BAA4B,CAAC,GAAQ;IAC7C,OAAO,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;AACvC,CAAC;AAED,2DAA2D;AAC3D,wDAAwD;AACxD,6EAA6E;AAC7E,SAAS,mBAAmB,CAAC,GAAoB;IAChD,IAAI,GAAG,YAAY,SAAS,EAAE;QAC7B,OAAO,WAAW,CAAC;KACnB;IACD,IAAI,GAAG,YAAY,UAAU,EAAE;QAC9B,OAAO,YAAY,CAAC;KACpB;IACD,IAAI,GAAG,YAAY,iBAAiB,EAAE;QACrC,OAAO,mBAAmB,CAAC;KAC3B;IACD,IAAI,GAAG,YAAY,UAAU,EAAE;QAC9B,OAAO,YAAY,CAAC;KACpB;IACD,IAAI,GAAG,YAAY,WAAW,EAAE;QAC/B,OAAO,aAAa,CAAC;KACrB;IACD,IAAI,GAAG,YAAY,UAAU,EAAE;QAC9B,OAAO,YAAY,CAAC;KACpB;IACD,IAAI,GAAG,YAAY,WAAW,EAAE;QAC/B,OAAO,aAAa,CAAC;KACrB;IACD,IAAI,GAAG,YAAY,YAAY,EAAE;QAChC,OAAO,cAAc,CAAC;KACtB;IACD,IAAI,GAAG,YAAY,YAAY,EAAE;QAChC,OAAO,cAAc,CAAC;KACtB;IACD,IAAI,GAAG,YAAY,QAAQ,EAAE;QAC5B,OAAO,UAAU,CAAC;KAClB;IACD,OAAO,EAAE,CAAC;AACX,CAAC;AAED,SAAS,yBAAyB,CACjC,cAAuB;IAEvB,OAAO;QACN,EAAE,EAAE,iBAAiB;QACrB,MAAM,EAAE,WAAW,CAAC,MAAM;QAC1B,QAAQ,EAAE,CAAO,GAAoB,EAAE,EAAE;YACxC,IAAI,cAAc,EAAE;gBACnB,2EAA2E;gBAC3E,IAAI,4BAA4B,CAAC,GAAG,CAAC,EAAE;oBACtC,MAAM,GAAG,CAAC,QAAQ,CAAC;iBACnB;aACD;YACD,OAAO;gBACN,IAAI,EAAE,mBAAmB,CAAC,GAAG,CAAC;gBAC9B,MAAM,EAAE,GAAG,CAAC,MAAM;aACX,CAAC;QACV,CAAC,CAAA;QACD,UAAU,EAAE,CAAO,IAAgC,EAAE,EAAE;YACtD,2BAA2B;YAC3B,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,eAAe,CAAC,CAAC;QAC9C,CAAC,CAAA;KACD,CAAC;AACH,CAAC;AASD,SAAS,OAAO,CAAC,EAAkC;IAClD,OAAQ,EAAyB,CAAC,OAAO,KAAK,SAAS,CAAC;AACzD,CAAC;AAMD,MAAM,mBAAmB,GAAsE;IAC9F,EAAE,EAAE,WAAW;IACf,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE;QAClB,MAAM,QAAQ,GAAG,CAAC,CAAC,cAAc,EAAE,CAAC;QACpC,oDAAoD;QACpD,MAAM,WAAW,GAAG,QAAQ,KAAK,oBAAoB,IAAI,QAAQ,KAAK,cAAc,CAAC;QACrF,MAAM,mBAAmB,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC;QACvD,OAAO,WAAW,IAAI,mBAAmB,CAAC;IAC3C,CAAC;IACD,QAAQ,EAAE,CAAO,EAAE,EAAE,EAAE;QACtB,6DAA6D;QAC7D,IAAI,OAAO,CAAC,EAAE,CAAC,EAAE;YAChB,OAAO;gBACN,UAAU,EAAE,IAAI;gBAChB,OAAO,EAAE,EAAE,CAAC,OAAO;gBACnB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,WAAW,EAAE,EAAE,CAAC,WAAW;gBAC3B,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,MAAM,EAAE,EAAE,CAAC,MAAM;aACjB,CAAC;SACF;QACD,MAAM,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAChD,OAAO;YACN,OAAO,EAAE;gBACR,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,GAAG;aACZ;YACD,UAAU,EAAE,IAAI;YAChB,SAAS,EAAE,EAAE,CAAC,SAAS;YACvB,WAAW,EAAE,EAAE,CAAC,WAAW;YAC3B,MAAM,EAAE,EAAE,CAAC,MAAM;YACjB,IAAI,EAAE,EAAE,CAAC,IAAI;SACb,CAAC;IACH,CAAC,CAAA;IACD,UAAU,EAAE,CAAO,GAAyB,EAAE,EAAE;QAC/C,0EAA0E;QAC1E,4BAA4B;QAC5B,IAAK,MAAc,CAAC,IAAI,EAAE;YACzB,MAAM,MAAM,qBAA8B,GAAG,CAAE,CAAC;YAChD,OAAO,MAAM,CAAC,UAAU,CAAC;YACzB,OAAO,MAAM,CAAC;SACd;QACD,OAAO,MAAM,EAAE,CAAC,SAAS,CACxB,GAAG,CAAC,OAAO,CAAC,MAAM,EAClB,GAAG,CAAC,OAAO,CAAC,OAAc,EAC1B,GAAG,CAAC,SAAgB,EACpB,GAAG,CAAC,WAAW,EACf,GAAG,CAAC,MAAM,CACV,CAAC;IACH,CAAC,CAAA;CACD,CAAC"} \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/webViewWorkerString.d.ts b/examples/react-native/src/webview-crypto/webViewWorkerString.d.ts new file mode 100644 index 00000000..0d667c32 --- /dev/null +++ b/examples/react-native/src/webview-crypto/webViewWorkerString.d.ts @@ -0,0 +1,3 @@ +export declare const base64InjString = "\n(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined'\n ? module.exports = factory(global)\n : typeof define === 'function' && define.amd\n ? define(factory) : factory(global)\n}((\n typeof self !== 'undefined' ? self\n : typeof window !== 'undefined' ? window\n : typeof global !== 'undefined' ? global\n: this\n), function(global) {\n 'use strict';\n // existing version for noConflict()\n var _Base64 = global.Base64;\n var version = \"2.4.9\";\n // if node.js and NOT React Native, we use Buffer\n var buffer;\n if (typeof module !== 'undefined' && module.exports) {\n try {\n buffer = eval(\"require('buffer').Buffer\");\n } catch (err) {\n buffer = undefined;\n }\n }\n // constants\n var b64chars\n = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n var b64tab = function(bin) {\n var t = {};\n for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;\n return t;\n }(b64chars);\n var fromCharCode = String.fromCharCode;\n // encoder stuff\n var cb_utob = function(c) {\n if (c.length < 2) {\n var cc = c.charCodeAt(0);\n return cc < 0x80 ? c\n : cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))\n + fromCharCode(0x80 | (cc & 0x3f)))\n : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))\n + fromCharCode(0x80 | ((cc >>> 6) & 0x3f))\n + fromCharCode(0x80 | ( cc & 0x3f)));\n } else {\n var cc = 0x10000\n + (c.charCodeAt(0) - 0xD800) * 0x400\n + (c.charCodeAt(1) - 0xDC00);\n return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))\n + fromCharCode(0x80 | ((cc >>> 12) & 0x3f))\n + fromCharCode(0x80 | ((cc >>> 6) & 0x3f))\n + fromCharCode(0x80 | ( cc & 0x3f)));\n }\n };\n var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\0-]/g;\n var utob = function(u) {\n return u.replace(re_utob, cb_utob);\n };\n var cb_encode = function(ccc) {\n var padlen = [0, 2, 1][ccc.length % 3],\n ord = ccc.charCodeAt(0) << 16\n | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)\n | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),\n chars = [\n b64chars.charAt( ord >>> 18),\n b64chars.charAt((ord >>> 12) & 63),\n padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),\n padlen >= 1 ? '=' : b64chars.charAt(ord & 63)\n ];\n return chars.join('');\n };\n var btoa = global.btoa ? function(b) {\n return global.btoa(b);\n } : function(b) {\n return b.replace(/[sS]{1,3}/g, cb_encode);\n };\n var _encode = buffer ?\n buffer.from && Uint8Array && buffer.from !== Uint8Array.from\n ? function (u) {\n return (u.constructor === buffer.constructor ? u : buffer.from(u))\n .toString('base64')\n }\n : function (u) {\n return (u.constructor === buffer.constructor ? u : new buffer(u))\n .toString('base64')\n }\n : function (u) { return btoa(utob(u)) }\n ;\n var encode = function(u, urisafe) {\n return !urisafe\n ? _encode(String(u))\n : _encode(String(u)).replace(/[+/]/g, function(m0) {\n return m0 == '+' ? '-' : '_';\n }).replace(/=/g, '');\n };\n var encodeURI = function(u) { return encode(u, true) };\n // decoder stuff\n var re_btou = new RegExp([\n '[\u00C0-\u00DF][\u0080-\u00BF]',\n '[\u00E0-\u00EF][\u0080-\u00BF]{2}',\n '[\u00F0-\u00F7][\u0080-\u00BF]{3}'\n ].join('|'), 'g');\n var cb_btou = function(cccc) {\n switch(cccc.length) {\n case 4:\n var cp = ((0x07 & cccc.charCodeAt(0)) << 18)\n | ((0x3f & cccc.charCodeAt(1)) << 12)\n | ((0x3f & cccc.charCodeAt(2)) << 6)\n | (0x3f & cccc.charCodeAt(3)),\n offset = cp - 0x10000;\n return (fromCharCode((offset >>> 10) + 0xD800)\n + fromCharCode((offset & 0x3FF) + 0xDC00));\n case 3:\n return fromCharCode(\n ((0x0f & cccc.charCodeAt(0)) << 12)\n | ((0x3f & cccc.charCodeAt(1)) << 6)\n | (0x3f & cccc.charCodeAt(2))\n );\n default:\n return fromCharCode(\n ((0x1f & cccc.charCodeAt(0)) << 6)\n | (0x3f & cccc.charCodeAt(1))\n );\n }\n };\n var btou = function(b) {\n return b.replace(re_btou, cb_btou);\n };\n var cb_decode = function(cccc) {\n var len = cccc.length,\n padlen = len % 4,\n n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)\n | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)\n | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)\n | (len > 3 ? b64tab[cccc.charAt(3)] : 0),\n chars = [\n fromCharCode( n >>> 16),\n fromCharCode((n >>> 8) & 0xff),\n fromCharCode( n & 0xff)\n ];\n chars.length -= [0, 0, 2, 1][padlen];\n return chars.join('');\n };\n var atob = global.atob ? function(a) {\n return global.atob(a);\n } : function(a){\n return a.replace(/[sS]{1,4}/g, cb_decode);\n };\n var _decode = buffer ?\n buffer.from && Uint8Array && buffer.from !== Uint8Array.from\n ? function(a) {\n return (a.constructor === buffer.constructor\n ? a : buffer.from(a, 'base64')).toString();\n }\n : function(a) {\n return (a.constructor === buffer.constructor\n ? a : new buffer(a, 'base64')).toString();\n }\n : function(a) { return btou(atob(a)) };\n var decode = function(a){\n return _decode(\n String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })\n .replace(/[^A-Za-z0-9+/]/g, '')\n );\n };\n var noConflict = function() {\n var Base64 = global.Base64;\n global.Base64 = _Base64;\n return Base64;\n };\n // export Base64\n global.Base64 = {\n VERSION: version,\n atob: atob,\n btoa: btoa,\n fromBase64: decode,\n toBase64: encode,\n utob: utob,\n encode: encode,\n encodeURI: encodeURI,\n btou: btou,\n decode: decode,\n noConflict: noConflict,\n __buffer__: buffer\n };\n // if ES5 is available, make Base64.extendString() available\n if (typeof Object.defineProperty === 'function') {\n var noEnum = function(v){\n return {value:v,enumerable:false,writable:true,configurable:true};\n };\n global.Base64.extendString = function () {\n Object.defineProperty(\n String.prototype, 'fromBase64', noEnum(function () {\n return decode(this)\n }));\n Object.defineProperty(\n String.prototype, 'toBase64', noEnum(function (urisafe) {\n return encode(this, urisafe)\n }));\n Object.defineProperty(\n String.prototype, 'toBase64URI', noEnum(function () {\n return encode(this, true)\n }));\n };\n }\n return {Base64: global.Base64}\n}));\n"; +declare const _default: "\nvar WebViewWorker = function(t) {\n function r(n) {\n if (e[n]) return e[n].exports;\n var o = e[n] = {\n exports: {},\n id: n,\n loaded: !1\n };\n return t[n].call(o.exports, o, o.exports, r), o.loaded = !0, o.exports\n }\n var e = {};\n return r.m = t, r.c = e, r.p = \"\", r(0)\n}([function(t, r, e) {\n t.exports = e(1)\n}, function(t, r, e) {\n \"use strict\";\n var n = this && this.__awaiter || function(t, r, e, n) {\n return new(e || (e = Promise))(function(o, i) {\n function a(t) {\n try {\n c(n.next(t))\n } catch (t) {\n i(t)\n }\n }\n\n function u(t) {\n try {\n c(n.throw(t))\n } catch (t) {\n i(t)\n }\n }\n\n function c(t) {\n t.done ? o(t.value) : new e(function(r) {\n r(t.value)\n }).then(a, u)\n }\n c((n = n.apply(t, r)).next())\n })\n },\n o = this && this.__generator || function(t, r) {\n function e(t) {\n return function(r) {\n return n([t, r])\n }\n }\n\n function n(e) {\n if (o) throw new TypeError(\"Generator is already executing.\");\n for (; u;) try {\n if (o = 1, i && (a = i[2 & e[0] ? \"return\" : e[0] ? \"throw\" : \"next\"]) && !(a = a.call(i, e[1])).done) return a;\n switch (i = 0, a && (e = [0, a.value]), e[0]) {\n case 0:\n case 1:\n a = e;\n break;\n case 4:\n return u.label++, {\n value: e[1],\n done: !1\n };\n case 5:\n u.label++, i = e[1], e = [0];\n continue;\n case 7:\n e = u.ops.pop(), u.trys.pop();\n continue;\n default:\n if (a = u.trys, !(a = a.length > 0 && a[a.length - 1]) && (6 === e[0] || 2 === e[0])) {\n u = 0;\n continue\n }\n if (3 === e[0] && (!a || e[1] > a[0] && e[1] < a[3])) {\n u.label = e[1];\n break\n }\n if (6 === e[0] && u.label < a[1]) {\n u.label = a[1], a = e;\n break\n }\n if (a && u.label < a[2]) {\n u.label = a[2], u.ops.push(e);\n break\n }\n a[2] && u.ops.pop(), u.trys.pop();\n continue\n }\n e = r.call(t, u)\n } catch (t) {\n e = [6, t], i = 0\n } finally {\n o = a = 0\n }\n if (5 & e[0]) throw e[1];\n return {\n value: e[0] ? e[1] : void 0,\n done: !0\n }\n }\n var o, i, a, u = {\n label: 0,\n sent: function() {\n if (1 & a[0]) throw a[1];\n return a[1]\n },\n trys: [],\n ops: []\n };\n return {\n next: e(0),\n throw: e(1),\n return: e(2)\n }\n },\n i = e(2),\n a = e(126),\n u = e(127),\n c = function() {\n function t(t) {\n this.sendToMain = t, t(\"We are ready!\")\n }\n return t.prototype.onMainMessage = function(t) {\n return n(this, void 0, void 0, function() {\n var r, e, n, c, s, f, p, l;\n return o(this, function(o) {\n switch (o.label) {\n case 0:\n return o.trys.push([0, 2, , 4]), [4, i.parse(t)];\n case 1:\n return l = o.sent(), r = l.id, e = l.method, n = l.args, [3, 4];\n case 2:\n return c = o.sent(), [4, this.send({\n reason: \"Couldn't parse data: \" + c\n })];\n case 3:\n return o.sent(), [2];\n case 4:\n return o.trys.push([4, 8, , 10]), \"getRandomValues\" !== e ? [3, 5] : (s = crypto.getRandomValues(n[0]), [3, 7]);\n case 5:\n // console.log(f, n)\n return f = e.split(\".\")[1], [4, a.subtle()[f].apply(a.subtle(), n)];\n case 6:\n s = o.sent(), \"importKey\" === f && (s._import = {\n format: n[0],\n keyData: n[1]\n }), o.label = 7;\n case 7:\n return [3, 10];\n case 8:\n return p = o.sent(), [4, this.send({\n id: r,\n reason: u(p)\n })];\n case 9:\n return o.sent(), [2];\n case 10:\n return [4, this.send({\n id: r,\n value: s\n })];\n case 11:\n return o.sent(), [2]\n }\n })\n })\n }, t.prototype.send = function(t) {\n return n(this, void 0, void 0, function() {\n var r, e, n;\n return o(this, function(o) {\n switch (o.label) {\n case 0:\n return o.trys.push([0, 2, , 3]), [4, i.stringify(t)];\n case 1:\n return r = o.sent(), [3, 3];\n case 2:\n return e = o.sent(), n = {\n id: t.id,\n reason: \"stringify error \" + e\n }, this.sendToMain(JSON.stringify(n)), [2];\n case 3:\n return this.sendToMain(r), [2]\n }\n })\n })\n }, t\n }();\n t.exports = c\n}, function(module, exports, __webpack_require__) {\n \"use strict\";\n\n function parse(t) {\n return __awaiter(this, void 0, void 0, function() {\n var r, e;\n return __generator(this, function(n) {\n switch (n.label) {\n case 0:\n // console.log('*** decoding', t);\n return r = unescape(t), e = JSON.parse(r), [4, asyncSerialize_1.fromObjects(serializers(!0), e)];\n case 1:\n return [2, n.sent()]\n }\n })\n })\n }\n\n function stringify(t, r) {\n return void 0 === r && (r = !0), __awaiter(this, void 0, void 0, function() {\n var e, n;\n return __generator(this, function(o) {\n switch (o.label) {\n case 0:\n return [4, asyncSerialize_1.toObjects(serializers(r), t)];\n case 1:\n // console.log('*** encoding', n);\n return e = o.sent(), n = JSON.stringify(e), [2, escape(n)]\n }\n })\n })\n }\n\n function serializers(t) {\n return [ArrayBufferSerializer, ArrayBufferViewSerializer(t), CryptoKeySerializer]\n }\n\n function isArrayBufferViewWithPromise(t) {\n return t.hasOwnProperty(\"_promise\")\n }\n\n function arrayBufferViewName(t) {\n return t instanceof Int8Array ? \"Int8Array\" : t instanceof Uint8Array ? \"Uint8Array\" : t instanceof Uint8ClampedArray ? \"Uint8ClampedArray\" : t instanceof Int16Array ? \"Int16Array\" : t instanceof Uint16Array ? \"Uint16Array\" : t instanceof Int32Array ? \"Int32Array\" : t instanceof Uint32Array ? \"Uint32Array\" : t instanceof Float32Array ? \"Float32Array\" : t instanceof Float64Array ? \"Float64Array\" : t instanceof DataView ? \"DataView\" : void 0\n }\n\n function ArrayBufferViewSerializer(waitForPromise) {\n var _this = this;\n return {\n id: \"ArrayBufferView\",\n isType: ArrayBuffer.isView,\n toObject: function(t) {\n return __awaiter(_this, void 0, void 0, function() {\n return __generator(this, function(r) {\n switch (r.label) {\n case 0:\n return waitForPromise && isArrayBufferViewWithPromise(t) ? [4, t._promise] : [3, 2];\n case 1:\n r.sent(), r.label = 2;\n case 2:\n return [2, {\n name: arrayBufferViewName(t),\n buffer: t.buffer\n }]\n }\n })\n })\n },\n fromObject: function(abvs) {\n return __awaiter(_this, void 0, void 0, function() {\n return __generator(this, function(_a) {\n return [2, eval(\"new \" + abvs.name + \"(abvs.buffer)\")]\n })\n })\n }\n }\n }\n\n function hasData(t) {\n return void 0 !== t._import\n }\n var __assign = this && this.__assign || Object.assign || function(t) {\n for (var r, e = 1, n = arguments.length; e < n; e++) {\n r = arguments[e];\n for (var o in r) Object.prototype.hasOwnProperty.call(r, o) && (t[o] = r[o])\n }\n return t\n },\n __awaiter = this && this.__awaiter || function(t, r, e, n) {\n return new(e || (e = Promise))(function(o, i) {\n function a(t) {\n try {\n c(n.next(t))\n } catch (t) {\n i(t)\n }\n }\n\n function u(t) {\n try {\n c(n.throw(t))\n } catch (t) {\n i(t)\n }\n }\n\n function c(t) {\n t.done ? o(t.value) : new e(function(r) {\n r(t.value)\n }).then(a, u)\n }\n c((n = n.apply(t, r)).next())\n })\n },\n __generator = this && this.__generator || function(t, r) {\n function e(t) {\n return function(r) {\n return n([t, r])\n }\n }\n\n function n(e) {\n if (o) throw new TypeError(\"Generator is already executing.\");\n for (; u;) try {\n if (o = 1, i && (a = i[2 & e[0] ? \"return\" : e[0] ? \"throw\" : \"next\"]) && !(a = a.call(i, e[1])).done) return a;\n switch (i = 0, a && (e = [0, a.value]), e[0]) {\n case 0:\n case 1:\n a = e;\n break;\n case 4:\n return u.label++, {\n value: e[1],\n done: !1\n };\n case 5:\n u.label++, i = e[1], e = [0];\n continue;\n case 7:\n e = u.ops.pop(), u.trys.pop();\n continue;\n default:\n if (a = u.trys, !(a = a.length > 0 && a[a.length - 1]) && (6 === e[0] || 2 === e[0])) {\n u = 0;\n continue\n }\n if (3 === e[0] && (!a || e[1] > a[0] && e[1] < a[3])) {\n u.label = e[1];\n break\n }\n if (6 === e[0] && u.label < a[1]) {\n u.label = a[1], a = e;\n break\n }\n if (a && u.label < a[2]) {\n u.label = a[2], u.ops.push(e);\n break\n }\n a[2] && u.ops.pop(), u.trys.pop();\n continue\n }\n e = r.call(t, u)\n } catch (t) {\n e = [6, t], i = 0\n } finally {\n o = a = 0\n }\n if (5 & e[0]) throw e[1];\n return {\n value: e[0] ? e[1] : void 0,\n done: !0\n }\n }\n var o, i, a, u = {\n label: 0,\n sent: function() {\n if (1 & a[0]) throw a[1];\n return a[1]\n },\n trys: [],\n ops: []\n };\n return {\n next: e(0),\n throw: e(1),\n return: e(2)\n }\n },\n _this = this,\n asyncSerialize_1 = __webpack_require__(3),\n compat_1 = __webpack_require__(126);\n exports.parse = parse, exports.stringify = stringify;\n var ArrayBufferSerializer = {\n id: \"ArrayBuffer\",\n isType: function(t) {\n return t instanceof ArrayBuffer\n },\n toObject: function(t) {\n return __awaiter(_this, void 0, void 0, function() {\n return __generator(this, function(r) {\n return [2, String.fromCharCode.apply(null, new Int8Array(t))]\n })\n })\n },\n fromObject: function(t) {\n return __awaiter(_this, void 0, void 0, function() {\n var r, e, n, o;\n return __generator(this, function(i) {\n for (r = new ArrayBuffer(t.length), e = new Int8Array(r), n = 0, o = t.length; n < o; n++) e[n] = t.charCodeAt(n);\n return [2, r]\n })\n })\n }\n },\n CryptoKeySerializer = {\n id: \"CryptoKey\",\n isType: function(t) {\n var r = t.toLocaleString(),\n e = \"[object CryptoKey]\" === r || \"[object Key]\" === r,\n n = t._import && !t.serialized;\n return e || n\n },\n toObject: function(t) {\n return __awaiter(_this, void 0, void 0, function() {\n var r;\n return __generator(this, function(e) {\n switch (e.label) {\n case 0:\n return hasData(t) ? [2, {\n serialized: !0,\n _import: t._import,\n type: t.type,\n extractable: t.extractable,\n algorithm: t.algorithm,\n usages: t.usages\n }] : [4, compat_1.subtle().exportKey(\"jwk\", t)];\n case 1:\n return r = e.sent(), [2, {\n _import: {\n format: \"jwk\",\n keyData: r\n },\n serialized: !0,\n algorithm: t.algorithm,\n extractable: t.extractable,\n usages: t.usages,\n type: t.type\n }]\n }\n })\n })\n },\n fromObject: function(t) {\n return __awaiter(_this, void 0, void 0, function() {\n var r;\n return __generator(this, function(e) {\n switch (e.label) {\n case 0:\n return crypto.fake ? (r = __assign({}, t), delete r.serialized, [2, r]) : [4, compat_1.subtle().importKey(t._import.format, t._import.keyData, t.algorithm, t.extractable, t.usages)];\n case 1:\n return [2, e.sent()]\n }\n })\n })\n }\n }\n}, function(t, r, e) {\n \"use strict\";\n\n function n(t) {\n return t.hasOwnProperty(\"__serializer_id\")\n }\n\n function o(t, r) {\n return a(this, void 0, void 0, function() {\n var e, n, i, a, s, f, p, l, v, h, y;\n return u(this, function(u) {\n switch (u.label) {\n case 0:\n return \"object\" != typeof r ? [2, r] : (e = c(t, function(t) {\n return t.isType(r)\n }), e ? e.toObject ? [4, e.toObject(r)] : [3, 2] : [3, 5]);\n case 1:\n return i = u.sent(), [3, 3];\n case 2:\n i = r, u.label = 3;\n case 3:\n return n = i, a = {\n __serializer_id: e.id\n }, [4, o(t, n)];\n case 4:\n return [2, (a.value = u.sent(), a)];\n case 5:\n s = r instanceof Array ? [] : {}, f = [];\n for (p in r) f.push(p);\n l = 0, u.label = 6;\n case 6:\n return l < f.length ? (v = f[l], h = s, y = v, [4, o(t, r[v])]) : [3, 9];\n case 7:\n h[y] = u.sent(), u.label = 8;\n case 8:\n return l++, [3, 6];\n case 9:\n return [2, s]\n }\n })\n })\n }\n\n function i(t, r) {\n return a(this, void 0, void 0, function() {\n var e, o, a, s, f, p, l, v, h;\n return u(this, function(u) {\n switch (u.label) {\n case 0:\n return \"object\" != typeof r ? [2, r] : n(r) ? [4, i(t, r.value)] : [3, 2];\n case 1:\n return e = u.sent(), o = c(t, [\"id\", r.__serializer_id]), o.fromObject ? [2, o.fromObject(e)] : [2, e];\n case 2:\n a = r instanceof Array ? [] : {}, s = [];\n for (f in r) s.push(f);\n p = 0, u.label = 3;\n case 3:\n return p < s.length ? (l = s[p], v = a, h = l, [4, i(t, r[l])]) : [3, 6];\n case 4:\n v[h] = u.sent(), u.label = 5;\n case 5:\n return p++, [3, 3];\n case 6:\n return [2, a]\n }\n })\n })\n }\n var a = this && this.__awaiter || function(t, r, e, n) {\n return new(e || (e = Promise))(function(o, i) {\n function a(t) {\n try {\n c(n.next(t))\n } catch (t) {\n i(t)\n }\n }\n\n function u(t) {\n try {\n c(n.throw(t))\n } catch (t) {\n i(t)\n }\n }\n\n function c(t) {\n t.done ? o(t.value) : new e(function(r) {\n r(t.value)\n }).then(a, u)\n }\n c((n = n.apply(t, r)).next())\n })\n },\n u = this && this.__generator || function(t, r) {\n function e(t) {\n return function(r) {\n return n([t, r])\n }\n }\n\n function n(e) {\n if (o) throw new TypeError(\"Generator is already executing.\");\n for (; u;) try {\n if (o = 1, i && (a = i[2 & e[0] ? \"return\" : e[0] ? \"throw\" : \"next\"]) && !(a = a.call(i, e[1])).done) return a;\n switch (i = 0, a && (e = [0, a.value]), e[0]) {\n case 0:\n case 1:\n a = e;\n break;\n case 4:\n return u.label++, {\n value: e[1],\n done: !1\n };\n case 5:\n u.label++, i = e[1], e = [0];\n continue;\n case 7:\n e = u.ops.pop(), u.trys.pop();\n continue;\n default:\n if (a = u.trys, !(a = a.length > 0 && a[a.length - 1]) && (6 === e[0] || 2 === e[0])) {\n u = 0;\n continue\n }\n if (3 === e[0] && (!a || e[1] > a[0] && e[1] < a[3])) {\n u.label = e[1];\n break\n }\n if (6 === e[0] && u.label < a[1]) {\n u.label = a[1], a = e;\n break\n }\n if (a && u.label < a[2]) {\n u.label = a[2], u.ops.push(e);\n break\n }\n a[2] && u.ops.pop(), u.trys.pop();\n continue\n }\n e = r.call(t, u)\n } catch (t) {\n e = [6, t], i = 0\n } finally {\n o = a = 0\n }\n if (5 & e[0]) throw e[1];\n return {\n value: e[0] ? e[1] : void 0,\n done: !0\n }\n }\n var o, i, a, u = {\n label: 0,\n sent: function() {\n if (1 & a[0]) throw a[1];\n return a[1]\n },\n trys: [],\n ops: []\n };\n return {\n next: e(0),\n throw: e(1),\n return: e(2)\n }\n },\n c = e(4);\n (function() {\n function t() {}\n return t\n })();\n r.toObjects = o, r.fromObjects = i\n}, function(t, r, e) {\n var n = e(5),\n o = e(121),\n i = n(o);\n t.exports = i\n}, function(t, r, e) {\n function n(t) {\n return function(r, e, n) {\n var u = Object(r);\n if (!i(r)) {\n var c = o(e, 3);\n r = a(r), e = function(t) {\n return c(u[t], t, u)\n }\n }\n var s = t(r, e, n);\n return s > -1 ? u[c ? r[s] : s] : void 0\n }\n }\n var o = e(6),\n i = e(92),\n a = e(73);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n return \"function\" == typeof t ? t : null == t ? a : \"object\" == typeof t ? u(t) ? i(t[0], t[1]) : o(t) : c(t)\n }\n var o = e(7),\n i = e(101),\n a = e(117),\n u = e(69),\n c = e(118);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n var r = i(t);\n return 1 == r.length && r[0][2] ? a(r[0][0], r[0][1]) : function(e) {\n return e === t || o(e, t, r)\n }\n }\n var o = e(8),\n i = e(98),\n a = e(100);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r, e, n) {\n var c = e.length,\n s = c,\n f = !n;\n if (null == t) return !s;\n for (t = Object(t); c--;) {\n var p = e[c];\n if (f && p[2] ? p[1] !== t[p[0]] : !(p[0] in t)) return !1\n }\n for (; ++c < s;) {\n p = e[c];\n var l = p[0],\n v = t[l],\n h = p[1];\n if (f && p[2]) {\n if (void 0 === v && !(l in t)) return !1\n } else {\n var y = new o;\n if (n) var _ = n(v, h, l, t, r, y);\n if (!(void 0 === _ ? i(h, v, a | u, n, y) : _)) return !1\n }\n }\n return !0\n }\n var o = e(9),\n i = e(53),\n a = 1,\n u = 2;\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n var r = this.__data__ = new o(t);\n this.size = r.size\n }\n var o = e(10),\n i = e(18),\n a = e(19),\n u = e(20),\n c = e(21),\n s = e(22);\n n.prototype.clear = i, n.prototype.delete = a, n.prototype.get = u, n.prototype.has = c, n.prototype.set = s, t.exports = n\n}, function(t, r, e) {\n function n(t) {\n var r = -1,\n e = null == t ? 0 : t.length;\n for (this.clear(); ++r < e;) {\n var n = t[r];\n this.set(n[0], n[1])\n }\n }\n var o = e(11),\n i = e(12),\n a = e(15),\n u = e(16),\n c = e(17);\n n.prototype.clear = o, n.prototype.delete = i, n.prototype.get = a, n.prototype.has = u, n.prototype.set = c, t.exports = n\n}, function(t, r) {\n function e() {\n this.__data__ = [], this.size = 0\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n var r = this.__data__,\n e = o(r, t);\n if (e < 0) return !1;\n var n = r.length - 1;\n return e == n ? r.pop() : a.call(r, e, 1), --this.size, !0\n }\n var o = e(13),\n i = Array.prototype,\n a = i.splice;\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n for (var e = t.length; e--;)\n if (o(t[e][0], r)) return e;\n return -1\n }\n var o = e(14);\n t.exports = n\n}, function(t, r) {\n function e(t, r) {\n return t === r || t !== t && r !== r\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n var r = this.__data__,\n e = o(r, t);\n return e < 0 ? void 0 : r[e][1]\n }\n var o = e(13);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n return o(this.__data__, t) > -1\n }\n var o = e(13);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n var e = this.__data__,\n n = o(e, t);\n return n < 0 ? (++this.size, e.push([t, r])) : e[n][1] = r, this\n }\n var o = e(13);\n t.exports = n\n}, function(t, r, e) {\n function n() {\n this.__data__ = new o, this.size = 0\n }\n var o = e(10);\n t.exports = n\n}, function(t, r) {\n function e(t) {\n var r = this.__data__,\n e = r.delete(t);\n return this.size = r.size, e\n }\n t.exports = e\n}, function(t, r) {\n function e(t) {\n return this.__data__.get(t)\n }\n t.exports = e\n}, function(t, r) {\n function e(t) {\n return this.__data__.has(t)\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t, r) {\n var e = this.__data__;\n if (e instanceof o) {\n var n = e.__data__;\n if (!i || n.length < u - 1) return n.push([t, r]), this.size = ++e.size, this;\n e = this.__data__ = new a(n)\n }\n return e.set(t, r), this.size = e.size, this\n }\n var o = e(10),\n i = e(23),\n a = e(38),\n u = 200;\n t.exports = n\n}, function(t, r, e) {\n var n = e(24),\n o = e(29),\n i = n(o, \"Map\");\n t.exports = i\n}, function(t, r, e) {\n function n(t, r) {\n var e = i(t, r);\n return o(e) ? e : void 0\n }\n var o = e(25),\n i = e(37);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n if (!a(t) || i(t)) return !1;\n var r = o(t) ? h : s;\n return r.test(u(t))\n }\n var o = e(26),\n i = e(34),\n a = e(33),\n u = e(36),\n c = /[\\\\^$.*+?()[\\]{}|]/g,\n s = /^\\[object .+?Constructor\\]$/,\n f = Function.prototype,\n p = Object.prototype,\n l = f.toString,\n v = p.hasOwnProperty,\n h = RegExp(\"^\" + l.call(v).replace(c, \"\\\\$&\").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, \"$1.*?\") + \"$\");\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n if (!i(t)) return !1;\n var r = o(t);\n return r == u || r == c || r == a || r == s\n }\n var o = e(27),\n i = e(33),\n a = \"[object AsyncFunction]\",\n u = \"[object Function]\",\n c = \"[object GeneratorFunction]\",\n s = \"[object Proxy]\";\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n return null == t ? void 0 === t ? c : u : s && s in Object(t) ? i(t) : a(t)\n }\n var o = e(28),\n i = e(31),\n a = e(32),\n u = \"[object Null]\",\n c = \"[object Undefined]\",\n s = o ? o.toStringTag : void 0;\n t.exports = n\n}, function(t, r, e) {\n var n = e(29),\n o = n.Symbol;\n t.exports = o\n}, function(t, r, e) {\n var n = e(30),\n o = \"object\" == typeof self && self && self.Object === Object && self,\n i = n || o || Function(\"return this\")();\n t.exports = i\n}, function(t, r) {\n (function(r) {\n var e = \"object\" == typeof r && r && r.Object === Object && r;\n t.exports = e\n }).call(r, function() {\n return this\n }())\n}, function(t, r, e) {\n function n(t) {\n var r = a.call(t, c),\n e = t[c];\n try {\n t[c] = void 0;\n var n = !0\n } catch (t) {}\n var o = u.call(t);\n return n && (r ? t[c] = e : delete t[c]), o\n }\n var o = e(28),\n i = Object.prototype,\n a = i.hasOwnProperty,\n u = i.toString,\n c = o ? o.toStringTag : void 0;\n t.exports = n\n}, function(t, r) {\n function e(t) {\n return o.call(t)\n }\n var n = Object.prototype,\n o = n.toString;\n t.exports = e\n}, function(t, r) {\n function e(t) {\n var r = typeof t;\n return null != t && (\"object\" == r || \"function\" == r)\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n return !!i && i in t\n }\n var o = e(35),\n i = function() {\n var t = /[^.]+$/.exec(o && o.keys && o.keys.IE_PROTO || \"\");\n return t ? \"Symbol(src)_1.\" + t : \"\"\n }();\n t.exports = n\n}, function(t, r, e) {\n var n = e(29),\n o = n[\"__core-js_shared__\"];\n t.exports = o\n}, function(t, r) {\n function e(t) {\n if (null != t) {\n try {\n return o.call(t)\n } catch (t) {}\n try {\n return t + \"\"\n } catch (t) {}\n }\n return \"\"\n }\n var n = Function.prototype,\n o = n.toString;\n t.exports = e\n}, function(t, r) {\n function e(t, r) {\n return null == t ? void 0 : t[r]\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n var r = -1,\n e = null == t ? 0 : t.length;\n for (this.clear(); ++r < e;) {\n var n = t[r];\n this.set(n[0], n[1])\n }\n }\n var o = e(39),\n i = e(47),\n a = e(50),\n u = e(51),\n c = e(52);\n n.prototype.clear = o, n.prototype.delete = i, n.prototype.get = a, n.prototype.has = u, n.prototype.set = c, t.exports = n\n}, function(t, r, e) {\n function n() {\n this.size = 0, this.__data__ = {\n hash: new o,\n map: new(a || i),\n string: new o\n }\n }\n var o = e(40),\n i = e(10),\n a = e(23);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n var r = -1,\n e = null == t ? 0 : t.length;\n for (this.clear(); ++r < e;) {\n var n = t[r];\n this.set(n[0], n[1])\n }\n }\n var o = e(41),\n i = e(43),\n a = e(44),\n u = e(45),\n c = e(46);\n n.prototype.clear = o, n.prototype.delete = i, n.prototype.get = a, n.prototype.has = u, n.prototype.set = c, t.exports = n\n}, function(t, r, e) {\n function n() {\n this.__data__ = o ? o(null) : {}, this.size = 0\n }\n var o = e(42);\n t.exports = n\n}, function(t, r, e) {\n var n = e(24),\n o = n(Object, \"create\");\n t.exports = o\n}, function(t, r) {\n function e(t) {\n var r = this.has(t) && delete this.__data__[t];\n return this.size -= r ? 1 : 0, r\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n var r = this.__data__;\n if (o) {\n var e = r[t];\n return e === i ? void 0 : e\n }\n return u.call(r, t) ? r[t] : void 0\n }\n var o = e(42),\n i = \"__lodash_hash_undefined__\",\n a = Object.prototype,\n u = a.hasOwnProperty;\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n var r = this.__data__;\n return o ? void 0 !== r[t] : a.call(r, t)\n }\n var o = e(42),\n i = Object.prototype,\n a = i.hasOwnProperty;\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n var e = this.__data__;\n return this.size += this.has(t) ? 0 : 1, e[t] = o && void 0 === r ? i : r, this\n }\n var o = e(42),\n i = \"__lodash_hash_undefined__\";\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n var r = o(this, t).delete(t);\n return this.size -= r ? 1 : 0, r\n }\n var o = e(48);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n var e = t.__data__;\n return o(r) ? e[\"string\" == typeof r ? \"string\" : \"hash\"] : e.map\n }\n var o = e(49);\n t.exports = n\n}, function(t, r) {\n function e(t) {\n var r = typeof t;\n return \"string\" == r || \"number\" == r || \"symbol\" == r || \"boolean\" == r ? \"__proto__\" !== t : null === t\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n return o(this, t).get(t)\n }\n var o = e(48);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n return o(this, t).has(t)\n }\n var o = e(48);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n var e = o(this, t),\n n = e.size;\n return e.set(t, r), this.size += e.size == n ? 0 : 1, this\n }\n var o = e(48);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r, e, a, u) {\n return t === r || (null == t || null == r || !i(t) && !i(r) ? t !== t && r !== r : o(t, r, e, a, n, u))\n }\n var o = e(54),\n i = e(78);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r, e, n, _, d) {\n var x = s(t),\n g = s(r),\n w = x ? h : c(t),\n j = g ? h : c(r);\n w = w == v ? y : w, j = j == v ? y : j;\n var m = w == y,\n O = j == y,\n A = w == j;\n if (A && f(t)) {\n if (!f(r)) return !1;\n x = !0, m = !1\n }\n if (A && !m) return d || (d = new o), x || p(t) ? i(t, r, e, n, _, d) : a(t, r, w, e, n, _, d);\n if (!(e & l)) {\n var z = m && b.call(t, \"__wrapped__\"),\n k = O && b.call(r, \"__wrapped__\");\n if (z || k) {\n var S = z ? t.value() : t,\n P = k ? r.value() : r;\n return d || (d = new o), _(S, P, e, n, d)\n }\n }\n return !!A && (d || (d = new o), u(t, r, e, n, _, d))\n }\n var o = e(9),\n i = e(55),\n a = e(61),\n u = e(65),\n c = e(93),\n s = e(69),\n f = e(79),\n p = e(83),\n l = 1,\n v = \"[object Arguments]\",\n h = \"[object Array]\",\n y = \"[object Object]\",\n _ = Object.prototype,\n b = _.hasOwnProperty;\n t.exports = n\n}, function(t, r, e) {\n function n(t, r, e, n, s, f) {\n var p = e & u,\n l = t.length,\n v = r.length;\n if (l != v && !(p && v > l)) return !1;\n var h = f.get(t);\n if (h && f.get(r)) return h == r;\n var y = -1,\n _ = !0,\n b = e & c ? new o : void 0;\n for (f.set(t, r), f.set(r, t); ++y < l;) {\n var d = t[y],\n x = r[y];\n if (n) var g = p ? n(x, d, y, r, t, f) : n(d, x, y, t, r, f);\n if (void 0 !== g) {\n if (g) continue;\n _ = !1;\n break\n }\n if (b) {\n if (!i(r, function(t, r) {\n if (!a(b, r) && (d === t || s(d, t, e, n, f))) return b.push(r)\n })) {\n _ = !1;\n break\n }\n } else if (d !== x && !s(d, x, e, n, f)) {\n _ = !1;\n break\n }\n }\n return f.delete(t), f.delete(r), _\n }\n var o = e(56),\n i = e(59),\n a = e(60),\n u = 1,\n c = 2;\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n var r = -1,\n e = null == t ? 0 : t.length;\n for (this.__data__ = new o; ++r < e;) this.add(t[r])\n }\n var o = e(38),\n i = e(57),\n a = e(58);\n n.prototype.add = n.prototype.push = i, n.prototype.has = a, t.exports = n\n}, function(t, r) {\n function e(t) {\n return this.__data__.set(t, n), this\n }\n var n = \"__lodash_hash_undefined__\";\n t.exports = e\n}, function(t, r) {\n function e(t) {\n return this.__data__.has(t)\n }\n t.exports = e\n}, function(t, r) {\n function e(t, r) {\n for (var e = -1, n = null == t ? 0 : t.length; ++e < n;)\n if (r(t[e], e, t)) return !0;\n return !1\n }\n t.exports = e\n}, function(t, r) {\n function e(t, r) {\n return t.has(r)\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t, r, e, n, o, m, A) {\n switch (e) {\n case j:\n if (t.byteLength != r.byteLength || t.byteOffset != r.byteOffset) return !1;\n t = t.buffer, r = r.buffer;\n case w:\n return !(t.byteLength != r.byteLength || !m(new i(t), new i(r)));\n case l:\n case v:\n case _:\n return a(+t, +r);\n case h:\n return t.name == r.name && t.message == r.message;\n case b:\n case x:\n return t == r + \"\";\n case y:\n var z = c;\n case d:\n var k = n & f;\n if (z || (z = s), t.size != r.size && !k) return !1;\n var S = A.get(t);\n if (S) return S == r;\n n |= p, A.set(t, r);\n var P = u(z(t), z(r), n, o, m, A);\n return A.delete(t), P;\n case g:\n if (O) return O.call(t) == O.call(r)\n }\n return !1\n }\n var o = e(28),\n i = e(62),\n a = e(14),\n u = e(55),\n c = e(63),\n s = e(64),\n f = 1,\n p = 2,\n l = \"[object Boolean]\",\n v = \"[object Date]\",\n h = \"[object Error]\",\n y = \"[object Map]\",\n _ = \"[object Number]\",\n b = \"[object RegExp]\",\n d = \"[object Set]\",\n x = \"[object String]\",\n g = \"[object Symbol]\",\n w = \"[object ArrayBuffer]\",\n j = \"[object DataView]\",\n m = o ? o.prototype : void 0,\n O = m ? m.valueOf : void 0;\n t.exports = n\n}, function(t, r, e) {\n var n = e(29),\n o = n.Uint8Array;\n t.exports = o\n}, function(t, r) {\n function e(t) {\n var r = -1,\n e = Array(t.size);\n return t.forEach(function(t, n) {\n e[++r] = [n, t]\n }), e\n }\n t.exports = e\n}, function(t, r) {\n function e(t) {\n var r = -1,\n e = Array(t.size);\n return t.forEach(function(t) {\n e[++r] = t\n }), e\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t, r, e, n, a, c) {\n var s = e & i,\n f = o(t),\n p = f.length,\n l = o(r),\n v = l.length;\n if (p != v && !s) return !1;\n for (var h = p; h--;) {\n var y = f[h];\n if (!(s ? y in r : u.call(r, y))) return !1\n }\n var _ = c.get(t);\n if (_ && c.get(r)) return _ == r;\n var b = !0;\n c.set(t, r), c.set(r, t);\n for (var d = s; ++h < p;) {\n y = f[h];\n var x = t[y],\n g = r[y];\n if (n) var w = s ? n(g, x, y, r, t, c) : n(x, g, y, t, r, c);\n if (!(void 0 === w ? x === g || a(x, g, e, n, c) : w)) {\n b = !1;\n break\n }\n d || (d = \"constructor\" == y)\n }\n if (b && !d) {\n var j = t.constructor,\n m = r.constructor;\n j != m && \"constructor\" in t && \"constructor\" in r && !(\"function\" == typeof j && j instanceof j && \"function\" == typeof m && m instanceof m) && (b = !1)\n }\n return c.delete(t), c.delete(r), b\n }\n var o = e(66),\n i = 1,\n a = Object.prototype,\n u = a.hasOwnProperty;\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n return o(t, a, i)\n }\n var o = e(67),\n i = e(70),\n a = e(73);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r, e) {\n var n = r(t);\n return i(t) ? n : o(n, e(t))\n }\n var o = e(68),\n i = e(69);\n t.exports = n\n}, function(t, r) {\n function e(t, r) {\n for (var e = -1, n = r.length, o = t.length; ++e < n;) t[o + e] = r[e];\n return t\n }\n t.exports = e\n}, function(t, r) {\n var e = Array.isArray;\n t.exports = e\n}, function(t, r, e) {\n var n = e(71),\n o = e(72),\n i = Object.prototype,\n a = i.propertyIsEnumerable,\n u = Object.getOwnPropertySymbols,\n c = u ? function(t) {\n return null == t ? [] : (t = Object(t), n(u(t), function(r) {\n return a.call(t, r)\n }))\n } : o;\n t.exports = c\n}, function(t, r) {\n function e(t, r) {\n for (var e = -1, n = null == t ? 0 : t.length, o = 0, i = []; ++e < n;) {\n var a = t[e];\n r(a, e, t) && (i[o++] = a)\n }\n return i\n }\n t.exports = e\n}, function(t, r) {\n function e() {\n return []\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n return a(t) ? o(t) : i(t)\n }\n var o = e(74),\n i = e(88),\n a = e(92);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n var e = a(t),\n n = !e && i(t),\n f = !e && !n && u(t),\n l = !e && !n && !f && s(t),\n v = e || n || f || l,\n h = v ? o(t.length, String) : [],\n y = h.length;\n for (var _ in t) !r && !p.call(t, _) || v && (\"length\" == _ || f && (\"offset\" == _ || \"parent\" == _) || l && (\"buffer\" == _ || \"byteLength\" == _ || \"byteOffset\" == _) || c(_, y)) || h.push(_);\n return h\n }\n var o = e(75),\n i = e(76),\n a = e(69),\n u = e(79),\n c = e(82),\n s = e(83),\n f = Object.prototype,\n p = f.hasOwnProperty;\n t.exports = n\n}, function(t, r) {\n function e(t, r) {\n for (var e = -1, n = Array(t); ++e < t;) n[e] = r(e);\n return n\n }\n t.exports = e\n}, function(t, r, e) {\n var n = e(77),\n o = e(78),\n i = Object.prototype,\n a = i.hasOwnProperty,\n u = i.propertyIsEnumerable,\n c = n(function() {\n return arguments\n }()) ? n : function(t) {\n return o(t) && a.call(t, \"callee\") && !u.call(t, \"callee\")\n };\n t.exports = c\n}, function(t, r, e) {\n function n(t) {\n return i(t) && o(t) == a\n }\n var o = e(27),\n i = e(78),\n a = \"[object Arguments]\";\n t.exports = n\n}, function(t, r) {\n function e(t) {\n return null != t && \"object\" == typeof t\n }\n t.exports = e\n}, function(t, r, e) {\n (function(t) {\n var n = e(29),\n o = e(81),\n i = \"object\" == typeof r && r && !r.nodeType && r,\n a = i && \"object\" == typeof t && t && !t.nodeType && t,\n u = a && a.exports === i,\n c = u ? n.Buffer : void 0,\n s = c ? c.isBuffer : void 0,\n f = s || o;\n t.exports = f\n }).call(r, e(80)(t))\n}, function(t, r) {\n t.exports = function(t) {\n return t.webpackPolyfill || (t.deprecate = function() {}, t.paths = [], t.children = [], t.webpackPolyfill = 1), t\n }\n}, function(t, r) {\n function e() {\n return !1\n }\n t.exports = e\n}, function(t, r) {\n function e(t, r) {\n return r = null == r ? n : r, !!r && (\"number\" == typeof t || o.test(t)) && t > -1 && t % 1 == 0 && t < r\n }\n var n = 9007199254740991,\n o = /^(?:0|[1-9]\\d*)$/;\n t.exports = e\n}, function(t, r, e) {\n var n = e(84),\n o = e(86),\n i = e(87),\n a = i && i.isTypedArray,\n u = a ? o(a) : n;\n t.exports = u\n}, function(t, r, e) {\n function n(t) {\n return a(t) && i(t.length) && !!T[o(t)]\n }\n var o = e(27),\n i = e(85),\n a = e(78),\n u = \"[object Arguments]\",\n c = \"[object Array]\",\n s = \"[object Boolean]\",\n f = \"[object Date]\",\n p = \"[object Error]\",\n l = \"[object Function]\",\n v = \"[object Map]\",\n h = \"[object Number]\",\n y = \"[object Object]\",\n _ = \"[object RegExp]\",\n b = \"[object Set]\",\n d = \"[object String]\",\n x = \"[object WeakMap]\",\n g = \"[object ArrayBuffer]\",\n w = \"[object DataView]\",\n j = \"[object Float32Array]\",\n m = \"[object Float64Array]\",\n O = \"[object Int8Array]\",\n A = \"[object Int16Array]\",\n z = \"[object Int32Array]\",\n k = \"[object Uint8Array]\",\n S = \"[object Uint8ClampedArray]\",\n P = \"[object Uint16Array]\",\n B = \"[object Uint32Array]\",\n T = {};\n T[j] = T[m] = T[O] = T[A] = T[z] = T[k] = T[S] = T[P] = T[B] = !0, T[u] = T[c] = T[g] = T[s] = T[w] = T[f] = T[p] = T[l] = T[v] = T[h] = T[y] = T[_] = T[b] = T[d] = T[x] = !1, t.exports = n\n}, function(t, r) {\n function e(t) {\n return \"number\" == typeof t && t > -1 && t % 1 == 0 && t <= n\n }\n var n = 9007199254740991;\n t.exports = e\n}, function(t, r) {\n function e(t) {\n return function(r) {\n return t(r)\n }\n }\n t.exports = e\n}, function(t, r, e) {\n (function(t) {\n var n = e(30),\n o = \"object\" == typeof r && r && !r.nodeType && r,\n i = o && \"object\" == typeof t && t && !t.nodeType && t,\n a = i && i.exports === o,\n u = a && n.process,\n c = function() {\n try {\n return u && u.binding && u.binding(\"util\")\n } catch (t) {}\n }();\n t.exports = c\n }).call(r, e(80)(t))\n}, function(t, r, e) {\n function n(t) {\n if (!o(t)) return i(t);\n var r = [];\n for (var e in Object(t)) u.call(t, e) && \"constructor\" != e && r.push(e);\n return r\n }\n var o = e(89),\n i = e(90),\n a = Object.prototype,\n u = a.hasOwnProperty;\n t.exports = n\n}, function(t, r) {\n function e(t) {\n var r = t && t.constructor,\n e = \"function\" == typeof r && r.prototype || n;\n return t === e\n }\n var n = Object.prototype;\n t.exports = e\n}, function(t, r, e) {\n var n = e(91),\n o = n(Object.keys, Object);\n t.exports = o\n}, function(t, r) {\n function e(t, r) {\n return function(e) {\n return t(r(e))\n }\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n return null != t && i(t.length) && !o(t)\n }\n var o = e(26),\n i = e(85);\n t.exports = n\n}, function(t, r, e) {\n var n = e(94),\n o = e(23),\n i = e(95),\n a = e(96),\n u = e(97),\n c = e(27),\n s = e(36),\n f = \"[object Map]\",\n p = \"[object Object]\",\n l = \"[object Promise]\",\n v = \"[object Set]\",\n h = \"[object WeakMap]\",\n y = \"[object DataView]\",\n _ = s(n),\n b = s(o),\n d = s(i),\n x = s(a),\n g = s(u),\n w = c;\n (n && w(new n(new ArrayBuffer(1))) != y || o && w(new o) != f || i && w(i.resolve()) != l || a && w(new a) != v || u && w(new u) != h) && (w = function(t) {\n var r = c(t),\n e = r == p ? t.constructor : void 0,\n n = e ? s(e) : \"\";\n if (n) switch (n) {\n case _:\n return y;\n case b:\n return f;\n case d:\n return l;\n case x:\n return v;\n case g:\n return h\n }\n return r\n }), t.exports = w\n}, function(t, r, e) {\n var n = e(24),\n o = e(29),\n i = n(o, \"DataView\");\n t.exports = i\n}, function(t, r, e) {\n var n = e(24),\n o = e(29),\n i = n(o, \"Promise\");\n t.exports = i\n}, function(t, r, e) {\n var n = e(24),\n o = e(29),\n i = n(o, \"Set\");\n t.exports = i\n}, function(t, r, e) {\n var n = e(24),\n o = e(29),\n i = n(o, \"WeakMap\");\n t.exports = i\n}, function(t, r, e) {\n function n(t) {\n for (var r = i(t), e = r.length; e--;) {\n var n = r[e],\n a = t[n];\n r[e] = [n, a, o(a)]\n }\n return r\n }\n var o = e(99),\n i = e(73);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n return t === t && !o(t)\n }\n var o = e(33);\n t.exports = n\n}, function(t, r) {\n function e(t, r) {\n return function(e) {\n return null != e && (e[t] === r && (void 0 !== r || t in Object(e)))\n }\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t, r) {\n return u(t) && c(r) ? s(f(t), r) : function(e) {\n var n = i(e, t);\n return void 0 === n && n === r ? a(e, t) : o(r, n, p | l)\n }\n }\n var o = e(53),\n i = e(102),\n a = e(114),\n u = e(105),\n c = e(99),\n s = e(100),\n f = e(113),\n p = 1,\n l = 2;\n t.exports = n\n}, function(t, r, e) {\n function n(t, r, e) {\n var n = null == t ? void 0 : o(t, r);\n return void 0 === n ? e : n\n }\n var o = e(103);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n r = o(r, t);\n for (var e = 0, n = r.length; null != t && e < n;) t = t[i(r[e++])];\n return e && e == n ? t : void 0\n }\n var o = e(104),\n i = e(113);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n return o(t) ? t : i(t, r) ? [t] : a(u(t))\n }\n var o = e(69),\n i = e(105),\n a = e(107),\n u = e(110);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n if (o(t)) return !1;\n var e = typeof t;\n return !(\"number\" != e && \"symbol\" != e && \"boolean\" != e && null != t && !i(t)) || (u.test(t) || !a.test(t) || null != r && t in Object(r))\n }\n var o = e(69),\n i = e(106),\n a = /\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/,\n u = /^\\w*$/;\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n return \"symbol\" == typeof t || i(t) && o(t) == a\n }\n var o = e(27),\n i = e(78),\n a = \"[object Symbol]\";\n t.exports = n\n}, function(t, r, e) {\n var n = e(108),\n o = /^\\./,\n i = /[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g,\n a = /\\\\(\\\\)?/g,\n u = n(function(t) {\n var r = [];\n return o.test(t) && r.push(\"\"), t.replace(i, function(t, e, n, o) {\n r.push(n ? o.replace(a, \"$1\") : e || t)\n }), r\n });\n t.exports = u\n}, function(t, r, e) {\n function n(t) {\n var r = o(t, function(t) {\n return e.size === i && e.clear(), t\n }),\n e = r.cache;\n return r\n }\n var o = e(109),\n i = 500;\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n if (\"function\" != typeof t || null != r && \"function\" != typeof r) throw new TypeError(i);\n var e = function() {\n var n = arguments,\n o = r ? r.apply(this, n) : n[0],\n i = e.cache;\n if (i.has(o)) return i.get(o);\n var a = t.apply(this, n);\n return e.cache = i.set(o, a) || i, a\n };\n return e.cache = new(n.Cache || o), e\n }\n var o = e(38),\n i = \"Expected a function\";\n n.Cache = o, t.exports = n\n}, function(t, r, e) {\n function n(t) {\n return null == t ? \"\" : o(t)\n }\n var o = e(111);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n if (\"string\" == typeof t) return t;\n if (a(t)) return i(t, n) + \"\";\n if (u(t)) return f ? f.call(t) : \"\";\n var r = t + \"\";\n return \"0\" == r && 1 / t == -c ? \"-0\" : r\n }\n var o = e(28),\n i = e(112),\n a = e(69),\n u = e(106),\n c = 1 / 0,\n s = o ? o.prototype : void 0,\n f = s ? s.toString : void 0;\n t.exports = n\n}, function(t, r) {\n function e(t, r) {\n for (var e = -1, n = null == t ? 0 : t.length, o = Array(n); ++e < n;) o[e] = r(t[e], e, t);\n return o\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n if (\"string\" == typeof t || o(t)) return t;\n var r = t + \"\";\n return \"0\" == r && 1 / t == -i ? \"-0\" : r\n }\n var o = e(106),\n i = 1 / 0;\n t.exports = n\n}, function(t, r, e) {\n function n(t, r) {\n return null != t && i(t, r, o)\n }\n var o = e(115),\n i = e(116);\n t.exports = n\n}, function(t, r) {\n function e(t, r) {\n return null != t && r in Object(t)\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t, r, e) {\n r = o(r, t);\n for (var n = -1, f = r.length, p = !1; ++n < f;) {\n var l = s(r[n]);\n if (!(p = null != t && e(t, l))) break;\n t = t[l]\n }\n return p || ++n != f ? p : (f = null == t ? 0 : t.length, !!f && c(f) && u(l, f) && (a(t) || i(t)))\n }\n var o = e(104),\n i = e(76),\n a = e(69),\n u = e(82),\n c = e(85),\n s = e(113);\n t.exports = n\n}, function(t, r) {\n function e(t) {\n return t\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n return a(t) ? o(u(t)) : i(t)\n }\n var o = e(119),\n i = e(120),\n a = e(105),\n u = e(113);\n t.exports = n\n}, function(t, r) {\n function e(t) {\n return function(r) {\n return null == r ? void 0 : r[t]\n }\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n return function(r) {\n return o(r, t)\n }\n }\n var o = e(103);\n t.exports = n\n}, function(t, r, e) {\n function n(t, r, e) {\n var n = null == t ? 0 : t.length;\n if (!n) return -1;\n var c = null == e ? 0 : a(e);\n return c < 0 && (c = u(n + c, 0)), o(t, i(r, 3), c)\n }\n var o = e(122),\n i = e(6),\n a = e(123),\n u = Math.max;\n t.exports = n\n}, function(t, r) {\n function e(t, r, e, n) {\n for (var o = t.length, i = e + (n ? 1 : -1); n ? i-- : ++i < o;)\n if (r(t[i], i, t)) return i;\n return -1\n }\n t.exports = e\n}, function(t, r, e) {\n function n(t) {\n var r = o(t),\n e = r % 1;\n return r === r ? e ? r - e : r : 0\n }\n var o = e(124);\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n if (!t) return 0 === t ? t : 0;\n if (t = o(t), t === i || t === -i) {\n var r = t < 0 ? -1 : 1;\n return r * a\n }\n return t === t ? t : 0\n }\n var o = e(125),\n i = 1 / 0,\n a = 1.7976931348623157e308;\n t.exports = n\n}, function(t, r, e) {\n function n(t) {\n if (\"number\" == typeof t) return t;\n if (i(t)) return a;\n if (o(t)) {\n var r = \"function\" == typeof t.valueOf ? t.valueOf() : t;\n t = o(r) ? r + \"\" : r\n }\n if (\"string\" != typeof t) return 0 === t ? t : +t;\n t = t.replace(u, \"\");\n var e = s.test(t);\n return e || f.test(t) ? p(t.slice(2), e ? 2 : 8) : c.test(t) ? a : +t\n }\n var o = e(33),\n i = e(106),\n a = NaN,\n u = /^\\s+|\\s+$/g,\n c = /^[-+]0x[0-9a-f]+$/i,\n s = /^0b[01]+$/i,\n f = /^0o[0-7]+$/i,\n p = parseInt;\n t.exports = n\n}, function(t, r) {\n \"use strict\";\n\n function e() {\n return window.crypto.subtle || window.crypto.webkitSubtle\n }\n r.subtle = e\n}, function(t, r) {\n \"use strict\";\n\n function e(t, r) {\n var n;\n return n = Array.isArray(t) ? [] : {}, r.push(t), Object.keys(t).forEach(function(o) {\n var i = t[o];\n if (\"function\" != typeof i) return i && \"object\" == typeof i ? r.indexOf(t[o]) === -1 ? void(n[o] = e(t[o], r.slice(0))) : void(n[o] = \"[Circular]\") : void(n[o] = i)\n }), \"string\" == typeof t.name && (n.name = t.name), \"string\" == typeof t.message && (n.message = t.message), \"string\" == typeof t.stack && (n.stack = t.stack), n\n }\n t.exports = function(t) {\n return \"object\" == typeof t ? e(t, []) : \"function\" == typeof t ? \"[Function: \" + (t.name || \"anonymous\") + \"]\" : t\n }\n}]);\n"; +export default _default; diff --git a/examples/react-native/src/webview-crypto/webViewWorkerString.js b/examples/react-native/src/webview-crypto/webViewWorkerString.js new file mode 100644 index 00000000..6da7a411 --- /dev/null +++ b/examples/react-native/src/webview-crypto/webViewWorkerString.js @@ -0,0 +1,2135 @@ +export const base64InjString = ` +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' + ? module.exports = factory(global) + : typeof define === 'function' && define.amd + ? define(factory) : factory(global) +}(( + typeof self !== 'undefined' ? self + : typeof window !== 'undefined' ? window + : typeof global !== 'undefined' ? global +: this +), function(global) { + 'use strict'; + // existing version for noConflict() + var _Base64 = global.Base64; + var version = "2.4.9"; + // if node.js and NOT React Native, we use Buffer + var buffer; + if (typeof module !== 'undefined' && module.exports) { + try { + buffer = eval("require('buffer').Buffer"); + } catch (err) { + buffer = undefined; + } + } + // constants + var b64chars + = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + var b64tab = function(bin) { + var t = {}; + for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i; + return t; + }(b64chars); + var fromCharCode = String.fromCharCode; + // encoder stuff + var cb_utob = function(c) { + if (c.length < 2) { + var cc = c.charCodeAt(0); + return cc < 0x80 ? c + : cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6)) + + fromCharCode(0x80 | (cc & 0x3f))) + : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) + + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + + fromCharCode(0x80 | ( cc & 0x3f))); + } else { + var cc = 0x10000 + + (c.charCodeAt(0) - 0xD800) * 0x400 + + (c.charCodeAt(1) - 0xDC00); + return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) + + fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) + + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + + fromCharCode(0x80 | ( cc & 0x3f))); + } + }; + var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; + var utob = function(u) { + return u.replace(re_utob, cb_utob); + }; + var cb_encode = function(ccc) { + var padlen = [0, 2, 1][ccc.length % 3], + ord = ccc.charCodeAt(0) << 16 + | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) + | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)), + chars = [ + b64chars.charAt( ord >>> 18), + b64chars.charAt((ord >>> 12) & 63), + padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63), + padlen >= 1 ? '=' : b64chars.charAt(ord & 63) + ]; + return chars.join(''); + }; + var btoa = global.btoa ? function(b) { + return global.btoa(b); + } : function(b) { + return b.replace(/[\s\S]{1,3}/g, cb_encode); + }; + var _encode = buffer ? + buffer.from && Uint8Array && buffer.from !== Uint8Array.from + ? function (u) { + return (u.constructor === buffer.constructor ? u : buffer.from(u)) + .toString('base64') + } + : function (u) { + return (u.constructor === buffer.constructor ? u : new buffer(u)) + .toString('base64') + } + : function (u) { return btoa(utob(u)) } + ; + var encode = function(u, urisafe) { + return !urisafe + ? _encode(String(u)) + : _encode(String(u)).replace(/[+\/]/g, function(m0) { + return m0 == '+' ? '-' : '_'; + }).replace(/=/g, ''); + }; + var encodeURI = function(u) { return encode(u, true) }; + // decoder stuff + var re_btou = new RegExp([ + '[\xC0-\xDF][\x80-\xBF]', + '[\xE0-\xEF][\x80-\xBF]{2}', + '[\xF0-\xF7][\x80-\xBF]{3}' + ].join('|'), 'g'); + var cb_btou = function(cccc) { + switch(cccc.length) { + case 4: + var cp = ((0x07 & cccc.charCodeAt(0)) << 18) + | ((0x3f & cccc.charCodeAt(1)) << 12) + | ((0x3f & cccc.charCodeAt(2)) << 6) + | (0x3f & cccc.charCodeAt(3)), + offset = cp - 0x10000; + return (fromCharCode((offset >>> 10) + 0xD800) + + fromCharCode((offset & 0x3FF) + 0xDC00)); + case 3: + return fromCharCode( + ((0x0f & cccc.charCodeAt(0)) << 12) + | ((0x3f & cccc.charCodeAt(1)) << 6) + | (0x3f & cccc.charCodeAt(2)) + ); + default: + return fromCharCode( + ((0x1f & cccc.charCodeAt(0)) << 6) + | (0x3f & cccc.charCodeAt(1)) + ); + } + }; + var btou = function(b) { + return b.replace(re_btou, cb_btou); + }; + var cb_decode = function(cccc) { + var len = cccc.length, + padlen = len % 4, + n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) + | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) + | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) + | (len > 3 ? b64tab[cccc.charAt(3)] : 0), + chars = [ + fromCharCode( n >>> 16), + fromCharCode((n >>> 8) & 0xff), + fromCharCode( n & 0xff) + ]; + chars.length -= [0, 0, 2, 1][padlen]; + return chars.join(''); + }; + var atob = global.atob ? function(a) { + return global.atob(a); + } : function(a){ + return a.replace(/[\s\S]{1,4}/g, cb_decode); + }; + var _decode = buffer ? + buffer.from && Uint8Array && buffer.from !== Uint8Array.from + ? function(a) { + return (a.constructor === buffer.constructor + ? a : buffer.from(a, 'base64')).toString(); + } + : function(a) { + return (a.constructor === buffer.constructor + ? a : new buffer(a, 'base64')).toString(); + } + : function(a) { return btou(atob(a)) }; + var decode = function(a){ + return _decode( + String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' }) + .replace(/[^A-Za-z0-9\+\/]/g, '') + ); + }; + var noConflict = function() { + var Base64 = global.Base64; + global.Base64 = _Base64; + return Base64; + }; + // export Base64 + global.Base64 = { + VERSION: version, + atob: atob, + btoa: btoa, + fromBase64: decode, + toBase64: encode, + utob: utob, + encode: encode, + encodeURI: encodeURI, + btou: btou, + decode: decode, + noConflict: noConflict, + __buffer__: buffer + }; + // if ES5 is available, make Base64.extendString() available + if (typeof Object.defineProperty === 'function') { + var noEnum = function(v){ + return {value:v,enumerable:false,writable:true,configurable:true}; + }; + global.Base64.extendString = function () { + Object.defineProperty( + String.prototype, 'fromBase64', noEnum(function () { + return decode(this) + })); + Object.defineProperty( + String.prototype, 'toBase64', noEnum(function (urisafe) { + return encode(this, urisafe) + })); + Object.defineProperty( + String.prototype, 'toBase64URI', noEnum(function () { + return encode(this, true) + })); + }; + } + return {Base64: global.Base64} +})); +`; +export default ` +var WebViewWorker = function(t) { + function r(n) { + if (e[n]) return e[n].exports; + var o = e[n] = { + exports: {}, + id: n, + loaded: !1 + }; + return t[n].call(o.exports, o, o.exports, r), o.loaded = !0, o.exports + } + var e = {}; + return r.m = t, r.c = e, r.p = "", r(0) +}([function(t, r, e) { + t.exports = e(1) +}, function(t, r, e) { + "use strict"; + var n = this && this.__awaiter || function(t, r, e, n) { + return new(e || (e = Promise))(function(o, i) { + function a(t) { + try { + c(n.next(t)) + } catch (t) { + i(t) + } + } + + function u(t) { + try { + c(n.throw(t)) + } catch (t) { + i(t) + } + } + + function c(t) { + t.done ? o(t.value) : new e(function(r) { + r(t.value) + }).then(a, u) + } + c((n = n.apply(t, r)).next()) + }) + }, + o = this && this.__generator || function(t, r) { + function e(t) { + return function(r) { + return n([t, r]) + } + } + + function n(e) { + if (o) throw new TypeError("Generator is already executing."); + for (; u;) try { + if (o = 1, i && (a = i[2 & e[0] ? "return" : e[0] ? "throw" : "next"]) && !(a = a.call(i, e[1])).done) return a; + switch (i = 0, a && (e = [0, a.value]), e[0]) { + case 0: + case 1: + a = e; + break; + case 4: + return u.label++, { + value: e[1], + done: !1 + }; + case 5: + u.label++, i = e[1], e = [0]; + continue; + case 7: + e = u.ops.pop(), u.trys.pop(); + continue; + default: + if (a = u.trys, !(a = a.length > 0 && a[a.length - 1]) && (6 === e[0] || 2 === e[0])) { + u = 0; + continue + } + if (3 === e[0] && (!a || e[1] > a[0] && e[1] < a[3])) { + u.label = e[1]; + break + } + if (6 === e[0] && u.label < a[1]) { + u.label = a[1], a = e; + break + } + if (a && u.label < a[2]) { + u.label = a[2], u.ops.push(e); + break + } + a[2] && u.ops.pop(), u.trys.pop(); + continue + } + e = r.call(t, u) + } catch (t) { + e = [6, t], i = 0 + } finally { + o = a = 0 + } + if (5 & e[0]) throw e[1]; + return { + value: e[0] ? e[1] : void 0, + done: !0 + } + } + var o, i, a, u = { + label: 0, + sent: function() { + if (1 & a[0]) throw a[1]; + return a[1] + }, + trys: [], + ops: [] + }; + return { + next: e(0), + throw: e(1), + return: e(2) + } + }, + i = e(2), + a = e(126), + u = e(127), + c = function() { + function t(t) { + this.sendToMain = t, t("We are ready!") + } + return t.prototype.onMainMessage = function(t) { + return n(this, void 0, void 0, function() { + var r, e, n, c, s, f, p, l; + return o(this, function(o) { + switch (o.label) { + case 0: + return o.trys.push([0, 2, , 4]), [4, i.parse(t)]; + case 1: + return l = o.sent(), r = l.id, e = l.method, n = l.args, [3, 4]; + case 2: + return c = o.sent(), [4, this.send({ + reason: "Couldn't parse data: " + c + })]; + case 3: + return o.sent(), [2]; + case 4: + return o.trys.push([4, 8, , 10]), "getRandomValues" !== e ? [3, 5] : (s = crypto.getRandomValues(n[0]), [3, 7]); + case 5: + // console.log(f, n) + return f = e.split(".")[1], [4, a.subtle()[f].apply(a.subtle(), n)]; + case 6: + s = o.sent(), "importKey" === f && (s._import = { + format: n[0], + keyData: n[1] + }), o.label = 7; + case 7: + return [3, 10]; + case 8: + return p = o.sent(), [4, this.send({ + id: r, + reason: u(p) + })]; + case 9: + return o.sent(), [2]; + case 10: + return [4, this.send({ + id: r, + value: s + })]; + case 11: + return o.sent(), [2] + } + }) + }) + }, t.prototype.send = function(t) { + return n(this, void 0, void 0, function() { + var r, e, n; + return o(this, function(o) { + switch (o.label) { + case 0: + return o.trys.push([0, 2, , 3]), [4, i.stringify(t)]; + case 1: + return r = o.sent(), [3, 3]; + case 2: + return e = o.sent(), n = { + id: t.id, + reason: "stringify error " + e + }, this.sendToMain(JSON.stringify(n)), [2]; + case 3: + return this.sendToMain(r), [2] + } + }) + }) + }, t + }(); + t.exports = c +}, function(module, exports, __webpack_require__) { + "use strict"; + + function parse(t) { + return __awaiter(this, void 0, void 0, function() { + var r, e; + return __generator(this, function(n) { + switch (n.label) { + case 0: + console.log('*** decoding', t); + return r = unescape(t), e = JSON.parse(r), [4, asyncSerialize_1.fromObjects(serializers(!0), e)]; + case 1: + return [2, n.sent()] + } + }) + }) + } + + function stringify(t, r) { + return void 0 === r && (r = !0), __awaiter(this, void 0, void 0, function() { + var e, n; + return __generator(this, function(o) { + switch (o.label) { + case 0: + return [4, asyncSerialize_1.toObjects(serializers(r), t)]; + case 1: + console.log('*** encoding', n); + return e = o.sent(), n = JSON.stringify(e), [2, escape(n)] + } + }) + }) + } + + function serializers(t) { + return [ArrayBufferSerializer, ArrayBufferViewSerializer(t), CryptoKeySerializer] + } + + function isArrayBufferViewWithPromise(t) { + return t.hasOwnProperty("_promise") + } + + function arrayBufferViewName(t) { + return t instanceof Int8Array ? "Int8Array" : t instanceof Uint8Array ? "Uint8Array" : t instanceof Uint8ClampedArray ? "Uint8ClampedArray" : t instanceof Int16Array ? "Int16Array" : t instanceof Uint16Array ? "Uint16Array" : t instanceof Int32Array ? "Int32Array" : t instanceof Uint32Array ? "Uint32Array" : t instanceof Float32Array ? "Float32Array" : t instanceof Float64Array ? "Float64Array" : t instanceof DataView ? "DataView" : void 0 + } + + function ArrayBufferViewSerializer(waitForPromise) { + var _this = this; + return { + id: "ArrayBufferView", + isType: ArrayBuffer.isView, + toObject: function(t) { + return __awaiter(_this, void 0, void 0, function() { + return __generator(this, function(r) { + switch (r.label) { + case 0: + return waitForPromise && isArrayBufferViewWithPromise(t) ? [4, t._promise] : [3, 2]; + case 1: + r.sent(), r.label = 2; + case 2: + return [2, { + name: arrayBufferViewName(t), + buffer: t.buffer + }] + } + }) + }) + }, + fromObject: function(abvs) { + return __awaiter(_this, void 0, void 0, function() { + return __generator(this, function(_a) { + return [2, eval("new " + abvs.name + "(abvs.buffer)")] + }) + }) + } + } + } + + function hasData(t) { + return void 0 !== t._import + } + var __assign = this && this.__assign || Object.assign || function(t) { + for (var r, e = 1, n = arguments.length; e < n; e++) { + r = arguments[e]; + for (var o in r) Object.prototype.hasOwnProperty.call(r, o) && (t[o] = r[o]) + } + return t + }, + __awaiter = this && this.__awaiter || function(t, r, e, n) { + return new(e || (e = Promise))(function(o, i) { + function a(t) { + try { + c(n.next(t)) + } catch (t) { + i(t) + } + } + + function u(t) { + try { + c(n.throw(t)) + } catch (t) { + i(t) + } + } + + function c(t) { + t.done ? o(t.value) : new e(function(r) { + r(t.value) + }).then(a, u) + } + c((n = n.apply(t, r)).next()) + }) + }, + __generator = this && this.__generator || function(t, r) { + function e(t) { + return function(r) { + return n([t, r]) + } + } + + function n(e) { + if (o) throw new TypeError("Generator is already executing."); + for (; u;) try { + if (o = 1, i && (a = i[2 & e[0] ? "return" : e[0] ? "throw" : "next"]) && !(a = a.call(i, e[1])).done) return a; + switch (i = 0, a && (e = [0, a.value]), e[0]) { + case 0: + case 1: + a = e; + break; + case 4: + return u.label++, { + value: e[1], + done: !1 + }; + case 5: + u.label++, i = e[1], e = [0]; + continue; + case 7: + e = u.ops.pop(), u.trys.pop(); + continue; + default: + if (a = u.trys, !(a = a.length > 0 && a[a.length - 1]) && (6 === e[0] || 2 === e[0])) { + u = 0; + continue + } + if (3 === e[0] && (!a || e[1] > a[0] && e[1] < a[3])) { + u.label = e[1]; + break + } + if (6 === e[0] && u.label < a[1]) { + u.label = a[1], a = e; + break + } + if (a && u.label < a[2]) { + u.label = a[2], u.ops.push(e); + break + } + a[2] && u.ops.pop(), u.trys.pop(); + continue + } + e = r.call(t, u) + } catch (t) { + e = [6, t], i = 0 + } finally { + o = a = 0 + } + if (5 & e[0]) throw e[1]; + return { + value: e[0] ? e[1] : void 0, + done: !0 + } + } + var o, i, a, u = { + label: 0, + sent: function() { + if (1 & a[0]) throw a[1]; + return a[1] + }, + trys: [], + ops: [] + }; + return { + next: e(0), + throw: e(1), + return: e(2) + } + }, + _this = this, + asyncSerialize_1 = __webpack_require__(3), + compat_1 = __webpack_require__(126); + exports.parse = parse, exports.stringify = stringify; + var ArrayBufferSerializer = { + id: "ArrayBuffer", + isType: function(t) { + return t instanceof ArrayBuffer + }, + toObject: function(t) { + return __awaiter(_this, void 0, void 0, function() { + return __generator(this, function(r) { + return [2, String.fromCharCode.apply(null, new Int8Array(t))] + }) + }) + }, + fromObject: function(t) { + return __awaiter(_this, void 0, void 0, function() { + var r, e, n, o; + return __generator(this, function(i) { + for (r = new ArrayBuffer(t.length), e = new Int8Array(r), n = 0, o = t.length; n < o; n++) e[n] = t.charCodeAt(n); + return [2, r] + }) + }) + } + }, + CryptoKeySerializer = { + id: "CryptoKey", + isType: function(t) { + var r = t.toLocaleString(), + e = "[object CryptoKey]" === r || "[object Key]" === r, + n = t._import && !t.serialized; + return e || n + }, + toObject: function(t) { + return __awaiter(_this, void 0, void 0, function() { + var r; + return __generator(this, function(e) { + switch (e.label) { + case 0: + return hasData(t) ? [2, { + serialized: !0, + _import: t._import, + type: t.type, + extractable: t.extractable, + algorithm: t.algorithm, + usages: t.usages + }] : [4, compat_1.subtle().exportKey("jwk", t)]; + case 1: + return r = e.sent(), [2, { + _import: { + format: "jwk", + keyData: r + }, + serialized: !0, + algorithm: t.algorithm, + extractable: t.extractable, + usages: t.usages, + type: t.type + }] + } + }) + }) + }, + fromObject: function(t) { + return __awaiter(_this, void 0, void 0, function() { + var r; + return __generator(this, function(e) { + switch (e.label) { + case 0: + return crypto.fake ? (r = __assign({}, t), delete r.serialized, [2, r]) : [4, compat_1.subtle().importKey(t._import.format, t._import.keyData, t.algorithm, t.extractable, t.usages)]; + case 1: + return [2, e.sent()] + } + }) + }) + } + } +}, function(t, r, e) { + "use strict"; + + function n(t) { + return t.hasOwnProperty("__serializer_id") + } + + function o(t, r) { + return a(this, void 0, void 0, function() { + var e, n, i, a, s, f, p, l, v, h, y; + return u(this, function(u) { + switch (u.label) { + case 0: + return "object" != typeof r ? [2, r] : (e = c(t, function(t) { + return t.isType(r) + }), e ? e.toObject ? [4, e.toObject(r)] : [3, 2] : [3, 5]); + case 1: + return i = u.sent(), [3, 3]; + case 2: + i = r, u.label = 3; + case 3: + return n = i, a = { + __serializer_id: e.id + }, [4, o(t, n)]; + case 4: + return [2, (a.value = u.sent(), a)]; + case 5: + s = r instanceof Array ? [] : {}, f = []; + for (p in r) f.push(p); + l = 0, u.label = 6; + case 6: + return l < f.length ? (v = f[l], h = s, y = v, [4, o(t, r[v])]) : [3, 9]; + case 7: + h[y] = u.sent(), u.label = 8; + case 8: + return l++, [3, 6]; + case 9: + return [2, s] + } + }) + }) + } + + function i(t, r) { + return a(this, void 0, void 0, function() { + var e, o, a, s, f, p, l, v, h; + return u(this, function(u) { + switch (u.label) { + case 0: + return "object" != typeof r ? [2, r] : n(r) ? [4, i(t, r.value)] : [3, 2]; + case 1: + return e = u.sent(), o = c(t, ["id", r.__serializer_id]), o.fromObject ? [2, o.fromObject(e)] : [2, e]; + case 2: + a = r instanceof Array ? [] : {}, s = []; + for (f in r) s.push(f); + p = 0, u.label = 3; + case 3: + return p < s.length ? (l = s[p], v = a, h = l, [4, i(t, r[l])]) : [3, 6]; + case 4: + v[h] = u.sent(), u.label = 5; + case 5: + return p++, [3, 3]; + case 6: + return [2, a] + } + }) + }) + } + var a = this && this.__awaiter || function(t, r, e, n) { + return new(e || (e = Promise))(function(o, i) { + function a(t) { + try { + c(n.next(t)) + } catch (t) { + i(t) + } + } + + function u(t) { + try { + c(n.throw(t)) + } catch (t) { + i(t) + } + } + + function c(t) { + t.done ? o(t.value) : new e(function(r) { + r(t.value) + }).then(a, u) + } + c((n = n.apply(t, r)).next()) + }) + }, + u = this && this.__generator || function(t, r) { + function e(t) { + return function(r) { + return n([t, r]) + } + } + + function n(e) { + if (o) throw new TypeError("Generator is already executing."); + for (; u;) try { + if (o = 1, i && (a = i[2 & e[0] ? "return" : e[0] ? "throw" : "next"]) && !(a = a.call(i, e[1])).done) return a; + switch (i = 0, a && (e = [0, a.value]), e[0]) { + case 0: + case 1: + a = e; + break; + case 4: + return u.label++, { + value: e[1], + done: !1 + }; + case 5: + u.label++, i = e[1], e = [0]; + continue; + case 7: + e = u.ops.pop(), u.trys.pop(); + continue; + default: + if (a = u.trys, !(a = a.length > 0 && a[a.length - 1]) && (6 === e[0] || 2 === e[0])) { + u = 0; + continue + } + if (3 === e[0] && (!a || e[1] > a[0] && e[1] < a[3])) { + u.label = e[1]; + break + } + if (6 === e[0] && u.label < a[1]) { + u.label = a[1], a = e; + break + } + if (a && u.label < a[2]) { + u.label = a[2], u.ops.push(e); + break + } + a[2] && u.ops.pop(), u.trys.pop(); + continue + } + e = r.call(t, u) + } catch (t) { + e = [6, t], i = 0 + } finally { + o = a = 0 + } + if (5 & e[0]) throw e[1]; + return { + value: e[0] ? e[1] : void 0, + done: !0 + } + } + var o, i, a, u = { + label: 0, + sent: function() { + if (1 & a[0]) throw a[1]; + return a[1] + }, + trys: [], + ops: [] + }; + return { + next: e(0), + throw: e(1), + return: e(2) + } + }, + c = e(4); + (function() { + function t() {} + return t + })(); + r.toObjects = o, r.fromObjects = i +}, function(t, r, e) { + var n = e(5), + o = e(121), + i = n(o); + t.exports = i +}, function(t, r, e) { + function n(t) { + return function(r, e, n) { + var u = Object(r); + if (!i(r)) { + var c = o(e, 3); + r = a(r), e = function(t) { + return c(u[t], t, u) + } + } + var s = t(r, e, n); + return s > -1 ? u[c ? r[s] : s] : void 0 + } + } + var o = e(6), + i = e(92), + a = e(73); + t.exports = n +}, function(t, r, e) { + function n(t) { + return "function" == typeof t ? t : null == t ? a : "object" == typeof t ? u(t) ? i(t[0], t[1]) : o(t) : c(t) + } + var o = e(7), + i = e(101), + a = e(117), + u = e(69), + c = e(118); + t.exports = n +}, function(t, r, e) { + function n(t) { + var r = i(t); + return 1 == r.length && r[0][2] ? a(r[0][0], r[0][1]) : function(e) { + return e === t || o(e, t, r) + } + } + var o = e(8), + i = e(98), + a = e(100); + t.exports = n +}, function(t, r, e) { + function n(t, r, e, n) { + var c = e.length, + s = c, + f = !n; + if (null == t) return !s; + for (t = Object(t); c--;) { + var p = e[c]; + if (f && p[2] ? p[1] !== t[p[0]] : !(p[0] in t)) return !1 + } + for (; ++c < s;) { + p = e[c]; + var l = p[0], + v = t[l], + h = p[1]; + if (f && p[2]) { + if (void 0 === v && !(l in t)) return !1 + } else { + var y = new o; + if (n) var _ = n(v, h, l, t, r, y); + if (!(void 0 === _ ? i(h, v, a | u, n, y) : _)) return !1 + } + } + return !0 + } + var o = e(9), + i = e(53), + a = 1, + u = 2; + t.exports = n +}, function(t, r, e) { + function n(t) { + var r = this.__data__ = new o(t); + this.size = r.size + } + var o = e(10), + i = e(18), + a = e(19), + u = e(20), + c = e(21), + s = e(22); + n.prototype.clear = i, n.prototype.delete = a, n.prototype.get = u, n.prototype.has = c, n.prototype.set = s, t.exports = n +}, function(t, r, e) { + function n(t) { + var r = -1, + e = null == t ? 0 : t.length; + for (this.clear(); ++r < e;) { + var n = t[r]; + this.set(n[0], n[1]) + } + } + var o = e(11), + i = e(12), + a = e(15), + u = e(16), + c = e(17); + n.prototype.clear = o, n.prototype.delete = i, n.prototype.get = a, n.prototype.has = u, n.prototype.set = c, t.exports = n +}, function(t, r) { + function e() { + this.__data__ = [], this.size = 0 + } + t.exports = e +}, function(t, r, e) { + function n(t) { + var r = this.__data__, + e = o(r, t); + if (e < 0) return !1; + var n = r.length - 1; + return e == n ? r.pop() : a.call(r, e, 1), --this.size, !0 + } + var o = e(13), + i = Array.prototype, + a = i.splice; + t.exports = n +}, function(t, r, e) { + function n(t, r) { + for (var e = t.length; e--;) + if (o(t[e][0], r)) return e; + return -1 + } + var o = e(14); + t.exports = n +}, function(t, r) { + function e(t, r) { + return t === r || t !== t && r !== r + } + t.exports = e +}, function(t, r, e) { + function n(t) { + var r = this.__data__, + e = o(r, t); + return e < 0 ? void 0 : r[e][1] + } + var o = e(13); + t.exports = n +}, function(t, r, e) { + function n(t) { + return o(this.__data__, t) > -1 + } + var o = e(13); + t.exports = n +}, function(t, r, e) { + function n(t, r) { + var e = this.__data__, + n = o(e, t); + return n < 0 ? (++this.size, e.push([t, r])) : e[n][1] = r, this + } + var o = e(13); + t.exports = n +}, function(t, r, e) { + function n() { + this.__data__ = new o, this.size = 0 + } + var o = e(10); + t.exports = n +}, function(t, r) { + function e(t) { + var r = this.__data__, + e = r.delete(t); + return this.size = r.size, e + } + t.exports = e +}, function(t, r) { + function e(t) { + return this.__data__.get(t) + } + t.exports = e +}, function(t, r) { + function e(t) { + return this.__data__.has(t) + } + t.exports = e +}, function(t, r, e) { + function n(t, r) { + var e = this.__data__; + if (e instanceof o) { + var n = e.__data__; + if (!i || n.length < u - 1) return n.push([t, r]), this.size = ++e.size, this; + e = this.__data__ = new a(n) + } + return e.set(t, r), this.size = e.size, this + } + var o = e(10), + i = e(23), + a = e(38), + u = 200; + t.exports = n +}, function(t, r, e) { + var n = e(24), + o = e(29), + i = n(o, "Map"); + t.exports = i +}, function(t, r, e) { + function n(t, r) { + var e = i(t, r); + return o(e) ? e : void 0 + } + var o = e(25), + i = e(37); + t.exports = n +}, function(t, r, e) { + function n(t) { + if (!a(t) || i(t)) return !1; + var r = o(t) ? h : s; + return r.test(u(t)) + } + var o = e(26), + i = e(34), + a = e(33), + u = e(36), + c = /[\\\\^$.*+?()[\\]{}|]/g, + s = /^\\[object .+?Constructor\\]$/, + f = Function.prototype, + p = Object.prototype, + l = f.toString, + v = p.hasOwnProperty, + h = RegExp("^" + l.call(v).replace(c, "\\\\$&").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, "$1.*?") + "$"); + t.exports = n +}, function(t, r, e) { + function n(t) { + if (!i(t)) return !1; + var r = o(t); + return r == u || r == c || r == a || r == s + } + var o = e(27), + i = e(33), + a = "[object AsyncFunction]", + u = "[object Function]", + c = "[object GeneratorFunction]", + s = "[object Proxy]"; + t.exports = n +}, function(t, r, e) { + function n(t) { + return null == t ? void 0 === t ? c : u : s && s in Object(t) ? i(t) : a(t) + } + var o = e(28), + i = e(31), + a = e(32), + u = "[object Null]", + c = "[object Undefined]", + s = o ? o.toStringTag : void 0; + t.exports = n +}, function(t, r, e) { + var n = e(29), + o = n.Symbol; + t.exports = o +}, function(t, r, e) { + var n = e(30), + o = "object" == typeof self && self && self.Object === Object && self, + i = n || o || Function("return this")(); + t.exports = i +}, function(t, r) { + (function(r) { + var e = "object" == typeof r && r && r.Object === Object && r; + t.exports = e + }).call(r, function() { + return this + }()) +}, function(t, r, e) { + function n(t) { + var r = a.call(t, c), + e = t[c]; + try { + t[c] = void 0; + var n = !0 + } catch (t) {} + var o = u.call(t); + return n && (r ? t[c] = e : delete t[c]), o + } + var o = e(28), + i = Object.prototype, + a = i.hasOwnProperty, + u = i.toString, + c = o ? o.toStringTag : void 0; + t.exports = n +}, function(t, r) { + function e(t) { + return o.call(t) + } + var n = Object.prototype, + o = n.toString; + t.exports = e +}, function(t, r) { + function e(t) { + var r = typeof t; + return null != t && ("object" == r || "function" == r) + } + t.exports = e +}, function(t, r, e) { + function n(t) { + return !!i && i in t + } + var o = e(35), + i = function() { + var t = /[^.]+$/.exec(o && o.keys && o.keys.IE_PROTO || ""); + return t ? "Symbol(src)_1." + t : "" + }(); + t.exports = n +}, function(t, r, e) { + var n = e(29), + o = n["__core-js_shared__"]; + t.exports = o +}, function(t, r) { + function e(t) { + if (null != t) { + try { + return o.call(t) + } catch (t) {} + try { + return t + "" + } catch (t) {} + } + return "" + } + var n = Function.prototype, + o = n.toString; + t.exports = e +}, function(t, r) { + function e(t, r) { + return null == t ? void 0 : t[r] + } + t.exports = e +}, function(t, r, e) { + function n(t) { + var r = -1, + e = null == t ? 0 : t.length; + for (this.clear(); ++r < e;) { + var n = t[r]; + this.set(n[0], n[1]) + } + } + var o = e(39), + i = e(47), + a = e(50), + u = e(51), + c = e(52); + n.prototype.clear = o, n.prototype.delete = i, n.prototype.get = a, n.prototype.has = u, n.prototype.set = c, t.exports = n +}, function(t, r, e) { + function n() { + this.size = 0, this.__data__ = { + hash: new o, + map: new(a || i), + string: new o + } + } + var o = e(40), + i = e(10), + a = e(23); + t.exports = n +}, function(t, r, e) { + function n(t) { + var r = -1, + e = null == t ? 0 : t.length; + for (this.clear(); ++r < e;) { + var n = t[r]; + this.set(n[0], n[1]) + } + } + var o = e(41), + i = e(43), + a = e(44), + u = e(45), + c = e(46); + n.prototype.clear = o, n.prototype.delete = i, n.prototype.get = a, n.prototype.has = u, n.prototype.set = c, t.exports = n +}, function(t, r, e) { + function n() { + this.__data__ = o ? o(null) : {}, this.size = 0 + } + var o = e(42); + t.exports = n +}, function(t, r, e) { + var n = e(24), + o = n(Object, "create"); + t.exports = o +}, function(t, r) { + function e(t) { + var r = this.has(t) && delete this.__data__[t]; + return this.size -= r ? 1 : 0, r + } + t.exports = e +}, function(t, r, e) { + function n(t) { + var r = this.__data__; + if (o) { + var e = r[t]; + return e === i ? void 0 : e + } + return u.call(r, t) ? r[t] : void 0 + } + var o = e(42), + i = "__lodash_hash_undefined__", + a = Object.prototype, + u = a.hasOwnProperty; + t.exports = n +}, function(t, r, e) { + function n(t) { + var r = this.__data__; + return o ? void 0 !== r[t] : a.call(r, t) + } + var o = e(42), + i = Object.prototype, + a = i.hasOwnProperty; + t.exports = n +}, function(t, r, e) { + function n(t, r) { + var e = this.__data__; + return this.size += this.has(t) ? 0 : 1, e[t] = o && void 0 === r ? i : r, this + } + var o = e(42), + i = "__lodash_hash_undefined__"; + t.exports = n +}, function(t, r, e) { + function n(t) { + var r = o(this, t).delete(t); + return this.size -= r ? 1 : 0, r + } + var o = e(48); + t.exports = n +}, function(t, r, e) { + function n(t, r) { + var e = t.__data__; + return o(r) ? e["string" == typeof r ? "string" : "hash"] : e.map + } + var o = e(49); + t.exports = n +}, function(t, r) { + function e(t) { + var r = typeof t; + return "string" == r || "number" == r || "symbol" == r || "boolean" == r ? "__proto__" !== t : null === t + } + t.exports = e +}, function(t, r, e) { + function n(t) { + return o(this, t).get(t) + } + var o = e(48); + t.exports = n +}, function(t, r, e) { + function n(t) { + return o(this, t).has(t) + } + var o = e(48); + t.exports = n +}, function(t, r, e) { + function n(t, r) { + var e = o(this, t), + n = e.size; + return e.set(t, r), this.size += e.size == n ? 0 : 1, this + } + var o = e(48); + t.exports = n +}, function(t, r, e) { + function n(t, r, e, a, u) { + return t === r || (null == t || null == r || !i(t) && !i(r) ? t !== t && r !== r : o(t, r, e, a, n, u)) + } + var o = e(54), + i = e(78); + t.exports = n +}, function(t, r, e) { + function n(t, r, e, n, _, d) { + var x = s(t), + g = s(r), + w = x ? h : c(t), + j = g ? h : c(r); + w = w == v ? y : w, j = j == v ? y : j; + var m = w == y, + O = j == y, + A = w == j; + if (A && f(t)) { + if (!f(r)) return !1; + x = !0, m = !1 + } + if (A && !m) return d || (d = new o), x || p(t) ? i(t, r, e, n, _, d) : a(t, r, w, e, n, _, d); + if (!(e & l)) { + var z = m && b.call(t, "__wrapped__"), + k = O && b.call(r, "__wrapped__"); + if (z || k) { + var S = z ? t.value() : t, + P = k ? r.value() : r; + return d || (d = new o), _(S, P, e, n, d) + } + } + return !!A && (d || (d = new o), u(t, r, e, n, _, d)) + } + var o = e(9), + i = e(55), + a = e(61), + u = e(65), + c = e(93), + s = e(69), + f = e(79), + p = e(83), + l = 1, + v = "[object Arguments]", + h = "[object Array]", + y = "[object Object]", + _ = Object.prototype, + b = _.hasOwnProperty; + t.exports = n +}, function(t, r, e) { + function n(t, r, e, n, s, f) { + var p = e & u, + l = t.length, + v = r.length; + if (l != v && !(p && v > l)) return !1; + var h = f.get(t); + if (h && f.get(r)) return h == r; + var y = -1, + _ = !0, + b = e & c ? new o : void 0; + for (f.set(t, r), f.set(r, t); ++y < l;) { + var d = t[y], + x = r[y]; + if (n) var g = p ? n(x, d, y, r, t, f) : n(d, x, y, t, r, f); + if (void 0 !== g) { + if (g) continue; + _ = !1; + break + } + if (b) { + if (!i(r, function(t, r) { + if (!a(b, r) && (d === t || s(d, t, e, n, f))) return b.push(r) + })) { + _ = !1; + break + } + } else if (d !== x && !s(d, x, e, n, f)) { + _ = !1; + break + } + } + return f.delete(t), f.delete(r), _ + } + var o = e(56), + i = e(59), + a = e(60), + u = 1, + c = 2; + t.exports = n +}, function(t, r, e) { + function n(t) { + var r = -1, + e = null == t ? 0 : t.length; + for (this.__data__ = new o; ++r < e;) this.add(t[r]) + } + var o = e(38), + i = e(57), + a = e(58); + n.prototype.add = n.prototype.push = i, n.prototype.has = a, t.exports = n +}, function(t, r) { + function e(t) { + return this.__data__.set(t, n), this + } + var n = "__lodash_hash_undefined__"; + t.exports = e +}, function(t, r) { + function e(t) { + return this.__data__.has(t) + } + t.exports = e +}, function(t, r) { + function e(t, r) { + for (var e = -1, n = null == t ? 0 : t.length; ++e < n;) + if (r(t[e], e, t)) return !0; + return !1 + } + t.exports = e +}, function(t, r) { + function e(t, r) { + return t.has(r) + } + t.exports = e +}, function(t, r, e) { + function n(t, r, e, n, o, m, A) { + switch (e) { + case j: + if (t.byteLength != r.byteLength || t.byteOffset != r.byteOffset) return !1; + t = t.buffer, r = r.buffer; + case w: + return !(t.byteLength != r.byteLength || !m(new i(t), new i(r))); + case l: + case v: + case _: + return a(+t, +r); + case h: + return t.name == r.name && t.message == r.message; + case b: + case x: + return t == r + ""; + case y: + var z = c; + case d: + var k = n & f; + if (z || (z = s), t.size != r.size && !k) return !1; + var S = A.get(t); + if (S) return S == r; + n |= p, A.set(t, r); + var P = u(z(t), z(r), n, o, m, A); + return A.delete(t), P; + case g: + if (O) return O.call(t) == O.call(r) + } + return !1 + } + var o = e(28), + i = e(62), + a = e(14), + u = e(55), + c = e(63), + s = e(64), + f = 1, + p = 2, + l = "[object Boolean]", + v = "[object Date]", + h = "[object Error]", + y = "[object Map]", + _ = "[object Number]", + b = "[object RegExp]", + d = "[object Set]", + x = "[object String]", + g = "[object Symbol]", + w = "[object ArrayBuffer]", + j = "[object DataView]", + m = o ? o.prototype : void 0, + O = m ? m.valueOf : void 0; + t.exports = n +}, function(t, r, e) { + var n = e(29), + o = n.Uint8Array; + t.exports = o +}, function(t, r) { + function e(t) { + var r = -1, + e = Array(t.size); + return t.forEach(function(t, n) { + e[++r] = [n, t] + }), e + } + t.exports = e +}, function(t, r) { + function e(t) { + var r = -1, + e = Array(t.size); + return t.forEach(function(t) { + e[++r] = t + }), e + } + t.exports = e +}, function(t, r, e) { + function n(t, r, e, n, a, c) { + var s = e & i, + f = o(t), + p = f.length, + l = o(r), + v = l.length; + if (p != v && !s) return !1; + for (var h = p; h--;) { + var y = f[h]; + if (!(s ? y in r : u.call(r, y))) return !1 + } + var _ = c.get(t); + if (_ && c.get(r)) return _ == r; + var b = !0; + c.set(t, r), c.set(r, t); + for (var d = s; ++h < p;) { + y = f[h]; + var x = t[y], + g = r[y]; + if (n) var w = s ? n(g, x, y, r, t, c) : n(x, g, y, t, r, c); + if (!(void 0 === w ? x === g || a(x, g, e, n, c) : w)) { + b = !1; + break + } + d || (d = "constructor" == y) + } + if (b && !d) { + var j = t.constructor, + m = r.constructor; + j != m && "constructor" in t && "constructor" in r && !("function" == typeof j && j instanceof j && "function" == typeof m && m instanceof m) && (b = !1) + } + return c.delete(t), c.delete(r), b + } + var o = e(66), + i = 1, + a = Object.prototype, + u = a.hasOwnProperty; + t.exports = n +}, function(t, r, e) { + function n(t) { + return o(t, a, i) + } + var o = e(67), + i = e(70), + a = e(73); + t.exports = n +}, function(t, r, e) { + function n(t, r, e) { + var n = r(t); + return i(t) ? n : o(n, e(t)) + } + var o = e(68), + i = e(69); + t.exports = n +}, function(t, r) { + function e(t, r) { + for (var e = -1, n = r.length, o = t.length; ++e < n;) t[o + e] = r[e]; + return t + } + t.exports = e +}, function(t, r) { + var e = Array.isArray; + t.exports = e +}, function(t, r, e) { + var n = e(71), + o = e(72), + i = Object.prototype, + a = i.propertyIsEnumerable, + u = Object.getOwnPropertySymbols, + c = u ? function(t) { + return null == t ? [] : (t = Object(t), n(u(t), function(r) { + return a.call(t, r) + })) + } : o; + t.exports = c +}, function(t, r) { + function e(t, r) { + for (var e = -1, n = null == t ? 0 : t.length, o = 0, i = []; ++e < n;) { + var a = t[e]; + r(a, e, t) && (i[o++] = a) + } + return i + } + t.exports = e +}, function(t, r) { + function e() { + return [] + } + t.exports = e +}, function(t, r, e) { + function n(t) { + return a(t) ? o(t) : i(t) + } + var o = e(74), + i = e(88), + a = e(92); + t.exports = n +}, function(t, r, e) { + function n(t, r) { + var e = a(t), + n = !e && i(t), + f = !e && !n && u(t), + l = !e && !n && !f && s(t), + v = e || n || f || l, + h = v ? o(t.length, String) : [], + y = h.length; + for (var _ in t) !r && !p.call(t, _) || v && ("length" == _ || f && ("offset" == _ || "parent" == _) || l && ("buffer" == _ || "byteLength" == _ || "byteOffset" == _) || c(_, y)) || h.push(_); + return h + } + var o = e(75), + i = e(76), + a = e(69), + u = e(79), + c = e(82), + s = e(83), + f = Object.prototype, + p = f.hasOwnProperty; + t.exports = n +}, function(t, r) { + function e(t, r) { + for (var e = -1, n = Array(t); ++e < t;) n[e] = r(e); + return n + } + t.exports = e +}, function(t, r, e) { + var n = e(77), + o = e(78), + i = Object.prototype, + a = i.hasOwnProperty, + u = i.propertyIsEnumerable, + c = n(function() { + return arguments + }()) ? n : function(t) { + return o(t) && a.call(t, "callee") && !u.call(t, "callee") + }; + t.exports = c +}, function(t, r, e) { + function n(t) { + return i(t) && o(t) == a + } + var o = e(27), + i = e(78), + a = "[object Arguments]"; + t.exports = n +}, function(t, r) { + function e(t) { + return null != t && "object" == typeof t + } + t.exports = e +}, function(t, r, e) { + (function(t) { + var n = e(29), + o = e(81), + i = "object" == typeof r && r && !r.nodeType && r, + a = i && "object" == typeof t && t && !t.nodeType && t, + u = a && a.exports === i, + c = u ? n.Buffer : void 0, + s = c ? c.isBuffer : void 0, + f = s || o; + t.exports = f + }).call(r, e(80)(t)) +}, function(t, r) { + t.exports = function(t) { + return t.webpackPolyfill || (t.deprecate = function() {}, t.paths = [], t.children = [], t.webpackPolyfill = 1), t + } +}, function(t, r) { + function e() { + return !1 + } + t.exports = e +}, function(t, r) { + function e(t, r) { + return r = null == r ? n : r, !!r && ("number" == typeof t || o.test(t)) && t > -1 && t % 1 == 0 && t < r + } + var n = 9007199254740991, + o = /^(?:0|[1-9]\\d*)$/; + t.exports = e +}, function(t, r, e) { + var n = e(84), + o = e(86), + i = e(87), + a = i && i.isTypedArray, + u = a ? o(a) : n; + t.exports = u +}, function(t, r, e) { + function n(t) { + return a(t) && i(t.length) && !!T[o(t)] + } + var o = e(27), + i = e(85), + a = e(78), + u = "[object Arguments]", + c = "[object Array]", + s = "[object Boolean]", + f = "[object Date]", + p = "[object Error]", + l = "[object Function]", + v = "[object Map]", + h = "[object Number]", + y = "[object Object]", + _ = "[object RegExp]", + b = "[object Set]", + d = "[object String]", + x = "[object WeakMap]", + g = "[object ArrayBuffer]", + w = "[object DataView]", + j = "[object Float32Array]", + m = "[object Float64Array]", + O = "[object Int8Array]", + A = "[object Int16Array]", + z = "[object Int32Array]", + k = "[object Uint8Array]", + S = "[object Uint8ClampedArray]", + P = "[object Uint16Array]", + B = "[object Uint32Array]", + T = {}; + T[j] = T[m] = T[O] = T[A] = T[z] = T[k] = T[S] = T[P] = T[B] = !0, T[u] = T[c] = T[g] = T[s] = T[w] = T[f] = T[p] = T[l] = T[v] = T[h] = T[y] = T[_] = T[b] = T[d] = T[x] = !1, t.exports = n +}, function(t, r) { + function e(t) { + return "number" == typeof t && t > -1 && t % 1 == 0 && t <= n + } + var n = 9007199254740991; + t.exports = e +}, function(t, r) { + function e(t) { + return function(r) { + return t(r) + } + } + t.exports = e +}, function(t, r, e) { + (function(t) { + var n = e(30), + o = "object" == typeof r && r && !r.nodeType && r, + i = o && "object" == typeof t && t && !t.nodeType && t, + a = i && i.exports === o, + u = a && n.process, + c = function() { + try { + return u && u.binding && u.binding("util") + } catch (t) {} + }(); + t.exports = c + }).call(r, e(80)(t)) +}, function(t, r, e) { + function n(t) { + if (!o(t)) return i(t); + var r = []; + for (var e in Object(t)) u.call(t, e) && "constructor" != e && r.push(e); + return r + } + var o = e(89), + i = e(90), + a = Object.prototype, + u = a.hasOwnProperty; + t.exports = n +}, function(t, r) { + function e(t) { + var r = t && t.constructor, + e = "function" == typeof r && r.prototype || n; + return t === e + } + var n = Object.prototype; + t.exports = e +}, function(t, r, e) { + var n = e(91), + o = n(Object.keys, Object); + t.exports = o +}, function(t, r) { + function e(t, r) { + return function(e) { + return t(r(e)) + } + } + t.exports = e +}, function(t, r, e) { + function n(t) { + return null != t && i(t.length) && !o(t) + } + var o = e(26), + i = e(85); + t.exports = n +}, function(t, r, e) { + var n = e(94), + o = e(23), + i = e(95), + a = e(96), + u = e(97), + c = e(27), + s = e(36), + f = "[object Map]", + p = "[object Object]", + l = "[object Promise]", + v = "[object Set]", + h = "[object WeakMap]", + y = "[object DataView]", + _ = s(n), + b = s(o), + d = s(i), + x = s(a), + g = s(u), + w = c; + (n && w(new n(new ArrayBuffer(1))) != y || o && w(new o) != f || i && w(i.resolve()) != l || a && w(new a) != v || u && w(new u) != h) && (w = function(t) { + var r = c(t), + e = r == p ? t.constructor : void 0, + n = e ? s(e) : ""; + if (n) switch (n) { + case _: + return y; + case b: + return f; + case d: + return l; + case x: + return v; + case g: + return h + } + return r + }), t.exports = w +}, function(t, r, e) { + var n = e(24), + o = e(29), + i = n(o, "DataView"); + t.exports = i +}, function(t, r, e) { + var n = e(24), + o = e(29), + i = n(o, "Promise"); + t.exports = i +}, function(t, r, e) { + var n = e(24), + o = e(29), + i = n(o, "Set"); + t.exports = i +}, function(t, r, e) { + var n = e(24), + o = e(29), + i = n(o, "WeakMap"); + t.exports = i +}, function(t, r, e) { + function n(t) { + for (var r = i(t), e = r.length; e--;) { + var n = r[e], + a = t[n]; + r[e] = [n, a, o(a)] + } + return r + } + var o = e(99), + i = e(73); + t.exports = n +}, function(t, r, e) { + function n(t) { + return t === t && !o(t) + } + var o = e(33); + t.exports = n +}, function(t, r) { + function e(t, r) { + return function(e) { + return null != e && (e[t] === r && (void 0 !== r || t in Object(e))) + } + } + t.exports = e +}, function(t, r, e) { + function n(t, r) { + return u(t) && c(r) ? s(f(t), r) : function(e) { + var n = i(e, t); + return void 0 === n && n === r ? a(e, t) : o(r, n, p | l) + } + } + var o = e(53), + i = e(102), + a = e(114), + u = e(105), + c = e(99), + s = e(100), + f = e(113), + p = 1, + l = 2; + t.exports = n +}, function(t, r, e) { + function n(t, r, e) { + var n = null == t ? void 0 : o(t, r); + return void 0 === n ? e : n + } + var o = e(103); + t.exports = n +}, function(t, r, e) { + function n(t, r) { + r = o(r, t); + for (var e = 0, n = r.length; null != t && e < n;) t = t[i(r[e++])]; + return e && e == n ? t : void 0 + } + var o = e(104), + i = e(113); + t.exports = n +}, function(t, r, e) { + function n(t, r) { + return o(t) ? t : i(t, r) ? [t] : a(u(t)) + } + var o = e(69), + i = e(105), + a = e(107), + u = e(110); + t.exports = n +}, function(t, r, e) { + function n(t, r) { + if (o(t)) return !1; + var e = typeof t; + return !("number" != e && "symbol" != e && "boolean" != e && null != t && !i(t)) || (u.test(t) || !a.test(t) || null != r && t in Object(r)) + } + var o = e(69), + i = e(106), + a = /\\.|\\[(?:[^[\\]]*|(["'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/, + u = /^\\w*$/; + t.exports = n +}, function(t, r, e) { + function n(t) { + return "symbol" == typeof t || i(t) && o(t) == a + } + var o = e(27), + i = e(78), + a = "[object Symbol]"; + t.exports = n +}, function(t, r, e) { + var n = e(108), + o = /^\\./, + i = /[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|(["'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g, + a = /\\\\(\\\\)?/g, + u = n(function(t) { + var r = []; + return o.test(t) && r.push(""), t.replace(i, function(t, e, n, o) { + r.push(n ? o.replace(a, "$1") : e || t) + }), r + }); + t.exports = u +}, function(t, r, e) { + function n(t) { + var r = o(t, function(t) { + return e.size === i && e.clear(), t + }), + e = r.cache; + return r + } + var o = e(109), + i = 500; + t.exports = n +}, function(t, r, e) { + function n(t, r) { + if ("function" != typeof t || null != r && "function" != typeof r) throw new TypeError(i); + var e = function() { + var n = arguments, + o = r ? r.apply(this, n) : n[0], + i = e.cache; + if (i.has(o)) return i.get(o); + var a = t.apply(this, n); + return e.cache = i.set(o, a) || i, a + }; + return e.cache = new(n.Cache || o), e + } + var o = e(38), + i = "Expected a function"; + n.Cache = o, t.exports = n +}, function(t, r, e) { + function n(t) { + return null == t ? "" : o(t) + } + var o = e(111); + t.exports = n +}, function(t, r, e) { + function n(t) { + if ("string" == typeof t) return t; + if (a(t)) return i(t, n) + ""; + if (u(t)) return f ? f.call(t) : ""; + var r = t + ""; + return "0" == r && 1 / t == -c ? "-0" : r + } + var o = e(28), + i = e(112), + a = e(69), + u = e(106), + c = 1 / 0, + s = o ? o.prototype : void 0, + f = s ? s.toString : void 0; + t.exports = n +}, function(t, r) { + function e(t, r) { + for (var e = -1, n = null == t ? 0 : t.length, o = Array(n); ++e < n;) o[e] = r(t[e], e, t); + return o + } + t.exports = e +}, function(t, r, e) { + function n(t) { + if ("string" == typeof t || o(t)) return t; + var r = t + ""; + return "0" == r && 1 / t == -i ? "-0" : r + } + var o = e(106), + i = 1 / 0; + t.exports = n +}, function(t, r, e) { + function n(t, r) { + return null != t && i(t, r, o) + } + var o = e(115), + i = e(116); + t.exports = n +}, function(t, r) { + function e(t, r) { + return null != t && r in Object(t) + } + t.exports = e +}, function(t, r, e) { + function n(t, r, e) { + r = o(r, t); + for (var n = -1, f = r.length, p = !1; ++n < f;) { + var l = s(r[n]); + if (!(p = null != t && e(t, l))) break; + t = t[l] + } + return p || ++n != f ? p : (f = null == t ? 0 : t.length, !!f && c(f) && u(l, f) && (a(t) || i(t))) + } + var o = e(104), + i = e(76), + a = e(69), + u = e(82), + c = e(85), + s = e(113); + t.exports = n +}, function(t, r) { + function e(t) { + return t + } + t.exports = e +}, function(t, r, e) { + function n(t) { + return a(t) ? o(u(t)) : i(t) + } + var o = e(119), + i = e(120), + a = e(105), + u = e(113); + t.exports = n +}, function(t, r) { + function e(t) { + return function(r) { + return null == r ? void 0 : r[t] + } + } + t.exports = e +}, function(t, r, e) { + function n(t) { + return function(r) { + return o(r, t) + } + } + var o = e(103); + t.exports = n +}, function(t, r, e) { + function n(t, r, e) { + var n = null == t ? 0 : t.length; + if (!n) return -1; + var c = null == e ? 0 : a(e); + return c < 0 && (c = u(n + c, 0)), o(t, i(r, 3), c) + } + var o = e(122), + i = e(6), + a = e(123), + u = Math.max; + t.exports = n +}, function(t, r) { + function e(t, r, e, n) { + for (var o = t.length, i = e + (n ? 1 : -1); n ? i-- : ++i < o;) + if (r(t[i], i, t)) return i; + return -1 + } + t.exports = e +}, function(t, r, e) { + function n(t) { + var r = o(t), + e = r % 1; + return r === r ? e ? r - e : r : 0 + } + var o = e(124); + t.exports = n +}, function(t, r, e) { + function n(t) { + if (!t) return 0 === t ? t : 0; + if (t = o(t), t === i || t === -i) { + var r = t < 0 ? -1 : 1; + return r * a + } + return t === t ? t : 0 + } + var o = e(125), + i = 1 / 0, + a = 1.7976931348623157e308; + t.exports = n +}, function(t, r, e) { + function n(t) { + if ("number" == typeof t) return t; + if (i(t)) return a; + if (o(t)) { + var r = "function" == typeof t.valueOf ? t.valueOf() : t; + t = o(r) ? r + "" : r + } + if ("string" != typeof t) return 0 === t ? t : +t; + t = t.replace(u, ""); + var e = s.test(t); + return e || f.test(t) ? p(t.slice(2), e ? 2 : 8) : c.test(t) ? a : +t + } + var o = e(33), + i = e(106), + a = NaN, + u = /^\\s+|\\s+$/g, + c = /^[-+]0x[0-9a-f]+$/i, + s = /^0b[01]+$/i, + f = /^0o[0-7]+$/i, + p = parseInt; + t.exports = n +}, function(t, r) { + "use strict"; + + function e() { + return window.crypto.subtle || window.crypto.webkitSubtle + } + r.subtle = e +}, function(t, r) { + "use strict"; + + function e(t, r) { + var n; + return n = Array.isArray(t) ? [] : {}, r.push(t), Object.keys(t).forEach(function(o) { + var i = t[o]; + if ("function" != typeof i) return i && "object" == typeof i ? r.indexOf(t[o]) === -1 ? void(n[o] = e(t[o], r.slice(0))) : void(n[o] = "[Circular]") : void(n[o] = i) + }), "string" == typeof t.name && (n.name = t.name), "string" == typeof t.message && (n.message = t.message), "string" == typeof t.stack && (n.stack = t.stack), n + } + t.exports = function(t) { + return "object" == typeof t ? e(t, []) : "function" == typeof t ? "[Function: " + (t.name || "anonymous") + "]" : t + } +}]); +`; +//# sourceMappingURL=webViewWorkerString.js.map \ No newline at end of file diff --git a/examples/react-native/src/webview-crypto/webViewWorkerString.js.map b/examples/react-native/src/webview-crypto/webViewWorkerString.js.map new file mode 100644 index 00000000..7fb99936 --- /dev/null +++ b/examples/react-native/src/webview-crypto/webViewWorkerString.js.map @@ -0,0 +1 @@ +{"version":3,"file":"webViewWorkerString.js","sourceRoot":"","sources":["../src/webViewWorkerString.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+M9B,CAAC;AAEF,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAq4Dd,CAAC"} \ No newline at end of file diff --git a/examples/style.css b/examples/style.css index 6f7896aa..a81e9b72 100644 --- a/examples/style.css +++ b/examples/style.css @@ -329,4 +329,24 @@ ul, li { 0% {opacity: 1;} 50% {opacity: 0.5;} 100% {opacity: 1;} +} + + +.joy { + width: 100px; + height: 100px; + position: absolute; + background: url(https://cdn.jsdelivr.net/npm/gun/examples/pop.png) no-repeat; + background-position: -2800px 0; + pointer-events: none; + z-index: 999999999; + animation: joy 1s steps(28); +} +@keyframes joy { + 0% { + background-position: 0 0; + } + 100% { + background-position: -2800px 0; + } } \ No newline at end of file diff --git a/gun.js b/gun.js index b56991d5..e01b93ce 100644 --- a/gun.js +++ b/gun.js @@ -1,22 +1,22 @@ ;(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){ - return arg.slice? USE[R(arg)] : function(mod, path){ - arg(mod = {exports: {}}); - USE[R(path)] = mod.exports; - } - function R(p){ - return p.split('/').slice(-1).toString().replace('.js',''); - } - } - if(typeof module !== "undefined"){ var common = module } - /* UNBUILD */ + /* 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: {}}); + USE[R(path)] = mod.exports; + } + function R(p){ + return p.split('/').slice(-1).toString().replace('.js',''); + } + } + if(typeof module !== "undefined"){ var common = module } + /* UNBUILD */ ;USE(function(module){ // Generic javascript utilities. @@ -172,13 +172,13 @@ var u, tag = (this.tag || (this.tag = {}))[tag] || (this.tag[tag] = {tag: tag, to: onto._ = { next: function(arg){ var tmp; - if((tmp = this.to)){ + if((tmp = this.to)){ tmp.next(arg); }} }}); if(arg instanceof Function){ var be = { - off: onto.off || + off: onto.off || (onto.off = function(){ if(this.next === onto._.next){ return !0 } if(this === this.the.last){ @@ -261,14 +261,14 @@ if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. if(text_is(v) // by "text" we mean strings. || bi_is(v) // by "binary" we mean boolean. - || num_is(v)){ // by "number" we mean integers or decimals. + || num_is(v)){ // by "number" we mean integers or decimals. return true; // simple values are valid. } - return Val.rel.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. + return Val.link.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. } Val.link = Val.rel = {_: '#'}; ;(function(){ - Val.rel.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} + Val.link.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object. var o = {}; obj_map(v, map, o); @@ -287,7 +287,7 @@ } } }()); - Val.rel.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it. + Val.link.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it. Type.obj.has._ = '.'; var rel_ = Val.link._, u; var bi_is = Type.bi.is; @@ -384,10 +384,10 @@ State.ify = function(n, k, s, v, soul){ // put a key's state on a node. if(!n || !n[N_]){ // reject if it is not node-like. if(!soul){ // unless they passed a soul - return; + return; } n = Node.soul.ify(n, soul); // then make it so! - } + } var tmp = obj_as(n[N_], State._); // grab the states data. if(u !== k && k !== N_){ if(num_is(s)){ @@ -400,7 +400,7 @@ return n; } State.to = function(from, k, to){ - var val = from[k]; // BUGGY! + var val = (from||{})[k]; if(obj_is(val)){ val = obj_copy(val); } @@ -473,8 +473,9 @@ env.map = env; } if(env.soul){ - at.rel = Val.rel.ify(env.soul); + at.link = Val.link.ify(env.soul); } + env.shell = (as||{}).shell; env.graph = env.graph || {}; env.seen = env.seen || []; env.as = env.as || as; @@ -487,14 +488,16 @@ at.env = env; at.soul = soul; if(Node.ify(at.obj, map, at)){ - //at.rel = at.rel || Val.rel.ify(Node.soul(at.node)); - env.graph[Val.rel.is(at.rel)] = at.node; + at.link = at.link || Val.link.ify(Node.soul(at.node)); + if(at.obj !== env.shell){ + env.graph[Val.link.is(at.link)] = at.node; + } } return at; } function map(v,k,n){ var at = this, env = at.env, is, tmp; - if(Node._ === k && obj_has(v,Val.rel._)){ + if(Node._ === k && obj_has(v,Val.link._)){ return n._; // TODO: Bug? } if(!(is = valid(v,k,n, at,env))){ return } @@ -503,8 +506,8 @@ if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ? at.node._ = obj_copy(v._); } - at.node = Node.soul.ify(at.node, Val.rel.is(at.rel)); - at.rel = at.rel || Val.rel.ify(Node.soul(at.node)); + at.node = Node.soul.ify(at.node, Val.link.is(at.link)); + at.link = at.link || Val.link.ify(Node.soul(at.node)); } if(tmp = env.map){ tmp.call(env.as || {}, v,k,n, at); @@ -523,14 +526,14 @@ } tmp = node(env, {obj: v, path: at.path.concat(k)}); if(!tmp.node){ return } - return tmp.rel; //{'#': Node.soul(tmp.node)}; + return tmp.link; //{'#': Node.soul(tmp.node)}; } function soul(id){ var at = this; - var prev = Val.link.is(at.rel), graph = at.env.graph; - at.rel = at.rel || Val.rel.ify(id); - at.rel[Val.rel._] = id; + var prev = Val.link.is(at.link), graph = at.env.graph; + at.link = at.link || Val.link.ify(id); + at.link[Val.link._] = id; if(at.node && at.node[Node._]){ - at.node[Node._][Val.rel._] = id; + at.node[Node._][Val.link._] = id; } if(obj_has(graph, prev)){ graph[id] = graph[prev]; @@ -570,13 +573,13 @@ } function map(v,k){ var tmp, obj; if(Node._ === k){ - if(obj_empty(v, Val.rel._)){ + if(obj_empty(v, Val.link._)){ return; } this.obj[k] = obj_copy(v); return; } - if(!(tmp = Val.rel.is(v))){ + if(!(tmp = Val.link.is(v))){ this.obj[k] = v; return; } @@ -635,7 +638,7 @@ dup.to = setTimeout(function(){ var now = time_is(); Type.obj.map(dup.s, function(it, id){ - if(opt.age > (now - it.was)){ return } + if(it && opt.age > (now - it.was)){ return } Type.obj.del(dup.s, id); }); dup.to = null; @@ -755,7 +758,7 @@ if(!at){ if(!(cat.opt||empty).super){ ctx.souls[soul] = false; - return; + return; } at = (ctx.$.get(soul)._); } @@ -802,9 +805,25 @@ } Gun.on.get = function(msg, gun){ - var root = gun._, soul = msg.get[_soul], node = root.graph[soul], has = msg.get[_has], tmp; + var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp; var next = root.next || (root.next = {}), at = next[soul]; - if(!node || !at){ return root.on('get', msg) } + if(obj_has(soul, '*')){ // TEMPORARY HACK FOR MARTTI, TESTING + var graph = {}; + Gun.obj.map(root.graph, function(node, s){ + if(Gun.text.match(s, soul)){ + graph[s] = Gun.obj.copy(node); + } + }); + if(!Gun.obj.empty(graph)){ + root.on('in', { + '@': msg['#'], + how: '*', + put: graph, + $: gun + }); + } + } // TEMPORARY HACK FOR MARTTI, TESTING + if(!node){ return root.on('get', msg) } if(has){ if(!obj_has(node, has)){ return root.on('get', msg) } node = Gun.state.to(node, has); @@ -815,7 +834,7 @@ node = Gun.obj.copy(node); } node = Gun.graph.node(node); - tmp = at.ack; + tmp = (at||empty).ack; root.on('in', { '@': msg['#'], how: 'mem', @@ -852,7 +871,7 @@ var list_is = Gun.list.is; var text = Gun.text, text_is = text.is, text_rand = text.random; var obj = Gun.obj, obj_is = obj.is, obj_has = obj.has, obj_to = obj.to, obj_map = obj.map, obj_copy = obj.copy; - var state_lex = Gun.state.lex, _soul = Gun.val.rel._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is; + var state_lex = Gun.state.lex, _soul = Gun.val.link._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is; var empty = {}, u; console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && (console.log.apply(console, arguments) || s) }; @@ -863,8 +882,8 @@ ;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; Gun.log.once("welcome", "Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"); ;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; - - if(typeof window !== "undefined"){ window.Gun = Gun } + + if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } try{ if(typeof common !== "undefined"){ common.exports = Gun } }catch(e){} module.exports = Gun; @@ -940,7 +959,6 @@ function output(msg){ var put, get, at = this.as, back = at.back, root = at.root, tmp; - if(!msg.I){ msg.I = at.$ } if(!msg.$){ msg.$ = at.$ } this.to.next(msg); if(get = msg.get){ @@ -948,7 +966,6 @@ at.on('in', at); return; }*/ - //console.log("out!", at.get, get); if(get['#'] || at.soul){ get['#'] = get['#'] || at.soul; msg['#'] || (msg['#'] = text_rand(9)); @@ -1064,7 +1081,7 @@ //if(tmp[cat.id]){ return } tmp.is = tmp.is || at.put; tmp[cat.id] = at.put || true; - //if(root.stop){ + //if(root.stop){ eve.to.next(msg) //} relate(cat, msg, at, rel); @@ -1077,7 +1094,7 @@ var tmp = (at.root.$.get(rel)._); if(at.has){ from = tmp; - } else + } else if(from.has){ relate(from, msg, from, rel); } @@ -1088,13 +1105,12 @@ not(at, msg); } tmp = from.id? ((at.map || (at.map = {}))[from.id] = at.map[from.id] || {at: from}) : {}; - //console.log("REL?", at.id, at.get, rel === tmp.link, tmp.pass || at.pass); if(rel === tmp.link){ if(!(tmp.pass || at.pass)){ return; } } - if(at.pass){ + if(at.pass){ Gun.obj.map(at.map, function(tmp){ tmp.pass = true }) obj_del(at, 'pass'); } @@ -1117,11 +1133,11 @@ if(!(at = next[key])){ return; } - //if(data && data[_soul] && (tmp = Gun.val.rel.is(data)) && (tmp = (cat.root.$.get(tmp)._)) && obj_has(tmp, 'put')){ + //if(data && data[_soul] && (tmp = Gun.val.link.is(data)) && (tmp = (cat.root.$.get(tmp)._)) && obj_has(tmp, 'put')){ // data = tmp.put; //} if(at.has){ - //if(!(data && data[_soul] && Gun.val.rel.is(data) === Gun.node.soul(at.put))){ + //if(!(data && data[_soul] && Gun.val.link.is(data) === Gun.node.soul(at.put))){ if(u === at.put || !Gun.val.link.is(data)){ at.put = data; } @@ -1130,7 +1146,7 @@ if(tmp = via.$){ tmp = (chain = via.$.get(key))._; if(u === tmp.put || !Gun.val.link.is(data)){ - tmp.put = data; + tmp.put = data; } } at.on('in', { @@ -1144,7 +1160,10 @@ if(!(at.has || at.soul)){ return } var tmp = at.map, root = at.root; at.map = null; - if(at.has){ at.link = null } + if(at.has){ + if(at.dub && at.root.stop){ at.dub = null } + at.link = null; + } //if(!root.now || !root.now[at.id]){ if(!at.pass){ if((!msg['@']) && null === tmp){ return } @@ -1198,13 +1217,12 @@ at.on('in', {get: at.get, put: Gun.val.link.ify(get['#']), $: at.$, '@': msg['@']}); return; } - msg.$ = at.root.$; Gun.on.put(msg, at.root.$); } var empty = {}, u; var obj = Gun.obj, obj_has = obj.has, obj_put = obj.put, obj_del = obj.del, obj_to = obj.to, obj_map = obj.map; var text_rand = Gun.text.random; - var _soul = Gun.val.rel._, node_ = Gun.node._; + var _soul = Gun.val.link._, node_ = Gun.node._; })(USE, './chain'); ;USE(function(module){ @@ -1270,13 +1288,15 @@ return at; } function soul(gun, cb, opt, as){ - var cat = gun._, tmp; - if(tmp = cat.soul){ return cb(tmp, as, cat), gun } - if(tmp = cat.link){ return cb(tmp, as, cat), gun } + var cat = gun._, acks = 0, tmp; + if(tmp = cat.soul || cat.link || cat.dub){ return cb(tmp, as, cat), gun } gun.get(function(msg, ev){ + if(u === msg.put && (tmp = (obj_map(cat.root.opt.peers, function(v,k,t){t(k)})||[]).length) && ++acks < tmp){ + return; + } ev.rid(msg); var at = ((at = msg.$) && at._) || {}; - tmp = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put); + tmp = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub; cb(tmp, as, msg, ev); }, {out: {get: {'.':true}}}); return gun; @@ -1292,9 +1312,9 @@ //else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); - //root.stop && (root.stop.ID = root.stop.ID || Gun.text.random(2)); + //root.stop && (root.stop.id = root.stop.id || Gun.text.random(2)); //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); - if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } + if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? if((tmp = data) && tmp[rel._] && (tmp = rel.is(tmp))){ tmp = ((msg.$$ = at.root.gun.get(tmp))._); @@ -1302,9 +1322,10 @@ msg = obj_to(msg, {put: data = tmp.put}); } } - if((tmp = root.mum) && at.id){ - if(tmp[at.id]){ return } - if(u !== data && !rel.is(data)){ tmp[at.id] = true; } + if((tmp = root.mum) && at.id){ // TODO: can we delete mum entirely now? + var id = at.id + (eve.id || (eve.id = Gun.text.random(9))); + if(tmp[id]){ return } + if(u !== data && !rel.is(data)){ tmp[id] = true; } } as.use(msg, eve); if(eve.stun){ @@ -1326,7 +1347,7 @@ //obj.del(map, at); // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. return; } - var obj = Gun.obj, obj_has = obj.has, obj_to = Gun.obj.to; + var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_to = Gun.obj.to; var num_is = Gun.num.is; var rel = Gun.val.link, node_soul = Gun.node.soul, node_ = Gun.node._; var empty = {}, u; @@ -1364,7 +1385,7 @@ }); return gun; } - as.$ = gun = root.get(as.soul); + as.$ = root.get(as.soul); as.ref = as.$; ify(as); return gun; @@ -1374,10 +1395,11 @@ if(!soul && Gun.val.is(msg.put)){ return Gun.log("The reference you are saving is a", typeof msg.put, '"'+ msg.put +'", not a node (object)!'); } - gun.put(Gun.val.rel.ify(soul), cb, as); + gun.put(Gun.val.link.ify(soul), cb, as); }, true); return gun; } + if(at.has && (tmp = Gun.val.link.is(data))){ at.dub = tmp } as.ref = as.ref || (root._ === (tmp = at.back))? gun : tmp.$; if(as.ref._.soul && Gun.val.is(as.data) && at.get){ as.data = obj_put({}, at.get, as.data); @@ -1473,7 +1495,7 @@ function soul(id, as, msg, eve){ var as = as.as, cat = as.at; as = as.as; var at = ((msg || {}).$ || {})._ || {}; - id = at.dub = at.dub || id || Gun.node.soul(cat.obj) || Gun.node.soul(msg.put || at.put) || Gun.val.rel.is(msg.put || at.put) || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous? + id = at.dub = at.dub || id || Gun.node.soul(cat.obj) || Gun.node.soul(msg.put || at.put) || Gun.val.link.is(msg.put || at.put) || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous? if(eve){ eve.stun = true } if(!id){ // polyfill async uuid for SEA at.via.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback @@ -1519,8 +1541,8 @@ as.data = obj_put({}, at.get, as.data); }); } - tmp = tmp || at.get; - at = (at.root.$.get(tmp)._); + tmp = tmp || at.soul || at.link || at.dub;// || at.get; + at = tmp? (at.root.$.get(tmp)._) : at; as.soul = tmp; data = as.data; } @@ -1532,7 +1554,7 @@ if(node_ == at.get){ as.soul = (at.put||empty)['#'] || at.dub; } - as.soul = as.soul || at.soul || at.soul || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); + as.soul = as.soul || at.soul || at.link || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); } if(!as.soul){ // polyfill async uuid for SEA as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback @@ -1638,18 +1660,19 @@ var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp; if(tmp = msg.$$){ link = tmp = (msg.$$._); - if(u === tmp.put){ - return; + if(u !== link.put){ + data = link.put; } - data = tmp.put; } if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) } - if(!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))){ + if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))) + || (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (!to && (link||at).ack <= tmp))){ tmp = (eve.wait = {})[at.id] = setTimeout(function(){ val.call({as:opt}, msg, eve, tmp || 1); }, opt.wait || 99); return; } + if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) } eve.rid(msg); opt.ok.call(gun || opt.$, data, msg.get); } @@ -1711,10 +1734,8 @@ gun.map().on(function(data, key, at, ev){ var next = (cb||noop).call(this, data, key, at, ev); if(u === next){ return } - if(data === next || Gun.is(next)){ - chain._.on('in', next._); - return; - } + if(data === next){ return chain._.on('in', at) } + if(Gun.is(next)){ return chain._.on('in', next._) } chain._.on('in', {get: key, put: next}); }); return chain; @@ -1739,13 +1760,12 @@ var gun = this, soul; cb = cb || function(){}; opt = opt || {}; opt.item = opt.item || item; - if(soul = Gun.node.soul(item)){ return gun.set(gun.back(-1).get(soul), cb, opt) } + if(soul = Gun.node.soul(item)){ item = Gun.obj.put({}, soul, Gun.val.link.ify(soul)) } if(!Gun.is(item)){ - var id = gun.back('opt.uuid')(); - if(id && Gun.obj.is(item)){ - return gun.set(gun._.root.$.put(item, id), cb, opt); + if(Gun.obj.is(item)){; + item = gun.back(-1).get(soul = soul || Gun.node.soul(item) || gun.back('opt.uuid')()).put(item); } - return gun.get((Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt); + return gun.get(soul || (Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt); } item.get(function(soul, o, msg){ if(!soul){ return cb.call(gun, {err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}) } @@ -1758,10 +1778,12 @@ ;USE(function(module){ if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs? - var root, noop = function(){}, u; - if(typeof window !== 'undefined'){ root = window } - var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop}; - + var root, noop = function(){}, store, u; + try{store = (Gun.window||noop).localStorage}catch(e){} + if(!store){ + console.log("Warning: No localStorage exists to persist data to!"); + store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; + } /* NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design! If you update anything here, consider updating the other adapters as well. @@ -1772,29 +1794,26 @@ // See the next 'opt' code below for actual saving of data. var ev = this.to, opt = root.opt; if(root.once){ return ev.next(root) } - if(false === opt.localStorage){ return ev.next(root) } - opt.file = opt.file || 'gun/'; - var gap = Gun.obj.ify(store.getItem('gap/'+opt.file)) || {}; + //if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless! + opt.prefix = opt.file || 'gun/'; + var gap = Gun.obj.ify(store.getItem('gap/'+opt.prefix)) || {}; var empty = Gun.obj.empty, id, to, go; // add re-sync command. if(!empty(gap)){ - root.on('localStorage', function(disk){ - this.off(); - var send = {} - Gun.obj.map(gap, function(node, soul){ - Gun.obj.map(node, function(val, key){ - send[soul] = Gun.state.to(disk[soul], key, send[soul]); - }); + var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}, send = {}; + Gun.obj.map(gap, function(node, soul){ + Gun.obj.map(node, function(val, key){ + send[soul] = Gun.state.to(disk[soul], key, send[soul]); }); - setTimeout(function(){ - root.on('out', {put: send, '#': root.ask(ack), I: root.$}); - },10); }); + setTimeout(function(){ + root.on('out', {put: send, '#': root.ask(ack)}); + },1); } root.on('out', function(msg){ if(msg.lS){ return } - if(msg.I && msg.put && !msg['@'] && !empty(opt.peers)){ + if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){ id = msg['#']; Gun.graph.is(msg.put, null, map); if(!to){ to = setTimeout(flush, opt.wait || 1) } @@ -1827,7 +1846,7 @@ var flush = function(){ clearTimeout(to); to = false; - try{store.setItem('gap/'+opt.file, JSON.stringify(gap)); + try{store.setItem('gap/'+opt.prefix, JSON.stringify(gap)); }catch(e){ Gun.log(err = e || "localStorage failure") } } }); @@ -1837,12 +1856,12 @@ var opt = root.opt; if(root.once){ return } if(false === opt.localStorage){ return } - opt.file = opt.file || opt.prefix || 'gun/'; // support old option name. + opt.prefix = opt.file || 'gun/'; var graph = root.graph, acks = {}, count = 0, to; - var disk = Gun.obj.ify(store.getItem(opt.file)) || {}; + var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}; var lS = function(){}, u; root.on('localStorage', disk); // NON-STANDARD EVENT! - + root.on('put', function(at){ this.to.next(at); Gun.graph.is(at.put, null, map); @@ -1870,7 +1889,7 @@ return; // Hmm, what if we have peers but we are disconnected? } //console.log("lS get", lex, data); - root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.I}); + root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$}); }; Gun.debug? setTimeout(to,1) : to(); }); @@ -1887,10 +1906,10 @@ var ack = acks; acks = {}; if(data){ disk = data } - try{store.setItem(opt.file, JSON.stringify(disk)); - }catch(e){ - Gun.log(err = e || "localStorage failure"); - root.on('localStorage:error', {err: err, file: opt.file, flush: disk, retry: flush}); + try{store.setItem(opt.prefix, JSON.stringify(disk)); + }catch(e){ + Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."); + root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush}); } if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers. Gun.obj.map(ack, function(yes, id){ @@ -1909,6 +1928,10 @@ function Mesh(ctx){ var mesh = function(){}; + var opt = ctx.opt || {}; + opt.log = opt.log || console.log; + opt.gap = opt.gap || opt.wait || 1; + opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB. mesh.out = function(msg){ var tmp; if(this.to){ this.to.next(msg) } @@ -1916,20 +1939,28 @@ if((tmp = msg['@']) && (tmp = ctx.dup.s[tmp]) && (tmp = tmp.it) - && tmp.mesh){ - mesh.say(msg, tmp.mesh.via); + && tmp._){ + mesh.say(msg, (tmp._).via, 1); tmp['##'] = msg['##']; return; } // add hook for AXE? + //if (Gun.AXE && opt && opt.super) { Gun.AXE.say(msg, mesh.say, this); return; } // rogowski mesh.say(msg); } + ctx.on('create', function(root){ + root.opt.pid = root.opt.pid || Type.text.random(9); + this.to.next(root); + ctx.on('out', mesh.out); + }); + mesh.hear = function(raw, peer){ if(!raw){ return } var dup = ctx.dup, id, hash, msg, tmp = raw[0]; + if(opt.pack <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) } try{msg = JSON.parse(raw); - }catch(e){} + }catch(e){opt.log('DAM JSON parse error', e)} if('{' === tmp){ if(!msg){ return } if(dup.check(id = msg['#'])){ return } @@ -1941,12 +1972,18 @@ (tmp = dup.s)[hash] = tmp[id]; } } - (msg.mesh = function(){}).via = peer; + (msg._ = function(){}).via = peer; if((tmp = msg['><'])){ - msg.mesh.to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); + (msg._).to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); + } + if(msg.dam){ + if(tmp = mesh.hear[msg.dam]){ + tmp(msg, peer, ctx); + } + return; } ctx.on('in', msg); - + return; } else if('[' === tmp){ @@ -1961,21 +1998,21 @@ } ;(function(){ - mesh.say = function(msg, peer){ + mesh.say = function(msg, peer, o){ /* TODO: Plenty of performance optimizations that can be made just based off of ordering, and reducing function calls for cached writes. */ if(!peer){ - Type.obj.map(ctx.opt.peers, function(peer){ + Type.obj.map(opt.peers, function(peer){ mesh.say(msg, peer); }); return; } - var tmp, wire = peer.wire || ((ctx.opt.wire) && ctx.opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen! + var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen! if(!wire){ return } - msh = msg.mesh || empty; + msh = (msg._) || empty; if(peer === msh.via){ return } if(!(raw = msh.raw)){ raw = mesh.raw(msg) } if((tmp = msg['@']) @@ -1985,31 +2022,32 @@ return; // TODO: this still needs to be tested in the browser! } } - if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id])){ return } // TODO: still needs to be tested + if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id]) && !o){ return } // TODO: still needs to be tested if(peer.batch){ - peer.batch.push(raw); - return; + peer.tail = (peer.tail || 0) + raw.length; + if(peer.tail <= opt.pack){ + peer.batch.push(raw); + return; + } + flush(peer); } peer.batch = []; - setTimeout(function(){ - var tmp = peer.batch; - if(!tmp){ return } - peer.batch = null; - if(!tmp.length){ return } - send(JSON.stringify(tmp), peer); - }, ctx.opt.gap || ctx.opt.wait || 1); + setTimeout(function(){flush(peer)}, opt.gap); send(raw, peer); } - + function flush(peer){ + var tmp = peer.batch; + if(!tmp){ return } + peer.batch = peer.tail = null; + if(!tmp.length){ return } + try{send(JSON.stringify(tmp), peer); + }catch(e){opt.log('DAM JSON stringify error', e)} + } function send(raw, peer){ var wire = peer.wire; try{ if(wire.send){ - if(wire.readyState === wire.OPEN){ - wire.send(raw); - } else { - (peer.queue = peer.queue || []).push(raw); - } + wire.send(raw); } else if(peer.say){ peer.say(raw); @@ -2020,12 +2058,12 @@ } }()); - + ;(function(){ mesh.raw = function(msg){ if(!msg){ return '' } - var dup = ctx.dup, msh = msg.mesh || {}, put, hash, tmp; + var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp; if(tmp = msh.raw){ return tmp } if(typeof msg === 'string'){ return msg } if(msg['@'] && (tmp = msg.put)){ @@ -2038,12 +2076,14 @@ msg['#'] = hash || msg['#']; if(put){ (msg = Type.obj.to(msg)).put = _ } } - var i = 0, to = []; Type.obj.map(ctx.opt.peers, function(p){ + var i = 0, to = []; Type.obj.map(opt.peers, function(p){ to.push(p.url || p.id); if(++i > 9){ return true } // limit server, fast fix, improve later! }); msg['><'] = to.join(); var raw = $(msg); if(u !== put){ - raw = raw.replace('"'+ _ +'"', put); + tmp = raw.indexOf(_, raw.indexOf('put')); + raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1); + //raw = raw.replace('"'+ _ +'"', put); // https://github.com/amark/gun/wiki/@$$ Heisenbug } if(msh){ msh.raw = raw; @@ -2064,18 +2104,36 @@ function map(k){ this.to[k] = this.on[k]; } - var $ = JSON.stringify, _ = ':])([:' + var $ = JSON.stringify, _ = ':])([:'; }()); mesh.hi = function(peer){ - ctx.on('hi', peer); - var queue = peer.queue; - peer.queue = []; - Type.obj.map(queue, function(msg){ + var tmp = peer.wire || {}; + if(peer.id || peer.url){ + opt.peers[peer.url || peer.id] = peer; + Type.obj.del(opt.peers, tmp.id); + } else { + tmp = tmp.id = tmp.id || Type.text.random(9); + mesh.say({dam: '?'}, opt.peers[tmp] = peer); + } + if(!tmp.hied){ ctx.on(tmp.hied = 'hi', peer) } + tmp = peer.queue; peer.queue = []; + Type.obj.map(tmp, function(msg){ mesh.say(msg, peer); }); } + mesh.bye = function(peer){ + Type.obj.del(opt.peers, peer.id); // assume if peer.url then reconnect + ctx.on('bye', peer); + } + + mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } + mesh.hear['?'] = function(msg, peer){ + if(!msg.pid){ return mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer) } + peer.id = peer.id || msg.pid; + mesh.hi(peer); + } return mesh; } @@ -2119,18 +2177,15 @@ opt.WebSocket = websocket; var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); - root.on('create', function(at){ - this.to.next(at); - root.on('out', mesh.out); - }); - opt.wire = opt.wire || open; - function open(peer){ - if(!peer || !peer.url){ return } + var wire = opt.wire; + opt.wire = open; + function open(peer){ try{ + if(!peer || !peer.url){ return wire && wire(peer) } var url = peer.url.replace('http', 'ws'); var wire = peer.wire = new opt.WebSocket(url); wire.onclose = function(){ - root.on('bye', peer); + opt.mesh.bye(peer); reconnect(peer); }; wire.onerror = function(error){ @@ -2141,15 +2196,14 @@ } }; wire.onopen = function(){ - mesh.hi(peer); + opt.mesh.hi(peer); } wire.onmessage = function(msg){ if(!msg){ return } - env.inLength = (env.inLength || 0) + (msg.data || msg).length; // TEMPORARY, NON-STANDARD, FOR DEBUG - mesh.hear(msg.data || msg, peer); + opt.mesh.hear(msg.data || msg, peer); }; return wire; - } + }catch(e){}} function reconnect(peer){ clearTimeout(peer.defer); diff --git a/gun.min.js b/gun.min.js index ba252adf..2a6b24fd 100644 --- a/gun.min.js +++ b/gun.min.js @@ -1 +1 @@ -!function(){var t;"undefined"!=typeof window&&(t=window),"undefined"!=typeof global&&(t=global);var b=(t=t||{}).console||{log:function(){}};function _(o){return o.slice?_[e(o)]:function(t,n){o(t={exports:{}}),_[e(n)]=t.exports};function e(t){return t.split("/").slice(-1).toString().replace(".js","")}}if("undefined"!=typeof module)var f=module;_(function(t){var p={fn:{is:function(t){return!!t&&"function"==typeof t}}};p.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},p.num={is:function(t){return!d(t)&&(0<=t-parseFloat(t)+1||1/0===t||-1/0===t)}},p.text={is:function(t){return"string"==typeof t}},p.text.ify=function(t){return p.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},p.text.random=function(t,n){var o="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";0")){if(!(n>t[">"]))return!1;o=!0}if(p.obj.has(t,"<")){if(!(n",s.drift=0,s.is=function(t,n,o){var e=n&&t&&t[m]&&t[m][s._]||o;if(e)return g(e=e[n])?e:-1/0},s.lex=function(){return s().toString(36).replace(".","")},s.ify=function(t,n,o,e,i){if(!t||!t[m]){if(!i)return;t=a.soul.ify(t,i)}var r=c(t[m],s._);return void 0!==n&&n!==m&&(g(o)&&(r[n]=o),void 0!==e&&(t[n]=e)),t},s.to=function(t,n,o){var e=t[n];return p(e)&&(e=d(e)),s.ify(o,n,s.is(t,n),e,a.soul(t))},function(){function u(t,n){m!==n&&s.ify(this.o,n,this.s)}s.map=function(i,r,a){var t=p(t=i||r)?t:null;return i=v(i=i||r)?i:null,t&&!i?(r=g(r)?r:s(),t[m]=t[m]||{},h(t,u,{o:t,s:r}),t):(a=a||p(r)?r:void 0,r=g(r)?r:s(),function(t,n,o,e){if(!i)return u.call({o:o,s:r},t,n),t;i.call(a||this||{},t,n,o,e),l(o,n)&&void 0===o[n]||u.call({o:o,s:r},t,n)})}}();var f=n.obj,c=f.as,l=f.has,p=f.is,h=f.map,d=f.copy,g=n.num.is,v=n.fn.is,m=a._;t.exports=s})(_,"./state"),_(function(t){var a=_("./type"),f=_("./val"),c=_("./node"),r={};!function(){function i(t,n){if(!t||n!==c.soul(t)||!c.is(t,this.fn,this.as))return!0;this.cb&&(o.n=t,o.as=this.as,this.cb.call(o.as,t,n,o))}function o(t){t&&c.is(o.n,t,o.as)}r.is=function(t,n,o,e){return!(!t||!l(t)||u(t))&&!s(t,i,{cb:n,fn:o,as:e})}}(),function(){function u(t,n){var o;return(o=function(t,n){var o,e=t.seen,i=e.length;for(;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}(t,n))?o:(n.env=t,n.soul=i,c.ify(n.obj,e,n)&&(t.graph[f.rel.is(n.rel)]=n.node),n)}function e(t,n,o){var e,i,r=this,a=r.env;if(c._===n&&h(t,f.rel._))return o._;if(e=s(t,n,o,r,a)){if(n||(r.node=r.node||o||{},h(t,c._)&&c.soul(t)&&(r.node._=d(t._)),r.node=c.soul.ify(r.node,f.rel.is(r.rel)),r.rel=r.rel||f.rel.ify(c.soul(r.node))),(i=a.map)&&(i.call(a.as||{},t,n,o,r),h(o,n))){if(void 0===(t=o[n]))return void p(o,n);if(!(e=s(t,n,o,r,a)))return}if(!n)return r.node;if(!0===e)return t;if((i=u(a,{obj:t,path:r.path.concat(n)})).node)return i.rel}}function i(t){var n=this,o=f.link.is(n.rel),e=n.env.graph;n.rel=n.rel||f.rel.ify(t),n.rel[f.rel._]=t,n.node&&n.node[c._]&&(n.node[c._][f.rel._]=t),h(e,o)&&(e[t]=e[o],p(e,o))}function s(t,n,o,e,i){var r;return!!f.is(t)||(l(t)?1:(r=i.invalid)?s(t=r.call(i.as||{},t,n,o),n,o,e,i):(i.err="Invalid value at '"+e.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}r.ify=function(t,n,o){var e={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(e.rel=f.rel.ify(n.soul)),n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||o,u(n,e),n.root=e.node,n.graph}}(),r.node=function(t){var n=c.soul(t);if(n)return o({},n,t)},function(){function i(t,n){var o,e;if(c._!==n)(o=f.rel.is(t))?(e=this.opt.seen[o])?this.obj[n]=e:this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt):this.obj[n]=t;else{if(u(t,f.rel._))return;this.obj[n]=d(t)}}r.to=function(t,n,o){if(t){var e={};return o=o||{seen:{}},s(t[n],i,{obj:e,graph:t,opt:o}),e}}}();a.fn.is;var n=a.obj,l=n.is,p=n.del,h=n.has,u=n.empty,o=n.put,s=n.map,d=n.copy;t.exports=r})(_,"./graph"),_(function(t){_("./onto"),t.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var o=t["#"]||t,e=(this.tag||empty)[o];if(!e)return;return e=this.on(o,n),clearTimeout(e.err),!0}o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet.",lack:!0}),i.off()},(this.opt||{}).lack||9e3),o}}})(_,"./ask"),_(function(t){var r=_("./type");var a=r.time.is;t.exports=function(e){var i={s:{}};return e=e||{max:1e3,age:9e3},i.check=function(t){var n;return!!(n=i.s[t])&&(n.pass?n.pass=!1:i.track(t))},i.track=function(t,n){var o=i.s[t]||(i.s[t]={});return o.was=a(),n&&(o.pass=!0),i.to||(i.to=setTimeout(function(){var o=a();r.obj.map(i.s,function(t,n){e.age>o-t.was||r.obj.del(i.s,n)}),i.to=null},e.age+9)),o},i}})(_,"./dup"),_(function(t){function c(t){return t instanceof c?(this._={gun:this,$:this}).$:this instanceof c?c.create(this._={gun:this,$:this,opt:t}):new c(t)}c.is=function(t){return t instanceof c||t&&t._&&t===t._.$||!1},c.version=.9,(c.chain=c.prototype).toJSON=function(){};var n=_("./type");n.obj.to(n,c),c.HAM=_("./HAM"),c.val=_("./val"),c.node=_("./node"),c.state=_("./state"),c.graph=_("./graph"),c.on=_("./onto"),c.ask=_("./ask"),c.dup=_("./dup"),function(){function a(t){var n,o,e=this.as,i=e.at||e,r=i.$;(o=t["#"])||(o=t["#"]=u(9)),(n=i.dup).check(o)?e.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(o),i.ask(t["@"],t)||(t.get&&c.on.get(t,r),t.put&&c.on.put(t,r)),this.to.next(t),e.out||(t.out=a,i.on("out",t)))}c.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||c.on,t.ask=t.ask||c.ask,t.dup=t.dup||c.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",a,t),t.on("out",a,{at:t,out:a}),c.on("create",t),t.on("create",t)),t.once=1,n}}(),function(){function i(t,n,o,e){var i=this,r=c.state.is(o,n);if(!r)return i.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=i.graph[e]||v,u=c.state.is(a,n,!0),s=a[n],f=c.HAM(i.machine,r,u,t,s);f.incoming?(i.put[e]=c.state.to(o,n,i.put[e]),(i.diff||(i.diff={}))[e]=c.state.to(o,n,i.diff[e]),i.souls[e]=!0):f.defer&&(i.defer=r<(i.defer||1/0)?r:i.defer)}function r(t,n){var o=this,e=o.$._,i=(e.next||v)[n];if(!i){if(!(e.opt||v).super)return void(o.souls[n]=!1);i=o.$.get(n)._}var r=o.map[n]={put:t,get:n,$:i.$},a={ctx:o,msg:r};o.async=!!e.tag.node,o.ack&&(r["@"]=o.ack),h(t,u,a),o.async&&(o.and||e.on("node",function(t){this.to.next(t),t===o.map[t.get]&&(o.souls[t.get]=!1,h(t.put,s,t),h(o.souls,function(t){if(t)return t})||o.c||(o.c=1,this.off(),h(o.map,f,o)))}),o.and=!0,e.on("node",r))}function u(t,n){var o=this.ctx,e=o.graph,i=this.msg,r=i.get,a=i.put,u=i.$._;e[r]=c.state.to(a,n,e[r]),o.async||(u.put=c.state.to(a,n,u.put))}function s(t,n){var o=this.put,e=this.$._;e.put=c.state.to(o,n,e.put)}function f(t,n){t.$&&(this.cat.stop=this.stop,t.$._.on("in",t),this.cat.stop=null)}c.on.put=function(t,n){var o=n._,e={$:n,graph:o.graph,put:{},map:{},souls:{},machine:c.state(),ack:t["@"],cat:o,stop:{}};if(c.graph.is(t.put,null,i,e)||(e.err="Error: Invalid graph!"),e.err)return o.on("in",{"@":t["#"],err:c.log(e.err)});h(e.put,r,e),e.async||h(e.map,f,e),void 0!==e.defer&&setTimeout(function(){c.on.put(t,n)},e.defer-e.machine),e.diff&&o.on("put",p(t,{put:e.diff}))},c.on.get=function(t,n){var o=n._,e=t.get[d],i=o.graph[e],r=t.get[g],a=(o.next||(o.next={}))[e];if(!i||!a)return o.on("get",t);if(r){if(!l(i,r))return o.on("get",t);i=c.state.to(i,r)}else i=c.obj.copy(i);i=c.graph.node(i),a.ack,o.on("in",{"@":t["#"],how:"mem",put:i,$:n}),o.on("get",t)}}(),c.chain.opt=function(t){t=t||{};var n=this._,o=t.peers||t;return a(t)||(t={}),a(n.opt)||(n.opt=t),i(o)&&(o=[o]),e(o)&&(o=h(o,function(t,n,o){o(t,{url:t})}),a(n.opt.peers)||(n.opt.peers={}),n.opt.peers=p(o,n.opt.peers)),n.opt.peers=n.opt.peers||{},p(t,n.opt),c.on("opt",n),n.opt.uuid=n.opt.uuid||function(){return s()+u(12)},this};var e=c.list.is,o=c.text,i=o.is,u=o.random,r=c.obj,a=r.is,l=r.has,p=r.to,h=r.map,s=(r.copy,c.state.lex),d=c.val.rel._,g=".",v=(c.node._,c.val.link.is,{});b.debug=function(t,n){return b.debug.i&&t===b.debug.i&&b.debug.i++&&(b.log.apply(b,arguments)||n)},(c.log=function(){return!c.log.off&&b.log.apply(b,arguments),[].slice.call(arguments).join(" ")}).once=function(t,n,o){return(o=c.log.once)[t]=o[t]||0,o[t]++||c.log(n)},c.log.once("welcome","Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"),"undefined"!=typeof window&&(window.Gun=c);try{void 0!==f&&(f.exports=c)}catch(t){}t.exports=c})(_,"./root"),_(function(t){var u=_("./root");u.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root.$;if(1===t)return(this._.back||this._).$;var o=this._;if("string"==typeof t&&(t=t.split(".")),t instanceof Array){for(var e=0,i=t.length,r=o;e .once, apologies unexpected."),this.once(t,n)},a.chain.once=function(t,n){var o=this,e=o._,i=e.put;if(0=(a.batch||1e3))return f();e||(e=setTimeout(f,a.wait||1))}),r.on("get",function(n){this.to.next(n);var o,e,i=n.get;function t(){if(i&&(o=i["#"])){var t=i["."];(e=s[o]||void 0)&&t&&(e=Gun.state.to(e,t)),(e||Gun.obj.empty(a.peers))&&r.on("in",{"@":n["#"],put:Gun.graph.node(e),how:"lS",lS:n.I})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,o,e){s[e]=Gun.state.to(o,n,s[e])},f=function(t){var o;u=0,clearTimeout(e),e=!1;var n=i;i={},t&&(s=t);try{c.setItem(a.file,JSON.stringify(s))}catch(t){Gun.log(o=t||"localStorage failure"),r.on("localStorage:error",{err:o,file:a.file,flush:s,retry:f})}(o||Gun.obj.empty(a.peers))&&Gun.obj.map(n,function(t,n){r.on("in",{"@":n,err:o,ok:0})})}}})}})(_,"./adapters/localStorage"),_(function(t){var d=_("../type");function o(p){var h=function(){};return h.out=function(t){var n;if(this.to&&this.to.next(t),(n=t["@"])&&(n=p.dup.s[n])&&(n=n.it)&&n.mesh)return h.say(t,n.mesh.via),void(n["##"]=t["##"]);h.say(t)},h.hear=function(t,n){if(t){var o,e,i,r=p.dup,a=t[0];try{i=JSON.parse(t)}catch(t){}if("{"===a){if(!i)return;if(r.check(o=i["#"]))return;if((a=(r.track(o,!0).it=i)["@"])&&i.put&&(a+=e=i["##"]||(i["##"]=h.hash(i)))!=o){if(r.check(a))return;(a=r.s)[e]=a[o]}return(i.mesh=function(){}).via=n,(a=i["><"])&&(i.mesh.to=d.obj.map(a.split(","),function(t,n,o){o(t,!0)})),void p.on("in",i)}if("["!==a);else{if(!i)return;for(var u,s=0;u=i[s++];)h.hear(u,n)}}},function(){function r(n,o){var t=o.wire;try{t.send?t.readyState===t.OPEN?t.send(n):(o.queue=o.queue||[]).push(n):o.say&&o.say(n)}catch(t){(o.queue=o.queue||[]).push(n)}}h.say=function(n,o){var t,e,i;o?(o.wire||p.opt.wire&&p.opt.wire(o))&&(e=n.mesh||a,o!==e.via&&((i=e.raw)||(i=h.raw(n)),(t=n["@"])&&(t=p.dup.s[t])&&(t=t.it)&&t.get&&t["##"]&&t["##"]===n["##"]||(t=e.to)&&(t[o.url]||t[o.id])||(o.batch?o.batch.push(i):(o.batch=[],setTimeout(function(){var t=o.batch;t&&(o.batch=null,t.length&&r(JSON.stringify(t),o))},p.opt.gap||p.opt.wait||1),r(i,o))))):d.obj.map(p.opt.peers,function(t){h.say(n,t)})}}(),function(){function f(t,n){var o;return n instanceof Object?(d.obj.map(Object.keys(n).sort(),e,{to:o={},on:n}),o):n}function e(t){this.to[t]=this.on[t]}h.raw=function(t){if(!t)return"";var n,o,e,i=p.dup,r=t.mesh||{};if(e=r.raw)return e;if("string"==typeof t)return t;t["@"]&&(e=t.put)&&((o=t["##"])||(n=c(e,f)||"",o=h.hash(t,n),t["##"]=o),(e=i.s)[o=t["@"]+o]=e[t["#"]],t["#"]=o||t["#"],n&&((t=d.obj.to(t)).put=l));var a=0,u=[];d.obj.map(p.opt.peers,function(t){if(u.push(t.url||t.id),9<++a)return!0}),t["><"]=u.join();var s=c(t);return g!==n&&(s=s.replace('"'+l+'"',n)),r&&(r.raw=s),s},h.hash=function(t,n){return o.hash(n||c(t.put,f)||"")||t["#"]||d.text.random(9)};var c=JSON.stringify,l=":])([:"}(),h.hi=function(n){p.on("hi",n);var t=n.queue;n.queue=[],d.obj.map(t,function(t){h.say(t,n)})},h}o.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o=0,e=t.length;o")){if(!(n>t[">"]))return!1;o=!0}if(p.obj.has(t,"<")){if(!(n",s.drift=0,s.is=function(t,n,o){var e=n&&t&&t[m]&&t[m][s._]||o;if(e)return g(e=e[n])?e:-1/0},s.lex=function(){return s().toString(36).replace(".","")},s.ify=function(t,n,o,e,i){if(!t||!t[m]){if(!i)return;t=a.soul.ify(t,i)}var r=c(t[m],s._);return void 0!==n&&n!==m&&(g(o)&&(r[n]=o),void 0!==e&&(t[n]=e)),t},s.to=function(t,n,o){var e=(t||{})[n];return p(e)&&(e=d(e)),s.ify(o,n,s.is(t,n),e,a.soul(t))},function(){function u(t,n){m!==n&&s.ify(this.o,n,this.s)}s.map=function(i,r,a){var t=p(t=i||r)?t:null;return i=v(i=i||r)?i:null,t&&!i?(r=g(r)?r:s(),t[m]=t[m]||{},h(t,u,{o:t,s:r}),t):(a=a||p(r)?r:void 0,r=g(r)?r:s(),function(t,n,o,e){if(!i)return u.call({o:o,s:r},t,n),t;i.call(a||this||{},t,n,o,e),l(o,n)&&void 0===o[n]||u.call({o:o,s:r},t,n)})}}();var f=n.obj,c=f.as,l=f.has,p=f.is,h=f.map,d=f.copy,g=n.num.is,v=n.fn.is,m=a._;t.exports=s})(_,"./state"),_(function(t){var a=_("./type"),f=_("./val"),c=_("./node"),r={};!function(){function i(t,n){if(!t||n!==c.soul(t)||!c.is(t,this.fn,this.as))return!0;this.cb&&(o.n=t,o.as=this.as,this.cb.call(o.as,t,n,o))}function o(t){t&&c.is(o.n,t,o.as)}r.is=function(t,n,o,e){return!(!t||!l(t)||u(t))&&!s(t,i,{cb:n,fn:o,as:e})}}(),function(){function u(t,n){var o;return(o=function(t,n){var o,e=t.seen,i=e.length;for(;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}(t,n))?o:(n.env=t,n.soul=i,c.ify(n.obj,e,n)&&(n.rel=n.rel||f.rel.ify(c.soul(n.node)),n.obj!==t.shell&&(t.graph[f.rel.is(n.rel)]=n.node)),n)}function e(t,n,o){var e,i,r=this,a=r.env;if(c._===n&&h(t,f.rel._))return o._;if(e=s(t,n,o,r,a)){if(n||(r.node=r.node||o||{},h(t,c._)&&c.soul(t)&&(r.node._=d(t._)),r.node=c.soul.ify(r.node,f.rel.is(r.rel)),r.rel=r.rel||f.rel.ify(c.soul(r.node))),(i=a.map)&&(i.call(a.as||{},t,n,o,r),h(o,n))){if(void 0===(t=o[n]))return void p(o,n);if(!(e=s(t,n,o,r,a)))return}if(!n)return r.node;if(!0===e)return t;if((i=u(a,{obj:t,path:r.path.concat(n)})).node)return i.rel}}function i(t){var n=this,o=f.link.is(n.rel),e=n.env.graph;n.rel=n.rel||f.rel.ify(t),n.rel[f.rel._]=t,n.node&&n.node[c._]&&(n.node[c._][f.rel._]=t),h(e,o)&&(e[t]=e[o],p(e,o))}function s(t,n,o,e,i){var r;return!!f.is(t)||(l(t)?1:(r=i.invalid)?s(t=r.call(i.as||{},t,n,o),n,o,e,i):(i.err="Invalid value at '"+e.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}r.ify=function(t,n,o){var e={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(e.rel=f.rel.ify(n.soul)),n.shell=(o||{}).shell,n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||o,u(n,e),n.root=e.node,n.graph}}(),r.node=function(t){var n=c.soul(t);if(n)return o({},n,t)},function(){function i(t,n){var o,e;if(c._!==n)(o=f.rel.is(t))?(e=this.opt.seen[o])?this.obj[n]=e:this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt):this.obj[n]=t;else{if(u(t,f.rel._))return;this.obj[n]=d(t)}}r.to=function(t,n,o){if(t){var e={};return o=o||{seen:{}},s(t[n],i,{obj:e,graph:t,opt:o}),e}}}();a.fn.is;var n=a.obj,l=n.is,p=n.del,h=n.has,u=n.empty,o=n.put,s=n.map,d=n.copy;t.exports=r})(_,"./graph"),_(function(t){_("./onto"),t.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var o=t["#"]||t,e=(this.tag||empty)[o];if(!e)return;return e=this.on(o,n),clearTimeout(e.err),!0}o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet.",lack:!0}),i.off()},(this.opt||{}).lack||9e3),o}}})(_,"./ask"),_(function(t){var r=_("./type");var a=r.time.is;t.exports=function(e){var i={s:{}};return e=e||{max:1e3,age:9e3},i.check=function(t){var n;return!!(n=i.s[t])&&(n.pass?n.pass=!1:i.track(t))},i.track=function(t,n){var o=i.s[t]||(i.s[t]={});return o.was=a(),n&&(o.pass=!0),i.to||(i.to=setTimeout(function(){var o=a();r.obj.map(i.s,function(t,n){t&&e.age>o-t.was||r.obj.del(i.s,n)}),i.to=null},e.age+9)),o},i}})(_,"./dup"),_(function(t){function c(t){return t instanceof c?(this._={gun:this,$:this}).$:this instanceof c?c.create(this._={gun:this,$:this,opt:t}):new c(t)}c.is=function(t){return t instanceof c||t&&t._&&t===t._.$||!1},c.version=.9,(c.chain=c.prototype).toJSON=function(){};var n=_("./type");n.obj.to(n,c),c.HAM=_("./HAM"),c.val=_("./val"),c.node=_("./node"),c.state=_("./state"),c.graph=_("./graph"),c.on=_("./onto"),c.ask=_("./ask"),c.dup=_("./dup"),function(){function a(t){var n,o,e=this.as,i=e.at||e,r=i.$;(o=t["#"])||(o=t["#"]=u(9)),(n=i.dup).check(o)?e.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(o),i.ask(t["@"],t)||(t.get&&c.on.get(t,r),t.put&&c.on.put(t,r)),this.to.next(t),e.out||(t.out=a,i.on("out",t)))}c.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||c.on,t.ask=t.ask||c.ask,t.dup=t.dup||c.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",a,t),t.on("out",a,{at:t,out:a}),c.on("create",t),t.on("create",t)),t.once=1,n}}(),function(){function i(t,n,o,e){var i=this,r=c.state.is(o,n);if(!r)return i.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=i.graph[e]||v,u=c.state.is(a,n,!0),s=a[n],f=c.HAM(i.machine,r,u,t,s);f.incoming?(i.put[e]=c.state.to(o,n,i.put[e]),(i.diff||(i.diff={}))[e]=c.state.to(o,n,i.diff[e]),i.souls[e]=!0):f.defer&&(i.defer=r<(i.defer||1/0)?r:i.defer)}function r(t,n){var o=this,e=o.$._,i=(e.next||v)[n];if(!i){if(!(e.opt||v).super)return void(o.souls[n]=!1);i=o.$.get(n)._}var r=o.map[n]={put:t,get:n,$:i.$},a={ctx:o,msg:r};o.async=!!e.tag.node,o.ack&&(r["@"]=o.ack),h(t,u,a),o.async&&(o.and||e.on("node",function(t){this.to.next(t),t===o.map[t.get]&&(o.souls[t.get]=!1,h(t.put,s,t),h(o.souls,function(t){if(t)return t})||o.c||(o.c=1,this.off(),h(o.map,f,o)))}),o.and=!0,e.on("node",r))}function u(t,n){var o=this.ctx,e=o.graph,i=this.msg,r=i.get,a=i.put,u=i.$._;e[r]=c.state.to(a,n,e[r]),o.async||(u.put=c.state.to(a,n,u.put))}function s(t,n){var o=this.put,e=this.$._;e.put=c.state.to(o,n,e.put)}function f(t,n){t.$&&(this.cat.stop=this.stop,t.$._.on("in",t),this.cat.stop=null)}c.on.put=function(t,n){var o=n._,e={$:n,graph:o.graph,put:{},map:{},souls:{},machine:c.state(),ack:t["@"],cat:o,stop:{}};if(c.graph.is(t.put,null,i,e)||(e.err="Error: Invalid graph!"),e.err)return o.on("in",{"@":t["#"],err:c.log(e.err)});h(e.put,r,e),e.async||h(e.map,f,e),void 0!==e.defer&&setTimeout(function(){c.on.put(t,n)},e.defer-e.machine),e.diff&&o.on("put",p(t,{put:e.diff}))},c.on.get=function(t,n){var o=n._,e=t.get,i=e[d],r=o.graph[i],a=e[g],u=(o.next||(o.next={}))[i];if(l(i,"*")){var s={};c.obj.map(o.graph,function(t,n){c.text.match(n,i)&&(s[n]=c.obj.copy(t))}),c.obj.empty(s)||o.on("in",{"@":t["#"],how:"*",put:s,$:n})}if(!r)return o.on("get",t);if(a){if(!l(r,a))return o.on("get",t);r=c.state.to(r,a)}else r=c.obj.copy(r);r=c.graph.node(r),(u||v).ack,o.on("in",{"@":t["#"],how:"mem",put:r,$:n}),o.on("get",t)}}(),c.chain.opt=function(t){t=t||{};var n=this._,o=t.peers||t;return a(t)||(t={}),a(n.opt)||(n.opt=t),i(o)&&(o=[o]),e(o)&&(o=h(o,function(t,n,o){o(t,{url:t})}),a(n.opt.peers)||(n.opt.peers={}),n.opt.peers=p(o,n.opt.peers)),n.opt.peers=n.opt.peers||{},p(t,n.opt),c.on("opt",n),n.opt.uuid=n.opt.uuid||function(){return s()+u(12)},this};var e=c.list.is,o=c.text,i=o.is,u=o.random,r=c.obj,a=r.is,l=r.has,p=r.to,h=r.map,s=(r.copy,c.state.lex),d=c.val.rel._,g=".",v=(c.node._,c.val.link.is,{});b.debug=function(t,n){return b.debug.i&&t===b.debug.i&&b.debug.i++&&(b.log.apply(b,arguments)||n)},(c.log=function(){return!c.log.off&&b.log.apply(b,arguments),[].slice.call(arguments).join(" ")}).once=function(t,n,o){return(o=c.log.once)[t]=o[t]||0,o[t]++||c.log(n)},c.log.once("welcome","Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"),"undefined"!=typeof window&&((window.GUN=window.Gun=c).window=window);try{void 0!==f&&(f.exports=c)}catch(t){}t.exports=c})(_,"./root"),_(function(t){var u=_("./root");u.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root.$;if(1===t)return(this._.back||this._).$;var o=this._;if("string"==typeof t&&(t=t.split(".")),t instanceof Array){for(var e=0,i=t.length,r=o;e .once, apologies unexpected."),this.once(t,n)},f.chain.once=function(t,n){var o=this,e=o._,i=e.put;if(0=(a.batch||1e3))return f();e||(e=setTimeout(f,a.wait||1))}),r.on("get",function(n){this.to.next(n);var o,e,i=n.get;function t(){if(i&&(o=i["#"])){var t=i["."];(e=s[o]||void 0)&&t&&(e=Gun.state.to(e,t)),(e||Gun.obj.empty(a.peers))&&r.on("in",{"@":n["#"],put:Gun.graph.node(e),how:"lS",lS:n.$||r.$})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,o,e){s[e]=Gun.state.to(o,n,s[e])},f=function(t){var o;u=0,clearTimeout(e),e=!1;var n=i;i={},t&&(s=t);try{p.setItem(a.prefix,JSON.stringify(s))}catch(t){Gun.log(o=(t||"localStorage failure")+" Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."),r.on("localStorage:error",{err:o,file:a.prefix,flush:s,retry:f})}(o||Gun.obj.empty(a.peers))&&Gun.obj.map(n,function(t,n){r.on("in",{"@":n,err:o,ok:0})})}}})}})(_,"./adapters/localStorage"),_(function(t){var g=_("../type");function o(p){var h=function(){},d=p.opt||{};return d.log=d.log||b.log,d.gap=d.gap||d.wait||1,d.pack=d.pack||.3*(d.memory?1e3*d.memory*1e3:1399e6),h.out=function(t){var n;if(this.to&&this.to.next(t),(n=t["@"])&&(n=p.dup.s[n])&&(n=n.it)&&n._)return h.say(t,n._.via,1),void(n["##"]=t["##"]);h.say(t)},p.on("create",function(t){t.opt.pid=t.opt.pid||g.text.random(9),this.to.next(t),p.on("out",h.out)}),h.hear=function(t,n){if(t){var o,e,i,r=p.dup,a=t[0];if(d.pack<=t.length)return h.say({dam:"!",err:"Message too big!"},n);try{i=JSON.parse(t)}catch(t){d.log("DAM JSON parse error",t)}if("{"===a){if(!i)return;if(r.check(o=i["#"]))return;if((a=(r.track(o,!0).it=i)["@"])&&i.put&&(a+=e=i["##"]||(i["##"]=h.hash(i)))!=o){if(r.check(a))return;(a=r.s)[e]=a[o]}return(i._=function(){}).via=n,(a=i["><"])&&(i._.to=g.obj.map(a.split(","),function(t,n,o){o(t,!0)})),i.dam?void((a=h.hear[i.dam])&&a(i,n,p)):void p.on("in",i)}if("["!==a);else{if(!i)return;for(var u,s=0;u=i[s++];)h.hear(u,n)}}},function(){function a(t){var n=t.batch;if(n&&(t.batch=t.tail=null,n.length))try{u(JSON.stringify(n),t)}catch(t){d.log("DAM JSON stringify error",t)}}function u(n,o){var t=o.wire;try{t.send?t.send(n):o.say&&o.say(n)}catch(t){(o.queue=o.queue||[]).push(n)}}h.say=function(n,t,o){var e,i,r;if(t){if((t.wire||d.wire&&d.wire(t))&&(i=n._||s,t!==i.via&&((r=i.raw)||(r=h.raw(n)),!((e=n["@"])&&(e=p.dup.s[e])&&(e=e.it)&&e.get&&e["##"]&&e["##"]===n["##"])&&(!(e=i.to)||!e[t.url]&&!e[t.id]||o)))){if(t.batch){if(t.tail=(t.tail||0)+r.length,t.tail<=d.pack)return void t.batch.push(r);a(t)}t.batch=[],setTimeout(function(){a(t)},d.gap),u(r,t)}}else g.obj.map(d.peers,function(t){h.say(n,t)})}}(),function(){function f(t,n){var o;return n instanceof Object?(g.obj.map(Object.keys(n).sort(),e,{to:o={},on:n}),o):n}function e(t){this.to[t]=this.on[t]}h.raw=function(t){if(!t)return"";var n,o,e,i=p.dup,r=t._||{};if(e=r.raw)return e;if("string"==typeof t)return t;t["@"]&&(e=t.put)&&((o=t["##"])||(n=c(e,f)||"",o=h.hash(t,n),t["##"]=o),(e=i.s)[o=t["@"]+o]=e[t["#"]],t["#"]=o||t["#"],n&&((t=g.obj.to(t)).put=l));var a=0,u=[];g.obj.map(d.peers,function(t){if(u.push(t.url||t.id),9<++a)return!0}),t["><"]=u.join();var s=c(t);return v!==n&&(e=s.indexOf(l,s.indexOf("put")),s=s.slice(0,e-1)+n+s.slice(e+l.length+1)),r&&(r.raw=s),s},h.hash=function(t,n){return o.hash(n||c(t.put,f)||"")||t["#"]||g.text.random(9)};var c=JSON.stringify,l=":])([:"}(),h.hi=function(n){var t=n.wire||{};n.id||n.url?(d.peers[n.url||n.id]=n,g.obj.del(d.peers,t.id)):(t=t.id=t.id||g.text.random(9),h.say({dam:"?"},d.peers[t]=n)),t.hied||p.on(t.hied="hi",n),t=n.queue,n.queue=[],g.obj.map(t,function(t){h.say(t,n)})},h.bye=function(t){g.obj.del(d.peers,t.id),p.on("bye",t)},h.hear["!"]=function(t,n){d.log("Error:",t.err)},h.hear["?"]=function(t,n){if(!t.pid)return h.say({dam:"?",pid:d.pid,"@":t["#"]},n);n.id=n.id||t.pid,h.hi(n)},h}o.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o=0,e=t.length;o { + e = e || {}; var $img = $('
    ') + .css({position: 'fixed', width: 100, + top: (e.y || e.clientY || (Math.random() * $(window).height()))-50, + left: (e.x || e.clientX || e.pageX || (Math.random() * $(window).width()))-50, + transform: 'rotate('+(Math.random() * 360)+'deg)' + }).appendTo('body'); + setTimeout(() => { $img.remove() },800); +},10)}; +$(document).on('keyup', fun).on('touchstart', fun).on('mousedown', fun); \ No newline at end of file diff --git a/lib/les.js b/lib/les.js new file mode 100644 index 00000000..75a44ece --- /dev/null +++ b/lib/les.js @@ -0,0 +1,222 @@ +; +(function() { + + // _ _____ ____ _ + // | | | ____/ ___| (_)___ + // | | | _| \___ \ | / __| + // | |___| |___ ___) | | \__ \ + // |_____|_____|____(_)/ |___/ + // ---------------------------- + // LES.js (Last rEcently uSed) + // ---------------------------- + // A Small, lightweight, queue-based + // Garbage Collector for Gun + // Originally By: Collin Conrad (@masterex1000) + + /** + * + * Usage: require the file in your application + * + * Gun Params: these are passed to the new gun constructor + * + * - gc_enable : enables the gc, good if you are running multiple instances of gun, etc... def. true + * - gc_delay : sets the amount of time between attempted garbage collections in milliseconds + * - gc_info_enable : Enables or Disables the info printout + * - gc_info : sets the ~ amount of time between info messages + * this is checked everytime the gc is ran + * - gc_info_mini : this will use a smaller, less user friendly info printout + * - gc_importance_func : This will be the function used for finding the importance of a potental collect + * takes the form of func(timestamp, ctime, memoryUsageRatio) {return val} + * Collects when returned value is 100 + */ + + //NOTE: set to false to use require for getting gun DEFUALT: false + var USELOCALGUN = false; + + + //NOTE: adds some debug messages DEFUALT: false + var DEBUG = false; + + if(!(typeof window !== "undefined") && USELOCALGUN) + console.log("NOTE: You currently have LES.js set to use the 'local' file version of gun, This might crash if set wrong!"); + + var Gun = (typeof window !== "undefined") ? window.Gun : (USELOCALGUN ? require('../gun') : require("gun")); + + //Removes a node from the garbage collection until next write + Gun.chain.gcDequeue = function() { + //console.log(this._.root.dequeueNode); + if(this._.root.dequeueNode) { // check that we actually have the dequeue command on this node + let ctx = this; + + this.get(function (soul) { + ctx._.root.dequeueNode(soul); + }, true); + } + } + + //Puts node at the front for garbage collection, NOTE: only collects when it is hit it's time + Gun.chain.gcCollect = function() { + if(this._.root.collectNode) { // check that we actually have the dequeue command on this node + let ctx = this; + + this.get(function (soul) { + ctx._.root.collectNode(soul); + }, true); + } + } + + Gun.on('opt', function(root) { + //Setup various options + + const gc_enable = root.opt.gc_enable ? root.opt.gc_enable : true; + const gc_delay = root.opt.gc_delay ? root.opt.gc_delay : 1000; + + const gc_info_enable = root.opt.gc_info_enable ? root.opt.gc_info_enable : true; + const gc_info = root.opt.gc_info ? root.opt.gc_info : 5000; + const gc_info_mini = root.opt.gc_info_mini ? root.opt.gc_info_mini : false; + + //This is long, but it works well + const calcRemoveImportance = root.opt.gc_importance_func ? root.opt.gc_importance_func : function (timestamp, ctime, memoryUsageRatio) { + var time = (ctime - timestamp) * 0.001; + return time * 10 * (memoryUsageRatio * memoryUsageRatio); + } + + if(DEBUG) console.log(root.opt); + + this.to.next(root); + + if (root.once) + return; + if (typeof process == 'undefined') + return + var mem = process.memoryUsage; + + if(!gc_enable) // exit because the gc is disabled + return; + + if (!mem) //exit because we are in the browser + return; + + var ev = {}; //stores the environment + var empty = {}; //An empty list used to prevent crashes + + //Figure out the most amount of memory we can use. TODO: make configurable? + ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 512) * 0.8; + + var nodes = {}; //checks if the node already exists + var nodesArray = []; //used to easily sort everything and store info about the nodes + var memoryUpdate = 0; // last time we printed the current memory stats + + root.dequeueNode = (soul) => { //forward the call to our gc + dequeueNode(soul); + } + + root.collectNode = (soul) => { //forward the call to our gc + collectNode(soul); + } + + var check = function() { + ev.used = mem().rss / 1024 / 1024; //Contains the amt. of used ram in MB + setTimeout(function() { // So we can handle requests etc. before we start collecting + GC(ev.used / ev.max); // Calculate the memory ratio, and execute the garbage collector + //GC(0.99); + }, 1); + } + + setInterval(check, gc_delay); // set the garbage collector to run every second + + //Executed every time a node gets modified + root.on("put", function(e) { + var ctime = Date.now(); + var souls = Object.keys(e.put || empty); // get all of the nodes in the update + for (var i = 0; i < souls.length; i++) { // iterate over them and add them + enqueueNode(souls[i], ctime); + } + }); + + //Adds a soul the garbage collectors "freeing" queue + function enqueueNode(soul, ctime) { + if (nodes[soul] == true) { //The node already exists in the queue + var index = nodesArray.findIndex(function(e) { + return e[0] === soul; + }); + if (index == -1) { + console.error("Something happened and the node '" + soul + "' won't get garbage collection unless the value is updated again"); + return; + } else { + nodesArray.splice(index, 1); // remove the existing ref. faster than dequeue + nodesArray.push([soul, ctime]); // push the new instance + } + } else { + nodesArray.push([soul, ctime]); + nodes[soul] = true; + } + } + + //Removes a node from the queue + function dequeueNode(soul) { + if (nodes[soul] == true) { //The node already exists in the queue + var index = nodesArray.findIndex(function(e) { + return e[0] === soul; + }); + if (index != -1) { + //nodesArray.splice(index, 1); // remove the existing ref. + nodesArray.shift(); + nodes[soul] = false; // store that we no longer have that node in the queue + } + } + } + + //Moves a node to the start of the queue + function collectNode(soul) { + if (nodes[soul] == true) { //The node already exists in the queue + var index = nodesArray.findIndex(function(e) { + return e[0] === soul; + }); + if (index != -1) { + //nodesArray.splice(index, 1); // remove the existing ref. + nodesArray.shift(); // WAY faster than splice + } + nodesArray.unshift([soul, nodesArray[0][1]]); // create a new node with the next nodes time stamp + nodes[soul] = true; // store that we no longer have that node in the queue + } + } + + //The main garbage collecting routine + function GC(memRatio) { + var curTime = Date.now(); // get the current time + + if (gc_info_enable && curTime - memoryUpdate >= gc_info) { // check if we need to print info + if(!gc_info_mini) + console.log("|GC| %s | Current Memory Ratio: %d | Current Ram Usage %sMB | Nodes in Memory %s", new Date().toLocaleString(), round(memRatio, 2), round(ev.used, 2), Object.keys(root.graph || empty).length); + else + console.log("|GC| %s, Mem Ratio %d, Ram %sMB, Nodes in mem %s, Tracked Nodes %s", new Date().toLocaleString(), round(memRatio, 2), round(ev.used, 2), Object.keys(root.graph || empty).length, nodesArray.length); + memoryUpdate = curTime; // reset the last update time + } + + var freed = 0; // Just a nice performance counter + + while (nodesArray.length > 0) { // iterate over all of our nodes + var soul = nodesArray[0][0]; + var nts = nodesArray[0][1]; + if (DEBUG) + console.log("Soul: " + soul + " | Remove Importance: " + calcRemoveImportance(nts, curTime, memRatio) + + " | Memory Ratio: " + memRatio + " | Time Existed: " + (curTime - nts) / 1000); + if (calcRemoveImportance(nodesArray[0][1], curTime, memRatio) >= 100) { + root.gun.get(nodesArray[0][0]).off(); //Remove the node + delete nodes[nodesArray[0][0]]; // remove the lookup value + //nodesArray.splice(0, 1); + nodesArray.shift(); + freed++; // add one to our perf counter + } else + break; // Break out of the loop because we don't have any more nodes to free + } + if (freed > 0) + console.log("|GC| Removed %s nodes in %s seconds-----------------------------------------------------------------", freed, (Date.now() - curTime) * 0.001); + } + + function round(value, decimals) { //a basic rounding function + return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals); + } + }); +}()); \ No newline at end of file diff --git a/lib/meta.js b/lib/meta.js new file mode 100644 index 00000000..b89e4725 --- /dev/null +++ b/lib/meta.js @@ -0,0 +1,360 @@ +$(function(){ + var m = window.meta = {edit:[], os:{}}, ua = '', u; + try{ua = navigator.userAgent.toLowerCase()}catch(e){} + m.os.is = { + win: (ua.search("win") >= 0)? "windows":false, + lin: (ua.search("linux") >= 0)? "linux":false, + mac: (ua.search("mac") >= 0)? "macintosh":false, + and: (ua.search("android") >= 0)? "android":false, + ios: (ua.search('ipod') >= 0 + || ua.search('iphone') >= 0 + || ua.search('ipad') >= 0)? "ios":false + } + var k = m.key = {ctrl: 17, cmd: 91}; + k.meta = (m.os.is.win||m.os.is.lin||m.os.is.and)? k.ctrl : k.cmd; + k.down = function(eve){ + if($(eve.target).is('input') || eve.repeat){ return } + (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode; + if(!eve.fake && eve.which === k.last){ return } + if(k.meta === (k.last = eve.which)){ k.down.meta = m.flip(k.wipe()) || true } + if(m.flip.is()){ + (k.combo || (k.combo = [])).push(eve.which); + m.check('on', eve.which, k.at || (k.at = m.edit)); + } + if(eve.metaKey && (k.meta !== eve.which)){ k.up(eve) } // on some systems, meta hijacks keyup + } + k.up = function(eve){ var tmp; + if($(eve.target).is('input')){ return } + k.eve = m.eve = eve; + k.last = null; + eve.which = eve.which || eve.fake || eve.keyCode; + if(m.flip.is()){ m.check('up', eve.which) } + if(tmp = (k.meta === eve.which)){ k.down.meta = false } + if(tmp && k.at === m.edit){ k.wipe() } + if(27 === eve.which){ return m.flip(false) } + } + m.flip = function(tmp, aid){ + if(aid){ + m.flip.aid = true; + setTimeout(function(){$(document).one('click',function(eve){m.flip(m.flip.aid = false)})},250); // ugly but important for visual aid. + } + var board = $('#meta .meta-menu'); + ((tmp === false) || (!tmp && board.is(':visible')))? + board.addClass('meta-none') + : board.removeClass('meta-none'); + } + m.flip.is = function(){ + if(m.flip.aid && ((m.eve||{}).fake || k.at !== m.edit)){ m.flip.aid = false } + return !m.flip.aid && $('#meta .meta-menu').is(':visible'); + } + m.flip.wait = 500; + m.check = function(how, key, at){ + at = k.at || m.edit; + //m.list(at); + var edit = at[key], tmp; + if(!edit){ return } + if(k.eve && k.eve.preventDefault){ k.eve.preventDefault() } + if(edit[how]){ + edit[how](m.eve); + if(k.at !== m.edit && 'up' === how){ + if(k.down.meta){ m.list(k.at = m.edit) } + else { k.wipe() } + } + } + if('up' != how){ return } + edit.back = at; + m.list(edit, at); + } + m.list = function(at){ + var l = []; + $.each(at, function(i,k){ 'back' != i && k.combo && l.push(k) }); + if(!l.length){ return } + k.at = at; + l = l.sort(function(a,b){ + a = a.combo.slice(-1)[0] || 0; + if(a.length){ a = a.toUpperCase().charCodeAt(0) } + b = b.combo.slice(-1)[0] || 0; + if(b.length){ b = b.toUpperCase().charCodeAt(0) } + return (a < b)? -1 : 1; + }); + var $ul = $('#meta .meta-menu ul') + $ul.children('li').addClass('meta-none').hide(); setTimeout(function(){ $ul.children('.meta-none').remove() },250); // necessary fix for weird bug glitch + $.each(l, function(i, k){ + $ul.append($('
  • ').text(k.name)); + }); + if(!at.back){ return } + $ul.append($('
  • ').html('←').one('click', function(){ m.list(k.at = at.back) })); + } + m.ask = function(help, cb){ + var $ul = $('#meta .meta-menu ul').empty(); + var $put = $('').attr('id', 'meta-ask').attr('placeholder', help); + var $form = $('
    ').append($put).on('submit', function(eve){ + eve.preventDefault(); + cb($put.val()); + $li.remove(); + k.wipe(); + }); + var $li = $('
  • ').append($form); + $ul.append($li); + $put.focus(); + } + k.wipe = function(){ + k.combo = []; + m.flip(false); + m.flip.aid = false; + m.list(k.at = m.edit); + }; + $(document).on('keydown', k.down).on('keyup', k.up); + m.tap = {}; + m.tap.select = function(eve){ + m.tap.range = null; + if(!(m.tap.text()||'').trim()){ + if(m.tap.was){ + m.tap.was = null; + m.flip(false); + } + return; + } + m.flip(m.tap.range = monotype((eve||{}).target), m.tap.was = true); + } + m.tap.text = function(tmp){ + return ((tmp = window.getSelection) && tmp().toString()) || + ((tmp = document.selection) && tmp.createRange().text) || ''; + } + $(window).on('blur', k.wipe).on('focus', k.wipe); + $(document).on('select contextmenu keyup mouseup', '[contenteditable=true]', m.tap.select); + //.on('keydown', '[contenteditable=true]', function(e){}); + $(document).on('touchstart', '#meta .meta-start', function(eve){ m.tap.stun = true }); + $(document).on('click', '#meta .meta-menu li', function(eve){ + if(m.tap.stun){ return m.tap.stun = false } + if(!(eve.fake = eve.which = (($(this).text().match(/[A-Z]/)||{})[0]||'').toUpperCase().charCodeAt(0))){ return } + eve.tap = true; + k.down(eve); + k.up(eve); + }); + meta.edit = function(edit){ + var tmp = edit.combow = []; + $.each(edit.combo || (edit.combo = []), function(i,k){ + if(!k || !k.length){ return } + tmp.push(k.toUpperCase().charCodeAt(0)); + }); + var at = meta.edit, l = edit.combo.length; + $.each(tmp, function(i,k){ at = at[k] = (++i >= l)? edit : at[k] || {} }); + edit.combow = edit.combow.join(','); + m.list(meta.edit); + } + meta.text = {zws: '​'}; + meta.text.editor = function(opt, as){ var tmp; + if(!opt){ return } + opt = (typeof opt == 'string')? {edit: opt} : opt.tag? opt : {tag: opt}; + var r = opt.range = opt.range || m.tap.range || monotype(), cmd = opt.edit; + as = opt.as = opt.as || as; + if(cmd && document.execCommand){ + r.restore(); + if(document.execCommand(cmd, null, as||null)){ return } + } + if(!opt.tag){ return } + opt.tag = $(opt.tag); + opt.name = opt.name || opt.tag.prop('tagName'); + if((tmp = $(r.get()).closest(opt.name)).length){ + if(r.s === r.e){ + tmp.after(meta.text.zws); + r = r.select(monotype.next(tmp[0]),1); + } else { + tmp.contents().unwrap(opt.name); + } + } else + if(r.s === r.e){ + r.insert(opt.tag); + r = r.select(opt.tag); + } else { + r.wrap(opt.tag); + } + r.restore(); + opt.range = null; + if(m.tap.range){ m.tap.range = monotype() } + } + ;(function(){try{ + /* UI */ + if(meta.css){ return } + var $m = $('
    ').attr('id', 'meta'); + $m.append($('').text('+').addClass('meta-start')); + $m.append($('
    ').addClass('meta-menu meta-none').append('
      ')); + $(document.body).append($m); + css({ + '#meta': { + display: 'block', + position: 'fixed', + bottom: '2em', + right: '2em', + background: 'white', + 'font-size': '18pt', + 'font-family': 'Tahoma, arial', + 'box-shadow': '0px 0px 1px #000044', + 'border-radius': '1em', + 'text-align': 'center', + 'z-index': 999999, + margin: 0, + padding: 0, + width: '2em', + height: '2em', + opacity: 0.7, + color: '#000044', + overflow: 'visible', + transition: 'all 0.2s ease-in' + }, + '#meta .meta-none': {display: 'none'}, + '#meta span': {'line-height': '2em'}, + '#meta .meta-menu': { + background: 'rgba(0,0,0,0.1)', + width: '12em', + right: '-2em', + bottom: '-2em', + overflow: 'visible', + position: 'absolute', + 'overflow-y': 'scroll', + 'text-align': 'right', + 'min-height': '20em', + height: '100vh' + }, + '#meta .meta-menu ul': { + padding: 0, + margin: '1em 1em 2em 0', + 'list-style-type': 'none' + }, + '#meta .meta-menu ul li': { + display: 'block', + background: 'white', + padding: '0.5em 1em', + 'border-radius': '1em', + 'margin-left': '0.25em', + 'margin-top': '0.25em', + 'float': 'right' + }, + '#meta a': {color: 'black'}, + '#meta:hover': {opacity: 1}, + '#meta .meta-menu ul:before': { + content: "' '", + display: 'block', + 'min-height': '15em', + height: '50vh' + }, + '#meta li': { + background: 'white', + padding: '0.5em 1em', + 'border-radius': '1em', + 'margin-left': '0.25em', + 'margin-top': '0.25em', + 'float': 'right' + }, + '#meta:hover .meta-menu': {display: 'block'} + }); + function css(css){ + var tmp = ''; + $.each(css, function(c,r){ + tmp += c + ' {\n'; + $.each(r, function(k,v){ + tmp += '\t'+ k +': '+ v +';\n'; + }); + tmp += '}\n'; + }); + (node = document.createElement('style')).innerHTML = tmp; + document.body.appendChild(node); + } + }catch(e){}}()); + ;(function(){ + // on fires when shortcut keydowns or on touch after command selected and then touchdown + meta.edit({ + name: "Bold", + combo: ['B'], + on: function(e){ + meta.text.editor('bold'); + }, + up: function(){} + }); + meta.edit({ + name: "Italic", + combo: ['I'], + on: function(e){ + meta.text.editor('italic'); + }, + up: function(){} + }); + meta.edit({ + name: "Underline", + combo: ['U'], + on: function(e){ + meta.text.editor('underline'); + }, + up: function(){} + }); + meta.edit({ + name: "linK", + combo: ['K'], + up: function(e){ + var range = meta.tap.range || monotype(); + meta.ask('Paste or type link...', function(url){ + meta.text.editor({tag: $('link'), edit: url? 'createLink' : 'unlink', as: url, range: range}); + }) + }, + on: function(){} + }); + meta.edit({name: "aliGn", combo: ['G']}); + meta.edit({ + name: "Left", + combo: ['G','L'], + on: function(e){ meta.text.editor('justifyLeft') }, + up: function(){} + }); + meta.edit({ + name: "Right", + combo: ['G','R'], + on: function(e){ meta.text.editor('justifyRight') }, + up: function(){ } + }); + meta.edit({ + name: "Middle", + combo: ['G','M'], + on: function(e){ meta.text.editor('justifyCenter') }, + up: function(){ } + }); + meta.edit({ + name: "Justify", + combo: ['G','J'], + on: function(e){ meta.text.editor('justifyFull') }, + up: function(){} + }); + // Align Number + // Align Points + // Align Strike + meta.edit({name: "Size", combo: ['S']}); + meta.edit({ + name: "Small", + combo: ['S','S'], + on: function(e){ meta.text.editor('fontSize', 2) }, + up: function(){ } + }); + meta.edit({ + name: "Normal", + combo: ['S','N'], + on: function(e){ meta.text.editor('fontSize', 5) }, + up: function(){} + }); + meta.edit({ + name: "Header", + combo: ['S','H'], + on: function(e){ meta.text.editor('fontSize', 6) }, + up: function(){} + }); + meta.edit({ + name: "Title", + combo: ['S','T'], + on: function(e){ meta.text.editor('fontSize', 7) }, + up: function(){} + }); + // Size Spacing + // Size Super + // Size Sub + meta.edit({name: "Edit", combo: ['E']}); + }()); +}); \ No newline at end of file diff --git a/lib/monotype.js b/lib/monotype.js new file mode 100644 index 00000000..3b02bf40 --- /dev/null +++ b/lib/monotype.js @@ -0,0 +1,310 @@ +;var monotype = monotype || (function(monotype){ + monotype.range = function(n){ + var R, s, t, n = n || 0, win = monotype.win || window, doc = win.document; + if(!arguments.length) return doc.createRange(); + if(!(win.Range && R instanceof Range)){ + s = win.getSelection? win.getSelection() : {}; + if(s.rangeCount){ + R = s.getRangeAt(n); + } else { + if(doc.createRange){ + R = doc.createRange(); + R.setStart(doc.body, 0); + } else + if (doc.selection){ // '+n+'
    ').contents():$(document.createTextNode(n)); + } + m.get = function(d){ + if(u === d){ return $([m.R.startContainer, m.R.endContainer]) } + return monotype.deep((d = (d && d > 0))? m.R.endContainer : m.R.startContainer + , d? m.R.endOffset : m.R.startOffset).container; + } + m.remove = function(n,R){ + R = m.R || m.range(); + R.deleteContents(); + monotype.restore(R); + m = monotype(m,opt); + return m; + } + m.insert = function(n,R){ + n = jqtxt(n); + R = m.R || m.range(); + R.deleteContents(); + $(n.get().reverse()).each(function(){ + R.insertNode(this); + }); + R.selectNodeContents(n.last()[0]); + monotype.restore(R); + m = monotype(m,opt); + return m; + } + m.wrap = function(n,R){ + var jq; + n = jqtxt(n); + n = n[0]; + R = m.R || m.range(); + if(monotype.text(R.startContainer) || monotype.text(R.endContainer)){ + var b = R.cloneContents(); + R.deleteContents(); + jq = $(n); + jq.html(b); + jq = jq[0]; + R.insertNode(jq); + }else{ + R.surroundContents(n); + } + R.selectNodeContents(jq||n); + monotype.restore(R); + m = monotype(m,opt); + return m; + } + m.select = function(n,i,e,j){ + var R = m.R || m.range(), t = e; + n = $(n); + if($.isNumeric(e)){ + e = j || n; + j = t; + } else { + e = e || n; + } + j = $.isNumeric(j)? j : $.isNumeric(i)? i : Infinity; + i = i || 0; + if(i < 0){ + t = n.contents().length || n.text().length; + i = t + i; + } if(j < 0){ + t = n.contents().length || n.text().length; + j = t + j; + } if(j === Infinity){ + R.selectNodeContents(n[0]); + } else { + R.setStart(n[0],i); + R.setEnd(e[0],j); + } + monotype.restore(R); + m = monotype(m,opt); + return m; + } + return m; +} \ No newline at end of file diff --git a/lib/normalize.js b/lib/normalize.js new file mode 100644 index 00000000..871ec6cf --- /dev/null +++ b/lib/normalize.js @@ -0,0 +1,284 @@ +(function(){ + + $.normalize = function(html, customOpt){ + var html, root$, wrapped, opt; + opt = html.opt || (customOpt ? prepareOptTags($.extend(true, baseOpt, customOpt)) + : defaultOpt); + if(!html.opt){ + // first call + unstableList.length = 0; // drop state from previous run (in case there has been error) + root$ = $('
    '+html+'
    '); + } + // initial recursion + (html.$ || root$).contents().each(function(){ + if(this.nodeType === this.TEXT_NODE) { + this.textContent = this.textContent.replace(/^[ \n]+|[ \n]+$/g, ' '); + return; + } + var a = {$: $(this), opt: opt}; + initTag(a); + $.normalize(a); + }); + if(root$){ + stateMachine(); + return root$.html(); + } + } + + var baseOpt = { + hierarchy: ['div', 'pre', 'ol', 'ul', 'li', + 'h1', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', // block + 'b', 'code', 'i', 'span', 's', 'sub', 'sup', 'u', // inline + 'br'] // empty + ,tags: { + 'a': {attrs:{'href':1}, exclude:{'a':1}}, + 'b': {exclude:{'b':1,'p':1}}, + 'br': {empty: 1}, + 'i': {exclude:{'i':1,'p':1}}, + 'span': {exclude:{'p':1,'ul':1,'ol':1,'li':1,'br':1}}, + 's': {space:1}, + 'u': {exclude:{'u':1,'p':1},space:1}, + } + ,convert: { + 'em': 'i', 'strong': 'b', 'strike': 's', + } + ,attrs: { + 'id':1 + ,'class':1 + ,'style':1 + } + ,blockTag: function(a){ + return a.opt.tags[a.tag].order < a.opt.tags.a.order; + } + ,mutate: [exclude, moveSpaceUp, next, parentOrderWrap] + } + + var defaultOpt = prepareOptTags($.extend(true, {}, baseOpt)); + + var unstableList = []; + + function addUnstable(a) { // NOT ES5 + if(!a.tag) { throw Error("not tag in ", a) } + if(a.unstable) return; + unstableList.push(a); + a.unstable = true; + } + + function initTag(a) { + // initial handling (container, convert, attributes): + a.tag = tag(a.$); + if(empty(a)) { + return; + } + parseAndRemoveAttrs(a); + convert(a); + setAttrs(a); + a.$[0].a = a; // link from dom element back to a + // state machine init + unstableList.push(a); + a.unstable = true; + return a; + } + + function stateMachine() { + if(unstableList.length===0) + return; + var a, i = -1; + while (a = unstableList.pop()) { // PERF: running index is probably faster than shift (mutates array) + a.unstable = false; + $(a.opt.mutate).each(function(i,fn){ + return fn && fn(a, addUnstable); + }); + } + } + + function prepareOptTags(opt) { + var name, tag, tags = opt.tags; + for(name in tags) { + if(opt.hierarchy.indexOf(name)===-1) + throw Error('tag "'+name+'" is missing hierachy definition'); + } + opt.hierarchy.forEach(function(name){ + if(!tags[name]){ + tags[name] = {attrs: opt.attrs}; + } + (tag=tags[name]).attrs = $.extend(tag.attrs||{}, opt.attrs); + tag.name = name; // not used, debug help (REMOVE later?) + // order + tag.order = opt.hierarchy.indexOf(name) + if(tag.order === -1) { + throw Error("Order of '"+name+"' not defined in hierarchy"); + } + }); + return opt; + } + + // GENERAL UTILS + + function get(o, args){ // path argments as separate string parameters + if(typeof args === 'string') + return o[args[0]]; + var i = 0, l = args.length, u; + while((o = o[args[i++]]) != null && i < l){}; + return i < l ? u : o; + } + + function has(obj,prop){ + return Object.prototype.hasOwnProperty.call(obj, prop); + } + + // ELEMENT UTILS + + function tag(e){ + return (($(e)[0]||{}).nodeName||'').toLowerCase(); + } + + function joint(e, d){ + d = (d? 'next' : 'previous') + 'Sibling'; + return $(($(e)[0]||{})[d]); + } + + // create key val attributes object from elements attributes + function attrsAsObj(e, filterCb){ + var attrObj = {}; + (e = $(e)) && e.length && $(e[0].attributes||[]).each(function(value,name){ + name = name.nodeName||name.name; + value = e.attr(name); + value = filterCb? filterCb(value,name,e) : value; + if(value !== undefined && value !== false) + attrObj[name] = value; + }); + return attrObj; + } + + // TODO: PERF testing - for loop to compare through? + function sameAttrs(a, b) { + return JSON.stringify(a.attr) === JSON.stringify(b.attr); + } + + // INITIAL MUTATORS + + function parseAndRemoveAttrs(a) { + a.attrs = []; + var tag = a.opt.convert[a.tag] || a.tag, + tOpt = a.opt.tags[tag]; + a.attr = tOpt && attrsAsObj(a.$, function(value,name){ + a.$.removeAttr(name); + if(tOpt.attrs[name.toLowerCase()]){ + a.attrs.push(name) + return value; + } + }); + } + + function setAttrs(a){ + var l = function(ind,name){ + var t = name; + name = a.attrs? name : ind; + var value = a.attrs? a.attr[name.toLowerCase()] : t; + a.$.attr(name, value); + } + a.attrs? $(a.attrs.sort()).each(l) : $.each(a.attr,l); + } + + function convert(a){ + var t; + if(t = a.opt.convert[a.tag]){ + a.$.replaceWith(a.$ = $('<'+ (a.tag = t.toLowerCase()) +'>').append(a.$.contents())); + } + } + + // LOOPING (STATE MACHINE) MUTATORS + + function exclude(a, addUnstable){ + var t = get(a.opt, ['tags', a.tag]), + pt = get(a.opt, ['tags', tag(a.$.parent())]); + if(!t || (pt && get(pt, ['exclude', a.tag]))){ + var c = a.$.contents(); + a.$.replaceWith(c); + c.length===1 && c[0].a && addUnstable(c[0].a); + return false; + } + } + + function moveSpaceUp(a, addUnstable){ + var n = a.$[0]; + if(moveSpace(n, true) + moveSpace(n, false)) { + // either front, back or both spaces moved + var c; + if(n.textContent==='') { + empty(a); + } else if((c = a.$.contents()[0]) && c.a) { + parentOrderWrap(c.a, addUnstable) + } + } + } + + function moveSpace(n, bef) { + var childRe = bef? /^ / : / $/, + parentRe = bef? / $/ : /^ /, + c = bef? 'firstChild' : 'lastChild', + s = bef? 'previousSibling' : 'nextSibling'; + sAdd = bef? 'after' : 'before'; + pAdd = bef? 'prepend' : 'append'; + if(!n || !n[c] || n[c].nodeType !== n.TEXT_NODE || !n[c].wholeText.match(childRe)) { + return 0; + } + if((n2 = n[s]) && !n.a.opt.blockTag(n.a)) { + if(n2.nodeType === 3 && !n2.textContent.match(parentRe)) { + n2.textContent = (bef?'':' ') + n2.textContent + (bef?' ':''); + } else if(n2.nodeType === 1) { + $(n2)[sAdd](' '); + } + } else if((n2 = n.parentNode) && !n.a.opt.blockTag(n.a)) { + $(n2)[pAdd](' '); + } else { + return 0; + } + n[c].textContent = n[c].wholeText.replace(childRe, ''); + if(!n[c].wholeText.length) + $(n[c]).remove(); + return 1; + } + + function next(a, addUnstable, t){ + var t = t || joint(a.$, true), sm; + if(!t.length || a.opt.blockTag(a)) + return; + if(a.opt.spaceMerge && t.length===1 && t[0].nodeType === 3 && t[0].wholeText===' '){ + if(!(t2 = joint(t, true)).length || a.opt.blockTag(t2[0].a)) + return; + t.remove(); + t2.prepend(' '); + return next(a, addUnstable, t2); + } + if(!t[0].a || a.tag !== t[0].a.tag || !sameAttrs(a, t[0].a)) + return; + t.prepend(a.$.contents()); + empty(a); + addUnstable(t[0].a); + (t = t.children(":first")).length && addUnstable(t[0].a); + } + + function empty(a){ + var t = a.opt.tags[a.tag]; + if((!t || !t.empty) && !a.$.contents().length && !a.$[0].attributes.length){ + a.$.remove(); + return true; // NOTE true/false - different API than in exclude + } + } + + function parentOrderWrap(a, addUnstable){ + var parent = a.$.parent(), children = parent.contents(), + tags = a.opt.tags, ptag; + + if(children.length===1 && children[0] === a.$[0] + && (ptag=tags[tag(parent)]) && ptag.order > tags[a.tag].order){ + parent.after(a.$); + parent.append(a.$.contents()); + a.$.append(parent); + addUnstable(parent[0].a); + addUnstable(a); + } + } +})(); \ No newline at end of file diff --git a/lib/radisk.js b/lib/radisk.js index e9c6dc48..9906e249 100644 --- a/lib/radisk.js +++ b/lib/radisk.js @@ -3,7 +3,9 @@ function Radisk(opt){ opt = opt || {}; + opt.log = opt.log || console.log; opt.file = String(opt.file || 'radata'); + opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB. opt.until = opt.until || opt.wait || 9; opt.batch = opt.batch || 10 * 1000; opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB @@ -11,19 +13,19 @@ opt.code.from = opt.code.from || '!'; function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') } - map = Gun.obj.map; + var map = Gun.obj.map; if(!opt.store){ - return Gun.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!"); + return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!"); } if(!opt.store.put){ - return Gun.log("ERROR: Radisk needs `store.put` interface with `(file, data, cb)`!"); + return opt.log("ERROR: Radisk needs `store.put` interface with `(file, data, cb)`!"); } if(!opt.store.get){ - return Gun.log("ERROR: Radisk needs `store.get` interface with `(file, cb)`!"); + return opt.log("ERROR: Radisk needs `store.get` interface with `(file, cb)`!"); } if(!opt.store.list){ - Gun.log("WARNING: `store.list` interface might be needed!"); + //opt.log("WARNING: `store.list` interface might be needed!"); } /* @@ -73,7 +75,7 @@ r.batch.ed = 0; r.save(batch, function(err, ok){ if(++i > 1){ return } - if(err){ Gun.log('err', err) } + if(err){ opt.log('err', err) } map(batch.acks, function(cb){ cb(err, ok) }); thrash.at = null; thrash.ing = false; @@ -141,7 +143,8 @@ f.file = file; f.each = function(val, key, k, pre){ if(u !== val){ f.count++ } - var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : '='+ Radisk.encode(val)) +'\n'; + if(opt.pack <= (val||'').length){ return cb("Record too big!"), true } + var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n'; if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !force){ f.text = ''; f.limit = Math.ceil(f.count/2); @@ -206,7 +209,7 @@ g.file = file; } g.it = function(err, disk){ - if(g.err = err){ Gun.log('err', err) } + if(g.err = err){ opt.log('err', err) } if(disk){ RAD = g.disk = disk } disk = Q[g.file]; delete Q[g.file]; map(disk, g.ack); @@ -243,6 +246,16 @@ //return cb(err, u);//map(q, p.ack); return map(q, p.ack); } + if(typeof data !== 'string'){ + try{ + if(opt.pack <= data.length){ + p.err = "Chunk too big!"; + } else { + data = data.toString(); + } + }catch(e){ p.err = e } + if(p.err){ return map(q, p.ack) } + } var tmp = p.split(data), pre = [], i, k, v; while(tmp){ k = v = u; @@ -257,7 +270,7 @@ } tmp = p.split(tmp[2])||''; if('\n' == tmp[0]){ continue } - if('=' == tmp[0]){ v = tmp[1] } + if('=' == tmp[0] || ':' == tmp[0]){ v = tmp[1] } if(u !== k && u !== v){ p.disk(pre.join(''), v) } tmp = p.split(tmp[2]); } @@ -309,7 +322,7 @@ } r.list.init = function(err, disk){ if(err){ - Gun.log('list', err); + opt.log('list', err); setTimeout(function(){ r.parse(f, r.list.init) }, 1000); return; } diff --git a/lib/radix.js b/lib/radix.js index 539aff7a..a0b270cb 100644 --- a/lib/radix.js +++ b/lib/radix.js @@ -49,7 +49,7 @@ return radix; }; - Radix.map = function map(radix, cb, opt, pre){ pre = pre || []; + Radix.map = function map(radix, cb, opt, pre){ pre = pre || []; var t = radix[_] || radix, keys = radix.sort || (radix.sort = Object.keys(t).sort()), i = 0, l = keys.length; for(;i < l; i++){ var key = keys[i], tree = t[key], tmp; if(u !== (tmp = tree[$])){ diff --git a/lib/reboot.js b/lib/reboot.js new file mode 100644 index 00000000..9a2ba612 --- /dev/null +++ b/lib/reboot.js @@ -0,0 +1,19 @@ +;(function(){ + var exec = require('child_process').execSync; + var dir = __dirname, tmp; + + try{exec("crontab -l"); + }catch(e){tmp = e} + if(0 > tmp.toString().indexOf('no')){ return } + + try{tmp = exec('which node').toString(); + }catch(e){console.log(e);return} + + try{tmp = exec('echo "@reboot '+tmp+' '+dir+'/../examples/http.js" > '+dir+'/reboot.cron'); + }catch(e){console.log(e);return} + + try{tmp = exec('crontab '+dir+'/reboot.cron'); + }catch(e){console.log(e);return} + console.log(tmp.toString()); + +}()); \ No newline at end of file diff --git a/lib/rfs.js b/lib/rfs.js index b86aa3a8..05595a28 100644 --- a/lib/rfs.js +++ b/lib/rfs.js @@ -22,7 +22,6 @@ function Store(opt){ } Gun.log("ERROR:", err) } - if(data){ data = data.toString() } cb(err, data); }); }; @@ -78,7 +77,7 @@ function Mem(opt){ cb(null, tmp); }, 1); }; - store.list = function(cb, match){ + store.list = function(cb, match){ // supporting this is no longer needed! Optional. setTimeout(function(){ Gun.obj.map(Object.keys(storage), cb) || cb(); }, 1); @@ -86,4 +85,4 @@ function Mem(opt){ return store; } -module.exports = Store;//Gun.TESTING? Mem : Store; \ No newline at end of file +module.exports = Store;//Gun.TESTING? Mem : Store; diff --git a/lib/rindexed.js b/lib/rindexed.js new file mode 100644 index 00000000..532e4e30 --- /dev/null +++ b/lib/rindexed.js @@ -0,0 +1,129 @@ +;(function(){ + var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); + + Gun.on('create', function(root){ + this.to.next(root); + root.opt.store = root.opt.store || Store(root.opt); + }); + + function Store(opt){ + opt = opt || {}; + opt.file = String(opt.file || 'radata'); + var db = null; + + opt.indexedDB = opt.indexedDB || window.indexedDB; + // Initialize indexedDB. Version 1. + var request = opt.indexedDB.open(opt.file, 1) + + // Create schema. onupgradeneeded is called only when DB is first created or when the DB version increases. + request.onupgradeneeded = function(event){ + var db = event.target.result; + db.createObjectStore(opt.file); + } + + // onsuccess is called when the DB is ready. + request.onsuccess = function(){ + db = request.result; + } + + request.onerror = function(event){ + console.log('ERROR: RAD IndexedDB generic error:', event); + }; + + var store = function Store(){}, u; + + store.put = function(file, data, cb){ + cb = cb || function(){}; + var doPut = function(){ + // Start a transaction. The transaction will be automaticallt closed when the last success/error handler took no new action. + var transaction = db.transaction([opt.file], 'readwrite'); + + // Add or update data. + var radStore = transaction.objectStore(opt.file); + var putRequest = radStore.put(data, file); + putRequest.onsuccess = radStore.onsuccess = transaction.onsuccess = function(){ + //console.log('RAD IndexedDB put transaction was succesful.'); + cb(null, 1); + }; + putRequest.onabort = radStore.onabort = transaction.onabort = function(){ + var es = 'ERROR: RAD IndexedDB put transaction was aborted.'; + console.log(es); + cb(es, undefined); + }; + putRequest.onerror = radStore.onerror = transaction.onerror = function(event){ + var es = 'ERROR: RAD IndexedDB put transaction was in error: ' + JSON.stringify(event) + console.log(es); + cb(es, undefined); + }; + } + if(!db){ + waitDbReady(doPut, 100, function(){ + var es = 'ERROR: Timeout: RAD IndexedDB not ready.'; + console.log(es); + cb(es, undefined); + }, 10) + } else { + doPut(); + } + }; + + store.get = function(file, cb){ + cb = cb || function(){}; + var doGet = function(){ + // Start a transaction. The transaction will be automaticallt closed when the last success/error handler took no new action. + var transaction = db.transaction([opt.file], 'readwrite'); + + // Read data. + var radStore = transaction.objectStore(opt.file); + var getRequest = radStore.get(file); + getRequest.onsuccess = function(){ + //console.log('RAD IndexedDB get transaction was succesful.'); + cb(null, getRequest.result); + }; + getRequest.onabort = function(){ + var es = 'ERROR: RAD IndexedDB get transaction was aborted.'; + console.log(es); + cb(es, undefined); + }; + getRequest.onerror = function(event){ + var es = 'ERROR: RAD IndexedDB get transaction was in error: ' + JSON.stringify(event) + console.log(es); + cb(es, undefined); + }; + } + if(!db){ + waitDbReady(doGet, 100, function(){ + var es = 'ERROR: Timeout: RAD IndexedDB not ready.'; + console.log(es); + cb(es, undefined); + }, 10) + } else { + doGet(); + } + }; + + var waitDbReady = function(readyFunc, checkInterval, timeoutFunc, timeoutSecs){ + var startTime = new Date(); + var checkFunc = function(){ + if(db){ + readyFunc(); + } else { + if((new Date() - startTime) / 1000 >= timeoutSecs){ + timeoutFunc(); + } else { + setTimeout(checkFunc, checkInterval); + } + } + }; + checkFunc(); + }; + + return store; + } + + if(Gun.window){ + Gun.window.RindexedDB = Store; + } else { + module.exports = Store; + } +}()); diff --git a/lib/rs3.js b/lib/rs3.js index ac63343f..9f81e43c 100644 --- a/lib/rs3.js +++ b/lib/rs3.js @@ -62,7 +62,7 @@ function Store(opt){ if(!ack){ cb(null); return; } cb(err, data); }; - if(data = (ack||{}).Body){ data = data.toString() } + data = (ack||{}).Body; //if(data = (ack||{}).Body){ data = data.toString() } Gun.obj.map(cbs, cbe); }); }; diff --git a/lib/serve.js b/lib/serve.js index 1e9cfc39..5e1c76cf 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -4,9 +4,10 @@ var path = require('path'); function CDN(dir){ return function(req, res){ if(serve(req, res)){ return } // filters GUN requests! - fs.createReadStream(path.join(dir, req.url)).on('error',function(){ // static files! + fs.createReadStream(path.join(dir, req.url)).on('error',function(tmp){ // static files! + try{ tmp = fs.readFileSync(path.join(dir, 'index.html')) }catch(e){} res.writeHead(200, {'Content-Type': 'text/html'}); - res.end(fs.readFileSync(path.join(dir, 'index.html'))); // or default to index + res.end(tmp+''); // or default to index }).pipe(res); // stream } } diff --git a/lib/server.js b/lib/server.js index a893322e..7870006c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -3,15 +3,17 @@ Gun.serve = require('./serve'); //process.env.GUN_ENV = process.env.GUN_ENV || 'debug'; Gun.on('opt', function(root){ + if(u === root.opt.super){ + root.opt.super = true; + } this.to.next(root); - if(root.once){ return } - if(u !== root.opt.super){ return } - root.opt.super = true; }) require('../nts'); require('./store'); require('./rs3'); require('./wire'); + try{require('../sea');}catch(e){} + //try{require('../axe');}catch(e){} require('./file'); require('./evict'); if('debug' === process.env.GUN_ENV){ require('./debug') } diff --git a/lib/space.js b/lib/space.js new file mode 100644 index 00000000..bb7e1a82 --- /dev/null +++ b/lib/space.js @@ -0,0 +1,118 @@ +;(function(){ + var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); + var ify = Gun.node.ify, empty = {}, u; + console.log("Index space is beta, API may change!"); + Gun.chain.space = function(key, data, opt){ + if(data instanceof Function){ + return travel(key, data, opt, this); + } + var gun = this; + if(Gun.is(data)){ + data.get(function(soul){ + if(!soul){ + return cb && cb({err: "Indexspace cannot link `undefined`!"}); + } + gun.space(key, Gun.val.link.ify(soul), opt); + }, true); + return gun; + } + var cb = (opt instanceof Function && opt), rank = (opt||empty).rank || opt, root = gun.back(-1), tmp; + gun.get(function(soul){ + if(!soul){ + soul = (gun.back('opt.uuid') || Gun.text.random)(9); + } + /*var space = ify({}, soul), sub = space, l = 0, tmp; + var atom = Gun.text.ify({get: key, put: data}); + Gun.list.map(index(0, key.length), function(i){ + sub[(tmp = key.slice(l, i))+'"'] = atom; + sub = sub[tmp] = ify({}, soul+'"'+key.slice(0,i)); + l = i; + }); + tmp = {}; tmp[key] = atom.put; tmp = ify(tmp, soul+'"'); + sub[key.slice(l, key.length)] = tmp; + console.log('????', space);*/ + var shell = {}, l = 0, tmp; + var atom = Gun.text.ify({get: key, put: data}); + tmp = {}; tmp[key] = data; + shell.$ = ify(tmp, soul); + tmp = {}; tmp[key.slice(0,l = 1)] = atom; + shell[0] = ify(tmp, soul+'"'); + Gun.list.map(index(1, key.length), function(i){ + tmp = {}; tmp[key.slice(l,i)] = atom; + shell[i] = ify(tmp, soul+'"'+key.slice(0,l)); + l = i; + }); + tmp = {}; tmp[key.slice(l, key.length)] = atom; + shell[l+1] = ify(tmp, soul+'"'+key.slice(0,l)); + //tmp = {}; tmp[key.slice(l, key.length)] = Gun.val.link.ify(soul); shell[l+1] = ify(tmp, soul+'"'+key.slice(0,l)); + //console.log('???', shell); + gun.put(shell, cb, {soul: soul, shell: shell}); + },true); + return gun; + } + function travel(key, cb, opt, ref){ + var root = ref.back(-1), tmp; + opt = opt || {}; + opt.ack = opt.ack || {}; + ref.get(function(soul){ + ref.get(key).get(function(msg, eve){ + eve.off(); + opt.exact = true; + opt.ack.key = key; + opt.ack.data = msg.put; + if(opt.match){ cb(opt.ack, key, msg, eve) } + }); + //if(u !== msg.put){ + // cb(msg.put, msg.get, msg, eve); + // return; + //} + opt.soul = soul; + opt.start = soul+'"'; + opt.key = key; + opt.top = index(0, opt.find); + opt.low = opt.top.reverse(); + find(opt, cb, root); + }, true); + } + function find(o, cb, root){ + var id = o.start+o.key.slice(0,o.low[0]); + root.get(id).get(function(msg, eve){ + eve.off(); + o.ack.tree = {}; + if(u === msg.put){ + if(!o.exact){ return o.match = true } + cb(o.ack, id, msg, eve); + return; + o.low = o.low.slice(1); + if(!o.low.length){ + cb(u, o.key, msg, eve); + return; + } + find(o, cb, root); + return; + } + Gun.node.is(msg.put, function(v,k){ + if(!(k = Gun.obj.ify(v) || empty).get){ return } + o.ack.tree[k.get] = k.put; + }); + if(!o.exact){ return o.match = true } + cb(o.ack, id, msg, eve); + }); + } + function index(n, m, l, k){ + l = l || []; + if(!m){ return l } + k = Math.ceil((n||1) / 10); + if((n+k) >= m){ return l } + l.push(n + k); + return index(n + k, m, l); + } +}()); + +/* +gun.user('google').space('martti', "testing 123!"); +gun.user('google').get('search').space('ma', function(){ + // tree & index + // UNFINISHED API! +}); +*/ diff --git a/lib/store.js b/lib/store.js index a3ea547a..10230707 100644 --- a/lib/store.js +++ b/lib/store.js @@ -3,16 +3,11 @@ var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); Gun.on('create', function(root){ this.to.next(root); var opt = root.opt, u; - if(typeof window !== "undefined"){ - opt.window = window; - } - //if(true !== opt.radisk && (!opt.window && !process.env.RAD_ENV && !process.env.AWS_S3_BUCKET) && false !== opt.localStorage){ return } - //if(true !== opt.radisk){ return } if(false === opt.radisk){ return } - var Radisk = (opt.window && opt.window.Radisk) || require('./radisk'); + var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk'); var Radix = Radisk.Radix; - opt.store = opt.store || (!opt.window && require('./rfs')(opt)); + opt.store = opt.store || (!Gun.window && require('./rfs')(opt)); var rad = Radisk(opt), esc = String.fromCharCode(27); root.on('put', function(msg){ @@ -41,8 +36,14 @@ Gun.on('create', function(root){ var id = msg['#'], soul = msg.get['#'], key = msg.get['.']||'', tmp = soul+'.'+key, node; rad(tmp, function(err, val){ if(val){ - Radix.map(val, each); - if(!node){ each(val, key) } + if(val && typeof val !== 'string'){ + if(key){ + val = u; + } else { + Radix.map(val, each) + } + } + if(!node && val){ each(val, key) } } root.on('in', {'@': id, put: Gun.graph.node(node), err: err? err : u, rad: Radix}); }); diff --git a/lib/super.js b/lib/super.js new file mode 100644 index 00000000..3f94ff3d --- /dev/null +++ b/lib/super.js @@ -0,0 +1,29 @@ +;(function(){ + var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); + var Rad = (Gun.window||{}).Radix || require('./radix'); + function input(msg){ + var at = this.as, to = this.to, peer = (msg.mesh||empty).via; + var get = msg.get, soul, key; + if(!peer || !get){ return to.next(msg) } + console.log("super", msg); + if(soul = get['#']){ + if(key = get['.']){ + + } else { + + } + subscribe(soul, peer, msg); + } + to.next(msg); + } + /// Store the subscribes + Gun.subscribe = {}; /// TODO: use Rad instead of plain object? + function subscribe(soul, peer, msg) { + if (!peer.id) { console.log('super jump peer without id: ', peer, msg); return; } /// TODO: this occurs in first subscription. Use peer reference or peer.wire.id? + Gun.subscribe[soul] = Gun.subscribe[soul] || {}; + Gun.subscribe[soul][peer.id] = 1; + } + var empty = {}, u; + if(Gun.window){ return } + try{module.exports = input}catch(e){} +}()); diff --git a/lib/upload.js b/lib/upload.js index 6cdadd21..aaacba29 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -25,9 +25,10 @@ if(!e){ return cb && cb({err: "No file!"}) } if(e.err){ return } var file = (((e.event || e).target || e).result || e), img = new Image(); + img.crossOrigin = "Anonymous"; img.src = file; img.onload = function(){ - if(img.width < w && img.height < (h||Infinity)){ + if(img.width < (w = w || 1000) && img.height < (h||Infinity) && "data:" == file.slice(0,5)){ e.base64 = file; return cb(e || file); } diff --git a/lib/webrtc.js b/lib/webrtc.js new file mode 100644 index 00000000..9d37d3de --- /dev/null +++ b/lib/webrtc.js @@ -0,0 +1,104 @@ +;(function(){ + var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); + + Gun.on('opt', function(root){ + this.to.next(root); + var opt = root.opt; + if(root.once){ return } + if(!Gun.Mesh){ return } + if(false === opt.RTCPeerConnection){ return } + + var env; + if(typeof window !== "undefined"){ env = window } + if(typeof global !== "undefined"){ env = global } + env = env || {}; + + var rtcpc = opt.RTCPeerConnection || env.RTCPeerConnection || env.webkitRTCPeerConnection || env.mozRTCPeerConnection; + var rtcsd = opt.RTCSessionDescription || env.RTCSessionDescription || env.webkitRTCSessionDescription || env.mozRTCSessionDescription; + var rtcic = opt.RTCIceCandidate || env.RTCIceCandidate || env.webkitRTCIceCandidate || env.mozRTCIceCandidate; + if(!rtcpc || !rtcsd || !rtcic){ return } + opt.RTCPeerConnection = rtcpc; + 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'} + ]}; + opt.rtc.dataChannel = opt.rtc.dataChannel || {ordered: false, maxRetransmits: 2}; + opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}}; + + var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); + root.on('create', function(at){ + this.to.next(at); + setTimeout(function(){ root.on('out', {rtc: {id: opt.pid}}) },1); // announce ourself + }); + root.on('in', function(msg){ + if(msg.rtc){ open(msg) } + this.to.next(msg); + }); + + function open(msg){ + var rtc = msg.rtc, peer, tmp; + if(!rtc || !rtc.id){ return } + if(tmp = rtc.answer){ + if(!(peer = opt.peers[rtc.id]) || peer.remoteSet){ return } + return peer.setRemoteDescription(peer.remoteSet = new opt.RTCSessionDescription(tmp)); + } + if(tmp = rtc.candidate){ + peer = opt.peers[rtc.id] || open({rtc: {id: rtc.id}}); + return peer.addIceCandidate(new opt.RTCIceCandidate(tmp)); + } + if(opt.peers[rtc.id]){ return } + (peer = new opt.RTCPeerConnection(opt.rtc)).id = rtc.id; + var wire = peer.wire = peer.createDataChannel('dc', opt.rtc.dataChannel); + mesh.hi(peer); + wire.onclose = function(){ + mesh.bye(peer); + peer.wire = null; + //reconnect(peer); + }; + wire.onerror = function(error){ + //reconnect(peer); // placement? + if(!error){ return } + if(error.code === 'ECONNREFUSED'){ + //reconnect(peer, as); + } + }; + wire.onopen = function(e){ + mesh.hi(peer); + } + wire.onmessage = function(msg){ + if(!msg){ return } + mesh.hear(msg.data || msg, peer); + }; + peer.onicecandidate = function(e){ // source: EasyRTC! + if(!e.candidate){ return } + root.on('out', {'@': msg['#'], rtc: {candidate: e.candidate, id: opt.pid}}); + } + peer.ondatachannel = function(e){ + var rc = e.channel; + rc.onmessage = wire.onmessage; + rc.onopen = wire.onopen; + rc.onclose = wire.onclose; + } + if(tmp = rtc.offer){ + peer.setRemoteDescription(new opt.RTCSessionDescription(tmp)); + peer.createAnswer(function(answer){ + peer.setLocalDescription(answer); + root.on('out', {'@': msg['#'], rtc: {answer: answer, id: opt.pid}}); + }, function(){}, opt.rtc.sdp); + return; + } + peer.createOffer(function(offer){ + peer.setLocalDescription(offer); + root.on('out', {'@': msg['#'], rtc: {offer: offer, id: opt.pid}}); + }, function(){}, opt.rtc.sdp); + return peer; + } + }); + var noop = function(){}; +}()); \ No newline at end of file diff --git a/lib/wire.js b/lib/wire.js index 2c815cb9..5e74b6b3 100644 --- a/lib/wire.js +++ b/lib/wire.js @@ -49,44 +49,37 @@ var Gun = require('../gun'); */ -var WebSocket = require('ws'); - -var url = require('url'); - -Gun.on('opt', function(ctx){ - var opt = ctx.opt; +Gun.on('opt', function(root){ + var opt = root.opt; if(false === opt.ws){ - this.to.next(ctx); + this.to.next(root); return; - } + } - opt.WebSocket = opt.WebSocket || WebSocket; + var url = require('url'); + opt.WebSocket = opt.WebSocket || require('ws'); var ws = opt.ws || {}; ws.server = ws.server || opt.web; if(ws.server && !ws.web){ + opt.mesh = opt.mesh || Gun.Mesh(root); ws.path = ws.path || '/gun'; + ws.maxPayload = ws.maxPayload; // || opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; ws.web = new opt.WebSocket.Server(ws); - ws.web.on('connection', function(wire){ + ws.web.on('connection', function(wire){ var peer; wire.upgradeReq = wire.upgradeReq || {}; wire.url = url.parse(wire.upgradeReq.url||'', true); - wire.id = wire.id || Gun.text.random(6); - var peer = opt.peers[wire.id] = {id: wire.id, wire: wire}; - wire.peer = function(){ return peer }; - ctx.on('hi', peer); + opt.mesh.hi(peer = {wire: wire}); wire.on('message', function(msg){ - //console.log("MESSAGE", msg); - ctx.msgsLength = (ctx.msgsLength || 0) + (msg.data || msg).length; // TEMPORARY, NON-STANDARD, FOR DEBUG opt.mesh.hear(msg.data || msg, peer); }); wire.on('close', function(){ - ctx.on('bye', peer); - Gun.obj.del(opt.peers, wire.id); + opt.mesh.bye(peer); }); wire.on('error', function(e){}); }); } - this.to.next(ctx); + this.to.next(root); }); \ No newline at end of file diff --git a/nts.js b/nts.js index e8389f69..c0ee6b5b 100644 --- a/nts.js +++ b/nts.js @@ -2,6 +2,7 @@ // NOTE: While the algorithm is P2P, // the current implementation is one sided, // only browsers self-modify, servers do not. + // Need to fix this! Since WebRTC is now working. var env; if(typeof global !== "undefined"){ env = global } if(typeof window !== "undefined"){ var Gun = (env = window).Gun } @@ -13,7 +14,7 @@ this.to.next(ctx); if(ctx.once){ return } ctx.on('in', function(at){ - if(!at.NTS){ + if(!at.nts && !at.NTS){ return this.to.next(at); } if(at['@']){ @@ -23,21 +24,21 @@ if(env.window){ return this.to.next(at); } - this.to.next({'@': at['#'], NTS: Gun.time.is()}); + this.to.next({'@': at['#'], nts: Gun.time.is()}); }); var ask = {}, noop = function(){}; if(!env.window){ return } Gun.state.drift = Gun.state.drift || 0; setTimeout(function ping(){ - var NTS = {}, ack = Gun.text.random(), msg = {'#': ack, NTS: true, gun: ctx.gun}; + var NTS = {}, ack = Gun.text.random(), msg = {'#': ack, nts: true}; NTS.start = Gun.state(); ask[ack] = function(at){ NTS.end = Gun.state(); Gun.obj.del(ask, ack); NTS.latency = (NTS.end - NTS.start)/2; - if(!at.NTS){ return } - NTS.calc = NTS.latency + at.NTS; + if(!at.nts && !at.NTS){ return } + NTS.calc = NTS.latency + (at.NTS || at.nts); Gun.state.drift -= (NTS.end - NTS.calc)/2; setTimeout(ping, 1000); } @@ -45,4 +46,4 @@ }, 1); }); // test by opening up examples/game/nts.html on devices that aren't NTP synced. -}()); +}()); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 414e3295..7fa8f7c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,32 +1,9 @@ { "name": "gun", - "version": "0.9.9998", + "version": "0.9.999999", "lockfileVersion": 1, "requires": true, "dependencies": { - "@trust/keyto": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@trust/keyto/-/keyto-0.3.2.tgz", - "integrity": "sha512-ywlelg2ePNpX4IlN+A3qXySzKBAZmI2ZxMdDL3amJLCTYhYhemYcv6Aa+PTETojUfB+k4z4X4970q/jjSzyLvw==", - "dev": true, - "requires": { - "asn1.js": "^4.9.1", - "base64url": "^2.0.0", - "elliptic": "^6.4.0" - } - }, - "@trust/webcrypto": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.7.1.tgz", - "integrity": "sha512-aix+LOG/3Ku3MzClfVxVH88QbSdIL1HcBQ+gjXL/VnX05uyORf28CaQZOvsoEcCzGnWIVBUNwE2gxLBapWANWw==", - "dev": true, - "requires": { - "@trust/keyto": "^0.3.1", - "base64url": "^2.0.0", - "node-rsa": "^0.4.0", - "text-encoding": "^0.6.1" - } - }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -55,23 +32,6 @@ "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", "dev": true }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true - }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", @@ -125,12 +85,6 @@ "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", "dev": true }, - "base64url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=", - "dev": true - }, "better-assert": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", @@ -152,12 +106,6 @@ "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", "dev": true }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - }, "body-parser": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", @@ -186,12 +134,6 @@ "concat-map": "0.0.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -222,9 +164,9 @@ "dev": true }, "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, "component-bind": { @@ -308,21 +250,6 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, - "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -571,9 +498,9 @@ } }, "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "has-binary": { @@ -600,38 +527,17 @@ "dev": true }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - } - }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -759,18 +665,6 @@ "mime-db": "~1.33.0" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -783,35 +677,33 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" } }, "mocha": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz", - "integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { "browser-stdout": "1.3.1", - "commander": "2.11.0", + "commander": "2.15.1", "debug": "3.1.0", "diff": "3.5.0", "escape-string-regexp": "1.0.5", "glob": "7.1.2", - "growl": "1.10.3", + "growl": "1.10.5", "he": "1.1.1", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "4.4.0" + "supports-color": "5.4.0" }, "dependencies": { "debug": { @@ -831,19 +723,28 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "nan": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "optional": true + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", "dev": true }, - "node-rsa": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz", - "integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=", - "dev": true, + "node-webcrypto-ossl": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/node-webcrypto-ossl/-/node-webcrypto-ossl-1.0.39.tgz", + "integrity": "sha512-cEq67y6GJ5jcKdANi5XqejqMvM/eIGxuOOE8F+c0XS950jSpvOcjUNHLmIe3/dN/UKyUkb+dri0BU4OgmCJd2g==", + "optional": true, "requires": { - "asn1": "0.2.3" + "mkdirp": "^0.5.1", + "nan": "^2.11.1", + "tslib": "^1.9.3", + "webcrypto-core": "^0.1.25" } }, "object-assign": { @@ -1242,19 +1143,19 @@ "dev": true }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "has-flag": "^3.0.0" } }, "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", - "dev": true + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", + "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", + "optional": true }, "to-array": { "version": "0.1.4", @@ -1262,6 +1163,11 @@ "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", "dev": true }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -1324,6 +1230,15 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, + "webcrypto-core": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-0.1.26.tgz", + "integrity": "sha512-BZVgJZkkHyuz8loKvsaOKiBDXDpmMZf5xG4QAOlSeYdXlFUl9c1FRrVnAXcOdb4fTHMG+TRu81odJwwSfKnWTA==", + "optional": true, + "requires": { + "tslib": "^1.7.1" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index f121ee42..2cf8e82d 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "gun", - "version": "0.9.9998", + "version": "0.9.9999991", "description": "A realtime, decentralized, offline-first, graph data synchronization engine.", "main": "index.js", "browser": "gun.min.js", "scripts": { "start": "node examples/http.js 8765", + "https": "HTTPS_KEY=test/https/server.key HTTPS_CERT=test/https/server.crt npm start", "prepublishOnly": "npm run unbuild", "test": "mocha", "testsea": "mocha test/sea.js", @@ -50,16 +51,18 @@ "dependencies": { "ws": "~>5.2.0" }, + "optionalDependencies": { + "text-encoding": "^0.7.0", + "node-webcrypto-ossl": "^1.0.39" + }, "devDependencies": { - "@trust/webcrypto": "^0.7.1", "aws-sdk": ">=2.153.0", + "concat-map": "^0.0.1", "express": ">=4.15.2", "ip": "^1.1.5", - "concat-map": "^0.0.1", - "mocha": ">=3.2.0", + "mocha": "^5.2.0", "panic-manager": "^1.2.0", "panic-server": "^1.1.1", - "text-encoding": "^0.6.4", "uglify-js": ">=2.8.22" } } diff --git a/sea.js b/sea.js index d49165d3..28264b4f 100644 --- a/sea.js +++ b/sea.js @@ -6,8 +6,8 @@ if(typeof global !== "undefined"){ root = global } root = root || {}; var console = root.console || {log: function(){}}; - function USE(arg){ - return arg.slice? USE[R(arg)] : function(mod, path){ + function USE(arg, req){ + return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ arg(mod = {exports: {}}); USE[R(path)] = mod.exports; } @@ -20,24 +20,30 @@ ;USE(function(module){ // Security, Encryption, and Authorization: SEA.js - // MANDATORY READING: http://gun.js.org/explainers/data/security.html + // MANDATORY READING: https://gun.eco/explainers/data/security.html + // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH. // THIS IS AN EARLY ALPHA! - function SEA(){} - if(typeof window !== "undefined"){ SEA.window = window } + if(typeof window !== "undefined"){ module.window = window } + var tmp = module.window || module; + var SEA = tmp.SEA || {}; + + if(SEA.window = module.window){ SEA.window.SEA = SEA } + + try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){} module.exports = SEA; })(USE, './root'); ;USE(function(module){ var SEA = USE('./root'); - if(SEA.window){ + try{ if(SEA.window){ if(location.protocol.indexOf('s') < 0 && location.host.indexOf('localhost') < 0 && location.protocol.indexOf('file:') < 0){ location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS! } - } + } }catch(e){} })(USE, './https'); ;USE(function(module){ @@ -112,7 +118,7 @@ } return buf } - const byteLength = input.byteLength + const byteLength = input.byteLength // what is going on here? FOR MARTTI const length = input.byteLength ? input.byteLength : input.length if (length) { let buf @@ -145,121 +151,94 @@ })(USE, './buffer'); ;USE(function(module){ + const SEA = USE('./root') const Buffer = USE('./buffer') const api = {Buffer: Buffer} + var o = {}; - if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') { - var crypto = window.crypto || window.msCrypto; - var subtle = crypto.subtle || crypto.webkitSubtle; - const TextEncoder = window.TextEncoder - const TextDecoder = window.TextDecoder + if(SEA.window){ + api.crypto = window.crypto || window.msCrypto; + api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle; + api.TextEncoder = window.TextEncoder; + api.TextDecoder = window.TextDecoder; + api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len)))) + } + if(!api.crypto){try{ + var crypto = USE('crypto', 1); + const { TextEncoder, TextDecoder } = USE('text-encoding', 1) Object.assign(api, { crypto, - subtle, + //subtle, TextEncoder, TextDecoder, - random: (len) => Buffer.from(crypto.getRandomValues(new Uint8Array(Buffer.alloc(len)))) - }) - } else { - try{ - var crypto = require('crypto'); - const { subtle } = require('@trust/webcrypto') // All but ECDH - const { TextEncoder, TextDecoder } = require('text-encoding') - Object.assign(api, { - crypto, - subtle, - TextEncoder, - TextDecoder, - random: (len) => Buffer.from(crypto.randomBytes(len)) - }); - try{ - const WebCrypto = require('node-webcrypto-ossl') - api.ossl = new WebCrypto({directory: 'key_storage'}).subtle // ECDH - }catch(e){ - console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed."); - } - }catch(e){ - console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!"); - TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; - } - } + random: (len) => Buffer.from(crypto.randomBytes(len)) + }); + //try{ + const WebCrypto = USE('node-webcrypto-ossl', 1); + api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH + //}catch(e){ + //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed."); + //} + }catch(e){ + console.log("node-webcrypto-ossl and text-encoding may not be included by default, please add it to your package.json!"); + OSSL_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; + }} module.exports = api })(USE, './shim'); ;USE(function(module){ - const Buffer = USE('./buffer') - const settings = {} - // Encryption parameters - const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 } + var SEA = USE('./root'); + var Buffer = USE('./buffer'); + var s = {}; + s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64}; + s.ecdsa = { + pair: {name: 'ECDSA', namedCurve: 'P-256'}, + sign: {name: 'ECDSA', hash: {name: 'SHA-256'}} + }; + s.ecdh = {name: 'ECDH', namedCurve: 'P-256'}; - const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } } - const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' } - const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' } - - const _initial_authsettings = { - validity: 12 * 60 * 60, // internally in seconds : 12 hours - hook: (props) => props // { iat, exp, alias, remember } - // or return new Promise((resolve, reject) => resolve(props) - } - // These are used to persist user's authentication "session" - const authsettings = Object.assign({}, _initial_authsettings) // This creates Web Cryptography API compliant JWK for sign/verify purposes - const keysToEcdsaJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true } + s.jwk = function(pub, d){ // d === priv + pub = pub.split('.'); + var x = pub[0], y = pub[1]; + var jwk = {kty: "EC", crv: "P-256", x: x, y: y, ext: true}; jwk.key_ops = d ? ['sign'] : ['verify']; if(d){ jwk.d = d } return jwk; + }; + s.recall = { + validity: 12 * 60 * 60, // internally in seconds : 12 hours + hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props) + }; + + s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) } + s.parse = function p(t){ try { + var yes = (typeof t == 'string'); + if(yes && 'SEA{' === t.slice(0,4)){ t = t.slice(3) } + return yes ? JSON.parse(t) : t; + } catch (e) {} + return t; } - Object.assign(settings, { - pbkdf2: pbkdf2, - ecdsa: { - pair: ecdsaKeyProps, - sign: ecdsaSignProps - }, - ecdh: ecdhKeyProps, - jwk: keysToEcdsaJwk, - recall: authsettings - }) - module.exports = settings + SEA.opt = s; + module.exports = s })(USE, './settings'); ;USE(function(module){ - module.exports = (props) => { - try { - if(props.slice && 'SEA{' === props.slice(0,4)){ - props = props.slice(3); - } - return props.slice ? JSON.parse(props) : props - } catch (e) {} //eslint-disable-line no-empty - return props + var shim = USE('./shim'); + module.exports = async function(d, o){ + var t = (typeof d == 'string')? d : JSON.stringify(d); + var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t)); + return shim.Buffer.from(hash); } - })(USE, './parse'); - - ;USE(function(module){ - const { - subtle, ossl = subtle, random: getRandomBytes, TextEncoder, TextDecoder - } = USE('./shim') - const Buffer = USE('./buffer') - const parse = USE('./parse') - const { pbkdf2 } = USE('./settings') - // This internal func returns SHA-256 hashed data for signing - const sha256hash = async (mm) => { - const m = parse(mm) - const hash = await ossl.digest({name: pbkdf2.hash}, new TextEncoder().encode(m)) - return Buffer.from(hash) - } - module.exports = sha256hash })(USE, './sha256'); ;USE(function(module){ // This internal func returns SHA-1 hashed data for KeyID generation const __shim = USE('./shim') const subtle = __shim.subtle - const ossl = __shim.ossl ? __shim.__ossl : subtle + const ossl = __shim.ossl ? __shim.ossl : subtle const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b)) module.exports = sha1hash })(USE, './sha1'); @@ -268,49 +247,41 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); + var sha = USE('./sha256'); var u; - SEA.work = async (data, pair, cb) => { try { // used to be named `proof` - var salt = pair.epub || pair; // epub not recommended, salt should be random! + SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof` + var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random! + var opt = opt || {}; if(salt instanceof Function){ cb = salt; salt = u; } salt = salt || shim.random(9); - if (SEA.window) { - // For browser subtle works fine - const key = await shim.subtle.importKey( - 'raw', new shim.TextEncoder().encode(data), { name: 'PBKDF2' }, false, ['deriveBits'] - ) - const result = await shim.subtle.deriveBits({ - name: 'PBKDF2', - iterations: S.pbkdf2.iter, - salt: new shim.TextEncoder().encode(salt), - hash: S.pbkdf2.hash, - }, key, S.pbkdf2.ks * 8) - data = shim.random(data.length) // Erase data in case of passphrase - const r = shim.Buffer.from(result, 'binary').toString('utf8') - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; + 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; } - // For NodeJS crypto.pkdf2 rocks - const crypto = shim.crypto; - const hash = crypto.pbkdf2Sync( - data, - new shim.TextEncoder().encode(salt), - S.pbkdf2.iter, - S.pbkdf2.ks, - S.pbkdf2.hash.replace('-', '').toLowerCase() - ) - data = shim.random(data.length) // Erase passphrase for app - const r = hash && hash.toString('utf8') + 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', + iterations: opt.iterations || S.pbkdf2.iter, + salt: new shim.TextEncoder().encode(opt.salt || salt), + hash: opt.hash || S.pbkdf2.hash, + }, key, opt.length || (S.pbkdf2.ks * 8)) + data = shim.random(data.length) // Erase data in case of passphrase + var r = shim.Buffer.from(work, 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.work; })(USE, './work'); @@ -319,22 +290,32 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer; + + SEA.name = SEA.name || (async (cb, opt) => { try { + if(cb){ try{ cb() }catch(e){console.log(e)} } + return; + } catch(e) { + console.log(e); + SEA.err = e; + if(SEA.throw){ throw e } + if(cb){ cb() } + return; + }}); //SEA.pair = async (data, proof, cb) => { try { - SEA.pair = async (cb) => { try { + SEA.pair = SEA.pair || (async (cb, opt) => { try { - const ecdhSubtle = shim.ossl || shim.subtle + var ecdhSubtle = shim.ossl || shim.subtle; // First: ECDSA keys for signing/verifying... var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) .then(async (keys) => { // privateKey scope doesn't leak out from here! //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) - const key = {}; + var key = {}; key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; - const pub = await shim.subtle.exportKey('jwk', keys.publicKey) + var pub = await shim.subtle.exportKey('jwk', keys.publicKey); //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old - key.pub = pub.x+'.'+pub.y // new + key.pub = pub.x+'.'+pub.y; // new // x and y are already base64 // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -349,11 +330,11 @@ var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) .then(async (keys) => { // privateKey scope doesn't leak out from here! - const key = {}; + var key = {}; key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; - const pub = await ecdhSubtle.exportKey('jwk', keys.publicKey) + var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey); //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old - key.epub = pub.x+'.'+pub.y // new + key.epub = pub.x+'.'+pub.y; // new // ex and ey are already base64 // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -365,14 +346,16 @@ else { throw e } } dh = dh || {}; - const r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } + var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.pair; })(USE, './pair'); @@ -381,34 +364,42 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var sha256hash = USE('./sha256'); + var sha = USE('./sha256'); + var u; - SEA.sign = async (data, pair, cb) => { try { - if(data.slice - && 'SEA{' === data.slice(0,4) - && '"m":' === data.slice(4,8)){ - // TODO: This would prevent pair2 signing pair1's signature. - // So we may want to change this in the future. - // but for now, we want to prevent duplicate double signature. - if(cb){ try{ cb(data) }catch(e){console.log(e)} } - return data; + SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { + opt = opt || {}; + if(!(pair||opt).priv){ + pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); } - const pub = pair.pub - const priv = pair.priv - const jwk = S.jwk(pub, priv) - const msg = JSON.stringify(data) - const hash = await sha256hash(msg) - const sig = await shim.subtle.importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) - .then((key) => shim.subtle.sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! - const r = 'SEA'+JSON.stringify({m: msg, s: shim.Buffer.from(sig, 'binary').toString('utf8')}); + if(u === data){ throw '`undefined` not allowed.' } + var json = S.parse(data); + var check = opt.check = opt.check || json; + if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m)) + && u !== await SEA.verify(check, pair)){ // don't sign if we already signed it. + var r = S.parse(check); + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + var pub = pair.pub; + var priv = pair.priv; + var jwk = S.jwk(pub, priv); + var hash = await sha(json); + var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) + .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! + var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.sign; })(USE, './sign'); @@ -417,37 +408,76 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var sha256hash = USE('./sha256'); - var parse = USE('./parse'); + var sha = USE('./sha256'); var u; - SEA.verify = async (data, pair, cb) => { try { - const json = parse(data) + SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { + var json = S.parse(data); if(false === pair){ // don't verify! - const raw = (json !== data)? - (json.s && json.m)? parse(json.m) : data - : json; + var raw = S.parse(json.m); if(cb){ try{ cb(raw) }catch(e){console.log(e)} } return raw; } - const pub = pair.pub || pair - const jwk = S.jwk(pub) - const key = await shim.subtle.importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) - const hash = await sha256hash(json.m) - const sig = new Uint8Array(shim.Buffer.from(json.s, 'utf8')) - const check = await shim.subtle.verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) - if(!check){ throw "Signature did not match." } - const r = check? parse(json.m) : u; + 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, S.ecdsa.pair, 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! + sig = new Uint8Array(buf); + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)); + if(!check){ throw "Signature did not match." } + }catch(e){ + if(SEA.opt.fallback){ + return await SEA.opt.fall_verify(data, pair, cb, opt); + } + } + var r = check? S.parse(json.m) : u; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); // mismatched owner FOR MARTTI SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.verify; + // legacy & ossl leak mitigation: + + var knownKeys = {}; + var keyForPair = SEA.opt.slow_leak = pair => { + if (knownKeys[pair]) return knownKeys[pair]; + var jwk = S.jwk(pair); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]); + return knownKeys[pair]; + }; + + + 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 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{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! + sig = new Uint8Array(buf) + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + }catch(e){ + buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! + sig = new Uint8Array(buf) + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + } + var r = check? S.parse(json.m) : u; + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + SEA.opt.fallback = 2; + })(USE, './verify'); ;USE(function(module){ @@ -469,29 +499,37 @@ var shim = USE('./shim'); var S = USE('./settings'); var aeskey = USE('./aeskey'); + var u; - SEA.encrypt = async (data, pair, cb, opt) => { try { - var opt = opt || {}; - const key = pair.epriv || pair; - const msg = JSON.stringify(data) - const rand = {s: shim.random(8), iv: shim.random(16)}; - const ct = await aeskey(key, rand.s, opt) - .then((aes) => shim.subtle.encrypt({ // Keeping the AES key scope as private as possible... + SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try { + opt = opt || {}; + var key = (pair||opt).epriv || pair; + if(u === data){ throw '`undefined` not allowed.' } + if(!key){ + pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why}); + key = pair.epriv || pair; + } + var msg = (typeof data == 'string')? data : JSON.stringify(data); + var rand = {s: shim.random(8), iv: shim.random(16)}; + var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) - }, aes, new shim.TextEncoder().encode(msg))) - const r = 'SEA'+JSON.stringify({ - ct: shim.Buffer.from(ct, 'binary').toString('utf8'), - iv: rand.iv.toString('utf8'), - s: rand.s.toString('utf8') - }); + }, aes, new shim.TextEncoder().encode(msg))); + var r = { + ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'), + iv: rand.iv.toString(opt.encode || 'base64'), + s: rand.s.toString(opt.encode || 'base64') + } + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.encrypt; })(USE, './encrypt'); @@ -501,25 +539,39 @@ var shim = USE('./shim'); var S = USE('./settings'); var aeskey = USE('./aeskey'); - var parse = USE('./parse'); - SEA.decrypt = async (data, pair, cb, opt) => { try { - var opt = opt || {}; - const key = pair.epriv || pair; - const json = parse(data) - const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt) - .then((aes) => shim.subtle.decrypt({ // Keeping aesKey scope as private as possible... - name: opt.name || 'AES-GCM', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8')) - }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8')))) - const r = parse(new shim.TextDecoder('utf8').decode(ct)) - + SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try { + opt = opt || {}; + var key = (pair||opt).epriv || pair; + if(!key){ + pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why}); + key = pair.epriv || pair; + } + var json = S.parse(data); + var buf, bufiv, bufct; try{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64'); + bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64'); + bufct = shim.Buffer.from(json.ct, opt.encode || 'base64'); + var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... + name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv) + }, aes, new Uint8Array(bufct))); + }catch(e){ + if('utf8' === opt.encode){ throw "Could not decrypt" } + if(SEA.opt.fallback){ + opt.encode = 'utf8'; + return await SEA.decrypt(data, pair, cb, opt); + } + } + var r = S.parse(new shim.TextDecoder('utf8').decode(ct)); if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.decrypt; })(USE, './decrypt'); @@ -528,37 +580,40 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - // Derive shared secret from other's pub and my epub/epriv - SEA.secret = async (key, pair, cb) => { try { - const pub = key.epub || key - const epub = pair.epub - const epriv = pair.epriv - const ecdhSubtle = shim.ossl || shim.subtle - const pubKeyData = keysToEcdhJwk(pub) - const props = Object.assign( - S.ecdh, - { public: await ecdhSubtle.importKey(...pubKeyData, true, []) } - ) - const privKeyData = keysToEcdhJwk(epub, epriv) - const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']) - .then(async (privKey) => { + // Derive shared secret from other's pub and my epub/epriv + SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try { + opt = opt || {}; + if(!pair || !pair.epriv || !pair.epub){ + pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why}); + } + var pub = key.epub || key; + var epub = pair.epub; + var epriv = pair.epriv; + var ecdhSubtle = shim.ossl || shim.subtle; + var pubKeyData = keysToEcdhJwk(pub); + var props = Object.assign(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }); + var privKeyData = keysToEcdhJwk(epub, epriv); + var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => { // privateKey scope doesn't leak out from here! - const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]) - return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k) + var derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]); + return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k); }) - const r = derived; + var r = derived; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); - const keysToEcdhJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - const jwk = d ? { d: d } : {} + // can this be replaced with settings.jwk? + var keysToEcdhJwk = (pub, d) => { // d === priv + //var [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old + var [ x, y ] = pub.split('.') // new + var jwk = d ? { d: d } : {} return [ // Use with spread returned value... 'jwk', Object.assign( @@ -573,45 +628,20 @@ })(USE, './secret'); ;USE(function(module){ - // Old Code... - const __gky10 = USE('./shim') - const crypto = __gky10.crypto - const subtle = __gky10.subtle - const ossl = __gky10.ossl - const TextEncoder = __gky10.TextEncoder - const TextDecoder = __gky10.TextDecoder - const getRandomBytes = __gky10.random - const EasyIndexedDB = USE('./indexed') - const Buffer = USE('./buffer') - var settings = USE('./settings'); - const __gky11 = USE('./settings') - const pbKdf2 = __gky11.pbkdf2 - const ecdsaKeyProps = __gky11.ecdsa.pair - const ecdsaSignProps = __gky11.ecdsa.sign - const ecdhKeyProps = __gky11.ecdh - const keysToEcdsaJwk = __gky11.jwk - const sha1hash = USE('./sha1') - const sha256hash = USE('./sha256') - const recallCryptoKey = USE('./remember') - const parseProps = USE('./parse') - + var shim = USE('./shim'); // Practical examples about usage found from ./test/common.js - const SEA = USE('./root'); + 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.random = getRandomBytes; - - // This is easy way to use IndexedDB, all methods are Promises - // Note: Not all SEA interfaces have to support this. - SEA.EasyIndexedDB = EasyIndexedDB; + SEA.random = SEA.random || shim.random; // This is Buffer used in SEA and usable from Gun/SEA application also. // For documentation see https://nodejs.org/api/buffer.html - SEA.Buffer = Buffer; + SEA.Buffer = SEA.Buffer || USE('./buffer'); // These SEA functions support now ony Promises or // async/await (compatible) code, use those like Promises. @@ -619,11 +649,11 @@ // Creates a wrapper library around Web Crypto API // for various AES, ECDSA, PBKDF2 functions we called above. // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string) - SEA.keyid = async (pub) => { + SEA.keyid = SEA.keyid || (async (pub) => { try { // base64('base64(x):base64(y)') => Buffer(xy) const pb = Buffer.concat( - Buffer.from(pub, 'base64').toString('utf8').split(':') + pub.replace(/-/g, '+').replace(/_/g, '/').split('.') .map((t) => Buffer.from(t, 'base64')) ) // id is PGPv4 compliant raw key @@ -637,7 +667,7 @@ console.log(e) throw e } - } + }); // all done! // Obviously it is missing MANY necessary features. This is only an alpha release. // Please experiment with it, audit what I've done so far, and complain about what needs to be added. @@ -647,411 +677,13 @@ // 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 || require('./gun'); + var Gun = (SEA.window||{}).Gun || USE('./gun', 1); Gun.SEA = SEA; - SEA.Gun = Gun; + SEA.GUN = SEA.Gun = Gun; module.exports = SEA })(USE, './sea'); - ;USE(function(module){ - var SEA = USE('./sea'); - var Gun = SEA.Gun; - // This is internal func queries public key(s) for alias. - const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => { - // load all public keys associated with the username alias we want to log in with. - gunRoot.get('~@'+alias).get((rat, rev) => { - rev.off(); - if (!rat.put) { - // if no user, don't do anything. - const err = 'No user!' - Gun.log(err) - return reject({ err }) - } - // then figuring out all possible candidates having matching username - const aliases = [] - let c = 0 - // TODO: how about having real chainable map without callback ? - Gun.obj.map(rat.put, (at, pub) => { - if (!pub.slice || '~' !== pub.slice(0, 1)) { - // TODO: ... this would then be .filter((at, pub)) - return - } - ++c - // grab the account associated with this public key. - gunRoot.get(pub).get((at, ev) => { - pub = pub.slice(1) - ev.off() - --c - if (at.put){ - aliases.push({ pub, at }) - } - if (!c && (c = -1)) { - resolve(aliases) - } - }) - }) - if (!c) { - reject({ err: 'Public key does not exist!' }) - } - }) - }) - module.exports = queryGunAliases - })(USE, './query'); - - ;USE(function(module){ - var SEA = USE('./sea'); - var Gun = SEA.Gun; - const queryGunAliases = USE('./query') - const parseProps = USE('./parse') - // This is internal User authentication func. - const authenticate = async (alias, pass, gunRoot) => { - // load all public keys associated with the username alias we want to log in with. - const aliases = (await queryGunAliases(alias, gunRoot)) - .filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put) - // Got any? - if (!aliases.length) { - throw { err: 'Public key does not exist!' } - } - let err - // then attempt to log into each one until we find ours! - // (if two users have the same username AND the same password... that would be bad) - const users = await Promise.all(aliases.map(async ({ at: at, pub: pub }, i) => { - // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) - const auth = parseProps(at.put.auth) - // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here. - // SEA.verify(at.put.auth, pub).then(function(auth){ - try { - const proof = await SEA.work(pass, auth.s) - const props = { pub: pub, proof: proof, at: at } - // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. - /* - MARK TO @mhelander : pub vs epub!??? - */ - const salt = auth.salt - const sea = await SEA.decrypt(auth.ek, proof) - if (!sea) { - err = 'Failed to decrypt secret! ' + i +'/'+aliases.length; - return - } - // now we have AES decrypted the private key, from when we encrypted it with the proof at registration. - // if we were successful, then that meanswe're logged in! - const priv = sea.priv - const epriv = sea.epriv - const epub = at.put.epub - // TODO: 'salt' needed? - err = null - if(typeof window !== 'undefined'){ - var tmp = window.sessionStorage; - if(tmp && gunRoot._.opt.remember){ - window.sessionStorage.alias = alias; - window.sessionStorage.tmp = pass; - } - } - return Object.assign(props, { priv: priv, salt: salt, epub: epub, epriv: epriv }) - } catch (e) { - err = 'Failed to decrypt secret!' - throw { err } - } - })) - var user = Gun.list.map(users, function(acc){ if(acc){ return acc } }) - if (!user) { - throw { err: err || 'Public key does not exist!' } - } - return user - } - module.exports = authenticate; - })(USE, './authenticate'); - - ;USE(function(module){ - const authsettings = USE('./settings') - const SEA = USE('./sea'); - const Gun = SEA.Gun; - //const { scope: seaIndexedDb } = USE('./indexed') - // This updates sessionStorage & IndexedDB to persist authenticated "session" - const updateStorage = (proof, key, pin) => async (props) => { - if (!Gun.obj.has(props, 'alias')) { - return // No 'alias' - we're done. - } - if (authsettings.validity && proof && Gun.obj.has(props, 'iat')) { - props.proof = proof - delete props.remember // Not stored if present - - const alias = props.alias - const id = props.alias - const remember = { alias: alias, pin: pin } - - try { - const signed = await SEA.sign(JSON.stringify(remember), key) - - sessionStorage.setItem('user', alias) - sessionStorage.setItem('remember', signed) - - const encrypted = await SEA.encrypt(props, pin) - - if (encrypted) { - const auth = await SEA.sign(encrypted, key) - await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out. - await seaIndexedDb.put(id, { auth: auth }) - } - - return props - } catch (err) { - throw { err: 'Session persisting failed!' } - } - } - - // Wiping IndexedDB completely when using random PIN - await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out. - // And remove sessionStorage data - sessionStorage.removeItem('user') - sessionStorage.removeItem('remember') - - return props - } - module.exports = updateStorage - })(USE, './update'); - - ;USE(function(module){ - const SEA = USE('./sea'); - const Gun = SEA.Gun; - const Buffer = USE('./buffer') - const authsettings = USE('./settings') - const updateStorage = USE('./update') - // This internal func persists User authentication if so configured - const authPersist = async (user, proof, opts) => { - // opts = { pin: 'string' } - // no opts.pin then uses random PIN - // How this works: - // called when app bootstraps, with wanted options - // IF authsettings.validity === 0 THEN no remember-me, ever - // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB - const pin = Buffer.from( - (Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10), - 'utf8' - ).toString('base64') - - const alias = user.alias - const exp = authsettings.validity // seconds // @mhelander what is `exp`??? - - if (proof && alias && exp) { - const iat = Math.ceil(Date.now() / 1000) // seconds - const remember = Gun.obj.has(opts, 'pin') || undefined // for hook - not stored - const props = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember }) - const pub = user.pub - const epub = user.epub - const priv = user.sea.priv - const epriv = user.sea.epriv - const key = { pub: pub, priv: priv, epub: epub, epriv: epriv } - if (props instanceof Promise) { - const asyncProps = await props.then() - return await updateStorage(proof, key, pin)(asyncProps) - } - return await updateStorage(proof, key, pin)(props) - } - return await updateStorage()({ alias: 'delete' }) - } - module.exports = authPersist - })(USE, './persist'); - - ;USE(function(module){ - const authPersist = USE('./persist') - // This internal func finalizes User authentication - const finalizeLogin = async (alias, key, gunRoot, opts) => { - const user = gunRoot._.user - // add our credentials in-memory only to our root gun instance - //var tmp = user._.tag; - var opt = user._.opt; - user._ = key.at.$._; - user._.opt = opt; - //user._.tag = tmp || user._.tag; - // so that way we can use the credentials to encrypt/decrypt data - // that is input/output through gun (see below) - const pub = key.pub - const priv = key.priv - const epub = key.epub - const epriv = key.epriv - user._.is = user.is = {alias: alias, pub: pub}; - Object.assign(user._, { alias: alias, pub: pub, epub: epub, sea: { pub: pub, priv: priv, epub: epub, epriv: epriv } }) - //console.log("authorized", user._); - // persist authentication - //await authPersist(user._, key.proof, opts) // temporarily disabled - // emit an auth event, useful for page redirects and stuff. - try { - gunRoot._.on('auth', user._) - } catch (e) { - console.log('Your \'auth\' callback crashed with:', e) - } - // returns success with the user data credentials. - return user._ - } - module.exports = finalizeLogin - })(USE, './login'); - - ;USE(function(module){ - const Buffer = USE('./buffer') - const authsettings = USE('./settings') - //const { scope: seaIndexedDb } = USE('./indexed') - const queryGunAliases = USE('./query') - const parseProps = USE('./parse') - const updateStorage = USE('./update') - const SEA = USE('./sea') - const Gun = SEA.Gun; - const finalizeLogin = USE('./login') - - // This internal func recalls persisted User authentication if so configured - const authRecall = async (gunRoot, authprops) => { - // window.sessionStorage only holds signed { alias, pin } !!! - const remember = authprops || sessionStorage.getItem('remember') - const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} // @mhelander what is pIn? - const pin = pIn && Buffer.from(pIn, 'utf8').toString('base64') - // Checks for existing proof, matching alias and expiration: - const checkRememberData = async ({ proof, alias: aLias, iat, exp, remember }) => { - if (!!proof && alias === aLias) { - const checkNotExpired = (args) => { - if (Math.floor(Date.now() / 1000) < (iat + args.exp)) { - // No way hook to update 'iat' - return Object.assign(args, { iat: iat, proof: proof }) - } else { - Gun.log('Authentication expired!') - } - } - // We're not gonna give proof to hook! - const hooked = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember }) - return ((hooked instanceof Promise) - && await hooked.then(checkNotExpired)) || checkNotExpired(hooked) - } - } - const readAndDecrypt = async (data, pub, key) => - parseProps(await SEA.decrypt(await SEA.verify(data, pub), key)) - - // Already authenticated? - if (gunRoot._.user - && Gun.obj.has(gunRoot._.user._, 'pub') - && Gun.obj.has(gunRoot._.user._, 'sea')) { - return gunRoot._.user._ // Yes, we're done here. - } - // No, got persisted 'alias'? - if (!alias) { - throw { err: 'No authentication session found!' } - } - // Yes, got persisted 'remember'? - if (!remember) { - throw { // And return proof if for matching alias - err: (await seaIndexedDb.get(alias, 'auth') && authsettings.validity - && 'Missing PIN and alias!') || 'No authentication session found!' - } - } - // Yes, let's get (all?) matching aliases - const aliases = (await queryGunAliases(alias, gunRoot)) - .filter(({ pub } = {}) => !!pub) - // Got any? - if (!aliases.length) { - throw { err: 'Public key does not exist!' } - } - let err - // Yes, then attempt to log into each one until we find ours! - // (if two users have the same username AND the same password... that would be bad) - const [ { key, at, proof, pin: newPin } = {} ] = await Promise - .all(aliases.filter(({ at: { put } = {} }) => !!put) - .map(async ({ at: at, pub: pub }) => { - const readStorageData = async (args) => { - const props = args || parseProps(await SEA.verify(remember, pub, true)) - let pin = props.pin - let aLias = props.alias - - const data = (!pin && alias === aLias) - // No PIN, let's try short-term proof if for matching alias - ? await checkRememberData(props) - // Got PIN so get IndexedDB secret if signature is ok - : await checkRememberData(await readAndDecrypt(await seaIndexedDb.get(alias, 'auth'), pub, pin)) - pin = pin || data.pin - delete data.pin - return { pin: pin, data: data } - } - // got pub, try auth with pin & alias :: or unwrap Storage data... - const __gky20 = await readStorageData(pin && { pin, alias }) - const data = __gky20.data - const newPin = __gky20.pin - const proof = data.proof - - if (!proof) { - if (!data) { - err = 'No valid authentication session found!' - return - } - try { // Wipes IndexedDB silently - await updateStorage()(data) - } catch (e) {} //eslint-disable-line no-empty - err = 'Expired session!' - return - } - - try { // auth parsing or decryption fails or returns empty - silently done - const auth= at.put.auth.auth - const sea = await SEA.decrypt(auth, proof) - if (!sea) { - err = 'Failed to decrypt private key!' - return - } - const priv = sea.priv - const epriv = sea.epriv - const epub = at.put.epub - // Success! we've found our private data! - err = null - return { proof: proof, at: at, pin: newPin, key: { pub: pub, priv: priv, epriv: epriv, epub: epub } } - } catch (e) { - err = 'Failed to decrypt private key!' - return - } - }).filter((props) => !!props)) - - if (!key) { - throw { err: err || 'Public key does not exist!' } - } - - // now we have AES decrypted the private key, - // if we were successful, then that means we're logged in! - try { - await updateStorage(proof, key, newPin || pin)(key) - - const user = Object.assign(key, { at: at, proof: proof }) - const pIN = newPin || pin - - const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') } - - return await finalizeLogin(alias, user, gunRoot, pinProp) - } catch (e) { // TODO: right log message ? - Gun.log('Failed to finalize login with new password!') - const { err = '' } = e || {} - throw { err: 'Finalizing new password login failed! Reason: '+err } - } - } - module.exports = authRecall - })(USE, './recall'); - - ;USE(function(module){ - const authPersist = USE('./persist') - const authsettings = USE('./settings') - //const { scope: seaIndexedDb } = USE('./indexed') - // This internal func executes logout actions - const authLeave = async (gunRoot, alias = gunRoot._.user._.alias) => { - var user = gunRoot._.user._ || {}; - [ 'get', 'soul', 'ack', 'put', 'is', 'alias', 'pub', 'epub', 'sea' ].map((key) => delete user[key]) - if(user.$){ - delete user.$.is; - } - // Let's use default - gunRoot.user(); - // Removes persisted authentication & CryptoKeys - try { - await authPersist({ alias: alias }) - } catch (e) {} //eslint-disable-line no-empty - return { ok: 0 } - } - module.exports = authLeave - })(USE, './leave'); - ;USE(function(module){ var Gun = USE('./sea').Gun; Gun.chain.then = function(cb){ @@ -1067,9 +699,8 @@ var Gun = SEA.Gun; var then = USE('./then'); - function User(){ - this._ = {$: this} - Gun.call() + function User(root){ + this._ = {$: this}; } User.prototype = (function(){ function F(){}; F.prototype = Gun.chain; return new F() }()) // Object.create polyfill User.prototype.constructor = User; @@ -1084,7 +715,7 @@ (at = (user = at.user = gun.chain(new User))._).opt = {}; at.opt.uuid = function(cb){ var id = uuid(), pub = root.user; - if(!pub || !(pub = (pub._).sea) || !(pub = pub.pub)){ return id } + if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id } id = id + '~' + pub + '.'; if(cb && cb.call){ cb(null, id) } return id; @@ -1099,22 +730,79 @@ // TODO: This needs to be split into all separate functions. // Not just everything thrown into 'create'. - const SEA = USE('./sea') - const User = USE('./user') - const authRecall = USE('./recall') - const authsettings = USE('./settings') - const authenticate = USE('./authenticate') - const finalizeLogin = USE('./login') - const authLeave = USE('./leave') - const _initial_authsettings = USE('./settings').recall - const Gun = SEA.Gun; + var SEA = USE('./sea'); + var User = USE('./user'); + var authsettings = USE('./settings'); + var Gun = SEA.Gun; + + var noop = function(){}; - var u; // Well first we have to actually create a user. That is what this function does. - User.prototype.create = function(username, pass, cb, opt){ - // TODO: Needs to be cleaned up!!! - const gunRoot = this.back(-1) - var gun = this, cat = (gun._); + User.prototype.create = function(alias, pass, cb, opt){ + var gun = this, cat = (gun._), root = gun.back(-1); + cb = cb || noop; + if(cat.ing){ + cb({err: Gun.log("User is already being created or authenticated!"), wait: true}); + return gun; + } + cat.ing = true; + opt = opt || {}; + var act = {}, u; + act.a = function(pubs){ + act.pubs = pubs; + if(pubs && !opt.already){ + // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed. + var ack = {err: Gun.log('User already created!')}; + cat.ing = false; + cb(ack); + gun.leave(); + return; + } + act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it. + SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks. + } + act.b = function(proof){ + act.proof = proof; + SEA.pair(act.c); // now we have generated a brand new ECDSA key pair for the user account. + } + act.c = function(pair){ var tmp; + act.pair = pair || {}; + if(tmp = cat.root.user){ + tmp._.sea = pair; + tmp.is = {pub: pair.pub, epub: pair.epub, alias: alias}; + } + // the user's public key doesn't need to be signed. But everything else needs to be signed with it! // we have now automated it! clean up these extra steps now! + act.data = {pub: pair.pub}; + act.d(); + } + act.d = function(){ + act.data.alias = alias; + act.e(); + } + act.e = function(){ + act.data.epub = act.pair.epub; + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f, {raw:1}); // to keep the private key safe, we AES encrypt it with the proof of work! + } + act.f = function(auth){ + act.data.auth = JSON.stringify({ek: auth, s: act.salt}); + act.g(act.data.auth); + } + act.g = function(auth){ var tmp; + act.data.auth = act.data.auth || auth; + root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID. + root.get('~@'+alias).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))); // next up, we want to associate the alias with the public key. So we add it to the alias list. + setTimeout(function(){ // we should be able to delete this now, right? + cat.ing = false; + cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) + if(noop === cb){ gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up. + },10); + } + root.get('~@'+alias).once(act.a); + return gun; + } + // now that we have created a user, we want to authenticate them! + User.prototype.auth = function(alias, pass, cb, opt){ + var gun = this, cat = (gun._), root = gun.back(-1); cb = cb || function(){}; if(cat.ing){ cb({err: Gun.log("User is already being created or authenticated!"), wait: true}); @@ -1122,226 +810,189 @@ } cat.ing = true; opt = opt || {}; - var resolve = function(){}, reject = resolve; - // Because more than 1 user might have the same username, we treat the alias as a list of those users. - if(cb){ resolve = reject = cb } - gunRoot.get('~@'+username).get(async (at, ev) => { - ev.off() - if (at.put && !opt.already) { - // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed. - const err = 'User already created!' - Gun.log(err) - cat.ing = false; - gun.leave(); - return reject({ err: err }) + var pair = (alias && (alias.pub || alias.epub))? alias : (pass && (pass.pub || pass.epub))? pass : null; + var act = {}, u; + act.a = function(data){ + if(!data){ return act.b() } + if(!data.pub){ + var tmp = []; + Gun.node.is(data, function(v){ tmp.push(v) }) + return act.b(tmp); } - const salt = Gun.text.random(64) - // pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it. - try { - const proof = await SEA.work(pass, salt) - // this will take some short amount of time to produce a proof, which slows brute force attacks. - const pairs = await SEA.pair() - // now we have generated a brand new ECDSA key pair for the user account. - const pub = pairs.pub - const priv = pairs.priv - const epriv = pairs.epriv - // the user's public key doesn't need to be signed. But everything else needs to be signed with it! - const alias = await SEA.sign(username, pairs) - if(u === alias){ throw SEA.err } - const epub = await SEA.sign(pairs.epub, pairs) - if(u === epub){ throw SEA.err } - // to keep the private key safe, we AES encrypt it with the proof of work! - const auth = await SEA.encrypt({ priv: priv, epriv: epriv }, proof) - .then((auth) => // TODO: So signedsalt isn't needed? - // SEA.sign(salt, pairs).then((signedsalt) => - SEA.sign({ek: auth, s: salt}, pairs) - // ) - ).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); cat.ing = false; gun.leave(); reject(e) }) - const user = { alias: alias, pub: pub, epub: epub, auth: auth } - const tmp = '~'+pairs.pub; - // awesome, now we can actually save the user with their public key as their ID. - try{ - - gunRoot.get(tmp).put(user) - }catch(e){console.log(e)} - // next up, we want to associate the alias with the public key. So we add it to the alias list. - gunRoot.get('~@'+username).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))) - // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) - setTimeout(() => { cat.ing = false; resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it. - } catch (e) { - Gun.log('SEA.create failed!') - cat.ing = false; - gun.leave(); - reject(e) - } - }) - return gun; // gun chain commands must return gun chains! - } - // now that we have created a user, we want to authenticate them! - User.prototype.auth = function(alias, pass, cb, opt){ - // TODO: Needs to be cleaned up!!!! - const opts = opt || (typeof cb !== 'function' && cb) - let pin = opts && opts.pin - let newpass = opts && opts.newpass - const gunRoot = this.back(-1) - cb = typeof cb === 'function' ? cb : () => {} - newpass = newpass || (opts||{}).change; - var gun = this, cat = (gun._); - if(cat.ing){ - cb({err: "User is already being created or authenticated!", wait: true}); - return gun; + if(act.name){ return act.f(data) } + act.c((act.data = data).auth); } - cat.ing = true; - - if (!pass && pin) { (async function(){ - try { - var r = await authRecall(gunRoot, { alias: alias, pin: pin }) - return cat.ing = false, cb(r), gun; - } catch (e) { - var err = { err: 'Auth attempt failed! Reason: No session data for alias & PIN' } - return cat.ing = false, gun.leave(), cb(err), gun; - }}()) - return gun; - } - - const putErr = (msg) => (e) => { - const { message, err = message || '' } = e - Gun.log(msg) - var error = { err: msg+' Reason: '+err } - return cat.ing = false, gun.leave(), cb(error), gun; - } - - (async function(){ try { - const keys = await authenticate(alias, pass, gunRoot) - if (!keys) { - return putErr('Auth attempt failed!')({ message: 'No keys' }) + act.b = function(list){ + var get = (act.list = (act.list||[]).concat(list||[])).shift(); + if(u === get){ + if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') } + return act.err('Wrong user or password.') } - const pub = keys.pub - const priv = keys.priv - const epub = keys.epub - const epriv = keys.epriv - // we're logged in! - if (newpass) { - // password update so encrypt private key using new pwd + salt - try { - const salt = Gun.text.random(64); - const encSigAuth = await SEA.work(newpass, salt) - .then((key) => - SEA.encrypt({ priv: priv, epriv: epriv }, key) - .then((auth) => SEA.sign({ek: auth, s: salt}, keys)) - ) - const signedEpub = await SEA.sign(epub, keys) - const signedAlias = await SEA.sign(alias, keys) - const user = { - pub: pub, - alias: signedAlias, - auth: encSigAuth, - epub: signedEpub - } - // awesome, now we can update the user using public key ID. - gunRoot.get('~'+user.pub).put(user) - // then we're done - const login = finalizeLogin(alias, keys, gunRoot, { pin }) - login.catch(putErr('Failed to finalize login with new password!')) - return cat.ing = false, cb(await login), gun - } catch (e) { - return putErr('Password set attempt failed!')(e) - } - } else { - const login = finalizeLogin(alias, keys, gunRoot, { pin: pin }) - login.catch(putErr('Finalizing login failed!')) - return cat.ing = false, cb(await login), gun; + root.get(get).once(act.a); + } + act.c = function(auth){ + if(u === auth){ return act.b() } + if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // in case of legacy + SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. + } + act.d = function(proof){ + SEA.decrypt(act.auth.ek, proof, act.e, act.enc); + } + act.e = function(half){ + if(u === half){ + if(!act.enc){ // try old format + act.enc = {encode: 'utf8'}; + return act.c(act.auth); + } act.enc = null; // end backwards + return act.b(); } - } catch (e) { - return putErr('Auth attempt failed!')(e) - } }()); + act.half = half; + act.f(act.data); + } + act.f = function(data){ + if(!data || !data.pub){ return act.b() } + var tmp = act.half || {}; + act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv}); + } + act.g = function(pair){ + act.pair = pair; + var user = (root._).user, at = (user._); + var tmp = at.tag; + var upt = at.opt; + at = user._ = root.get('~'+pair.pub)._; + at.opt = upt; + // add our credentials in-memory only to our root user instance + user.is = {pub: pair.pub, epub: pair.epub, alias: alias}; + at.sea = act.pair; + cat.ing = false; + try{if(pass && !Gun.obj.has(Gun.obj.ify(cat.root.graph['~'+pair.pub].auth), ':')){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle! + opt.change? act.z() : cb(at); + if(SEA.window && ((gun.back('user')._).opt||opt).remember){ + // TODO: this needs to be modular. + try{var sS = {}; + sS = window.sessionStorage; + sS.recall = true; + sS.alias = alias; + sS.tmp = pass; + }catch(e){} + } + try{ + (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do. + //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data. + }catch(e){ + Gun.log("Your 'auth' callback crashed with:", e); + } + } + act.z = function(){ + // password update so encrypt private key using new pwd + salt + act.salt = Gun.text.random(64); // pseudo-random + SEA.work(opt.change, act.salt, act.y); + } + act.y = function(proof){ + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x, {raw:1}); + } + act.x = function(auth){ + act.w(JSON.stringify({ek: auth, s: act.salt})); + } + act.w = function(auth){ + if(opt.shuffle){ // delete in future! + console.log('migrate core account from UTF8 & shuffle'); + var tmp = Gun.obj.to(act.data); + Gun.obj.del(tmp, '_'); + tmp.auth = auth; + root.get('~'+act.pair.pub).put(tmp); + } // end delete + root.get('~'+act.pair.pub).get('auth').put(auth, cb); + } + act.err = function(e){ + var ack = {err: Gun.log(e || 'User cannot be found!')}; + cat.ing = false; + cb(ack); + } + act.plugin = function(name){ + if(!(act.name = name)){ return act.err() } + var tmp = [name]; + if('~' !== name[0]){ + tmp[1] = '~'+name; + tmp[2] = '~@'+name; + } + act.b(tmp); + } + if(pair){ + act.g(pair); + } else + if(alias){ + root.get('~@'+alias).once(act.a); + } else + if(!alias && !pass){ + SEA.name(act.plugin); + } return gun; } User.prototype.pair = function(){ + console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!"); var user = this; if(!user.is){ return false } return user._.sea; } - User.prototype.leave = async function(){ + User.prototype.leave = function(opt, cb){ var gun = this, user = (gun.back(-1)._).user; if(user){ delete user.is; delete user._.is; delete user._.sea; } - return await authLeave(this.back(-1)) + if(SEA.window){ + try{var sS = {}; + sS = window.sessionStorage; + delete sS.alias; + delete sS.tmp; + delete sS.recall; + }catch(e){}; + } + return gun; } // If authenticated user wants to delete his/her account, let's support it! - User.prototype.delete = async function(alias, pass){ - const gunRoot = this.back(-1) + User.prototype.delete = async function(alias, pass, cb){ + var gun = this, root = gun.back(-1), user = gun.back('user'); try { - const __gky40 = await authenticate(alias, pass, gunRoot) - const pub = __gky40.pub - await authLeave(gunRoot, alias) - // Delete user data - gunRoot.get('~'+pub).put(null) - // Wipe user data from memory - const { user = { _: {} } } = gunRoot._; - // TODO: is this correct way to 'logout' user from Gun.User ? - [ 'alias', 'sea', 'pub' ].map((key) => delete user._[key]) - user._.is = user.is = {} - gunRoot.user() - return { ok: 0 } // TODO: proper return codes??? + user.auth(alias, pass, function(ack){ + var pub = (user.is||{}).pub; + // Delete user data + user.map().once(function(){ this.put(null) }); + // Wipe user data from memory + user.leave(); + (cb || noop)({ok: 0}); + }); } catch (e) { - Gun.log('User.delete failed! Error:', e) - throw e // TODO: proper error codes??? + Gun.log('User.delete failed! Error:', e); } + return gun; } - // If authentication is to be remembered over reloads or browser closing, - // set validity time in minutes. - User.prototype.recall = async function(setvalidity, options){ - const gunRoot = this.back(-1) - - let validity - let opts - - var o = setvalidity; - if(o && o.sessionStorage){ - if(typeof window !== 'undefined'){ - var tmp = window.sessionStorage; - if(tmp){ - gunRoot._.opt.remember = true; - if(tmp.alias && tmp.tmp){ - gunRoot.user().auth(tmp.alias, tmp.tmp); + User.prototype.recall = function(opt, cb){ + var gun = this, root = gun.back(-1), tmp; + opt = opt || {}; + if(opt && opt.sessionStorage){ + if(SEA.window){ + try{var sS = {}; + sS = window.sessionStorage; + if(sS){ + (root._).opt.remember = true; + ((gun.back('user')._).opt||opt).remember = true; + if(sS.recall || (sS.alias && sS.tmp)){ + root.user().auth(sS.alias, sS.tmp, cb); } } + }catch(e){} } - return this; - } - - if (!Gun.val.is(setvalidity)) { - opts = setvalidity - validity = _initial_authsettings.validity - } else { - opts = options - validity = setvalidity * 60 // minutes to seconds - } - - try { - // opts = { hook: function({ iat, exp, alias, proof }) } - // iat == Date.now() when issued, exp == seconds to expire from iat - // How this works: - // called when app bootstraps, with wanted options - // IF authsettings.validity === 0 THEN no remember-me, ever - // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB - authsettings.validity = typeof validity !== 'undefined' - ? validity : _initial_authsettings.validity - authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function') - ? opts.hook : _initial_authsettings.hook - // All is good. Should we do something more with actual recalled data? - return await authRecall(gunRoot) - } catch (e) { - const err = 'No session!' - Gun.log(err) - // NOTE! It's fine to resolve recall with reason why not successful - // instead of rejecting... - return { err: (e && e.err) || err } + return gun; } + /* + TODO: copy mhelander's expiry code back in. + Although, we should check with community, + should expiry be core or a plugin? + */ + return gun; } User.prototype.alive = async function(){ const gunRoot = this.back(-1) @@ -1367,7 +1018,7 @@ User.prototype.grant = function(to, cb){ console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; - gun.back(function(at){ if(at.pub){ return } path += (at.get||'') }); + gun.back(function(at){ if(at.is){ return } path += (at.get||'') }); (async function(){ var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); sec = await SEA.decrypt(sec, pair); @@ -1388,7 +1039,7 @@ User.prototype.secret = function(data, cb){ console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; - gun.back(function(at){ if(at.pub){ return } path += (at.get||'') }); + gun.back(function(at){ if(at.is){ return } path += (at.get||'') }); (async function(){ var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); sec = await SEA.decrypt(sec, pair); @@ -1438,25 +1089,30 @@ // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!! // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT. var to = this.to, vertex = (msg.$._).put, c = 0, d; - Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node - // TODO: consider async/await use here... + Gun.node.is(msg.put, function(val, key, node){ + // only process if SEA formatted? + var tmp = Gun.obj.ify(val) || noop; + if(u !== tmp[':']){ + node[key] = SEA.opt.unpack(tmp); + return; + } + if(!SEA.opt.check(val)){ return } + c++; // for each property on the node SEA.verify(val, false, function(data){ c--; // false just extracts the plain data. - node[key] = val = data; // transform to plain value. + node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value. if(d && !c && (c = -1)){ to.next(msg) } }); }); - d = true; - if(d && !c){ to.next(msg) } - return; + if((d = true) && !c){ to.next(msg) } } // signature handles data output, it is a proxy to the security function. function signature(msg){ - if(msg.user){ + if((msg._||noop).user){ return this.to.next(msg); } var ctx = this.as; - msg.user = ctx.user; + (msg._||(msg._=function(){})).user = ctx.user; security.call(this, msg); } @@ -1469,6 +1125,7 @@ // if there is a request to read data from us, then... var soul = msg.get['#']; if(soul){ // for now, only allow direct IDs to be read. + if(typeof soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors. if('alias' === soul){ // Allow reading the list of usernames/aliases in the system? return to.next(msg); // yes. } else @@ -1496,9 +1153,9 @@ each.pubs(val, key, node, soul); return; } if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key. - each.pub(val, key, node, soul, tmp, msg.user); return; + each.pub(val, key, node, soul, tmp, (msg._||noop).user); return; } - each.any(val, key, node, soul, msg.user); return; + each.any(val, key, node, soul, (msg._||noop).user); return; return each.end({err: "No other data allowed!"}); }; each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}} @@ -1511,60 +1168,43 @@ if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys. }; - each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}} + each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}} if('pub' === key){ if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key. return each.end({err: "Account must match!"}); } check['user'+soul+key] = 1; - if(user && (user = user._) && user.sea && pub === user.pub){ - //var id = Gun.text.random(3); - SEA.sign(val, user.sea, function(data){ var rel; + if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){ + SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel; if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) } if(rel = Gun.val.link.is(val)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } - node[key] = data; + node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s}); check['user'+soul+key] = 0; each.end({ok: 1}); - }); - // TODO: Handle error!!!! + }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1}); return; } - SEA.verify(val, pub, function(data){ var rel, tmp; + SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp; + data = SEA.opt.unpack(data, key, node); if(u === data){ // make sure the signature matches the account it claims to be on. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account. } - if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ + if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } check['user'+soul+key] = 0; each.end({ok: 1}); }); }; - function relpub(s){ - if(!s){ return } - s = s.split('~'); - if(!s || !(s = s[1])){ return } - s = s.split('.'); - if(!s || 2 > s.length){ return } - s = s.slice(0,2).join('.'); - return s; - } each.any = function(val, key, node, soul, user){ var tmp, pub; - if(!user || !(user = user._) || !(user = user.sea)){ - if(tmp = relpub(soul)){ - check['any'+soul+key] = 1; - SEA.verify(val, pub = tmp, function(data){ var rel; - if(!data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } - if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ - (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; - } - check['any'+soul+key] = 0; - each.end({ok: 1}); - }); + if(!(pub = SEA.opt.pub(soul))){ + if(at.opt.secure){ + each.end({err: "Soul is missing public key at '" + key + "'."}); return; } + // TODO: Ask community if should auto-sign non user-graph data. check['any'+soul+key] = 1; at.on('secure', function(msg){ this.off(); check['any'+soul+key] = 0; @@ -1574,41 +1214,30 @@ //each.end({err: "Data cannot be modified."}); return; } - if(!(tmp = relpub(soul))){ - if(at.opt.secure){ - each.end({err: "Soul is missing public key at '" + key + "'."}); + if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){ + /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ + if((user.is||{}).pub !== p){ return p } + }); + if(other){ + each.any(val, key, node, soul); return; - } - if(val && val.slice && 'SEA{' === (val).slice(0,4)){ + }*/ + check['any'+soul+key] = 1; + SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ + if(u === data){ return each.end({err: 'My signature fail.'}) } + node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s}); check['any'+soul+key] = 0; each.end({ok: 1}); - return; - } - //check['any'+soul+key] = 1; - //SEA.sign(val, user, function(data){ - // if(u === data){ return each.end({err: 'Any signature failed.'}) } - // node[key] = data; - check['any'+soul+key] = 0; - each.end({ok: 1}); - //}); + }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1}); return; } - var pub = tmp; - if(pub !== user.pub){ - each.any(val, key, node, soul); - return; - } - /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ - if(user.pub !== p){ return p } - }); - if(other){ - each.any(val, key, node, soul); - return; - }*/ check['any'+soul+key] = 1; - SEA.sign(val, user, function(data){ - if(u === data){ return each.end({err: 'My signature fail.'}) } - node[key] = data; + SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel; + data = SEA.opt.unpack(data, key, node); + if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski ! + if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){ + (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; + } check['any'+soul+key] = 0; each.end({ok: 1}); }); @@ -1616,13 +1245,14 @@ each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb? if(each.err){ return } if((each.err = ctx.err) || ctx.no){ - console.log('NO!', each.err, msg.put); + console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI return; } if(!each.end.ed){ return } if(Gun.obj.map(check, function(no){ if(no){ return true } })){ return } + (msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out. to.next(msg); }; Gun.obj.map(msg.put, each.node); @@ -1631,6 +1261,42 @@ } to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols). } + 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.slice(0,2).join('.'); + return s; + } + SEA.opt.prep = function(d,k, n,s){ // prep for signing + return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)}; + } + 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; + } + SEA.opt.unpack = function(d, k, n){ var tmp; + if(u === d){ return } + if(d && (u !== (tmp = d[':']))){ return tmp } + 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); + if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){ + return d[2]; + } + if(s < SEA.opt.shuffle_attack){ + return d; + } + } + SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 + var noop = function(){}, u; + var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. + var rel_is = Gun.val.rel.is; + // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. })(USE, './index'); }()); \ No newline at end of file diff --git a/sea/authenticate.js b/sea/authenticate.js deleted file mode 100644 index 06c2eb26..00000000 --- a/sea/authenticate.js +++ /dev/null @@ -1,63 +0,0 @@ - - var SEA = require('./sea'); - var Gun = SEA.Gun; - const queryGunAliases = require('./query') - const parseProps = require('./parse') - // This is internal User authentication func. - const authenticate = async (alias, pass, gunRoot) => { - // load all public keys associated with the username alias we want to log in with. - const aliases = (await queryGunAliases(alias, gunRoot)) - .filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put) - // Got any? - if (!aliases.length) { - throw { err: 'Public key does not exist!' } - } - let err - // then attempt to log into each one until we find ours! - // (if two users have the same username AND the same password... that would be bad) - const [ user ] = await Promise.all(aliases.map(async ({ at: at, pub: pub }) => { - // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) - const auth = parseProps(at.put.auth) - // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here. - // SEA.verify(at.put.auth, pub).then(function(auth){ - try { - const proof = await SEA.work(pass, auth.s) - const props = { pub: pub, proof: proof, at: at } - // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. - /* - MARK TO @mhelander : pub vs epub!??? - */ - const salt = auth.salt - const sea = await SEA.decrypt(auth.ek, proof) - if (!sea) { - err = 'Failed to decrypt secret!' - return - } - // now we have AES decrypted the private key, from when we encrypted it with the proof at registration. - // if we were successful, then that meanswe're logged in! - const priv = sea.priv - const epriv = sea.epriv - const epub = at.put.epub - // TODO: 'salt' needed? - err = null - if(typeof window !== 'undefined'){ - var tmp = window.sessionStorage; - if(tmp && gunRoot._.opt.remember){ - window.sessionStorage.alias = alias; - window.sessionStorage.tmp = pass; - } - } - return Object.assign(props, { priv: priv, salt: salt, epub: epub, epriv: epriv }) - } catch (e) { - err = 'Failed to decrypt secret!' - throw { err } - } - })) - - if (!user) { - throw { err: err || 'Public key does not exist!' } - } - return user - } - module.exports = authenticate; - \ No newline at end of file diff --git a/sea/buffer.js b/sea/buffer.js index c17702b7..1854bd1c 100644 --- a/sea/buffer.js +++ b/sea/buffer.js @@ -45,7 +45,7 @@ } return buf } - const byteLength = input.byteLength + const byteLength = input.byteLength // what is going on here? FOR MARTTI const length = input.byteLength ? input.byteLength : input.length if (length) { let buf diff --git a/sea/create.js b/sea/create.js index ac14b49d..e0946b40 100644 --- a/sea/create.js +++ b/sea/create.js @@ -2,242 +2,269 @@ // TODO: This needs to be split into all separate functions. // Not just everything thrown into 'create'. - const SEA = require('./sea') - const User = require('./user') - const authRecall = require('./recall') - const authsettings = require('./settings') - const authenticate = require('./authenticate') - const finalizeLogin = require('./login') - const authLeave = require('./leave') - const _initial_authsettings = require('./settings').recall - const Gun = SEA.Gun; + var SEA = require('./sea'); + var User = require('./user'); + var authsettings = require('./settings'); + var Gun = SEA.Gun; + + var noop = function(){}; - var u; // Well first we have to actually create a user. That is what this function does. - User.prototype.create = function(username, pass, cb){ - // TODO: Needs to be cleaned up!!! - const gunRoot = this.back(-1) - var gun = this, cat = (gun._); + User.prototype.create = function(alias, pass, cb, opt){ + var gun = this, cat = (gun._), root = gun.back(-1); + cb = cb || noop; + if(cat.ing){ + cb({err: Gun.log("User is already being created or authenticated!"), wait: true}); + return gun; + } + cat.ing = true; + opt = opt || {}; + var act = {}, u; + act.a = function(pubs){ + act.pubs = pubs; + if(pubs && !opt.already){ + // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed. + var ack = {err: Gun.log('User already created!')}; + cat.ing = false; + cb(ack); + gun.leave(); + return; + } + act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it. + SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks. + } + act.b = function(proof){ + act.proof = proof; + SEA.pair(act.c); // now we have generated a brand new ECDSA key pair for the user account. + } + act.c = function(pair){ var tmp; + act.pair = pair || {}; + if(tmp = cat.root.user){ + tmp._.sea = pair; + tmp.is = {pub: pair.pub, epub: pair.epub, alias: alias}; + } + // the user's public key doesn't need to be signed. But everything else needs to be signed with it! // we have now automated it! clean up these extra steps now! + act.data = {pub: pair.pub}; + act.d(); + } + act.d = function(){ + act.data.alias = alias; + act.e(); + } + act.e = function(){ + act.data.epub = act.pair.epub; + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f, {raw:1}); // to keep the private key safe, we AES encrypt it with the proof of work! + } + act.f = function(auth){ + act.data.auth = JSON.stringify({ek: auth, s: act.salt}); + act.g(act.data.auth); + } + act.g = function(auth){ var tmp; + act.data.auth = act.data.auth || auth; + root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID. + root.get('~@'+alias).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))); // next up, we want to associate the alias with the public key. So we add it to the alias list. + setTimeout(function(){ // we should be able to delete this now, right? + cat.ing = false; + cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) + if(noop === cb){ gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up. + },10); + } + root.get('~@'+alias).once(act.a); + return gun; + } + // now that we have created a user, we want to authenticate them! + User.prototype.auth = function(alias, pass, cb, opt){ + var gun = this, cat = (gun._), root = gun.back(-1); cb = cb || function(){}; if(cat.ing){ cb({err: Gun.log("User is already being created or authenticated!"), wait: true}); return gun; } cat.ing = true; - var resolve = function(){}, reject = resolve; - // Because more than 1 user might have the same username, we treat the alias as a list of those users. - if(cb){ resolve = reject = cb } - gunRoot.get('~@'+username).get(async (at, ev) => { - ev.off() - if (at.put) { - // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed. - const err = 'User already created!' - Gun.log(err) - cat.ing = false; - gun.leave(); - return reject({ err: err }) + opt = opt || {}; + var pair = (alias && (alias.pub || alias.epub))? alias : (pass && (pass.pub || pass.epub))? pass : null; + var act = {}, u; + act.a = function(data){ + if(!data){ return act.b() } + if(!data.pub){ + var tmp = []; + Gun.node.is(data, function(v){ tmp.push(v) }) + return act.b(tmp); } - const salt = Gun.text.random(64) - // pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it. - try { - const proof = await SEA.work(pass, salt) - // this will take some short amount of time to produce a proof, which slows brute force attacks. - const pairs = await SEA.pair() - // now we have generated a brand new ECDSA key pair for the user account. - const pub = pairs.pub - const priv = pairs.priv - const epriv = pairs.epriv - // the user's public key doesn't need to be signed. But everything else needs to be signed with it! - const alias = await SEA.sign(username, pairs) - if(u === alias){ throw SEA.err } - const epub = await SEA.sign(pairs.epub, pairs) - if(u === epub){ throw SEA.err } - // to keep the private key safe, we AES encrypt it with the proof of work! - const auth = await SEA.encrypt({ priv: priv, epriv: epriv }, proof) - .then((auth) => // TODO: So signedsalt isn't needed? - // SEA.sign(salt, pairs).then((signedsalt) => - SEA.sign({ek: auth, s: salt}, pairs) - // ) - ).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); cat.ing = false; gun.leave(); reject(e) }) - const user = { alias: alias, pub: pub, epub: epub, auth: auth } - const tmp = '~'+pairs.pub; - // awesome, now we can actually save the user with their public key as their ID. - try{ - - gunRoot.get(tmp).put(user) - }catch(e){console.log(e)} - // next up, we want to associate the alias with the public key. So we add it to the alias list. - gunRoot.get('~@'+username).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))) - // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) - setTimeout(() => { cat.ing = false; resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it. - } catch (e) { - Gun.log('SEA.create failed!') - cat.ing = false; - gun.leave(); - reject(e) - } - }) - return gun; // gun chain commands must return gun chains! - } - // now that we have created a user, we want to authenticate them! - User.prototype.auth = function(alias, pass, cb, opt){ - // TODO: Needs to be cleaned up!!!! - const opts = opt || (typeof cb !== 'function' && cb) - let pin = opts && opts.pin - let newpass = opts && opts.newpass - const gunRoot = this.back(-1) - cb = typeof cb === 'function' ? cb : () => {} - newpass = newpass || (opts||{}).change; - var gun = this, cat = (gun._); - if(cat.ing){ - cb({err: "User is already being created or authenticated!", wait: true}); - return gun; + if(act.name){ return act.f(data) } + act.c((act.data = data).auth); } - cat.ing = true; - - if (!pass && pin) { (async function(){ - try { - var r = await authRecall(gunRoot, { alias: alias, pin: pin }) - return cat.ing = false, cb(r), gun; - } catch (e) { - var err = { err: 'Auth attempt failed! Reason: No session data for alias & PIN' } - return cat.ing = false, gun.leave(), cb(err), gun; - }}()) - return gun; - } - - const putErr = (msg) => (e) => { - const { message, err = message || '' } = e - Gun.log(msg) - var error = { err: msg+' Reason: '+err } - return cat.ing = false, gun.leave(), cb(error), gun; - } - - (async function(){ try { - const keys = await authenticate(alias, pass, gunRoot) - if (!keys) { - return putErr('Auth attempt failed!')({ message: 'No keys' }) + act.b = function(list){ + var get = (act.list = (act.list||[]).concat(list||[])).shift(); + if(u === get){ + if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') } + return act.err('Wrong user or password.') } - const pub = keys.pub - const priv = keys.priv - const epub = keys.epub - const epriv = keys.epriv - // we're logged in! - if (newpass) { - // password update so encrypt private key using new pwd + salt - try { - const salt = Gun.text.random(64); - const encSigAuth = await SEA.work(newpass, salt) - .then((key) => - SEA.encrypt({ priv: priv, epriv: epriv }, key) - .then((auth) => SEA.sign({ek: auth, s: salt}, keys)) - ) - const signedEpub = await SEA.sign(epub, keys) - const signedAlias = await SEA.sign(alias, keys) - const user = { - pub: pub, - alias: signedAlias, - auth: encSigAuth, - epub: signedEpub - } - // awesome, now we can update the user using public key ID. - gunRoot.get('~'+user.pub).put(user) - // then we're done - const login = finalizeLogin(alias, keys, gunRoot, { pin }) - login.catch(putErr('Failed to finalize login with new password!')) - return cat.ing = false, cb(await login), gun - } catch (e) { - return putErr('Password set attempt failed!')(e) - } - } else { - const login = finalizeLogin(alias, keys, gunRoot, { pin: pin }) - login.catch(putErr('Finalizing login failed!')) - return cat.ing = false, cb(await login), gun; + root.get(get).once(act.a); + } + act.c = function(auth){ + if(u === auth){ return act.b() } + if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // in case of legacy + SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. + } + act.d = function(proof){ + SEA.decrypt(act.auth.ek, proof, act.e, act.enc); + } + act.e = function(half){ + if(u === half){ + if(!act.enc){ // try old format + act.enc = {encode: 'utf8'}; + return act.c(act.auth); + } act.enc = null; // end backwards + return act.b(); } - } catch (e) { - return putErr('Auth attempt failed!')(e) - } }()); + act.half = half; + act.f(act.data); + } + act.f = function(data){ + if(!data || !data.pub){ return act.b() } + var tmp = act.half || {}; + act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv}); + } + act.g = function(pair){ + act.pair = pair; + var user = (root._).user, at = (user._); + var tmp = at.tag; + var upt = at.opt; + at = user._ = root.get('~'+pair.pub)._; + at.opt = upt; + // add our credentials in-memory only to our root user instance + user.is = {pub: pair.pub, epub: pair.epub, alias: alias}; + at.sea = act.pair; + cat.ing = false; + try{if(pass && !Gun.obj.has(Gun.obj.ify(cat.root.graph['~'+pair.pub].auth), ':')){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle! + opt.change? act.z() : cb(at); + if(SEA.window && ((gun.back('user')._).opt||opt).remember){ + // TODO: this needs to be modular. + try{var sS = {}; + sS = window.sessionStorage; + sS.recall = true; + sS.alias = alias; + sS.tmp = pass; + }catch(e){} + } + try{ + (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do. + //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data. + }catch(e){ + Gun.log("Your 'auth' callback crashed with:", e); + } + } + act.z = function(){ + // password update so encrypt private key using new pwd + salt + act.salt = Gun.text.random(64); // pseudo-random + SEA.work(opt.change, act.salt, act.y); + } + act.y = function(proof){ + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x, {raw:1}); + } + act.x = function(auth){ + act.w(JSON.stringify({ek: auth, s: act.salt})); + } + act.w = function(auth){ + if(opt.shuffle){ // delete in future! + console.log('migrate core account from UTF8 & shuffle'); + var tmp = Gun.obj.to(act.data); + Gun.obj.del(tmp, '_'); + tmp.auth = auth; + root.get('~'+act.pair.pub).put(tmp); + } // end delete + root.get('~'+act.pair.pub).get('auth').put(auth, cb); + } + act.err = function(e){ + var ack = {err: Gun.log(e || 'User cannot be found!')}; + cat.ing = false; + cb(ack); + } + act.plugin = function(name){ + if(!(act.name = name)){ return act.err() } + var tmp = [name]; + if('~' !== name[0]){ + tmp[1] = '~'+name; + tmp[2] = '~@'+name; + } + act.b(tmp); + } + if(pair){ + act.g(pair); + } else + if(alias){ + root.get('~@'+alias).once(act.a); + } else + if(!alias && !pass){ + SEA.name(act.plugin); + } return gun; } User.prototype.pair = function(){ + console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!"); var user = this; if(!user.is){ return false } return user._.sea; } - User.prototype.leave = async function(){ - return await authLeave(this.back(-1)) + User.prototype.leave = function(opt, cb){ + var gun = this, user = (gun.back(-1)._).user; + if(user){ + delete user.is; + delete user._.is; + delete user._.sea; + } + if(SEA.window){ + try{var sS = {}; + sS = window.sessionStorage; + delete sS.alias; + delete sS.tmp; + delete sS.recall; + }catch(e){}; + } + return gun; } // If authenticated user wants to delete his/her account, let's support it! - User.prototype.delete = async function(alias, pass){ - const gunRoot = this.back(-1) + User.prototype.delete = async function(alias, pass, cb){ + var gun = this, root = gun.back(-1), user = gun.back('user'); try { - const __gky40 = await authenticate(alias, pass, gunRoot) - const pub = __gky40.pub - await authLeave(gunRoot, alias) - // Delete user data - gunRoot.get('~'+pub).put(null) - // Wipe user data from memory - const { user = { _: {} } } = gunRoot._; - // TODO: is this correct way to 'logout' user from Gun.User ? - [ 'alias', 'sea', 'pub' ].map((key) => delete user._[key]) - user._.is = user.is = {} - gunRoot.user() - return { ok: 0 } // TODO: proper return codes??? + user.auth(alias, pass, function(ack){ + var pub = (user.is||{}).pub; + // Delete user data + user.map().once(function(){ this.put(null) }); + // Wipe user data from memory + user.leave(); + (cb || noop)({ok: 0}); + }); } catch (e) { - Gun.log('User.delete failed! Error:', e) - throw e // TODO: proper error codes??? + Gun.log('User.delete failed! Error:', e); } + return gun; } - // If authentication is to be remembered over reloads or browser closing, - // set validity time in minutes. - User.prototype.recall = async function(setvalidity, options){ - const gunRoot = this.back(-1) - - let validity - let opts - - var o = setvalidity; - if(o && o.sessionStorage){ - if(typeof window !== 'undefined'){ - var tmp = window.sessionStorage; - if(tmp){ - gunRoot._.opt.remember = true; - if(tmp.alias && tmp.tmp){ - gunRoot.user().auth(tmp.alias, tmp.tmp); + User.prototype.recall = function(opt, cb){ + var gun = this, root = gun.back(-1), tmp; + opt = opt || {}; + if(opt && opt.sessionStorage){ + if(SEA.window){ + try{var sS = {}; + sS = window.sessionStorage; + if(sS){ + (root._).opt.remember = true; + ((gun.back('user')._).opt||opt).remember = true; + if(sS.recall || (sS.alias && sS.tmp)){ + root.user().auth(sS.alias, sS.tmp, cb); } } + }catch(e){} } - return this; - } - - if (!Gun.val.is(setvalidity)) { - opts = setvalidity - validity = _initial_authsettings.validity - } else { - opts = options - validity = setvalidity * 60 // minutes to seconds - } - - try { - // opts = { hook: function({ iat, exp, alias, proof }) } - // iat == Date.now() when issued, exp == seconds to expire from iat - // How this works: - // called when app bootstraps, with wanted options - // IF authsettings.validity === 0 THEN no remember-me, ever - // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB - authsettings.validity = typeof validity !== 'undefined' - ? validity : _initial_authsettings.validity - authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function') - ? opts.hook : _initial_authsettings.hook - // All is good. Should we do something more with actual recalled data? - return await authRecall(gunRoot) - } catch (e) { - const err = 'No session!' - Gun.log(err) - // NOTE! It's fine to resolve recall with reason why not successful - // instead of rejecting... - return { err: (e && e.err) || err } + return gun; } + /* + TODO: copy mhelander's expiry code back in. + Although, we should check with community, + should expiry be core or a plugin? + */ + return gun; } User.prototype.alive = async function(){ const gunRoot = this.back(-1) @@ -263,7 +290,7 @@ User.prototype.grant = function(to, cb){ console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; - gun.back(function(at){ if(at.pub){ return } path += (at.get||'') }); + gun.back(function(at){ if(at.is){ return } path += (at.get||'') }); (async function(){ var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); sec = await SEA.decrypt(sec, pair); @@ -284,7 +311,7 @@ User.prototype.secret = function(data, cb){ console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; - gun.back(function(at){ if(at.pub){ return } path += (at.get||'') }); + gun.back(function(at){ if(at.is){ return } path += (at.get||'') }); (async function(){ var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); sec = await SEA.decrypt(sec, pair); diff --git a/sea/decrypt.js b/sea/decrypt.js index 2625eccd..736fd4cc 100644 --- a/sea/decrypt.js +++ b/sea/decrypt.js @@ -3,25 +3,39 @@ var shim = require('./shim'); var S = require('./settings'); var aeskey = require('./aeskey'); - var parse = require('./parse'); - SEA.decrypt = async (data, pair, cb, opt) => { try { - var opt = opt || {}; - const key = pair.epriv || pair; - const json = parse(data) - const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt) - .then((aes) => shim.subtle.decrypt({ // Keeping aesKey scope as private as possible... - name: opt.name || 'AES-GCM', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8')) - }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8')))) - const r = parse(new shim.TextDecoder('utf8').decode(ct)) - + SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try { + opt = opt || {}; + var key = (pair||opt).epriv || pair; + if(!key){ + pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why}); + key = pair.epriv || pair; + } + var json = S.parse(data); + var buf, bufiv, bufct; try{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64'); + bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64'); + bufct = shim.Buffer.from(json.ct, opt.encode || 'base64'); + var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... + name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv) + }, aes, new Uint8Array(bufct))); + }catch(e){ + if('utf8' === opt.encode){ throw "Could not decrypt" } + if(SEA.opt.fallback){ + opt.encode = 'utf8'; + return await SEA.decrypt(data, pair, cb, opt); + } + } + var r = S.parse(new shim.TextDecoder('utf8').decode(ct)); if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.decrypt; \ No newline at end of file diff --git a/sea/encrypt.js b/sea/encrypt.js index c83cd339..dc31cc04 100644 --- a/sea/encrypt.js +++ b/sea/encrypt.js @@ -3,29 +3,37 @@ var shim = require('./shim'); var S = require('./settings'); var aeskey = require('./aeskey'); + var u; - SEA.encrypt = async (data, pair, cb, opt) => { try { - var opt = opt || {}; - const key = pair.epriv || pair; - const msg = JSON.stringify(data) - const rand = {s: shim.random(8), iv: shim.random(16)}; - const ct = await aeskey(key, rand.s, opt) - .then((aes) => shim.subtle.encrypt({ // Keeping the AES key scope as private as possible... + SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try { + opt = opt || {}; + var key = (pair||opt).epriv || pair; + if(u === data){ throw '`undefined` not allowed.' } + if(!key){ + pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why}); + key = pair.epriv || pair; + } + var msg = (typeof data == 'string')? data : JSON.stringify(data); + var rand = {s: shim.random(8), iv: shim.random(16)}; + var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) - }, aes, new shim.TextEncoder().encode(msg))) - const r = 'SEA'+JSON.stringify({ - ct: shim.Buffer.from(ct, 'binary').toString('utf8'), - iv: rand.iv.toString('utf8'), - s: rand.s.toString('utf8') - }); + }, aes, new shim.TextEncoder().encode(msg))); + var r = { + ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'), + iv: rand.iv.toString(opt.encode || 'base64'), + s: rand.s.toString(opt.encode || 'base64') + } + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.encrypt; \ No newline at end of file diff --git a/sea/https.js b/sea/https.js index 1c7fddf9..cb7916dc 100644 --- a/sea/https.js +++ b/sea/https.js @@ -1,10 +1,10 @@ var SEA = require('./root'); - if(SEA.window){ + try{ if(SEA.window){ if(location.protocol.indexOf('s') < 0 && location.host.indexOf('localhost') < 0 && location.protocol.indexOf('file:') < 0){ location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS! } - } + } }catch(e){} \ No newline at end of file diff --git a/sea/index.js b/sea/index.js index c11292e4..a1d880af 100644 --- a/sea/index.js +++ b/sea/index.js @@ -31,25 +31,30 @@ // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!! // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT. var to = this.to, vertex = (msg.$._).put, c = 0, d; - Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node - // TODO: consider async/await use here... + Gun.node.is(msg.put, function(val, key, node){ + // only process if SEA formatted? + var tmp = Gun.obj.ify(val) || noop; + if(u !== tmp[':']){ + node[key] = SEA.opt.unpack(tmp); + return; + } + if(!SEA.opt.check(val)){ return } + c++; // for each property on the node SEA.verify(val, false, function(data){ c--; // false just extracts the plain data. - node[key] = val = data; // transform to plain value. + node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value. if(d && !c && (c = -1)){ to.next(msg) } }); }); - d = true; - if(d && !c){ to.next(msg) } - return; + if((d = true) && !c){ to.next(msg) } } // signature handles data output, it is a proxy to the security function. function signature(msg){ - if(msg.user){ + if((msg._||noop).user){ return this.to.next(msg); } var ctx = this.as; - msg.user = ctx.user; + (msg._||(msg._=function(){})).user = ctx.user; security.call(this, msg); } @@ -62,6 +67,7 @@ // if there is a request to read data from us, then... var soul = msg.get['#']; if(soul){ // for now, only allow direct IDs to be read. + if(typeof soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors. if('alias' === soul){ // Allow reading the list of usernames/aliases in the system? return to.next(msg); // yes. } else @@ -89,9 +95,9 @@ each.pubs(val, key, node, soul); return; } if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key. - each.pub(val, key, node, soul, tmp, msg.user); return; + each.pub(val, key, node, soul, tmp, (msg._||noop).user); return; } - each.any(val, key, node, soul, msg.user); return; + each.any(val, key, node, soul, (msg._||noop).user); return; return each.end({err: "No other data allowed!"}); }; each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}} @@ -104,60 +110,43 @@ if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys. }; - each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}} + each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}} if('pub' === key){ if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key. return each.end({err: "Account must match!"}); } check['user'+soul+key] = 1; - if(user && (user = user._) && user.sea && pub === user.pub){ - //var id = Gun.text.random(3); - SEA.sign(val, user.sea, function(data){ var rel; + if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){ + SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel; if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) } if(rel = Gun.val.link.is(val)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } - node[key] = data; + node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s}); check['user'+soul+key] = 0; each.end({ok: 1}); - }); - // TODO: Handle error!!!! + }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1}); return; } - SEA.verify(val, pub, function(data){ var rel, tmp; + SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp; + data = SEA.opt.unpack(data, key, node); if(u === data){ // make sure the signature matches the account it claims to be on. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account. } - if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ + if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } check['user'+soul+key] = 0; each.end({ok: 1}); }); }; - function relpub(s){ - if(!s){ return } - s = s.split('~'); - if(!s || !(s = s[1])){ return } - s = s.split('.'); - if(!s || 2 > s.length){ return } - s = s.slice(0,2).join('.'); - return s; - } each.any = function(val, key, node, soul, user){ var tmp, pub; - if(!user || !(user = user._) || !(user = user.sea)){ - if(tmp = relpub(soul)){ - check['any'+soul+key] = 1; - SEA.verify(val, pub = tmp, function(data){ var rel; - if(!data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } - if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ - (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; - } - check['any'+soul+key] = 0; - each.end({ok: 1}); - }); + if(!(pub = SEA.opt.pub(soul))){ + if(at.opt.secure){ + each.end({err: "Soul is missing public key at '" + key + "'."}); return; } + // TODO: Ask community if should auto-sign non user-graph data. check['any'+soul+key] = 1; at.on('secure', function(msg){ this.off(); check['any'+soul+key] = 0; @@ -167,41 +156,30 @@ //each.end({err: "Data cannot be modified."}); return; } - if(!(tmp = relpub(soul))){ - if(at.opt.secure){ - each.end({err: "Soul is missing public key at '" + key + "'."}); + if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){ + /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ + if((user.is||{}).pub !== p){ return p } + }); + if(other){ + each.any(val, key, node, soul); return; - } - if(val && val.slice && 'SEA{' === (val).slice(0,4)){ + }*/ + check['any'+soul+key] = 1; + SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ + if(u === data){ return each.end({err: 'My signature fail.'}) } + node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s}); check['any'+soul+key] = 0; each.end({ok: 1}); - return; - } - //check['any'+soul+key] = 1; - //SEA.sign(val, user, function(data){ - // if(u === data){ return each.end({err: 'Any signature failed.'}) } - // node[key] = data; - check['any'+soul+key] = 0; - each.end({ok: 1}); - //}); + }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1}); return; } - var pub = tmp; - if(pub !== user.pub){ - each.any(val, key, node, soul); - return; - } - /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ - if(user.pub !== p){ return p } - }); - if(other){ - each.any(val, key, node, soul); - return; - }*/ check['any'+soul+key] = 1; - SEA.sign(val, user, function(data){ - if(u === data){ return each.end({err: 'My signature fail.'}) } - node[key] = data; + SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel; + data = SEA.opt.unpack(data, key, node); + if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski ! + if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){ + (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; + } check['any'+soul+key] = 0; each.end({ok: 1}); }); @@ -209,13 +187,14 @@ each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb? if(each.err){ return } if((each.err = ctx.err) || ctx.no){ - console.log('NO!', each.err, msg.put); + console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI return; } if(!each.end.ed){ return } if(Gun.obj.map(check, function(no){ if(no){ return true } })){ return } + (msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out. to.next(msg); }; Gun.obj.map(msg.put, each.node); @@ -224,5 +203,41 @@ } to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols). } + 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.slice(0,2).join('.'); + return s; + } + SEA.opt.prep = function(d,k, n,s){ // prep for signing + return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)}; + } + 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; + } + SEA.opt.unpack = function(d, k, n){ var tmp; + if(u === d){ return } + if(d && (u !== (tmp = d[':']))){ return tmp } + 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); + if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){ + return d[2]; + } + if(s < SEA.opt.shuffle_attack){ + return d; + } + } + SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 + var noop = function(){}, u; + var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. + var rel_is = Gun.val.rel.is; + // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. \ No newline at end of file diff --git a/sea/leave.js b/sea/leave.js deleted file mode 100644 index df497b8f..00000000 --- a/sea/leave.js +++ /dev/null @@ -1,21 +0,0 @@ - - const authPersist = require('./persist') - const authsettings = require('./settings') - //const { scope: seaIndexedDb } = require('./indexed') - // This internal func executes logout actions - const authLeave = async (gunRoot, alias = gunRoot._.user._.alias) => { - var user = gunRoot._.user._ || {}; - [ 'get', 'soul', 'ack', 'put', 'is', 'alias', 'pub', 'epub', 'sea' ].map((key) => delete user[key]) - if(user.$){ - delete user.$.is; - } - // Let's use default - gunRoot.user(); - // Removes persisted authentication & CryptoKeys - try { - await authPersist({ alias: alias }) - } catch (e) {} //eslint-disable-line no-empty - return { ok: 0 } - } - module.exports = authLeave - \ No newline at end of file diff --git a/sea/login.js b/sea/login.js deleted file mode 100644 index 6d9fa0c4..00000000 --- a/sea/login.js +++ /dev/null @@ -1,33 +0,0 @@ - - const authPersist = require('./persist') - // This internal func finalizes User authentication - const finalizeLogin = async (alias, key, gunRoot, opts) => { - const user = gunRoot._.user - // add our credentials in-memory only to our root gun instance - //var tmp = user._.tag; - var opt = user._.opt; - user._ = key.at.$._; - user._.opt = opt; - //user._.tag = tmp || user._.tag; - // so that way we can use the credentials to encrypt/decrypt data - // that is input/output through gun (see below) - const pub = key.pub - const priv = key.priv - const epub = key.epub - const epriv = key.epriv - user._.is = user.is = {alias: alias, pub: pub}; - Object.assign(user._, { alias: alias, pub: pub, epub: epub, sea: { pub: pub, priv: priv, epub: epub, epriv: epriv } }) - //console.log("authorized", user._); - // persist authentication - //await authPersist(user._, key.proof, opts) // temporarily disabled - // emit an auth event, useful for page redirects and stuff. - try { - gunRoot._.on('auth', user._) - } catch (e) { - console.log('Your \'auth\' callback crashed with:', e) - } - // returns success with the user data credentials. - return user._ - } - module.exports = finalizeLogin - \ No newline at end of file diff --git a/sea/pair.js b/sea/pair.js index 793083eb..3a59e177 100644 --- a/sea/pair.js +++ b/sea/pair.js @@ -2,22 +2,32 @@ var SEA = require('./root'); var shim = require('./shim'); var S = require('./settings'); - var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer; + + SEA.name = SEA.name || (async (cb, opt) => { try { + if(cb){ try{ cb() }catch(e){console.log(e)} } + return; + } catch(e) { + console.log(e); + SEA.err = e; + if(SEA.throw){ throw e } + if(cb){ cb() } + return; + }}); //SEA.pair = async (data, proof, cb) => { try { - SEA.pair = async (cb) => { try { + SEA.pair = SEA.pair || (async (cb, opt) => { try { - const ecdhSubtle = shim.ossl || shim.subtle + var ecdhSubtle = shim.ossl || shim.subtle; // First: ECDSA keys for signing/verifying... var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) .then(async (keys) => { // privateKey scope doesn't leak out from here! //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) - const key = {}; + var key = {}; key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; - const pub = await shim.subtle.exportKey('jwk', keys.publicKey) + var pub = await shim.subtle.exportKey('jwk', keys.publicKey); //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old - key.pub = pub.x+'.'+pub.y // new + key.pub = pub.x+'.'+pub.y; // new // x and y are already base64 // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -32,11 +42,11 @@ var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) .then(async (keys) => { // privateKey scope doesn't leak out from here! - const key = {}; + var key = {}; key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; - const pub = await ecdhSubtle.exportKey('jwk', keys.publicKey) + var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey); //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old - key.epub = pub.x+'.'+pub.y // new + key.epub = pub.x+'.'+pub.y; // new // ex and ey are already base64 // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -48,14 +58,16 @@ else { throw e } } dh = dh || {}; - const r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } + var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.pair; \ No newline at end of file diff --git a/sea/parse.js b/sea/parse.js deleted file mode 100644 index 7372bc1f..00000000 --- a/sea/parse.js +++ /dev/null @@ -1,11 +0,0 @@ - - module.exports = (props) => { - try { - if(props.slice && 'SEA{' === props.slice(0,4)){ - props = props.slice(3); - } - return props.slice ? JSON.parse(props) : props - } catch (e) {} //eslint-disable-line no-empty - return props - } - \ No newline at end of file diff --git a/sea/persist.js b/sea/persist.js deleted file mode 100644 index 511decc8..00000000 --- a/sea/persist.js +++ /dev/null @@ -1,41 +0,0 @@ - - const SEA = require('./sea'); - const Gun = SEA.Gun; - const Buffer = require('./buffer') - const authsettings = require('./settings') - const updateStorage = require('./update') - // This internal func persists User authentication if so configured - const authPersist = async (user, proof, opts) => { - // opts = { pin: 'string' } - // no opts.pin then uses random PIN - // How this works: - // called when app bootstraps, with wanted options - // IF authsettings.validity === 0 THEN no remember-me, ever - // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB - const pin = Buffer.from( - (Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10), - 'utf8' - ).toString('base64') - - const alias = user.alias - const exp = authsettings.validity // seconds // @mhelander what is `exp`??? - - if (proof && alias && exp) { - const iat = Math.ceil(Date.now() / 1000) // seconds - const remember = Gun.obj.has(opts, 'pin') || undefined // for hook - not stored - const props = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember }) - const pub = user.pub - const epub = user.epub - const priv = user.sea.priv - const epriv = user.sea.epriv - const key = { pub: pub, priv: priv, epub: epub, epriv: epriv } - if (props instanceof Promise) { - const asyncProps = await props.then() - return await updateStorage(proof, key, pin)(asyncProps) - } - return await updateStorage(proof, key, pin)(props) - } - return await updateStorage()({ alias: 'delete' }) - } - module.exports = authPersist - \ No newline at end of file diff --git a/sea/query.js b/sea/query.js deleted file mode 100644 index b34e3891..00000000 --- a/sea/query.js +++ /dev/null @@ -1,44 +0,0 @@ - - var SEA = require('./sea'); - var Gun = SEA.Gun; - // This is internal func queries public key(s) for alias. - const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => { - // load all public keys associated with the username alias we want to log in with. - gunRoot.get('~@'+alias).get((rat, rev) => { - rev.off(); - if (!rat.put) { - // if no user, don't do anything. - const err = 'No user!' - Gun.log(err) - return reject({ err }) - } - // then figuring out all possible candidates having matching username - const aliases = [] - let c = 0 - // TODO: how about having real chainable map without callback ? - Gun.obj.map(rat.put, (at, pub) => { - if (!pub.slice || '~' !== pub.slice(0, 1)) { - // TODO: ... this would then be .filter((at, pub)) - return - } - ++c - // grab the account associated with this public key. - gunRoot.get(pub).get((at, ev) => { - pub = pub.slice(1) - ev.off() - --c - if (at.put){ - aliases.push({ pub, at }) - } - if (!c && (c = -1)) { - resolve(aliases) - } - }) - }) - if (!c) { - reject({ err: 'Public key does not exist!' }) - } - }) - }) - module.exports = queryGunAliases - \ No newline at end of file diff --git a/sea/recall.js b/sea/recall.js deleted file mode 100644 index 998f39de..00000000 --- a/sea/recall.js +++ /dev/null @@ -1,141 +0,0 @@ - - const Buffer = require('./buffer') - const authsettings = require('./settings') - //const { scope: seaIndexedDb } = require('./indexed') - const queryGunAliases = require('./query') - const parseProps = require('./parse') - const updateStorage = require('./update') - const SEA = require('./sea') - const Gun = SEA.Gun; - const finalizeLogin = require('./login') - - // This internal func recalls persisted User authentication if so configured - const authRecall = async (gunRoot, authprops) => { - // window.sessionStorage only holds signed { alias, pin } !!! - const remember = authprops || sessionStorage.getItem('remember') - const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} // @mhelander what is pIn? - const pin = pIn && Buffer.from(pIn, 'utf8').toString('base64') - // Checks for existing proof, matching alias and expiration: - const checkRememberData = async ({ proof, alias: aLias, iat, exp, remember }) => { - if (!!proof && alias === aLias) { - const checkNotExpired = (args) => { - if (Math.floor(Date.now() / 1000) < (iat + args.exp)) { - // No way hook to update 'iat' - return Object.assign(args, { iat: iat, proof: proof }) - } else { - Gun.log('Authentication expired!') - } - } - // We're not gonna give proof to hook! - const hooked = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember }) - return ((hooked instanceof Promise) - && await hooked.then(checkNotExpired)) || checkNotExpired(hooked) - } - } - const readAndDecrypt = async (data, pub, key) => - parseProps(await SEA.decrypt(await SEA.verify(data, pub), key)) - - // Already authenticated? - if (gunRoot._.user - && Gun.obj.has(gunRoot._.user._, 'pub') - && Gun.obj.has(gunRoot._.user._, 'sea')) { - return gunRoot._.user._ // Yes, we're done here. - } - // No, got persisted 'alias'? - if (!alias) { - throw { err: 'No authentication session found!' } - } - // Yes, got persisted 'remember'? - if (!remember) { - throw { // And return proof if for matching alias - err: (await seaIndexedDb.get(alias, 'auth') && authsettings.validity - && 'Missing PIN and alias!') || 'No authentication session found!' - } - } - // Yes, let's get (all?) matching aliases - const aliases = (await queryGunAliases(alias, gunRoot)) - .filter(({ pub } = {}) => !!pub) - // Got any? - if (!aliases.length) { - throw { err: 'Public key does not exist!' } - } - let err - // Yes, then attempt to log into each one until we find ours! - // (if two users have the same username AND the same password... that would be bad) - const [ { key, at, proof, pin: newPin } = {} ] = await Promise - .all(aliases.filter(({ at: { put } = {} }) => !!put) - .map(async ({ at: at, pub: pub }) => { - const readStorageData = async (args) => { - const props = args || parseProps(await SEA.verify(remember, pub, true)) - let pin = props.pin - let aLias = props.alias - - const data = (!pin && alias === aLias) - // No PIN, let's try short-term proof if for matching alias - ? await checkRememberData(props) - // Got PIN so get IndexedDB secret if signature is ok - : await checkRememberData(await readAndDecrypt(await seaIndexedDb.get(alias, 'auth'), pub, pin)) - pin = pin || data.pin - delete data.pin - return { pin: pin, data: data } - } - // got pub, try auth with pin & alias :: or unwrap Storage data... - const __gky20 = await readStorageData(pin && { pin, alias }) - const data = __gky20.data - const newPin = __gky20.pin - const proof = data.proof - - if (!proof) { - if (!data) { - err = 'No valid authentication session found!' - return - } - try { // Wipes IndexedDB silently - await updateStorage()(data) - } catch (e) {} //eslint-disable-line no-empty - err = 'Expired session!' - return - } - - try { // auth parsing or decryption fails or returns empty - silently done - const auth= at.put.auth.auth - const sea = await SEA.decrypt(auth, proof) - if (!sea) { - err = 'Failed to decrypt private key!' - return - } - const priv = sea.priv - const epriv = sea.epriv - const epub = at.put.epub - // Success! we've found our private data! - err = null - return { proof: proof, at: at, pin: newPin, key: { pub: pub, priv: priv, epriv: epriv, epub: epub } } - } catch (e) { - err = 'Failed to decrypt private key!' - return - } - }).filter((props) => !!props)) - - if (!key) { - throw { err: err || 'Public key does not exist!' } - } - - // now we have AES decrypted the private key, - // if we were successful, then that means we're logged in! - try { - await updateStorage(proof, key, newPin || pin)(key) - - const user = Object.assign(key, { at: at, proof: proof }) - const pIN = newPin || pin - - const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') } - - return await finalizeLogin(alias, user, gunRoot, pinProp) - } catch (e) { // TODO: right log message ? - Gun.log('Failed to finalize login with new password!') - const { err = '' } = e || {} - throw { err: 'Finalizing new password login failed! Reason: '+err } - } - } - module.exports = authRecall - \ No newline at end of file diff --git a/sea/root.js b/sea/root.js index 23cd2bcc..7b99eb97 100644 --- a/sea/root.js +++ b/sea/root.js @@ -1,10 +1,16 @@ // Security, Encryption, and Authorization: SEA.js - // MANDATORY READING: http://gun.js.org/explainers/data/security.html + // MANDATORY READING: https://gun.eco/explainers/data/security.html + // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH. // THIS IS AN EARLY ALPHA! - function SEA(){} - if(typeof window !== "undefined"){ SEA.window = window } + if(typeof window !== "undefined"){ module.window = window } + var tmp = module.window || module; + var SEA = tmp.SEA || {}; + + if(SEA.window = module.window){ SEA.window.SEA = SEA } + + try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){} module.exports = SEA; \ No newline at end of file diff --git a/sea/sea.js b/sea/sea.js index e2c4aa35..75f8b0dd 100644 --- a/sea/sea.js +++ b/sea/sea.js @@ -1,43 +1,18 @@ - // Old Code... - const __gky10 = require('./shim') - const crypto = __gky10.crypto - const subtle = __gky10.subtle - const ossl = __gky10.ossl - const TextEncoder = __gky10.TextEncoder - const TextDecoder = __gky10.TextDecoder - const getRandomBytes = __gky10.random - const EasyIndexedDB = require('./indexed') - const Buffer = require('./buffer') - var settings = require('./settings'); - const __gky11 = require('./settings') - const pbKdf2 = __gky11.pbkdf2 - const ecdsaKeyProps = __gky11.ecdsa.pair - const ecdsaSignProps = __gky11.ecdsa.sign - const ecdhKeyProps = __gky11.ecdh - const keysToEcdsaJwk = __gky11.jwk - const sha1hash = require('./sha1') - const sha256hash = require('./sha256') - const recallCryptoKey = require('./remember') - const parseProps = require('./parse') - + var shim = require('./shim'); // Practical examples about usage found from ./test/common.js - const SEA = require('./root'); + var SEA = require('./root'); SEA.work = require('./work'); SEA.sign = require('./sign'); SEA.verify = require('./verify'); SEA.encrypt = require('./encrypt'); SEA.decrypt = require('./decrypt'); - SEA.random = getRandomBytes; - - // This is easy way to use IndexedDB, all methods are Promises - // Note: Not all SEA interfaces have to support this. - SEA.EasyIndexedDB = EasyIndexedDB; + SEA.random = SEA.random || shim.random; // This is Buffer used in SEA and usable from Gun/SEA application also. // For documentation see https://nodejs.org/api/buffer.html - SEA.Buffer = Buffer; + SEA.Buffer = SEA.Buffer || require('./buffer'); // These SEA functions support now ony Promises or // async/await (compatible) code, use those like Promises. @@ -45,11 +20,11 @@ // Creates a wrapper library around Web Crypto API // for various AES, ECDSA, PBKDF2 functions we called above. // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string) - SEA.keyid = async (pub) => { + SEA.keyid = SEA.keyid || (async (pub) => { try { // base64('base64(x):base64(y)') => Buffer(xy) const pb = Buffer.concat( - Buffer.from(pub, 'base64').toString('utf8').split(':') + pub.replace(/-/g, '+').replace(/_/g, '/').split('.') .map((t) => Buffer.from(t, 'base64')) ) // id is PGPv4 compliant raw key @@ -63,7 +38,7 @@ console.log(e) throw e } - } + }); // all done! // Obviously it is missing MANY necessary features. This is only an alpha release. // Please experiment with it, audit what I've done so far, and complain about what needs to be added. @@ -73,9 +48,9 @@ // 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 || require('./gun'); + var Gun = (SEA.window||{}).Gun || require('./gun', 1); Gun.SEA = SEA; - SEA.Gun = Gun; + SEA.GUN = SEA.Gun = Gun; module.exports = SEA \ No newline at end of file diff --git a/sea/secret.js b/sea/secret.js index 915965da..4009ee31 100644 --- a/sea/secret.js +++ b/sea/secret.js @@ -2,37 +2,40 @@ var SEA = require('./root'); var shim = require('./shim'); var S = require('./settings'); - // Derive shared secret from other's pub and my epub/epriv - SEA.secret = async (key, pair, cb) => { try { - const pub = key.epub || key - const epub = pair.epub - const epriv = pair.epriv - const ecdhSubtle = shim.ossl || shim.subtle - const pubKeyData = keysToEcdhJwk(pub) - const props = Object.assign( - S.ecdh, - { public: await ecdhSubtle.importKey(...pubKeyData, true, []) } - ) - const privKeyData = keysToEcdhJwk(epub, epriv) - const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']) - .then(async (privKey) => { + // Derive shared secret from other's pub and my epub/epriv + SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try { + opt = opt || {}; + if(!pair || !pair.epriv || !pair.epub){ + pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why}); + } + var pub = key.epub || key; + var epub = pair.epub; + var epriv = pair.epriv; + var ecdhSubtle = shim.ossl || shim.subtle; + var pubKeyData = keysToEcdhJwk(pub); + var props = Object.assign(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }); + var privKeyData = keysToEcdhJwk(epub, epriv); + var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => { // privateKey scope doesn't leak out from here! - const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]) - return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k) + var derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]); + return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k); }) - const r = derived; + var r = derived; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); - const keysToEcdhJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - const jwk = d ? { d: d } : {} + // can this be replaced with settings.jwk? + var keysToEcdhJwk = (pub, d) => { // d === priv + //var [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old + var [ x, y ] = pub.split('.') // new + var jwk = d ? { d: d } : {} return [ // Use with spread returned value... 'jwk', Object.assign( diff --git a/sea/settings.js b/sea/settings.js index c8d662bc..8e68e8c5 100644 --- a/sea/settings.js +++ b/sea/settings.js @@ -1,39 +1,37 @@ - const Buffer = require('./buffer') - const settings = {} - // Encryption parameters - const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 } + var SEA = require('./root'); + var Buffer = require('./buffer'); + var s = {}; + s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64}; + s.ecdsa = { + pair: {name: 'ECDSA', namedCurve: 'P-256'}, + sign: {name: 'ECDSA', hash: {name: 'SHA-256'}} + }; + s.ecdh = {name: 'ECDH', namedCurve: 'P-256'}; - const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } } - const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' } - const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' } - - const _initial_authsettings = { - validity: 12 * 60 * 60, // internally in seconds : 12 hours - hook: (props) => props // { iat, exp, alias, remember } - // or return new Promise((resolve, reject) => resolve(props) - } - // These are used to persist user's authentication "session" - const authsettings = Object.assign({}, _initial_authsettings) // This creates Web Cryptography API compliant JWK for sign/verify purposes - const keysToEcdsaJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true } + s.jwk = function(pub, d){ // d === priv + pub = pub.split('.'); + var x = pub[0], y = pub[1]; + var jwk = {kty: "EC", crv: "P-256", x: x, y: y, ext: true}; jwk.key_ops = d ? ['sign'] : ['verify']; if(d){ jwk.d = d } return jwk; + }; + s.recall = { + validity: 12 * 60 * 60, // internally in seconds : 12 hours + hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props) + }; + + s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) } + s.parse = function p(t){ try { + var yes = (typeof t == 'string'); + if(yes && 'SEA{' === t.slice(0,4)){ t = t.slice(3) } + return yes ? JSON.parse(t) : t; + } catch (e) {} + return t; } - Object.assign(settings, { - pbkdf2: pbkdf2, - ecdsa: { - pair: ecdsaKeyProps, - sign: ecdsaSignProps - }, - ecdh: ecdhKeyProps, - jwk: keysToEcdsaJwk, - recall: authsettings - }) - module.exports = settings + SEA.opt = s; + module.exports = s \ No newline at end of file diff --git a/sea/sha1.js b/sea/sha1.js index c18ba691..9272cffc 100644 --- a/sea/sha1.js +++ b/sea/sha1.js @@ -2,7 +2,7 @@ // This internal func returns SHA-1 hashed data for KeyID generation const __shim = require('./shim') const subtle = __shim.subtle - const ossl = __shim.ossl ? __shim.__ossl : subtle + const ossl = __shim.ossl ? __shim.ossl : subtle const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b)) module.exports = sha1hash \ No newline at end of file diff --git a/sea/sha256.js b/sea/sha256.js index 43f97dc9..c3182317 100644 --- a/sea/sha256.js +++ b/sea/sha256.js @@ -1,15 +1,8 @@ - const { - subtle, ossl = subtle, random: getRandomBytes, TextEncoder, TextDecoder - } = require('./shim') - const Buffer = require('./buffer') - const parse = require('./parse') - const { pbkdf2 } = require('./settings') - // This internal func returns SHA-256 hashed data for signing - const sha256hash = async (mm) => { - const m = parse(mm) - const hash = await ossl.digest({name: pbkdf2.hash}, new TextEncoder().encode(m)) - return Buffer.from(hash) + var shim = require('./shim'); + module.exports = async function(d, o){ + var t = (typeof d == 'string')? d : JSON.stringify(d); + var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t)); + return shim.Buffer.from(hash); } - module.exports = sha256hash \ No newline at end of file diff --git a/sea/shim.js b/sea/shim.js index 83e52389..b6026e8c 100644 --- a/sea/shim.js +++ b/sea/shim.js @@ -1,42 +1,36 @@ + const SEA = require('./root') const Buffer = require('./buffer') const api = {Buffer: Buffer} + var o = {}; - if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') { - var crypto = window.crypto || window.msCrypto; - var subtle = crypto.subtle || crypto.webkitSubtle; - const TextEncoder = window.TextEncoder - const TextDecoder = window.TextDecoder + if(SEA.window){ + api.crypto = window.crypto || window.msCrypto; + api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle; + api.TextEncoder = window.TextEncoder; + api.TextDecoder = window.TextDecoder; + api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len)))) + } + if(!api.crypto){try{ + var crypto = require('crypto', 1); + const { TextEncoder, TextDecoder } = require('text-encoding', 1) Object.assign(api, { crypto, - subtle, + //subtle, TextEncoder, TextDecoder, - random: (len) => Buffer.from(crypto.getRandomValues(new Uint8Array(Buffer.alloc(len)))) - }) - } else { - try{ - var crypto = require('crypto'); - const { subtle } = require('@trust/webcrypto') // All but ECDH - const { TextEncoder, TextDecoder } = require('text-encoding') - Object.assign(api, { - crypto, - subtle, - TextEncoder, - TextDecoder, - random: (len) => Buffer.from(crypto.randomBytes(len)) - }); - try{ - const WebCrypto = require('node-webcrypto-ossl') - api.ossl = new WebCrypto({directory: 'key_storage'}).subtle // ECDH - }catch(e){ - console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed."); - } - }catch(e){ - console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!"); - TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; - } - } + random: (len) => Buffer.from(crypto.randomBytes(len)) + }); + //try{ + const WebCrypto = require('node-webcrypto-ossl', 1); + api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH + //}catch(e){ + //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed."); + //} + }catch(e){ + console.log("node-webcrypto-ossl and text-encoding may not be included by default, please add it to your package.json!"); + OSSL_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; + }} module.exports = api \ No newline at end of file diff --git a/sea/sign.js b/sea/sign.js index fe4ed994..4efd2e5f 100644 --- a/sea/sign.js +++ b/sea/sign.js @@ -2,34 +2,42 @@ var SEA = require('./root'); var shim = require('./shim'); var S = require('./settings'); - var sha256hash = require('./sha256'); + var sha = require('./sha256'); + var u; - SEA.sign = async (data, pair, cb) => { try { - if(data.slice - && 'SEA{' === data.slice(0,4) - && '"m":' === data.slice(4,8)){ - // TODO: This would prevent pair2 signing pair1's signature. - // So we may want to change this in the future. - // but for now, we want to prevent duplicate double signature. - if(cb){ try{ cb(data) }catch(e){console.log(e)} } - return data; + SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { + opt = opt || {}; + if(!(pair||opt).priv){ + pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); } - const pub = pair.pub - const priv = pair.priv - const jwk = S.jwk(pub, priv) - const msg = JSON.stringify(data) - const hash = await sha256hash(msg) - const sig = await shim.subtle.importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) - .then((key) => shim.subtle.sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! - const r = 'SEA'+JSON.stringify({m: msg, s: shim.Buffer.from(sig, 'binary').toString('utf8')}); + if(u === data){ throw '`undefined` not allowed.' } + var json = S.parse(data); + var check = opt.check = opt.check || json; + if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m)) + && u !== await SEA.verify(check, pair)){ // don't sign if we already signed it. + var r = S.parse(check); + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + var pub = pair.pub; + var priv = pair.priv; + var jwk = S.jwk(pub, priv); + var hash = await sha(json); + var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) + .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! + var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.sign; \ No newline at end of file diff --git a/sea/update.js b/sea/update.js deleted file mode 100644 index a312d63f..00000000 --- a/sea/update.js +++ /dev/null @@ -1,48 +0,0 @@ - - const authsettings = require('./settings') - const SEA = require('./sea'); - const Gun = SEA.Gun; - //const { scope: seaIndexedDb } = require('./indexed') - // This updates sessionStorage & IndexedDB to persist authenticated "session" - const updateStorage = (proof, key, pin) => async (props) => { - if (!Gun.obj.has(props, 'alias')) { - return // No 'alias' - we're done. - } - if (authsettings.validity && proof && Gun.obj.has(props, 'iat')) { - props.proof = proof - delete props.remember // Not stored if present - - const alias = props.alias - const id = props.alias - const remember = { alias: alias, pin: pin } - - try { - const signed = await SEA.sign(JSON.stringify(remember), key) - - sessionStorage.setItem('user', alias) - sessionStorage.setItem('remember', signed) - - const encrypted = await SEA.encrypt(props, pin) - - if (encrypted) { - const auth = await SEA.sign(encrypted, key) - await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out. - await seaIndexedDb.put(id, { auth: auth }) - } - - return props - } catch (err) { - throw { err: 'Session persisting failed!' } - } - } - - // Wiping IndexedDB completely when using random PIN - await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out. - // And remove sessionStorage data - sessionStorage.removeItem('user') - sessionStorage.removeItem('remember') - - return props - } - module.exports = updateStorage - \ No newline at end of file diff --git a/sea/user.js b/sea/user.js index 05a91f1c..daab6da3 100644 --- a/sea/user.js +++ b/sea/user.js @@ -3,9 +3,8 @@ var Gun = SEA.Gun; var then = require('./then'); - function User(){ - this._ = {$: this} - Gun.call() + function User(root){ + this._ = {$: this}; } User.prototype = (function(){ function F(){}; F.prototype = Gun.chain; return new F() }()) // Object.create polyfill User.prototype.constructor = User; @@ -20,7 +19,7 @@ (at = (user = at.user = gun.chain(new User))._).opt = {}; at.opt.uuid = function(cb){ var id = uuid(), pub = root.user; - if(!pub || !(pub = (pub._).sea) || !(pub = pub.pub)){ return id } + if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id } id = id + '~' + pub + '.'; if(cb && cb.call){ cb(null, id) } return id; diff --git a/sea/verify.js b/sea/verify.js index 1ce4347a..06286e8c 100644 --- a/sea/verify.js +++ b/sea/verify.js @@ -2,35 +2,74 @@ var SEA = require('./root'); var shim = require('./shim'); var S = require('./settings'); - var sha256hash = require('./sha256'); - var parse = require('./parse'); + var sha = require('./sha256'); var u; - SEA.verify = async (data, pair, cb) => { try { - const json = parse(data) + SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { + var json = S.parse(data); if(false === pair){ // don't verify! - const raw = (json !== data)? - (json.s && json.m)? parse(json.m) : data - : json; + var raw = S.parse(json.m); if(cb){ try{ cb(raw) }catch(e){console.log(e)} } return raw; } - const pub = pair.pub || pair - const jwk = S.jwk(pub) - const key = await shim.subtle.importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) - const hash = await sha256hash(json.m) - const sig = new Uint8Array(shim.Buffer.from(json.s, 'utf8')) - const check = await shim.subtle.verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) - if(!check){ throw "Signature did not match." } - const r = check? parse(json.m) : u; + 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, S.ecdsa.pair, 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! + sig = new Uint8Array(buf); + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)); + if(!check){ throw "Signature did not match." } + }catch(e){ + if(SEA.opt.fallback){ + return await SEA.opt.fall_verify(data, pair, cb, opt); + } + } + var r = check? S.parse(json.m) : u; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); // mismatched owner FOR MARTTI SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.verify; + // legacy & ossl leak mitigation: + + var knownKeys = {}; + var keyForPair = SEA.opt.slow_leak = pair => { + if (knownKeys[pair]) return knownKeys[pair]; + var jwk = S.jwk(pair); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]); + return knownKeys[pair]; + }; + + + 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 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{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! + sig = new Uint8Array(buf) + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + }catch(e){ + buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! + sig = new Uint8Array(buf) + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + } + var r = check? S.parse(json.m) : u; + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + SEA.opt.fallback = 2; + \ No newline at end of file diff --git a/sea/work.js b/sea/work.js index f4e436c9..37a6c043 100644 --- a/sea/work.js +++ b/sea/work.js @@ -2,49 +2,41 @@ var SEA = require('./root'); var shim = require('./shim'); var S = require('./settings'); + var sha = require('./sha256'); var u; - SEA.work = async (data, pair, cb) => { try { // used to be named `proof` - var salt = pair.epub || pair; // epub not recommended, salt should be random! + SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof` + var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random! + var opt = opt || {}; if(salt instanceof Function){ cb = salt; salt = u; } salt = salt || shim.random(9); - if (SEA.window) { - // For browser subtle works fine - const key = await shim.subtle.importKey( - 'raw', new shim.TextEncoder().encode(data), { name: 'PBKDF2' }, false, ['deriveBits'] - ) - const result = await shim.subtle.deriveBits({ - name: 'PBKDF2', - iterations: S.pbkdf2.iter, - salt: new shim.TextEncoder().encode(salt), - hash: S.pbkdf2.hash, - }, key, S.pbkdf2.ks * 8) - data = shim.random(data.length) // Erase data in case of passphrase - const r = shim.Buffer.from(result, 'binary').toString('utf8') - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; + 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; } - // For NodeJS crypto.pkdf2 rocks - const crypto = shim.crypto; - const hash = crypto.pbkdf2Sync( - data, - new shim.TextEncoder().encode(salt), - S.pbkdf2.iter, - S.pbkdf2.ks, - S.pbkdf2.hash.replace('-', '').toLowerCase() - ) - data = shim.random(data.length) // Erase passphrase for app - const r = hash && hash.toString('utf8') + 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', + iterations: opt.iterations || S.pbkdf2.iter, + salt: new shim.TextEncoder().encode(opt.salt || salt), + hash: opt.hash || S.pbkdf2.hash, + }, key, opt.length || (S.pbkdf2.ks * 8)) + data = shim.random(data.length) // Erase data in case of passphrase + var r = shim.Buffer.from(work, 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; + if(SEA.throw){ throw e } if(cb){ cb() } return; - }} + }}); module.exports = SEA.work; \ No newline at end of file diff --git a/src/adapters/localStorage.js b/src/adapters/localStorage.js index c3672909..e2a5fdea 100644 --- a/src/adapters/localStorage.js +++ b/src/adapters/localStorage.js @@ -1,10 +1,12 @@ if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs? -var root, noop = function(){}, u; -if(typeof window !== 'undefined'){ root = window } -var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop}; - +var root, noop = function(){}, store, u; +try{store = (Gun.window||noop).localStorage}catch(e){} +if(!store){ + console.log("Warning: No localStorage exists to persist data to!"); + store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; +} /* NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design! If you update anything here, consider updating the other adapters as well. @@ -15,29 +17,26 @@ Gun.on('create', function(root){ // See the next 'opt' code below for actual saving of data. var ev = this.to, opt = root.opt; if(root.once){ return ev.next(root) } - if(false === opt.localStorage){ return ev.next(root) } - opt.file = opt.file || 'gun/'; - var gap = Gun.obj.ify(store.getItem('gap/'+opt.file)) || {}; + //if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless! + opt.prefix = opt.file || 'gun/'; + var gap = Gun.obj.ify(store.getItem('gap/'+opt.prefix)) || {}; var empty = Gun.obj.empty, id, to, go; // add re-sync command. if(!empty(gap)){ - root.on('localStorage', function(disk){ - this.off(); - var send = {} - Gun.obj.map(gap, function(node, soul){ - Gun.obj.map(node, function(val, key){ - send[soul] = Gun.state.to(disk[soul], key, send[soul]); - }); + var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}, send = {}; + Gun.obj.map(gap, function(node, soul){ + Gun.obj.map(node, function(val, key){ + send[soul] = Gun.state.to(disk[soul], key, send[soul]); }); - setTimeout(function(){ - root.on('out', {put: send, '#': root.ask(ack), I: root.$}); - },10); }); + setTimeout(function(){ + root.on('out', {put: send, '#': root.ask(ack)}); + },1); } root.on('out', function(msg){ if(msg.lS){ return } - if(msg.I && msg.put && !msg['@'] && !empty(opt.peers)){ + if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){ id = msg['#']; Gun.graph.is(msg.put, null, map); if(!to){ to = setTimeout(flush, opt.wait || 1) } @@ -70,7 +69,7 @@ Gun.on('create', function(root){ var flush = function(){ clearTimeout(to); to = false; - try{store.setItem('gap/'+opt.file, JSON.stringify(gap)); + try{store.setItem('gap/'+opt.prefix, JSON.stringify(gap)); }catch(e){ Gun.log(err = e || "localStorage failure") } } }); @@ -80,12 +79,12 @@ Gun.on('create', function(root){ var opt = root.opt; if(root.once){ return } if(false === opt.localStorage){ return } - opt.file = opt.file || opt.prefix || 'gun/'; // support old option name. + opt.prefix = opt.file || 'gun/'; var graph = root.graph, acks = {}, count = 0, to; - var disk = Gun.obj.ify(store.getItem(opt.file)) || {}; + var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}; var lS = function(){}, u; root.on('localStorage', disk); // NON-STANDARD EVENT! - + root.on('put', function(at){ this.to.next(at); Gun.graph.is(at.put, null, map); @@ -113,7 +112,7 @@ Gun.on('create', function(root){ return; // Hmm, what if we have peers but we are disconnected? } //console.log("lS get", lex, data); - root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.I}); + root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$}); }; Gun.debug? setTimeout(to,1) : to(); }); @@ -130,10 +129,10 @@ Gun.on('create', function(root){ var ack = acks; acks = {}; if(data){ disk = data } - try{store.setItem(opt.file, JSON.stringify(disk)); - }catch(e){ - Gun.log(err = e || "localStorage failure"); - root.on('localStorage:error', {err: err, file: opt.file, flush: disk, retry: flush}); + try{store.setItem(opt.prefix, JSON.stringify(disk)); + }catch(e){ + Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."); + root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush}); } if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers. Gun.obj.map(ack, function(yes, id){ diff --git a/src/adapters/mesh.js b/src/adapters/mesh.js index 8c5d4fb8..b4401b46 100644 --- a/src/adapters/mesh.js +++ b/src/adapters/mesh.js @@ -3,6 +3,10 @@ var Type = require('../type'); function Mesh(ctx){ var mesh = function(){}; + var opt = ctx.opt || {}; + opt.log = opt.log || console.log; + opt.gap = opt.gap || opt.wait || 1; + opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB. mesh.out = function(msg){ var tmp; if(this.to){ this.to.next(msg) } @@ -10,20 +14,28 @@ function Mesh(ctx){ if((tmp = msg['@']) && (tmp = ctx.dup.s[tmp]) && (tmp = tmp.it) - && tmp.mesh){ - mesh.say(msg, tmp.mesh.via); + && tmp._){ + mesh.say(msg, (tmp._).via, 1); tmp['##'] = msg['##']; return; } // add hook for AXE? + //if (Gun.AXE && opt && opt.super) { Gun.AXE.say(msg, mesh.say, this); return; } // rogowski mesh.say(msg); } + ctx.on('create', function(root){ + root.opt.pid = root.opt.pid || Type.text.random(9); + this.to.next(root); + ctx.on('out', mesh.out); + }); + mesh.hear = function(raw, peer){ if(!raw){ return } var dup = ctx.dup, id, hash, msg, tmp = raw[0]; + if(opt.pack <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) } try{msg = JSON.parse(raw); - }catch(e){} + }catch(e){opt.log('DAM JSON parse error', e)} if('{' === tmp){ if(!msg){ return } if(dup.check(id = msg['#'])){ return } @@ -35,12 +47,18 @@ function Mesh(ctx){ (tmp = dup.s)[hash] = tmp[id]; } } - (msg.mesh = function(){}).via = peer; + (msg._ = function(){}).via = peer; if((tmp = msg['><'])){ - msg.mesh.to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); + (msg._).to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); + } + if(msg.dam){ + if(tmp = mesh.hear[msg.dam]){ + tmp(msg, peer, ctx); + } + return; } ctx.on('in', msg); - + return; } else if('[' === tmp){ @@ -55,21 +73,21 @@ function Mesh(ctx){ } ;(function(){ - mesh.say = function(msg, peer){ + mesh.say = function(msg, peer, o){ /* TODO: Plenty of performance optimizations that can be made just based off of ordering, and reducing function calls for cached writes. */ if(!peer){ - Type.obj.map(ctx.opt.peers, function(peer){ + Type.obj.map(opt.peers, function(peer){ mesh.say(msg, peer); }); return; } - var tmp, wire = peer.wire || ((ctx.opt.wire) && ctx.opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen! + var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen! if(!wire){ return } - msh = msg.mesh || empty; + msh = (msg._) || empty; if(peer === msh.via){ return } if(!(raw = msh.raw)){ raw = mesh.raw(msg) } if((tmp = msg['@']) @@ -79,31 +97,32 @@ function Mesh(ctx){ return; // TODO: this still needs to be tested in the browser! } } - if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id])){ return } // TODO: still needs to be tested + if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id]) && !o){ return } // TODO: still needs to be tested if(peer.batch){ - peer.batch.push(raw); - return; + peer.tail = (peer.tail || 0) + raw.length; + if(peer.tail <= opt.pack){ + peer.batch.push(raw); + return; + } + flush(peer); } peer.batch = []; - setTimeout(function(){ - var tmp = peer.batch; - if(!tmp){ return } - peer.batch = null; - if(!tmp.length){ return } - send(JSON.stringify(tmp), peer); - }, ctx.opt.gap || ctx.opt.wait || 1); + setTimeout(function(){flush(peer)}, opt.gap); send(raw, peer); } - + function flush(peer){ + var tmp = peer.batch; + if(!tmp){ return } + peer.batch = peer.tail = null; + if(!tmp.length){ return } + try{send(JSON.stringify(tmp), peer); + }catch(e){opt.log('DAM JSON stringify error', e)} + } function send(raw, peer){ var wire = peer.wire; try{ if(wire.send){ - if(wire.readyState === wire.OPEN){ - wire.send(raw); - } else { - (peer.queue = peer.queue || []).push(raw); - } + wire.send(raw); } else if(peer.say){ peer.say(raw); @@ -114,12 +133,12 @@ function Mesh(ctx){ } }()); - + ;(function(){ mesh.raw = function(msg){ if(!msg){ return '' } - var dup = ctx.dup, msh = msg.mesh || {}, put, hash, tmp; + var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp; if(tmp = msh.raw){ return tmp } if(typeof msg === 'string'){ return msg } if(msg['@'] && (tmp = msg.put)){ @@ -132,12 +151,14 @@ function Mesh(ctx){ msg['#'] = hash || msg['#']; if(put){ (msg = Type.obj.to(msg)).put = _ } } - var i = 0, to = []; Type.obj.map(ctx.opt.peers, function(p){ + var i = 0, to = []; Type.obj.map(opt.peers, function(p){ to.push(p.url || p.id); if(++i > 9){ return true } // limit server, fast fix, improve later! }); msg['><'] = to.join(); var raw = $(msg); if(u !== put){ - raw = raw.replace('"'+ _ +'"', put); + tmp = raw.indexOf(_, raw.indexOf('put')); + raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1); + //raw = raw.replace('"'+ _ +'"', put); // https://github.com/amark/gun/wiki/@$$ Heisenbug } if(msh){ msh.raw = raw; @@ -158,18 +179,36 @@ function Mesh(ctx){ function map(k){ this.to[k] = this.on[k]; } - var $ = JSON.stringify, _ = ':])([:' + var $ = JSON.stringify, _ = ':])([:'; }()); mesh.hi = function(peer){ - ctx.on('hi', peer); - var queue = peer.queue; - peer.queue = []; - Type.obj.map(queue, function(msg){ + var tmp = peer.wire || {}; + if(peer.id || peer.url){ + opt.peers[peer.url || peer.id] = peer; + Type.obj.del(opt.peers, tmp.id); + } else { + tmp = tmp.id = tmp.id || Type.text.random(9); + mesh.say({dam: '?'}, opt.peers[tmp] = peer); + } + if(!tmp.hied){ ctx.on(tmp.hied = 'hi', peer) } + tmp = peer.queue; peer.queue = []; + Type.obj.map(tmp, function(msg){ mesh.say(msg, peer); }); } + mesh.bye = function(peer){ + Type.obj.del(opt.peers, peer.id); // assume if peer.url then reconnect + ctx.on('bye', peer); + } + + mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } + mesh.hear['?'] = function(msg, peer){ + if(!msg.pid){ return mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer) } + peer.id = peer.id || msg.pid; + mesh.hi(peer); + } return mesh; } diff --git a/src/adapters/websocket.js b/src/adapters/websocket.js index 9464bb7b..ec71cf88 100644 --- a/src/adapters/websocket.js +++ b/src/adapters/websocket.js @@ -18,18 +18,15 @@ Gun.on('opt', function(root){ opt.WebSocket = websocket; var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); - root.on('create', function(at){ - this.to.next(at); - root.on('out', mesh.out); - }); - opt.wire = opt.wire || open; - function open(peer){ - if(!peer || !peer.url){ return } + var wire = opt.wire; + opt.wire = open; + function open(peer){ try{ + if(!peer || !peer.url){ return wire && wire(peer) } var url = peer.url.replace('http', 'ws'); var wire = peer.wire = new opt.WebSocket(url); wire.onclose = function(){ - root.on('bye', peer); + opt.mesh.bye(peer); reconnect(peer); }; wire.onerror = function(error){ @@ -40,15 +37,14 @@ Gun.on('opt', function(root){ } }; wire.onopen = function(){ - mesh.hi(peer); + opt.mesh.hi(peer); } wire.onmessage = function(msg){ if(!msg){ return } - env.inLength = (env.inLength || 0) + (msg.data || msg).length; // TEMPORARY, NON-STANDARD, FOR DEBUG - mesh.hear(msg.data || msg, peer); + opt.mesh.hear(msg.data || msg, peer); }; return wire; - } + }catch(e){}} function reconnect(peer){ clearTimeout(peer.defer); diff --git a/src/chain.js b/src/chain.js index f2c5d8b0..a707a940 100644 --- a/src/chain.js +++ b/src/chain.js @@ -16,7 +16,6 @@ Gun.chain.chain = function(sub){ function output(msg){ var put, get, at = this.as, back = at.back, root = at.root, tmp; - if(!msg.I){ msg.I = at.$ } if(!msg.$){ msg.$ = at.$ } this.to.next(msg); if(get = msg.get){ @@ -24,7 +23,6 @@ function output(msg){ at.on('in', at); return; }*/ - //console.log("out!", at.get, get); if(get['#'] || at.soul){ get['#'] = get['#'] || at.soul; msg['#'] || (msg['#'] = text_rand(9)); @@ -140,7 +138,7 @@ function input(msg){ //if(tmp[cat.id]){ return } tmp.is = tmp.is || at.put; tmp[cat.id] = at.put || true; - //if(root.stop){ + //if(root.stop){ eve.to.next(msg) //} relate(cat, msg, at, rel); @@ -153,7 +151,7 @@ function relate(at, msg, from, rel){ var tmp = (at.root.$.get(rel)._); if(at.has){ from = tmp; - } else + } else if(from.has){ relate(from, msg, from, rel); } @@ -164,13 +162,12 @@ function relate(at, msg, from, rel){ not(at, msg); } tmp = from.id? ((at.map || (at.map = {}))[from.id] = at.map[from.id] || {at: from}) : {}; - //console.log("REL?", at.id, at.get, rel === tmp.link, tmp.pass || at.pass); if(rel === tmp.link){ if(!(tmp.pass || at.pass)){ return; } } - if(at.pass){ + if(at.pass){ Gun.obj.map(at.map, function(tmp){ tmp.pass = true }) obj_del(at, 'pass'); } @@ -206,7 +203,7 @@ function map(data, key){ // Map over only the changes on every update. if(tmp = via.$){ tmp = (chain = via.$.get(key))._; if(u === tmp.put || !Gun.val.link.is(data)){ - tmp.put = data; + tmp.put = data; } } at.on('in', { @@ -274,7 +271,6 @@ function ack(msg, ev){ at.on('in', {get: at.get, put: Gun.val.link.ify(get['#']), $: at.$, '@': msg['@']}); return; } - msg.$ = at.root.$; Gun.on.put(msg, at.root.$); } var empty = {}, u; diff --git a/src/dup.js b/src/dup.js index 0d99434b..040089ac 100644 --- a/src/dup.js +++ b/src/dup.js @@ -16,7 +16,7 @@ function Dup(opt){ dup.to = setTimeout(function(){ var now = time_is(); Type.obj.map(dup.s, function(it, id){ - if(opt.age > (now - it.was)){ return } + if(it && opt.age > (now - it.was)){ return } Type.obj.del(dup.s, id); }); dup.to = null; diff --git a/src/get.js b/src/get.js index 54d2e9e1..06c96a84 100644 --- a/src/get.js +++ b/src/get.js @@ -61,13 +61,16 @@ function cache(key, back){ return at; } function soul(gun, cb, opt, as){ - var cat = gun._, tmp; + var cat = gun._, acks = 0, tmp; if(tmp = cat.soul){ return cb(tmp, as, cat), gun } if(tmp = cat.link){ return cb(tmp, as, cat), gun } gun.get(function(msg, ev){ + if(u === msg.put && (tmp = (obj_map(cat.root.opt.peers, function(v,k,t){t(k)})||[]).length) && ++acks < tmp){ + return; + } ev.rid(msg); var at = ((at = msg.$) && at._) || {}; - tmp = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put); + tmp = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub; cb(tmp, as, msg, ev); }, {out: {get: {'.':true}}}); return gun; @@ -83,9 +86,9 @@ function use(msg){ //else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); - //root.stop && (root.stop.ID = root.stop.ID || Gun.text.random(2)); + //root.stop && (root.stop.id = root.stop.id || Gun.text.random(2)); //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); - if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } + if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? if((tmp = data) && tmp[rel._] && (tmp = rel.is(tmp))){ tmp = ((msg.$$ = at.root.gun.get(tmp))._); @@ -93,9 +96,10 @@ function use(msg){ msg = obj_to(msg, {put: data = tmp.put}); } } - if((tmp = root.mum) && at.id){ - if(tmp[at.id]){ return } - if(u !== data && !rel.is(data)){ tmp[at.id] = true; } + if((tmp = root.mum) && at.id){ // TODO: can we delete mum entirely now? + var id = at.id + (eve.id || (eve.id = Gun.text.random(9))); + if(tmp[id]){ return } + if(u !== data && !rel.is(data)){ tmp[id] = true; } } as.use(msg, eve); if(eve.stun){ @@ -117,7 +121,7 @@ function rid(at){ //obj.del(map, at); // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. return; } -var obj = Gun.obj, obj_has = obj.has, obj_to = Gun.obj.to; +var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_to = Gun.obj.to; var num_is = Gun.num.is; var rel = Gun.val.link, node_soul = Gun.node.soul, node_ = Gun.node._; var empty = {}, u; diff --git a/src/graph.js b/src/graph.js index 49b305ed..a7f6f682 100644 --- a/src/graph.js +++ b/src/graph.js @@ -33,6 +33,7 @@ var Graph = {}; if(env.soul){ at.rel = Val.rel.ify(env.soul); } + env.shell = (as||{}).shell; env.graph = env.graph || {}; env.seen = env.seen || []; env.as = env.as || as; @@ -45,8 +46,10 @@ var Graph = {}; at.env = env; at.soul = soul; if(Node.ify(at.obj, map, at)){ - //at.rel = at.rel || Val.rel.ify(Node.soul(at.node)); - env.graph[Val.rel.is(at.rel)] = at.node; + at.rel = at.rel || Val.rel.ify(Node.soul(at.node)); + if(at.obj !== env.shell){ + env.graph[Val.rel.is(at.rel)] = at.node; + } } return at; } diff --git a/src/map.js b/src/map.js index 04cc4518..b3072500 100644 --- a/src/map.js +++ b/src/map.js @@ -14,10 +14,8 @@ Gun.chain.map = function(cb, opt, t){ gun.map().on(function(data, key, at, ev){ var next = (cb||noop).call(this, data, key, at, ev); if(u === next){ return } - if(data === next || Gun.is(next)){ - chain._.on('in', next._); - return; - } + if(data === next){ return chain._.on('in', at) } + if(Gun.is(next)){ return chain._.on('in', next._) } chain._.on('in', {get: key, put: next}); }); return chain; diff --git a/src/on.js b/src/on.js index 80d7dfe3..f9b3998a 100644 --- a/src/on.js +++ b/src/on.js @@ -78,18 +78,19 @@ function val(msg, eve, to){ var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp; if(tmp = msg.$$){ link = tmp = (msg.$$._); - if(u === tmp.put){ - return; + if(u !== link.put){ + data = link.put; } - data = tmp.put; } if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) } - if(!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))){ + if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))) + || (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (!to && (link||at).ack <= tmp))){ tmp = (eve.wait = {})[at.id] = setTimeout(function(){ val.call({as:opt}, msg, eve, tmp || 1); }, opt.wait || 99); return; } + if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) } eve.rid(msg); opt.ok.call(gun || opt.$, data, msg.get); } diff --git a/src/onto.js b/src/onto.js index 78827257..4851d32b 100644 --- a/src/onto.js +++ b/src/onto.js @@ -5,13 +5,13 @@ module.exports = function onto(tag, arg, as){ var u, tag = (this.tag || (this.tag = {}))[tag] || (this.tag[tag] = {tag: tag, to: onto._ = { next: function(arg){ var tmp; - if((tmp = this.to)){ + if((tmp = this.to)){ tmp.next(arg); }} }}); if(arg instanceof Function){ var be = { - off: onto.off || + off: onto.off || (onto.off = function(){ if(this.next === onto._.next){ return !0 } if(this === this.the.last){ diff --git a/src/polyfill/unbuild.js b/src/polyfill/unbuild.js index a3738f02..20d0d0e9 100644 --- a/src/polyfill/unbuild.js +++ b/src/polyfill/unbuild.js @@ -1,16 +1,17 @@ -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){ - return arg.slice? USE[R(arg)] : function(mod, path){ - arg(mod = {exports: {}}); - USE[R(path)] = mod.exports; - } - function R(p){ - return p.split('/').slice(-1).toString().replace('.js',''); - } -} -if(typeof module !== "undefined"){ var common = module } + 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: {}}); + USE[R(path)] = mod.exports; + } + function R(p){ + return p.split('/').slice(-1).toString().replace('.js',''); + } + } + if(typeof module !== "undefined"){ var common = module } + \ No newline at end of file diff --git a/src/put.js b/src/put.js index 52c1dd2c..967310b1 100644 --- a/src/put.js +++ b/src/put.js @@ -30,7 +30,7 @@ Gun.chain.put = function(data, cb, as){ }); return gun; } - as.$ = gun = root.get(as.soul); + as.$ = root.get(as.soul); as.ref = as.$; ify(as); return gun; @@ -185,8 +185,8 @@ function any(soul, as, msg, eve){ as.data = obj_put({}, at.get, as.data); }); } - tmp = tmp || at.get; - at = (at.root.$.get(tmp)._); + tmp = tmp || at.soul || at.link || at.dub;// || at.get; + at = tmp? (at.root.$.get(tmp)._) : at; as.soul = tmp; data = as.data; } diff --git a/src/root.js b/src/root.js index e362dd34..642a96b7 100644 --- a/src/root.js +++ b/src/root.js @@ -104,7 +104,7 @@ Gun.dup = require('./dup'); if(!at){ if(!(cat.opt||empty).super){ ctx.souls[soul] = false; - return; + return; } at = (ctx.$.get(soul)._); } @@ -151,9 +151,25 @@ Gun.dup = require('./dup'); } Gun.on.get = function(msg, gun){ - var root = gun._, soul = msg.get[_soul], node = root.graph[soul], has = msg.get[_has], tmp; + var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp; var next = root.next || (root.next = {}), at = next[soul]; - if(!node || !at){ return root.on('get', msg) } + if(obj_has(soul, '*')){ // TEMPORARY HACK FOR MARTTI, TESTING + var graph = {}; + Gun.obj.map(root.graph, function(node, s){ + if(Gun.text.match(s, soul)){ + graph[s] = Gun.obj.copy(node); + } + }); + if(!Gun.obj.empty(graph)){ + root.on('in', { + '@': msg['#'], + how: '*', + put: graph, + $: gun + }); + } + } // TEMPORARY HACK FOR MARTTI, TESTING + if(!node){ return root.on('get', msg) } if(has){ if(!obj_has(node, has)){ return root.on('get', msg) } node = Gun.state.to(node, has); @@ -164,7 +180,7 @@ Gun.dup = require('./dup'); node = Gun.obj.copy(node); } node = Gun.graph.node(node); - tmp = at.ack; + tmp = (at||empty).ack; root.on('in', { '@': msg['#'], how: 'mem', @@ -213,7 +229,7 @@ Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ Gun.log.once("welcome", "Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"); ;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; -if(typeof window !== "undefined"){ window.Gun = Gun } +if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } try{ if(typeof common !== "undefined"){ common.exports = Gun } }catch(e){} module.exports = Gun; diff --git a/src/set.js b/src/set.js index 1e1c6072..e489b2b4 100644 --- a/src/set.js +++ b/src/set.js @@ -4,13 +4,12 @@ Gun.chain.set = function(item, cb, opt){ var gun = this, soul; cb = cb || function(){}; opt = opt || {}; opt.item = opt.item || item; - if(soul = Gun.node.soul(item)){ return gun.set(gun.back(-1).get(soul), cb, opt) } + if(soul = Gun.node.soul(item)){ item = Gun.obj.put({}, soul, Gun.val.link.ify(soul)) } if(!Gun.is(item)){ - var id = gun.back('opt.uuid')(); - if(id && Gun.obj.is(item)){ - return gun.set(gun._.root.$.put(item, id), cb, opt); + if(Gun.obj.is(item)){; + item = gun.back(-1).get(soul = soul || Gun.node.soul(item) || gun.back('opt.uuid')()).put(item); } - return gun.get((Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt); + return gun.get(soul || (Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt); } item.get(function(soul, o, msg){ if(!soul){ return cb.call(gun, {err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}) } diff --git a/src/state.js b/src/state.js index 36004c14..1cddd3f1 100644 --- a/src/state.js +++ b/src/state.js @@ -26,10 +26,10 @@ State.lex = function(){ return State().toString(36).replace('.','') } State.ify = function(n, k, s, v, soul){ // put a key's state on a node. if(!n || !n[N_]){ // reject if it is not node-like. if(!soul){ // unless they passed a soul - return; + return; } n = Node.soul.ify(n, soul); // then make it so! - } + } var tmp = obj_as(n[N_], State._); // grab the states data. if(u !== k && k !== N_){ if(num_is(s)){ @@ -42,7 +42,7 @@ State.ify = function(n, k, s, v, soul){ // put a key's state on a node. return n; } State.to = function(from, k, to){ - var val = from[k]; // BUGGY! + var val = (from||{})[k]; if(obj_is(val)){ val = obj_copy(val); } diff --git a/src/val.js b/src/val.js index fa4ac8f7..fb7cf4ee 100644 --- a/src/val.js +++ b/src/val.js @@ -7,7 +7,7 @@ Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. if(text_is(v) // by "text" we mean strings. || bi_is(v) // by "binary" we mean boolean. - || num_is(v)){ // by "number" we mean integers or decimals. + || num_is(v)){ // by "number" we mean integers or decimals. return true; // simple values are valid. } return Val.rel.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. diff --git a/test/common.js b/test/common.js index 49a3a491..5254bbcd 100644 --- a/test/common.js +++ b/test/common.js @@ -6,7 +6,7 @@ describe('Gun', function(){ if(typeof global !== 'undefined'){ env = global } if(typeof window !== 'undefined'){ env = window } root = env.window? env.window : global; - env.window && root.localStorage && root.localStorage.clear(); + try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){} try{ require('fs').unlinkSync('data.json') }catch(e){} //root.Gun = root.Gun || require('../gun'); if(root.Gun){ @@ -19,6 +19,7 @@ describe('Gun', function(){ //require('../lib/file'); require('../lib/store'); require('../lib/rfs'); + require('./sea/sea.js'); } }(this)); //Gun.log.squelch = true; @@ -1263,6 +1264,16 @@ describe('Gun', function(){ describe('API', function(){ var gopt = {wire:{put:function(n,cb){cb()},get:function(k,cb){cb()}}}; + if(Gun.window && location.search){ + /*console.log("LOCALHOST PEER MUST BE ON!"); + var peer = {url: 'http://localhost:8765/gun'}; + Gun.on('opt', function(root){ + if(root.opt.test_no_peer){ return this.to.next(root) } + root.opt.peers = root.opt.peers || {}; + root.opt.peers['http://localhost:8765/gun'] = peer; + this.to.next(root); + });*/ + } var gun = Gun(); it.skip('gun chain separation', function(done){ // TODO: UNDO! @@ -1371,7 +1382,7 @@ describe('Gun', function(){ }); describe('plural chains', function(){ - this.timeout(5000); + this.timeout(9000); it('uncached synchronous map on', function(done){ /* Biggest challenges so far: @@ -1548,6 +1559,7 @@ describe('Gun', function(){ //expect(count.Alice).to.be(1); //expect(count.Bob).to.be(1); //expect(count['undefined']).to.be(1); + if(done.c){ return } done.c = 1; done(); },10); } @@ -2835,7 +2847,7 @@ describe('Gun', function(){ cat.slave = bob; gun.on('put', {$: gun, put: Gun.graph.ify(user, s)}); //console.debug.i=1;console.log("-------------"); - gun.get(s.soul).get('bob').get('pet').get('slave').val(function(data){ + gun.get(s.soul).get('bob').get('pet').get('slave').once(function(data){ //clearTimeout(done.to); //setTimeout(function(){ //console.log("*****************", data);return; @@ -2879,7 +2891,7 @@ describe('Gun', function(){ it('empty val followed', function(done){ var gun = Gun(); - gun.get('val/follow').val(function(data){ + gun.get('val/follow').once(function(data){ //console.log("val", data); }).get(function(at){ //console.log("?????", at); @@ -2928,7 +2940,7 @@ describe('Gun', function(){ list.set(gun.get('dave').put({name: "Dave", group: "awesome", married: true})); var check = {}, count = {}; - list.map().val(function(data, id){ + list.map().once(function(data, id){ //console.log("***************", id, data); check[id] = data; count[id] = (count[id] || 0) + 1; @@ -3050,7 +3062,7 @@ describe('Gun', function(){ setTimeout(function(){ var gun2 = Gun(); //console.log(require('fs').readFileSync('./radata/!').toString()); - gun2.get('stef').get('address').val(function(data){ // Object {_: Object, country: "Netherlands", zip: "1766KP"} "adress" + gun2.get('stef').get('address').once(function(data){ // Object {_: Object, country: "Netherlands", zip: "1766KP"} "adress" //console.log("******", data); done.a = true; expect(data.country).to.be('Netherlands'); @@ -3059,7 +3071,7 @@ describe('Gun', function(){ if(done.c){ return } done.c = 1; done(); }); - gun2.get('stef').val(function(data){ //Object {_: Object, address: Object} "stef" + gun2.get('stef').once(function(data){ //Object {_: Object, address: Object} "stef" //console.log("**************", data); //return; done.s = true; @@ -3237,14 +3249,14 @@ describe('Gun', function(){ var check = {A: {}, B: {}}; setTimeout(function(){ - gun.get('usersMM').map().map().val(function(data){ + gun.get('usersMM').map().map().once(function(data){ //console.log('A', data); check.A[data.pub] = true; }) }, 900); setTimeout(function(){ - gun.get('usersMM').map().map().val(function(data){ + gun.get('usersMM').map().map().once(function(data){ //console.log('B', data, check); check.B[data.pub] = true; if(check.A['asdf'] && check.A['fdsa'] && check.B['asdf'] && check.B['fdsa']){ @@ -3302,7 +3314,7 @@ describe('Gun', function(){ var app = gun.get(s.soul); //console.debug.i=1;console.log("==================="); - app.get('alias').get('mark').map().val(function(alias){ + app.get('alias').get('mark').map().once(function(alias){ //console.log("***", alias); done.alias = alias; }); @@ -3366,6 +3378,30 @@ describe('Gun', function(){ list.set({name: 'dave', age: 25}); }); + it('once map function once', function(done){ + var gun = Gun(), s = 'o/mf/o', u; + var app = gun.get(s); + var list = app.get('list'); + + var check = {}; + gun.get('user').get('alice').put({name:'Alice', email:'alice@example.com'}) + gun.get('user').get('bob').put({name:'Bob', email:'bob@example.com'}) + gun.get('user').get('carl').put({name:'Carl', email:'carl@example.com'}) + + gun.get('user').once().map(v => { + //console.log('this gets called', v); + return v + }).once((v, k) => { + //console.log('this is never called', k, v); + check[k] = (check[k] || 0) + 1; + if(1 === check.alice && 1 === check.bob && 1 === check.carl){ + if(done.c){return}done.c=1; + done(); + } + }); + + }); + it('val and then map', function(done){ var gun = Gun(), s = 'val/then/map', u; var list = gun.get(s); @@ -3456,19 +3492,19 @@ describe('Gun', function(){ it('val should now get called if no data is found', function(done){ var gun = Gun(); - gun.get('nv/foo').get('bar').get('baz').val(function(val, key){ + gun.get('nv/foo').get('bar').get('baz').once(function(val, key){ //console.log('*******', key, val); expect(val).to.be(undefined); done.fbb = true; }); - gun.get('nv/totesnothing').val(function(val, key){ + gun.get('nv/totesnothing').once(function(val, key){ //console.log('***********', key, val); expect(val).to.be(undefined); done.t = true; }); - gun.get('nv/bz').get('lul').val(function(val, key){ + gun.get('nv/bz').get('lul').once(function(val, key){ //console.log('*****************', key, val); expect(val).to.be(undefined); done.bzl = true; @@ -3490,7 +3526,7 @@ describe('Gun', function(){ data.b = 2; }); - gun.get('ds/safe').val(function(data){ + gun.get('ds/safe').once(function(data){ expect(gun.back(-1)._.graph['ds/safe'].b).to.not.be.ok(); if(done.c){ return } done.c = 1; done(); @@ -3555,6 +3591,7 @@ describe('Gun', function(){ }); it('Soul above but not beneath', function(done){ + this.timeout(5000); var gun = Gun(); var a = gun.get('sabnb'); @@ -3564,7 +3601,7 @@ describe('Gun', function(){ setTimeout(function(){ a.get('profile').get('said').get('asdf').put('yes'); setTimeout(function(){ - a.val(function(data){ + a.once(function(data){ expect(data.profile).to.be.eql({'#': 'sabnbprofile'}); if(done.c){ return } done.c = 1; done(); @@ -3638,7 +3675,7 @@ describe('Gun', function(){ it('get map should not slowdown', function(done){ this.timeout(5000); - var gun = Gun().get('g/m/no/slow'); + var gun = Gun({test_no_peer:true}).get('g/m/no/slow'); //console.log("---------- setup data done -----------"); var prev, diff, max = 25, total = 9, largest = -1, gone = {}; //var prev, diff, max = Infinity, total = 10000, largest = -1, gone = {}; @@ -3679,6 +3716,59 @@ describe('Gun', function(){ done(); }); }); + + it('Multiple subscribes should trigger', function(done){ + // thanks to @ivkan for reporting and providing test. + var gun = Gun(); + var check = {}; + gun.get('m/s/key').put({property: 'value'}); + + gun.get('m/s/key').on(function(data, key){ + check['a'+data.property] = 1; + }); + + gun.get('m/s/key').on(function(data, key){ + check['b'+data.property] = 1; + if(check.avalue && check.bvalue && check.anewValue && check.bnewValue){ + if(done.c){ return } done.c = true; + done(); + } + }); + + setTimeout(function(){ + gun.get('m/s/key').put({property: 'newValue'}); + }, 1000); + }); + + it('Deep puts with peer should work', function(done){ + // tests in async mode now automatically connect to localhost peer. + //var gun = Gun('http://localhost:8765/gun'); + var gun = Gun(); + //var user = gun.user(); + //user.create('alice', 'password', function(){ + gun.get('who').get('all').put({what: "hello world!", when: Gun.state()}, function(ack){ + //user.get('who').get('all').put({what: "hello world!", when: Gun.state()}, function(ack){ + gun.get('who').get('all').once(function(data){ + expect(data.what).to.be.ok(); + expect(data.when).to.be.ok(); + done(); + }); + }); + //}); + }); + + it('Set a ref should be found', function(done){ + var gun = Gun(); + var msg = {what: 'hello world'}; + //var ref = user.get('who').get('all').set(msg); + //user.get('who').get('said').set(ref); + var ref = gun.get('who').get('all').set(msg); + gun.get('who').get('said').set(ref); + gun.get('who').get('said').map().once(function(data){ + expect(data.what).to.be.ok(); + done(); + }) + }); return; it('Nested listener should be called', function(done){ @@ -3737,7 +3827,7 @@ describe('Gun', function(){ var chain = this.chain(); var context = this; var _tags; - context.val(function(obj, key){ + context.once(function(obj, key){ if(!obj.tags){ console.warn('Not tagged to anything!'); context._.valid = false; @@ -3775,7 +3865,7 @@ describe('Gun', function(){ gun.get('fake1')//.map() .filter(['a','b']) // Gun.chain.filter = function(tags){ .... } .get(function(no){console.log("NO!", no)}) - .val(function(yes){console.log("YES!", yes)}) + .once(function(yes){console.log("YES!", yes)}) }); it.only('Check that events are called with multiple instances', function(done){ @@ -4623,7 +4713,7 @@ describe('Gun', function(){ var gun = Gun().put({foo:'lol', extra: 'yes'}).key('key/path/put'); var data = gun.get('key/path/put'); data.path('foo').put('epic'); - data.val(function(val, field){ + data.once(function(val, field){ expect(val.foo).to.be('epic'); expect(Gun.node.soul(val)).to.be('key/path/put'); done(); @@ -4747,7 +4837,7 @@ describe('Gun', function(){ var get = gun.get('shallow/path'); var path = get.path('one'); var put = path.put('good'); - put.val(function(val, field){ + put.once(function(val, field){ expect(val).to.be('good'); expect(field).to.be('one'); done(); @@ -4759,7 +4849,7 @@ describe('Gun', function(){ var get = gun.get('slightly/shallow/path'); var path = get.path('one'); var put = path.put({you: 'are', here: 1}); - put.val(function(val, field){ + put.once(function(val, field){ //console.log('***********', field, val); expect(val.you).to.be('are'); expect(val.here).to.be(1); @@ -4815,7 +4905,7 @@ describe('Gun', function(){ },100); }); - it('any any not', function(done){ + it('get get not', function(done){ var s = Gun.state.map(); s.soul = 'a'; Gun.on('put', {$: gun, put: Gun.graph.ify({b: 1, c: 2}, s)}); @@ -4835,9 +4925,9 @@ describe('Gun', function(){ done(); } } - gun.get('a').path('b').any(cb);//.err(cb).not(cb).on(cb).val(cb); - gun.get('a').path('c').any(cb);//.err(cb).not(cb).on(cb).val(cb); - gun.get('a').path('d').any(cb);//.err(cb).not(cb).on(cb).val(cb); + gun.get('a').path('b').get(cb);//.err(cb).not(cb).on(cb).once(cb); + gun.get('a').path('c').get(cb);//.err(cb).not(cb).on(cb).once(cb); + gun.get('a').path('d').get(cb);//.err(cb).not(cb).on(cb).once(cb); }); it('any not any not any not', function(done){ @@ -4855,16 +4945,16 @@ describe('Gun', function(){ done(); } } - gun.get('x').path('b').any(cb);//.err(cb).not(cb).on(cb).val(cb); - gun.get('x').path('c').any(cb);//.err(cb).not(cb).on(cb).val(cb); - gun.get('x').path('d').any(cb);//.err(cb).not(cb).on(cb).val(cb); + gun.get('x').path('b').any(cb);//.err(cb).not(cb).on(cb).once(cb); + gun.get('x').path('c').any(cb);//.err(cb).not(cb).on(cb).once(cb); + gun.get('x').path('d').any(cb);//.err(cb).not(cb).on(cb).once(cb); }); it('get put, put deep', function(done){ var gun = Gun(); var get = gun.get('put/deep/ish'); get.put({}); - get.val(function(data){ // TODO: API CHANGE! Empty objects should react. + get.once(function(data){ // TODO: API CHANGE! Empty objects should react. //console.log("...1", data); expect(Gun.obj.empty(data, '_')).to.be.ok(); // API CHANGED, //expect(Gun.val.link.is(data.very)).to.be.ok(); @@ -4879,12 +4969,12 @@ describe('Gun', function(){ } } }); - get.val(function(data){ + get.once(function(data){ //console.log("...2", data); expect(Gun.val.link.is(data.very)).to.be.ok(); }); setTimeout(function(){ - put.val(function(data){ + put.once(function(data){ //console.log("...3", data); expect(Gun.val.link.is(data.very)).to.be.ok(); done.val = true; @@ -4892,7 +4982,7 @@ describe('Gun', function(){ var p = put.path('very'); p.put({we: 'have gone!'}); setTimeout(function(){ - p.val(function(data){ + p.once(function(data){ //console.log("...4", data); expect(data.we).to.be('have gone!'); expect(Gun.val.link.is(data.deep)).to.be.ok(); @@ -4912,7 +5002,7 @@ describe('Gun', function(){ var get = gun.get('slightly/shallow/path/swoop'); var path = get.path('one.two'); var put = path.put({oh: 'okay'}); - put.val(function(val, field){ + put.once(function(val, field){ //console.log("****", field, val); expect(val.oh).to.be('okay'); expect(field).to.be('two'); @@ -4926,13 +5016,13 @@ describe('Gun', function(){ var path = get.path('one.two'); var path3 = path.path('three'); var put = path3.put({you: 'found', the: 'bottom!'}); - put.val(function(val, field){ + put.once(function(val, field){ //console.log("********1********", field, val); expect(val.you).to.be('found'); expect(val.the).to.be('bottom!'); expect(field).to.be('three'); }); - gun.get('deep/path').path('one.two.three.you').put('are').val(function(val, field){ + gun.get('deep/path').path('one.two.three.you').put('are').once(function(val, field){ //console.log("********2*********", field, val);return; expect(val).to.be('are'); expect(field).to.be('you'); @@ -5052,13 +5142,13 @@ describe('Gun', function(){ mark.path('wife').put(amber, function(err){ expect(err).to.not.be.ok(); }); - mark.path('wife.name').val(function(val){ + mark.path('wife.name').once(function(val){ expect(val).to.be("Amber Nadal"); }); }); it('put val', function(done){ - gun.put({hello: "world"}).val(function(val){ + gun.put({hello: "world"}).once(function(val){ expect(val.hello).to.be('world'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5070,7 +5160,7 @@ describe('Gun', function(){ }); it('put key val', function(done){ - gun.put({hello: "world"}).key('hello/world').val(function(val, field){ + gun.put({hello: "world"}).key('hello/world').once(function(val, field){ if(done.c){ return } expect(val.hello).to.be('world'); expect(done.c).to.not.be.ok(); @@ -5083,7 +5173,7 @@ describe('Gun', function(){ }); it('get val', function(done){ - gun.get('hello/world').val(function(val, field){ + gun.get('hello/world').once(function(val, field){ expect(val.hello).to.be('world'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5095,7 +5185,7 @@ describe('Gun', function(){ }); it('get path', function(done){ - gun.get('hello/world').path('hello').val(function(val){ + gun.get('hello/world').path('hello').once(function(val){ //console.log("**************", val); expect(val).to.be('world'); expect(done.c).to.not.be.ok(); @@ -5108,7 +5198,7 @@ describe('Gun', function(){ }); it('get put path', function(done){ - gun.get('hello/world').put({hello: 'Mark'}).path('hello').val(function(val, field){ + gun.get('hello/world').put({hello: 'Mark'}).path('hello').once(function(val, field){ expect(val).to.be('Mark'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5120,7 +5210,7 @@ describe('Gun', function(){ }); it('get path put', function(done){ - gun.get('hello/world').path('hello').put('World').val(function(val){ + gun.get('hello/world').path('hello').put('World').once(function(val){ expect(val).to.be('World'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5148,7 +5238,7 @@ describe('Gun', function(){ it('get path empty put val', function(done){ var gun = Gun({init: true}).put({hello: "Mark"}).key('hello/world/not'); - gun.get('hello/world/not').path('earth').put('mars').val(function(val){ + gun.get('hello/world/not').path('earth').put('mars').once(function(val){ done.c = 1; }); setTimeout(function(){ @@ -5161,7 +5251,7 @@ describe('Gun', function(){ var gun = Gun(); var get = gun.get('hello/imp/world'); var put = get.put({planet: 'the earth'}); - put.val(function(val){ + put.once(function(val){ expect(val.planet).to.be('the earth'); done(); }); @@ -5172,7 +5262,7 @@ describe('Gun', function(){ var get = gun.get('hello/imp/where'); var path = get.path('where'); var put = path.put('the mars'); - var val = put.val(function(val, field){ + var val = put.once(function(val, field){ expect(field).to.be('where'); expect(val).to.be('the mars'); done(); @@ -5180,7 +5270,7 @@ describe('Gun', function(){ }); it('get path empty put val implicit', function(done){ - gun.get('hello/world').path('earth').put('mars').val(function(val, field){ + gun.get('hello/world').path('earth').put('mars').once(function(val, field){ expect(val).to.be('mars'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5194,7 +5284,7 @@ describe('Gun', function(){ it('get path val', function(done){ var gun = Gun({init: true}).put({hello: "Mark"}).key('hello/world/not'); gun.get('hello/world').path('earth').put('mars'); - gun.get('hello/world/not').path('earth').val(function(val){ + gun.get('hello/world/not').path('earth').once(function(val){ expect(val).to.be('mars'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5206,7 +5296,7 @@ describe('Gun', function(){ }); it('get path val implicit', function(done){ - gun.get('hello/world').path('earth').val(function(val){ + gun.get('hello/world').path('earth').once(function(val){ expect(val).to.be('mars'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5221,7 +5311,7 @@ describe('Gun', function(){ it('get not kick val', function(done){ gun.get("some/empty/thing").not(function(key, kick){ // that if you call not first this.put({now: 'exists'}).key(key); // you can put stuff - }).val(function(val){ // and THEN still retrieve it. + }).once(function(val){ // and THEN still retrieve it. expect(val.now).to.be('exists'); done(); }); @@ -5233,7 +5323,7 @@ describe('Gun', function(){ foo.not(function(key, kick){ done.not = true; this.put({now: 'THIS SHOULD NOT HAPPEN'}).key(key); - }).val(function(val){ + }).once(function(val){ expect(val.now).to.be('exists'); expect(done.not).to.not.be.ok(); done(); @@ -5242,7 +5332,7 @@ describe('Gun', function(){ }); it('put path val sub', function(done){ - gun.put({last: {some: 'object'}}).path('last').val(function(val){ + gun.put({last: {some: 'object'}}).path('last').once(function(val){ expect(val.some).to.be('object'); done(); }); @@ -5279,11 +5369,11 @@ describe('Gun', function(){ }); it('get put null', function(done){ - gun.put({last: {some: 'object'}}).path('last').val(function(val, field){ + gun.put({last: {some: 'object'}}).path('last').once(function(val, field){ //console.log("**", field, val); expect(field).to.be('last'); expect(val.some).to.be('object'); - }).put(null).val(function(val, field){ + }).put(null).once(function(val, field){ //console.log("******", field, val); expect(field).to.be('last'); expect(val).to.be(null); @@ -5293,11 +5383,11 @@ describe('Gun', function(){ it('Gun get put null', function(done){ // flip flop bug var gun = Gun(); - gun.put({last: {some: 'object'}}).path('last').val(function(val, field){ + gun.put({last: {some: 'object'}}).path('last').once(function(val, field){ //console.log("**", field, val); done.some = true; expect(val.some).to.be('object'); - }).put(null).val(function(val, field){ + }).put(null).once(function(val, field){ //console.log("********", field, val); expect(val).to.be(null); expect(done.some).to.be.ok(); @@ -5309,7 +5399,7 @@ describe('Gun', function(){ var foo = gun.put({foo: 'bar'}).key('foo/bar'); foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original setTimeout(function(){ - foo.path('foo').val(function(val){ // and then the original should be able to be reused later + foo.path('foo').once(function(val){ // and then the original should be able to be reused later expect(val).to.be('bar'); // this should work done(); }); @@ -5320,7 +5410,7 @@ describe('Gun', function(){ var foo = gun.get('foo/bar'); foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original setTimeout(function(){ - foo.path('foo').val(function(val){ // and then the original should be able to be reused later + foo.path('foo').once(function(val){ // and then the original should be able to be reused later expect(val).to.be('bar'); // this should work done(); }); @@ -5334,10 +5424,10 @@ describe('Gun', function(){ title: 'awesome title', todos: {} }).key(key); - }).val(function(data){ + }).once(function(data){ expect(data.id).to.be('foobar'); - //}).path('todos').val(function(todos, field){ - }).path('todos').val(function(todos, field){ + //}).path('todos').once(function(todos, field){ + }).path('todos').once(function(todos, field){ expect(field).to.be('todos'); expect(todos).to.not.have.property('id'); done(); @@ -5353,7 +5443,7 @@ describe('Gun', function(){ data.b.parent = data.a; gun.put(data, function(err, ok){ expect(err).to.not.be.ok(); - }).val(function(val){ + }).once(function(val){ setTimeout(function(){ // TODO: Is this cheating? I don't think so cause we are using things outside of the API! var a = gun.back(-1)._.graph[Gun.val.link.is(val.a)]; var b = gun.back(-1)._.graph[Gun.val.link.is(val.b)]; @@ -5417,7 +5507,7 @@ describe('Gun', function(){ lol: true } } - }).path('foo.bar.lol').val(function(val){ + }).path('foo.bar.lol').once(function(val){ expect(val).to.be(true); done(); }); @@ -5430,7 +5520,7 @@ describe('Gun', function(){ lol: {ok: true} } } - }).path('foo.bar.lol').val(function(val){ + }).path('foo.bar.lol').once(function(val){ expect(val.ok).to.be(true); done(); }); @@ -5459,15 +5549,15 @@ describe('Gun', function(){ //console.debug.i=1;console.log("------------"); gun.put(mark, function(err, ok){ expect(err).to.not.be.ok(); - }).val(function(val){ + }).once(function(val){ expect(val.age).to.be(23); expect(val.name).to.be("Mark Nadal"); expect(Gun.val.link.is(val.wife)).to.be.ok(); expect(Gun.val.link.is(val.pet)).to.be.ok(); - }).path('wife.pet.name').val(function(val){ + }).path('wife.pet.name').once(function(val){ //console.debug(1, "*****************", val); expect(val).to.be('Hobbes'); - }).back().path('pet.master').val(function(val){ + }).back().path('pet.master').once(function(val){ //console.log("*****************", val); expect(val.name).to.be("Amber Nadal"); expect(val.phd).to.be.ok(); @@ -5489,14 +5579,14 @@ describe('Gun', function(){ it('put partial sub merge', function(done){ var gun = Gun(); - var mark = gun.put({name: "Mark", wife: { name: "Amber" }}).key('person/mark').val(function(mark){ + var mark = gun.put({name: "Mark", wife: { name: "Amber" }}).key('person/mark').once(function(mark){ //console.log("VAL1", mark); done.marksoul = Gun.node.soul(mark); expect(mark.name).to.be("Mark"); }); mark.put({age: 23, wife: {age: 23}}); setTimeout(function(){ - mark.put({citizen: "USA", wife: {citizen: "USA"}}).val(function(mark){ + mark.put({citizen: "USA", wife: {citizen: "USA"}}).once(function(mark){ //console.log("VAL2", mark, gun); expect(mark.name).to.be("Mark"); expect(mark.age).to.be(23); @@ -5516,10 +5606,10 @@ describe('Gun', function(){ it('path path', function(done){ var deep = gun.put({some: {deeply: {nested: 'value'}}}); - deep.path('some.deeply.nested').val(function(val){ + deep.path('some.deeply.nested').once(function(val){ expect(val).to.be('value'); }); - deep.path('some').path('deeply').path('nested').val(function(val){ + deep.path('some').path('deeply').path('nested').once(function(val){ expect(val).to.be('value'); done(); }); @@ -5537,7 +5627,7 @@ describe('Gun', function(){ var gun = Gun(); var fo = gun.put({fo: 'bar'}); Gun.log.ba = 1; - fo.put({ba: {}}).val(function(obj, field){ + fo.put({ba: {}}).once(function(obj, field){ c += 1; expect(c).to.be(1); done(); @@ -5550,7 +5640,7 @@ describe('Gun', function(){ describe('random', function(){ var foo; it('context null put node', function(done){ - foo = gun.put({foo: 'bar'}).val(function(obj){ + foo = gun.put({foo: 'bar'}).once(function(obj){ expect(obj.foo).to.be('bar'); done(); //setTimeout(function(){ done() },1); }); @@ -5566,7 +5656,7 @@ describe('Gun', function(){ it('context node put node', function(done){ // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! - foo.put({bar: {zoo: 'who'}}).val(function(obj, field){ + foo.put({bar: {zoo: 'who'}}).once(function(obj, field){ //console.log("terribly terrilby unpleasant", field, obj); expect(obj.foo).to.be('bar'); expect(Gun.val.link.is(obj.bar)).to.ok(); @@ -5579,7 +5669,7 @@ describe('Gun', function(){ // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! bar = foo.path('bar'); expect(gleak.check()).to.not.be.ok(); - bar.put({combo: 'double'}).val(function(obj, field){ + bar.put({combo: 'double'}).once(function(obj, field){ //expect(obj.zoo).to.be('who'); expect(obj.combo).to.be('double'); done(); //setTimeout(function(){ done() },1); @@ -5589,7 +5679,7 @@ describe('Gun', function(){ it('context node and field put value', function(done){ // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! var tar = foo.path('tar'); - tar.put('zebra').val(function(val){ + tar.put('zebra').once(function(val){ expect(val).to.be('zebra'); done(); //setTimeout(function(){ done() },1); }); @@ -5597,10 +5687,10 @@ describe('Gun', function(){ it('context node and field, put node', function(done){ // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! - bar.path('combo').put({another: 'node'}).val(function(obj){ + bar.path('combo').put({another: 'node'}).once(function(obj){ expect(obj.another).to.be('node'); // double .vals here also RELATED to the #"context no double emit" but because of a faulty .not or .init system. - bar.val(function(node){ + bar.once(function(node){ expect(Gun.val.link.is(node.combo)).to.be.ok(); expect(Gun.val.link.is(node.combo)).to.be(Gun.node.soul(obj)); done(); //setTimeout(function(){ done() },1); @@ -5615,10 +5705,10 @@ describe('Gun', function(){ var al = gun.put({gender:'m', age:30, name:'alfred'}).key('user/alfred'); var beth = gun.put({gender:'f', age:22, name:'beth'}).key('user/beth'); - al.val(function(a){ + al.once(function(a){ beth.put({friend: a}, function(err, ok){ expect(err).to.not.be.ok(); - }).path('friend').val(function(aa){ + }).path('friend').once(function(aa){ expect(Gun.node.soul(a)).to.be(Gun.node.soul(aa)); done(); }); @@ -5635,7 +5725,7 @@ describe('Gun', function(){ gun.put({gender:'m', age:30, name:'alfred'}).key('user/alfred'); gun.put({gender:'f', age:22, name:'beth' }).key('user/beth'); //gun.get('user/beth').path('friend').put(gun.get('user/alfred')); // ideal format which we have a future test for. - gun.get('user/alfred').val(function(a){ + gun.get('user/alfred').once(function(a){ //console.log("*****", a); //expect(a['_']['key']).to.be.ok(); gun.get('user/beth').put({friend: a}, function(err, ok){ // b - friend_of -> a @@ -5644,13 +5734,13 @@ describe('Gun', function(){ var c = soulnode(gun, keynode), soul = c[0]; expect(c.length).to.be(1); }); - gun.get('user/beth').val(function(b){ + gun.get('user/beth').once(function(b){ //console.log("beth", b); - gun.get('user/alfred').put({friend: b}).val(function(al){ // a - friend_of -> b + gun.get('user/alfred').put({friend: b}).once(function(al){ // a - friend_of -> b //console.log("al again", al); - gun.get('user/beth').put({cat: {name: "fluffy", age: 3, coat: "tabby"}}).val(function(bet){ + gun.get('user/beth').put({cat: {name: "fluffy", age: 3, coat: "tabby"}}).once(function(bet){ gun.get('user/alfred').path('friend.cat').key('the/cat'); - gun.get('the/cat').val(function(c){ + gun.get('the/cat').once(function(c){ //console.log("cat!!!", c); expect(c.name).to.be('fluffy'); expect(c.age).to.be(3); @@ -5714,7 +5804,7 @@ describe('Gun', function(){ }}}), soul = Gun.text.random(); gun.get(soul).not(function(err, ok){ done.fail = true; - }).val(function(val){ + }).once(function(val){ setTimeout(function(){ expect(val.a).to.be('b'); expect(val.c).to.be('d'); @@ -5745,7 +5835,7 @@ describe('Gun', function(){ gun.get('me', function(err, data){ - }).val(function(val){ + }).once(function(val){ done.count = (done.count || 0) + 1; setTimeout(function(){ expect(val.a).to.be('b'); @@ -5773,7 +5863,7 @@ describe('Gun', function(){ cb(null, n); }}}), soul = Gun.text.random(); - gun.get(soul).path('a').val(function(val){ + gun.get(soul).path('a').once(function(val){ done.count = (done.count || 0) + 1; setTimeout(function(){ expect(val).to.be('b'); @@ -5791,7 +5881,7 @@ describe('Gun', function(){ setTimeout(function(){ gun.not(function(){ done.not = true; - }).val(function(){ + }).once(function(){ expect(done.not).to.not.be.ok(); done(); }, {empty: true}); @@ -5913,7 +6003,7 @@ describe('Gun', function(){ }); it('instance.val', function(done){ - Gun().val(); + Gun().once(); done(); }); }); @@ -6076,7 +6166,7 @@ describe('Gun', function(){ it('set', function(done){ done.c = 0; var u, gun = Gun(); - gun.get('set').set().set().val(function(val){ + gun.get('set').set().set().once(function(val){ var keynode = gun.__.graph['set']; expect(Gun.node.soul.ify(keynode, '.')).to.be.ok(); Gun.is.node(keynode, function(rel, soul){ @@ -6102,7 +6192,7 @@ describe('Gun', function(){ // TODO: BUG! We need 2 more tests... without .set()... and multiple paths on the same node. it('set multiple', function(done){ // kinda related to flip flop? var gun = Gun().get('sets').set(), i = 0; - gun.val(function(val){ + gun.once(function(val){ console.log("TEST 1", val); expect(Gun.obj.empty(val, Gun._.meta)).to.be.ok(); expect(Gun.node.soul(val)).to.be('sets'); @@ -6111,7 +6201,7 @@ describe('Gun', function(){ }); gun.set(1); //.set(2).set(3).set(4); // if you set an object you'd have to do a `.back` gun.map(function(val, field){ - //gun.map().val(function(val, field){ // TODO: SEAN! DON'T LET ME FORGET! + //gun.map().once(function(val, field){ // TODO: SEAN! DON'T LET ME FORGET! console.log("\n TEST 2+", field, val); return; i += 1; @@ -6135,7 +6225,7 @@ describe('Gun', function(){ users.path(Gun.text.random()).put('bob'); users.path(Gun.text.random()).put('sam'); setTimeout(function(){ - users.val(function(v){ + users.once(function(v){ expect(Gun.val.link.is(v)).to.not.be.ok(); expect(Object.keys(v).length).to.be(3); done(); @@ -6170,7 +6260,7 @@ describe('Gun', function(){ gun.put({a: 1, z: -1}).key('pseudo'); gun.put({b: 2, z: 0}).key('pseudo'); - gun.get('pseudo').val(function(val){ + gun.get('pseudo').once(function(val){ expect(val.a).to.be(1); expect(val.b).to.be(2); expect(val.z).to.be(0); @@ -6228,14 +6318,14 @@ describe('Gun', function(){ var connect, gun1 = Gun({alice: true}).get('pseudo/merge').put({hello: 'world!'})/*.not(function(key){ this.put({hello: "world!"}).key(key); })*/, gun2; - gun1.val(function(val){ + gun1.once(function(val){ expect(val.hello).to.be('world!'); }); setTimeout(function(){ gun2 = Gun({bob: true}).get('pseudo/merge').put({hi: 'mars!'})/*.not(function(key){ this.put({hi: "mars!"}).key(key); });*/ - gun2.val(function(val){ + gun2.once(function(val){ expect(val.hi).to.be('mars!'); }); setTimeout(function(){ @@ -6246,13 +6336,13 @@ describe('Gun', function(){ //gun1.get('pseudo/merge', null, {force: true}); // fake a browser refersh, in real world we should auto-reconnect //gun2.get('pseudo/merge', null, {force: true}); // fake a browser refersh, in real world we should auto-reconnect setTimeout(function(){ - gun1.val(function(val){ + gun1.once(function(val){ expect(val.hello).to.be('world!'); expect(val.hi).to.be('mars!'); done.g1 = true; }); //return; - gun2.val(function(val){ + gun2.once(function(val){ expect(val.hello).to.be('world!'); expect(val.hi).to.be('mars!'); expect(done.g1).to.be.ok(); @@ -6278,8 +6368,8 @@ describe('Gun', function(){ }}).key(key); }); // this is now a list of passengers that we will map over. var ctx = {n: 0, d: 0, l: 0}; - passengers.map().val(function(passenger, id){ - this.map().val(function(change, field){ + passengers.map().once(function(passenger, id){ + this.map().once(function(change, field){ if('name' == field){ expect(change).to.be(passenger.name); ctx.n++ } if('direction' == field){ expect(change).to.be(passenger.direction); ctx.d++ } if('location' == field){ @@ -6320,7 +6410,7 @@ describe('Gun', function(){ list.put({a: {x:1}, b: {y: 1}}); list.path('a').path('w').put(2); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ check[f] = v; console.log("*************************", f,v); if(check.a && check.b){ @@ -6336,7 +6426,7 @@ describe('Gun', function(){ var g = Gun(); var list = gun.get('map/sub/val/after'); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ check[f] = v; if(check.a && check.b){ setTimeout(function(){ @@ -6358,7 +6448,7 @@ describe('Gun', function(){ var g = Gun(); var list = gun.get('map/sub/val/after/to'); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ //console.log("*************", f,v);return; check[f] = v; if(check.a && check.b){ @@ -6379,7 +6469,7 @@ describe('Gun', function(){ var g = Gun(); var list = gun.get('map/simple/after'); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ check[f] = v; if(check.a && check.b){ setTimeout(function(){ @@ -6398,7 +6488,7 @@ describe('Gun', function(){ var g = Gun(); var list = gun.get('map/simple/after/to'); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ check[f] = v; if(check.a && check.b){ setTimeout(function(){ @@ -6455,7 +6545,7 @@ describe('Gun', function(){ }}).key(key); }); // this is now a list of passengers that we will map over. var ctx = {n: 0, d: 0, l: 0}; - passengers.map().map().val(function(val, field){ + passengers.map().map().once(function(val, field){ if('name' == field){ expect(val).to.be(!ctx.n? 'Bob' : 'Fred'); ctx.n++ } if('direction' == field){ expect(val).to.be(!ctx.d? '128.2' : 'f128.2'); ctx.d++ } if('location' == field){ @@ -6490,7 +6580,7 @@ describe('Gun', function(){ }).key('n/b/l/a/c'); }); var check = {a:{},b:{}}, F = 'a'; - g.map().map().val(function(v,f){ + g.map().map().once(function(v,f){ var c = check[F]; c[f] = v; if(check.b && check.b.x && check.b.y){ @@ -6519,7 +6609,7 @@ describe('Gun', function(){ }).key('n/b/l/a'); }); var check = {}; - g.map().map().val(function(v,f){ + g.map().map().once(function(v,f){ check[f] = v; if(check.x && check.y && check.w && check.u){ expect(check.x).to.be(1); @@ -6539,7 +6629,7 @@ describe('Gun', function(){ var g = gun.get('b/l/a'); g.put({a: {x:1,y:1}}); var check = {}; - g.map().map().val(function(v,f){ + g.map().map().once(function(v,f){ check[f] = v; if(check.x && check.y && check.w && check.u && check.z){ expect(check.x).to.be(1); @@ -6560,7 +6650,7 @@ describe('Gun', function(){ var g = gun.get('b/d/l/a'); g.put({a: {x:1,y:1}}); var check = {}; - g.map().map().val(function(v,f){ + g.map().map().once(function(v,f){ check[f] = v; if(check.x && check.y && check.w && check.u){ expect(check.x).to.be(1); @@ -6600,7 +6690,7 @@ describe('Gun', function(){ } }); var check = {}; - g.map().map().map().map().val(function(v,f){ + g.map().map().map().map().once(function(v,f){ check[f] = (check[f] || 0) + 1; if(check.d === 2 && check.e === 2 && check.f === 2){ done(); @@ -6777,7 +6867,7 @@ describe('Gun', function(){ direction: '128.2' }}).key(key); }); - passengers.map().path('location.lng').val(function(val, field){ + passengers.map().path('location.lng').once(function(val, field){ //passengers.map().path('location.lng').on(function(val, field){ console.log("******", field, val); expect(field).to.be('lng'); @@ -6855,9 +6945,9 @@ describe('Gun', function(){ it("put path deep val -> path val", function(done){ // Terje's bug var gun = Gun(); - gun.put({you: {have: {got: {to: {be: {kidding: "me!"}}}}}}).path('you.have.got.to.be').val(function(val, field){ + gun.put({you: {have: {got: {to: {be: {kidding: "me!"}}}}}}).path('you.have.got.to.be').once(function(val, field){ expect(val.kidding).to.be('me!'); - this.path('kidding').val(function(val){ + this.path('kidding').once(function(val){ expect(val).to.be('me!'); done(); }); @@ -6871,10 +6961,10 @@ describe('Gun', function(){ passengers = passengers.put({randombob: {name: 'Bob', direction: {}}}); passengers.path('randombob.direction', function(err, ok, field){ }).put({lol: {just: 'kidding', dude: '!'}}); - passengers.map().path('direction.lol').val(function(val){ - this.path('just').val(function(val){ + passengers.map().path('direction.lol').once(function(val){ + this.path('just').once(function(val){ expect(val).to.be('kidding'); - }).back().path('dude').val(function(val){ + }).back().path('dude').once(function(val){ expect(val).to.be('!'); done(); }); @@ -7171,7 +7261,7 @@ describe('Gun', function(){ if(call[hash]){ return } gun.__.meta($.soul).put = true; call[hash] = true; - if(Gun.is.val(obj)){ + if(Gun.is.once(obj)){ if($.from && $.at){ $.soul = $.from; $.field = $.at; @@ -7387,7 +7477,7 @@ describe('Gun', function(){ } }, function(err,ok){ expect(done.c++).to.be(0); - }).val(function(p){ + }).once(function(p){ done.p = Gun.node.soul(p); done.m = Gun.val.link.is(p[0]); expect(Gun.val.link.is(p[0])).to.be.ok(); @@ -7433,7 +7523,7 @@ describe('Gun', function(){ var u; var gun = Gun(gopt); var game = gun.get('game1/players'); - var me = game.path('player1').val(function(val){ + var me = game.path('player1').once(function(val){ if(!done.c){ done.fail = true } expect(val).to.not.be(u); expect(val.x).to.be(0); @@ -7485,7 +7575,7 @@ describe('Gun', function(){ var u; var gun = Gun(gopt).opt({init: true}); var game = gun.get('game4/players').init(); - var me = game.path('player4').init().path('alias').init().put({oh: 'awesome'}).val(function(val, field){ + var me = game.path('player4').init().path('alias').init().put({oh: 'awesome'}).once(function(val, field){ expect(val.oh).to.be('awesome'); expect(field).to.be('alias'); done(); @@ -7512,7 +7602,7 @@ describe('Gun', function(){ var chat = gun.get('example/chat/data/graph/field').not(function(key){ gun.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); }); - chat.map().val(function renderToDo(val, field){ + chat.map().once(function renderToDo(val, field){ expect(field).to.be.ok(); expect(val.who).to.be.ok(); expect(val.when).to.be.ok(); @@ -7623,7 +7713,7 @@ describe('Gun', function(){ var chat = gun.get('example/chat/data/graph/field').not(function(key){ gun.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); }); - chat.map().val(function renderToDo(val, field){ + chat.map().once(function renderToDo(val, field){ //console.log("ALICE", field, val); expect(field).to.be.ok(); expect(val.who).to.be.ok(); @@ -7637,7 +7727,7 @@ describe('Gun', function(){ //console.log("BOB's key", key); gun2.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); }); - chat2.map().val(function renderToDo(val, field){ + chat2.map().once(function renderToDo(val, field){ //console.log("BOB", field, val); expect(field).to.be.ok(); expect(val.who).to.be.ok(); @@ -7665,7 +7755,7 @@ describe('Gun', function(){ // Test set with new object var alan = users.set({name: 'alan', birth: Math.random()}).key('person/alan'); - alan.val(function(alan) { + alan.once(function(alan) { // Test set with node dave.path('friends').set(alan); }); @@ -7687,7 +7777,7 @@ describe('Gun', function(){ alice.path('team').put(team); bob.path('team').put(team); - dave.path('friends').map().path('team.members').map().val(function(member){ + dave.path('friends').map().path('team.members').map().once(function(member){ //console.log("Dave's friend is on a team that has", member.name, "on it."); if('alice' === member.name){ done.alice = true; @@ -7728,14 +7818,14 @@ describe('Gun', function(){ it("get context", function(done){ // TODO: HUH?????? This was randomly causing errors? var gun = Gun(); var ref = gun.get('ctx/lol').get('ctx/foo').put({hello: 'world'}); - gun.get('ctx/lol').val(function(implicit){ + gun.get('ctx/lol').once(function(implicit){ done.fail = true; expect(implicit).to.not.be.ok(); }); gun.get('ctx/lol').not(function(){ done.please = true; }); - gun.get('ctx/foo').val(function(data){ + gun.get('ctx/foo').once(function(data){ expect(data.hello).to.be('world'); expect(done.fail).to.not.be.ok(); expect(done.please).to.be.ok(); @@ -7747,7 +7837,7 @@ describe('Gun', function(){ var gun = Gun(); gun.get('users/cv').set(gun.put({name: 'alice'})); gun.get('users/cv').set(gun.put({name: 'bob'}));; - gun.get('users/cv').val().map(function(person){ + gun.get('users/cv').once().map(function(person){ if(person.name === 'alice'){ done.alice = true; } @@ -7814,12 +7904,12 @@ describe('Gun', function(){ }); setTimeout(function(){ - //list.path('next').val('wat'); + //list.path('next').once('wat'); //console.log("!!!!!!", gun.__.graph); // try to read the third item - list.path('next.to').val(function () { // TODO: BUG! If this is 'next.next' as with the data, then it fails. + list.path('next.to').once(function () { // TODO: BUG! If this is 'next.next' as with the data, then it fails. done(); }); },100); @@ -7873,7 +7963,7 @@ describe('Gun', function(){ BSMI.path(path).put({status:false}); }); setTimeout(function(){ - BSMI.path(allPaths[0]).val(function(a,b,c){ + BSMI.path(allPaths[0]).once(function(a,b,c){ expect(a.a).to.be(1); expect(a.b).to.be(2); expect(a.c).to.be(3); @@ -7903,7 +7993,7 @@ describe('Gun', function(){ it("Don't put on parents", function(done){ // TODO: ADD TO 0.5 BRANCH! // Another Stefdv find. var test = gun.get('test'); test.path('try.this.at.lvl4').put({msg:'hoi'}) - test.val(function(node,b){ + test.once(function(node,b){ delete node._; expect(Gun.obj.empty(node, 'try')).to.be.ok(); node = Gun.obj.copy(gun.__.graph[Gun.val.link.is(node.try)]); @@ -7939,7 +8029,7 @@ describe('Gun', function(){ var world = 0; player.path("id").put(id); player.path("world_id").put(world); - }).val(function(data){ + }).once(function(data){ //console.log("we have value!", data); expect(done.not).to.be.ok(); expect(data).to.be('fluffy'); @@ -7963,7 +8053,7 @@ describe('Gun', function(){ // 3: bacon // 9: `.not` - depp.path('spouse.pet.name').val().on(log); + depp.path('spouse.pet.name').once().on(log); // 0: fluffy // 1: fluff */ @@ -8004,7 +8094,7 @@ describe('Gun', function(){ ctx.length = i; } ctx.get.fake = Gun.is.node.ify(ctx.get.fake, 'big'); - var big = peer.put(ctx.get.fake).val(function(val){ + var big = peer.put(ctx.get.fake).once(function(val){ ref = val; ctx.get({'#': 'big'}, function(err, graph){ if(Gun.obj.empty(graph)){ done() } @@ -8015,7 +8105,7 @@ describe('Gun', function(){ it('map chain', function(done){ var set = gun.put({a: {here: 'you'}, b: {go: 'dear'}, c: {sir: '!'} }); - set.map().val(function(obj, field){ + set.map().once(function(obj, field){ if(obj.here){ done.a = obj.here; expect(obj.here).to.be('you'); @@ -8044,7 +8134,7 @@ describe('Gun', function(){ pet: {coat: "tux", name: "Casper"} } }); - set.map().path('pet').val(function(obj, field){ + set.map().path('pet').once(function(obj, field){ if(obj.name === 'Hobbes'){ done.hobbes = obj.name; expect(obj.name).to.be('Hobbes'); @@ -8107,7 +8197,7 @@ describe('Gun', function(){ it('get val', function(done){ this.timeout(ctx.gen * ctx.extra); - g().get('big').val(function(obj){ + g().get('big').once(function(obj){ delete obj._; expect(obj.f1).to.be(1); expect(obj['f' + ctx.length]).to.be(ctx.length); @@ -8122,7 +8212,7 @@ describe('Gun', function(){ it('get big map val', function(done){ this.timeout(ctx.gen * ctx.extra); var test = {c: 0, seen: {}}; - g().get('big').map().val(function(val, field){ + g().get('big').map().once(function(val, field){ if(test.seen[field]){ return } test.seen[field] = true; delete val._; @@ -8145,7 +8235,7 @@ describe('Gun', function(){ chat.put({random5: {who: 'mark', what: "5", when: 5}}); var seen = {1: false, 2: false, 3: false, 4: false, 5: false} setTimeout(function(){ - chat.map(function(m){ }).val(function(msg, field){ + chat.map(function(m){ }).once(function(msg, field){ var msg = Gun.obj.copy(msg); if(msg.what){ expect(msg.what).to.be.ok(); diff --git a/test/mocha.html b/test/mocha.html index 86e4d79e..f09ea754 100644 --- a/test/mocha.html +++ b/test/mocha.html @@ -17,13 +17,21 @@ + + + + }); + run.on("fail", function(test, err){ + console.log("!!!!!!!!!!!", test, err); + //alert(5); + }) + \ No newline at end of file diff --git a/test/panic/b2s2s2b.js b/test/panic/b2s2s2b.js index 631c2f09..1b30bcda 100644 --- a/test/panic/b2s2s2b.js +++ b/test/panic/b2s2s2b.js @@ -3,8 +3,8 @@ var config = { port: 8765, servers: 2, browsers: 2, - each: 100000, - burst: 2, + each: 500, + burst: 50, wait: 1, notrad: true, route: { diff --git a/test/panic/who.js b/test/panic/who.js index a6f9e3e3..1deaa5d5 100644 --- a/test/panic/who.js +++ b/test/panic/who.js @@ -7,7 +7,6 @@ var config = { '/': __dirname + '/index.html', '/gun.js': __dirname + '/../../gun.js', '/jquery.js': __dirname + '/../../examples/jquery.js', - '/cryptomodules.js': __dirname + '/../../lib/cryptomodules.js', '/sea.js': __dirname + '/../../sea.js' } } @@ -57,6 +56,7 @@ describe("Make sure SEA syncs correctly", function(){ res.end("I am "+ env.i +"!"); }); var Gun = require('gun'); + require('gun/sea'); var gun = Gun({file: env.i+'data', web: server}); server.listen(port, function(){ test.done(); @@ -80,10 +80,8 @@ describe("Make sure SEA syncs correctly", function(){ script.onload = cb; script.src = src; document.head.appendChild(script); } - load('cryptomodules.js', function(){ - load('sea.js', function(){ - test.done(); - }); + load('sea.js', function(){ + test.done(); }); }, {i: i += 1, config: config})); }); @@ -140,8 +138,8 @@ describe("Make sure SEA syncs correctly", function(){ return bob.run(function(test){ test.async(); - window.gun.get('alias/alice').map().val(function(data){ - window.ref = gun.get('pub/'+data.pub); + window.gun.get('~@alice').map().once(function(data){ + window.ref = gun.get('~'+data.pub); test.done(); }); }); @@ -152,7 +150,7 @@ describe("Make sure SEA syncs correctly", function(){ test.async(); window.count = []; - ref.get('who').get('said').map().val(function(data){ + ref.get('who').get('said').map().once(function(data){ console.log("read...", data); window.count.push(data); if(window.count.length - 1){ return } @@ -181,10 +179,8 @@ describe("Make sure SEA syncs correctly", function(){ script.onload = cb; script.src = src; document.head.appendChild(script); } - load('cryptomodules.js', function(){ - load('sea.js', function(){ - test.done(); - }); + load('sea.js', function(){ + test.done(); }); }, {i: 1, config: config}); }); diff --git a/test/sea/sea.html b/test/sea/sea.html index 82c16de7..72aaea9e 100644 --- a/test/sea/sea.html +++ b/test/sea/sea.html @@ -1,6 +1,6 @@ - - - + + + + + + + +
    the world is a beautiful place.
    +
    The world is a beautiful place.
    +
    + + + + +
    + + + + \ No newline at end of file diff --git a/test/tmp/indexedDB.html b/test/tmp/indexedDB.html new file mode 100644 index 00000000..159fcd2e --- /dev/null +++ b/test/tmp/indexedDB.html @@ -0,0 +1,17 @@ +

    RindexedDB

    + + + + + + + + diff --git a/test/tmp/mitra/client_to_server.js b/test/tmp/mitra/client_to_server.js new file mode 100644 index 00000000..c6adacd3 --- /dev/null +++ b/test/tmp/mitra/client_to_server.js @@ -0,0 +1,6 @@ +process.env.GUN_ENV = "false"; +var Gun=require('gun') +var g = new Gun({peers: ['http://localhost:4246/gun'],localStorage: false}); +g.get("FOOxx").get("BARxx").once(data=>console.log("RCVD: (SHOULD NOT BE UNDEFINED!)", data)); + +console.log('now run ```var g = new Gun({peers: ["http://localhost:4246/gun"],localStorage: false}); g.get("FOOxx").get("BARxx").once(data=>console.log("RCVD:", data))```'); \ No newline at end of file diff --git a/test/tmp/mitra/gun_https2.js b/test/tmp/mitra/gun_https2.js new file mode 100644 index 00000000..b1497f81 --- /dev/null +++ b/test/tmp/mitra/gun_https2.js @@ -0,0 +1,103 @@ +//var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 8080; + +const port = 4246; + +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +process.env.GUN_ENV = "false"; +const Gun = require('gun'); +const path = require('path'); + +const usehttps = false; + +const options = + usehttps ? { + key: fs.readFileSync('/etc/letsencrypt/live/dweb.me/privkey.pem'), + cert: fs.readFileSync('/etc/letsencrypt/live/dweb.me/fullchain.pem'), + } : {}; +var h = usehttps ? https : http +//var server = h.createServer(options, (req, res) => { +var server = h.createServer((req, res) => { + if(Gun.serve(req, res)){ return } // filters gun requests! + res.writeHead(200); + res.end('go away - nothing for browsers here\n'); + + /* + fs.createReadStream(path.join(__dirname, req.url)) + .on('error',function(){ // static files! + res.writeHead(200, {'Content-Type': 'text/html'}); + res.end(fs.readFileSync(path.join(__dirname, 'index.html'))); // or default to index + }) + .pipe(res); // stream + */ +}); + + +//TODO-GUN put this into a seperate require +function hijack(cb) { + /* Intercept outgoing message and replace result with + result from cb({soul, key, msg, original}) + */ + + Gun.on('opt', function (root) { + console.log("GUN: Hikacking loading trap"); + if (root.once) { + return + } + root.on('out', function (msg) { + console.log("GUN: Hikacking starting outgoing message=", msg); + let to = this.to; + // TODO-GUN - this wont work when running locally in a script ONLY when running in server + if(msg['@'] && !msg.put) { + console.log("GUN: Hikacking outgoing message", msg); + setTimeout(function(){ // TODO-GUN its unclear why this timeout is here, other than for testing + let tmp = root.dup.s[msg['@']]; + let original = tmp && tmp.it && tmp.it.get; + console.log("GUN: Hikacking outgoing message original=", original); + if (original) { + let soul = original['#']; + let key = original['.']; + console.log("GUN.hijack: soul=",soul,"key=", key); + let res; + try { + //TODO - this res now has to be async + res = cb({soul, key, msg, original}); // Note response can be undefined if error + } catch(err) { + console.warn("Gun.hijack callback error",err); + res = undefined; + } + msg.put = { + [soul]: { + _: { + '#': soul, + '>': {[key]: Gun.state()} + }, + [key]: res // Note undefined should (hopefully) be a valid response + } }; + console.log("GUN.hijack updated msg =", msg); + // NOTE: this doesn't necessarily save it back to + // this peers GUN data, (I (Mitra) thinks that may depend on other processes and order of Gun.on) + } + to.next(msg); + }, 100); //Just for testing and note that its async + } else { + to.next(msg); // pass to next middleware + } + }); + this.to.next(root); // This is next for the Gun.on('opt'), not for the root.on('out') + }); +} +hijack(function({soul=undefined, key=undefined, msg=undefined, original=undefined}={}) { + console.log("GUN: hijack testing", soul, key, msg, original); + return ("GUN: This is a test result"); +}); + +var gun = new Gun({ + web: server +}); + + +server.listen(port); + +console.log(usehttps ? "HTTPS" : "HTTP", 'Server started on port ' + port + ' with /gun'); diff --git a/test/tmp/nab.html b/test/tmp/nab.html deleted file mode 100644 index a5c4b368..00000000 --- a/test/tmp/nab.html +++ /dev/null @@ -1,101 +0,0 @@ -

    notabug

    - - - - - - - -

    homepage

    -
      -
    - -
  • - - -
  • -
  • - - - - - - \ No newline at end of file diff --git a/test/tmp/seanode.js b/test/tmp/seanode.js new file mode 100644 index 00000000..1a6855f9 --- /dev/null +++ b/test/tmp/seanode.js @@ -0,0 +1,13 @@ +var Gun = require('../../gun.js'); +require('../../sea.js'); + +var pub = "tgEJ8TTeTN8Xi0D3oMVbZGyVVFDGT4AUoqyrUguAguU.9-yZfWtPSZ_4ILnttWy-KWvwUUyO2-dB1DHRYud-CDE"; + +var sig = decodeURIComponent("SEA%7B%22m%22%3A%22%5C%22hello%5C%22%22%2C%22s%22%3A%22%C2%90%C3%B1%C3%93sy%5Cu0011%C3%87%C2%A5%C2%97%C3%93%5Cu0011%C3%A1JV%C2%AA%C3%94C%C3%A3%C3%85%C2%80a%5Cu0006%C2%B6%C2%A7%C3%BE%C2%9F%C2%92%5Cu000eS%C2%90%C3%A8%C3%B1kH%7D%C2%9A%5Cb%C3%B0g%C2%B9%7F%C2%B2%C3%9F%C3%A0j%C3%9Bk%5C%22%3E%C2%B6%C2%8F%5Cu001b%C3%81%C2%8B%C2%97%C3%92%C2%AA%C3%A5%C2%B6%5D%C3%85%C2%9A%3BA%22%7D"); + +console.log(sig); + +;(async function(){ + var test = await Gun.SEA.verify(sig, pub); + console.log("???", test); +}()); \ No newline at end of file diff --git a/test/tmp/space.html b/test/tmp/space.html new file mode 100644 index 00000000..72a7f15e --- /dev/null +++ b/test/tmp/space.html @@ -0,0 +1,50 @@ +

    Search

    + + + + + +
    +
      + +Note: No data is indexed by default, you need to add some! + + + + + + + \ No newline at end of file diff --git a/test/tmp/time.html b/test/tmp/time.html index 7bf617ec..4d1c1d49 100644 --- a/test/tmp/time.html +++ b/test/tmp/time.html @@ -1,60 +1,72 @@ -

      User

      - -
      - - - - -
      +

      Infinite Stream of Tweets/Chats:

        - -
        - - -
        +
        while in scroll, latest tweet should show here
        +Scroll: + + - - - + + \ No newline at end of file