Full Code of philholden/redux-swarmlog for AI

master 8b53d605876f cached
44 files
46.4 KB
13.9k tokens
34 symbols
1 requests
Download .txt
Repository: philholden/redux-swarmlog
Branch: master
Commit: 8b53d605876f
Files: 44
Total size: 46.4 KB

Directory structure:
gitextract_retdfhs6/

├── .babelrc
├── .builderrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── .travis.yml-old
├── LICENSE
├── README.md
├── consume.html
├── devServer.js
├── examples/
│   ├── consume.html
│   ├── index.html
│   ├── keys.json
│   ├── publish.html
│   └── src/
│       ├── __tests__/
│       │   ├── index.test.js
│       │   └── null-compiler.js
│       ├── actions/
│       │   └── index.js
│       ├── api.js
│       ├── components/
│       │   ├── app.js
│       │   ├── song-item-container.js
│       │   ├── song-list-container.js
│       │   ├── song-store-item-container.js
│       │   ├── song-store-list-container.js
│       │   └── song-store-sync-container.js
│       ├── consume.js
│       ├── generate-keys.js
│       ├── index.js
│       ├── publish.js
│       ├── reducers/
│       │   ├── index.js
│       │   └── song-stores.js
│       └── sagas/
│           └── index.js
├── index.html
├── keys.json
├── package.json
├── publish.html
├── src/
│   ├── __tests__/
│   │   ├── index.test.js
│   │   └── null-compiler.js
│   ├── index.js
│   └── redux-swarmlog.js
├── webpack.config.dev.js
├── webpack.config.lib.js
└── webpack.config.prod.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["react", "es2015", "stage-1"],
  "env": {
    "development": {
      "presets": ["react-hmre"]
    },
    "lib": {
      "plugins": [
        [
          "babel-plugin-webpack-loaders",
          {
            "config": "./webpack.config.lib.js",
            "verbose": false,
          }
        ]
      ]
    }
  }
}


================================================
FILE: .builderrc
================================================
---
archetypes:
  - component-archetype

================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true


[*]

# Change these settings to your own preference
indent_style = space
indent_size = 2

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

================================================
FILE: .eslintrc
================================================
{
  "extends": "rackt",
  "ecmaFeatures": {
    "jsx": true,
    "modules": true
  },
  "env": {
    "browser": true,
    "node": true,
    "mocha": true
  },
  "parser": "babel-eslint",
  "rules": {
    "quotes": [2, "single"],
    "strict": [2, "never"],
    "babel/generator-star-spacing": 1,
    "babel/object-shorthand": 1,
    "babel/no-await-in-loop": 1,
    "react/jsx-uses-react": 2,
    "react/jsx-uses-vars": 2,
    "react/react-in-jsx-scope": 2
  },
  "plugins": [
    "babel",
    "react"
  ]
}


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
npm-debug.log
dist
.nyc_output
coverage
lib

================================================
FILE: .npmignore
================================================
.DS_Store
*.log
src
test
examples
docs
demo
coverage
*.sublime-project
*.sublime-workspace


================================================
FILE: .travis.yml
================================================
sudo: false
language: node_js
cache:
  directories:
    - node_modules
branches:
  only:
    - master
notifications:
  email: false
node_js:
  - "5.1.0"
before_install:
  - npm i -g npm@^3.0.0
before_script:
  - npm prune
script:
  - npm run test:cover
  - npm run check-coverage
  - npm run build
after_success:
  - npm run report-coverage
  - npm run semantic-release
branches:
  except:
    - "/^v\\d+\\.\\d+\\.\\d+$/"


================================================
FILE: .travis.yml-old
================================================
sudo: false
language: node_js
cache:
  directories:
    - node_modules
branches:
  only:
    - master
notifications:
  email: false
node_js:
  - "5.1.0"
before_install:
  - npm i -g npm@^3.0.0
before_script:
  - npm prune
script:
  - npm run test:cover
  - npm run check-coverage
  - npm run build
after_success:
  - npm run report-coverage
  - npm run semantic-release
branches:
  except:
    - "/^v\\d+\\.\\d+\\.\\d+$/"


================================================
FILE: LICENSE
================================================
Creative Commons Legal Code

CC0 1.0 Universal

    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
    HEREUNDER.

Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.

For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.

1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:

  i. the right to reproduce, adapt, distribute, perform, display,
     communicate, and translate a Work;
 ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
     likeness depicted in a Work;
 iv. rights protecting against unfair competition in regards to a Work,
     subject to the limitations in paragraph 4(a), below;
  v. rights protecting the extraction, dissemination, use and reuse of data
     in a Work;
 vi. database rights (such as those arising under Directive 96/9/EC of the
     European Parliament and of the Council of 11 March 1996 on the legal
     protection of databases, and under any national implementation
     thereof, including any amended or successor version of such
     directive); and
vii. other similar, equivalent or corresponding rights throughout the
     world based on applicable law or treaty, and any national
     implementations thereof.

2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.

3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.

4. Limitations and Disclaimers.

 a. No trademark or patent rights held by Affirmer are waived, abandoned,
    surrendered, licensed or otherwise affected by this document.
 b. Affirmer offers the Work as-is and makes no representations or
    warranties of any kind concerning the Work, express, implied,
    statutory or otherwise, including without limitation warranties of
    title, merchantability, fitness for a particular purpose, non
    infringement, or the absence of latent or other defects, accuracy, or
    the present or absence of errors, whether or not discoverable, all to
    the greatest extent permissible under applicable law.
 c. Affirmer disclaims responsibility for clearing rights of other persons
    that may apply to the Work or any use thereof, including without
    limitation any person's Copyright and Related Rights in the Work.
    Further, Affirmer disclaims responsibility for obtaining any necessary
    consents, permissions or other rights required for any use of the
    Work.
 d. Affirmer understands and acknowledges that Creative Commons is not a
    party to this document and has no duty or obligation with respect to
    this CC0 or use of the Work.


================================================
FILE: README.md
================================================
# Redux Swarmlog

[![travis build](https://img.shields.io/travis/philholden/redux-swarmlog.svg?style=flat-square)](https://travis-ci.org/philholden/redux-swarmlog) [![version](https://img.shields.io/npm/v/@philholden/redux-swarmlog.svg?style=flat-square)](http://npm.im/@philholden/redux-swarmlog)

[![Video](http://img.youtube.com/vi/M99djS07Ph8/0.jpg)](http://www.youtube.com/watch?v=M99djS07Ph8)

_(Click image to watch React Europe lightning video)_

<a href="https://egghead.io/lessons/react-redux-peer-to-peer-todomvc-over-webrtc-using-swarmlog" target="_blank">![ScreenShot](https://raw.github.com/philholden/todomvc-redux-swarmlog/master/redux-swarmlog-egghead.png)</a>

_(Click image to watch Egghead.io intro video)_

A super simple way of writing distributed Redux applications. The [Redux](https://github.com/reactjs/redux) action log is persisted in an IndexDB and synced with other peers via a [WebRTC Swarm](https://github.com/mafintosh/webrtc-swarm) using [Swarmlog](https://github.com/substack/swarmlog).

When an application reloads the Redux store is initialsed by reducing all the persisted actions in the IndexDB and syncing any new actions from remote peers. Watch the Egghead video above to find out more.

## Pros

* offline data by default
* super simple mental model for writing distributed apps
* UIs update automatically as remote actions come in 
* works offline by default
* scales globally for free with no bandwidth or storage costs for the developer
* the developer is not responsible for client data
* friends not cooperations hold user data 
* public / private key authentication is lighter weight than user accounts
* time travel 

## Cons

  * Action logs use more bandwidth than raw data so initial sync could be slow for a very long log.
  
  __workaround:__ Break down long logs into lots of smaller logs e.g. log per month, week or day. Only fetch the most recent log if its all thats needed
  
  * Extra storage space needed on client
  
  __workaround:__ The price of SSDs is falling very rapidly. Stop thinking about cloud and thin client, but cache encrypted data where it is needed. This gives privacy, enables working offline, provides backups and can act as a CDN. Once you start using a distributed system like Git you soon stop thinking about the extra space it requires. 

  * Permanence: even if an action deletes an item it can still be retrieved from the log.
  
 __workaround:__ Use a log for versioning other logs. Every so often the main log is reduced and a single action is written to a new log which creates the store in the current state. The old log is marked as stale in the version log and its database is deleted (purging old actions).
 
  * Can't get most up to date data if the peer holding it is offline

  __workaround:__ A small device like a Raspberry PI kept online should be all that is needed to make sure there is always at least one up to date source of truth. With 5G and IoT we are heading towards an era of always online small connected devices. Let's start thinking that way now.

## Play to Strengths

Redux Swarmlog works well for apps that support some kind of physical live event. Because you know the action log will be short and the users will be online at the same time. Examples might be providing subtitles via mobile phone for a theatre show or letting a teacher see in realtime how each individuals in a class is answering a question. 


================================================
FILE: consume.html
================================================
<!doctype html>
<html>
  <head>
    <title>React Transform Boilerplate</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/consume.js"></script>
  </body>
</html>


================================================
FILE: devServer.js
================================================
var path = require('path')  // eslint-disable-line no-var
var express = require('express')  // eslint-disable-line no-var
var webpack = require('webpack')  // eslint-disable-line no-var
var config = require('./webpack.config.dev') // eslint-disable-line no-var
var compression = require('compression') // eslint-disable-line no-var
var ssbKeys = require('ssb-keys')

// var requestProxy = require('express-request-proxy')
// var objectAssign = require('object-assign')

var app = express() // eslint-disable-line no-var
var server = require('http').createServer(app) // eslint-disable-line no-var
var io = require('socket.io')(server) // eslint-disable-line no-var

var compiler = webpack(config) // eslint-disable-line no-var
var port = 3000 // eslint-disable-line no-var

app.use(require('webpack-dev-middleware')(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath
}))

app.use(require('webpack-hot-middleware')(compiler))

app.use(compression({
  threshold: 512
}))

app.use('/', express.static('.'))
app.get('/keys/', (req, res) => {
  res.setHeader('Content-Type', 'application/json')
  res.send(JSON.stringify(require('ssb-keys').generate()))
})

// app.all('*', function(req, res, next) {
//   var url = require('url').parse(req.url)
//   var conf = objectAssign({}, req, {
//     url: 'http://127.0.0.1:8888' + url.pathname,
//     timeout: 120000
//   })
//   requestProxy(conf)(req, res, next)
// })

// app.get('*', function (req, res) {
//   res.sendFile(path.join(__dirname, 'index.html'))
// })

server.listen(port, '0.0.0.0', function (err) {
  if (err) {
    console.log(err) // eslint-disable-line no-console
    return
  }
  console.log('Listening at http://localhost:' + port) // eslint-disable-line no-console
})

io.on('connection', function (socket) {
  io.set('origins', '*:*')
  console.log('connected') // eslint-disable-line no-console
  socket.emit('update', 'connected')
  socket.on('single', function () {
    socket.emit('update', 'single')
  })
  socket.on('publish', function (data) {
    io.sockets.emit('update', data)
  })
})


================================================
FILE: examples/consume.html
================================================
<!doctype html>
<html>
  <head>
    <title>React Transform Boilerplate</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/consume.js"></script>
  </body>
</html>


================================================
FILE: examples/index.html
================================================
<!doctype html>
<html>
  <head>
    <title>React Transform Boilerplate</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/bundle.js"></script>
  </body>
</html>


================================================
FILE: examples/keys.json
================================================
{
  "curve": "ed25519",
  "public": "q8oQyaB0t9k8bAog6om+q86FRbBoYUklC0eQToR+nw8=.ed25519",
  "private": "PmS89eOtLC35JJlcRMquh6qS8oHG4uZpQQcpw3aRHHWryhDJoHS32TxsCiDqib6rzoVFsGhhSSULR5BOhH6fDw==.ed25519",
  "id": "@q8oQyaB0t9k8bAog6om+q86FRbBoYUklC0eQToR+nw8=.ed25519"
}


================================================
FILE: examples/publish.html
================================================
<!doctype html>
<html>
  <head>
    <title>React Transform Boilerplate</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/publish.js"></script>
  </body>
</html>


================================================
FILE: examples/src/__tests__/index.test.js
================================================
import test from 'ava'
import is from 'is_js'
import React from 'react'
import { createRenderer } from 'react-addons-test-utils'
import expect from 'expect'
import expectJSX from 'expect-jsx'
import { HelloWorld } from '../App'

expect.extend(expectJSX)

test('is an array of numbers', t => {
  t.true(
    [ 1, 2, 3 ].every(item => typeof item === 'number')
  )
})

test('1 is in array', t => {
  t.true(
    is.inArray(1, [ 1, 2, 3 ])
  )
})

test('MyComponent default render', () => {
  const renderer = createRenderer()

  renderer.render(
    <HelloWorld />
  )
  expect(
    renderer.getRenderOutput()
  )
  .toEqualJSX(
    <div>Hello World.</div>
  )
})


================================================
FILE: examples/src/__tests__/null-compiler.js
================================================
// Prevent Mocha from compiling class
function noop() {
  return null
}

require.extensions['.css'] = noop
require.extensions['.png'] = noop


================================================
FILE: examples/src/actions/index.js
================================================
export const ADD_SONG_STORE = 'ADD_SONG_STORE'
export const REMOVE_SONG_STORE = 'REMOVE_SONG_STORE'
export const PUT_SONG_IN_SONG_STORE = 'PUT_SONG_IN_SONG_STORE'
export const REMOVE_SONG_FROM_SONG_STORE = 'REMOVE_SONG_FROM_SONG_STORE'
export const ADD_SONG_STORE_SUCCEEDED = 'ADD_SONG_STORE_SUCCEEDED'
export const REMOVE_SONG_STORE_SUCCEEDED = 'REMOVE_SONG_STORE_SUCCEEDED'

export const songStoreActions = [
  ADD_SONG_STORE,
  REMOVE_SONG_STORE,
  PUT_SONG_IN_SONG_STORE,
  REMOVE_SONG_FROM_SONG_STORE,
  ADD_SONG_STORE_SUCCEEDED
]

export function addSongStore(swarmLogMeta) {
  return {
    type: ADD_SONG_STORE,
    swarmLogMeta
  }
}

export function addSongStoreSucceeded(swarmLogMeta) {
  return {
    type: ADD_SONG_STORE_SUCCEEDED,
    swarmLogMeta
  }
}

export function removeSongStore(songStoreId) {
  return {
    type: REMOVE_SONG_STORE,
    songStoreId
  }
}

export function removeSongStoreSucceeded(songStoreId) {
  return {
    type: REMOVE_SONG_STORE_SUCCEEDED,
    songStoreId
  }
}

export function putSongInSongStore(songStoreId, song) {
  return {
    type: PUT_SONG_IN_SONG_STORE,
    songStoreId,
    song,
    reduxSwarmLogId: songStoreId
  }
}

export function removeSongFromSongStore(songStoreId, songId) {
  return {
    type: REMOVE_SONG_FROM_SONG_STORE,
    songStoreId,
    songId,
    reduxSwarmLogId: songStoreId
  }
}


================================================
FILE: examples/src/api.js
================================================

export function generateKeys() {
  return fetch(`/keys`, {
    method: 'get',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'text/plain'
    }
  })
  .then((res) => {
    if (res.status >= 400) {
      throw new Error('Bad response from server')
    }
    return res.json()
  })
  .catch(function (err) {
    throw new Error('Bad response from server: ', err.message)
  })
}


================================================
FILE: examples/src/components/app.js
================================================
import React, { Component } from 'react'
import { connect } from 'react-redux'
import SongStoreListContainer from './song-store-list-container'


class App extends Component {
  render() {
    return (
      <div>
      <SongStoreListContainer />
      <pre>
        {
        //  JSON.stringify(this.props.state, null, 2)
        }
      </pre>
      </div>

    )
  }
}

export default connect(state => ({ state }))(App)


================================================
FILE: examples/src/components/song-item-container.js
================================================
import React from 'react'
import { connect } from 'react-redux'

import {
  removeSongFromSongStore
} from '../actions/index'

const SongItem = ({ song, onRemove }) => {
  return (
    <div>
      <span style={styles.songStoreTitle}>{ song.id }</span>
      {' '}
      <button onClick={onRemove}>Remove</button>
    </div>
  )
}

const mapStateToProps = (
  { songStores },
  { songStoreId, songId }
) => {
  return {
    songStoreId,
    song: songStores[songStoreId].songs[songId]
  }
}

const mapDispatchToProps = (dispatch, { songStoreId, songId }) => ({
  onRemove: () => dispatch(removeSongFromSongStore(
    songStoreId,
    songId
  ))
})

const SongItemContainer = connect(
  mapStateToProps,
  mapDispatchToProps
) (SongItem)

export default SongItemContainer

const styles = {
  songStoreTitle: {
    fontSize: 16,
    fontFamily: 'sans-serif'
  }
}


================================================
FILE: examples/src/components/song-list-container.js
================================================
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import SongItemContainer from './song-item-container'
import {
  putSongInSongStore
} from '../actions/index'


const AddSongForm = ({
  songStoreId,
  putSongInSongStore
}) => {
  let input

  const onAddSong = e => {
    e.preventDefault()
    putSongInSongStore(
      songStoreId,
      {
        id: input.value,
        name: input.value
      }
    )
  }

  return (
    <div>
      <form onSubmit={onAddSong}>
        <input type="text" ref={el => input = el} />
        {' '}
        <button onClick={onAddSong}>Add Song</button>
      </form>
    </div>
  )
}

const SongList = ({
  songIds,
  songStoreId,
  putSongInSongStore
}) => {

  return (
    <div>
      {
        songIds.map(songId => (
          <SongItemContainer
            songId={songId}
            songStoreId={songStoreId}
            key={songId}
          />
        ))
      }

      <AddSongForm { ...{
        songStoreId,
        putSongInSongStore
      } } />
    </div>
  )
}

const mapStateToProps = (
  { songStores },
  { songStoreId }
) => {
  const songs = songStores[songStoreId].songs
  return {
    songStoreId,
    songIds: Object.keys(songs)
  }
}

const mapDispatchToProps = dispatch => bindActionCreators({
  putSongInSongStore
}, dispatch)

const SongListContainer = connect(
  mapStateToProps,
  mapDispatchToProps
) (SongList)

export default SongListContainer

const styles = {
  songStoreTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    fontFamily: 'sans-serif'
  }
}


================================================
FILE: examples/src/components/song-store-item-container.js
================================================
import React from 'react'
import { connect } from 'react-redux'
import SongListContainer from './song-list-container'

import {
  removeSongStore
} from '../actions/index'

const SongStoreItem = ({ name, keys, id, onRemove }) => {
  return (
    <div style={styles.outer}>
      <div style={styles.hr}>
        <span style={styles.songStoreTitle}>{ name }</span>
        {' '}
        <button onClick={onRemove}>Remove</button>
      </div>
      <SongListContainer songStoreId={id} />
      <div style={styles.keys}>
        <b>public key:</b> <input value={keys.public} readOnly={true}/><br />
        <b>private key:</b> <input value={keys.private} readOnly={true}/><br />
      </div>
    </div>
  )
}

const mapStateToProps = ({ songStores }, { id }) => {
  const { swarmLogMeta } = songStores[id]
  return {
    ...swarmLogMeta
  }
}

const mapDispatchToProps = (dispatch, { id }) => ({
  onRemove: () => dispatch(removeSongStore(id))
})

const SongStoreItemContainer = connect(
  mapStateToProps,
  mapDispatchToProps
) (SongStoreItem)

export default SongStoreItemContainer

const styles = {
  songStoreTitle: {
    fontSize: 20,
    fontWeight: 'bold',
  },
  outer: {
    border: '1px solid #ccc',
    borderRadius: 3,
    padding: 20,
    fontFamily: 'sans-serif',
    lineHeight: 1.8,
    marginBottom: '1em'
  },
  hr: {
    borderBottom: '2px solid #eee',
    margin: '0 0 7px'
  },
  keys: {
    fontSize: 11,
  }
}


================================================
FILE: examples/src/components/song-store-list-container.js
================================================
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import SongStoreSyncContainer from './song-store-sync-container'
import SongStoreItemContainer from './song-store-item-container'
import {
  addSongStore
} from '../actions/index'

const SongStoreList = ({
  songStores
}) => {
  let input
  return (
    <div>
      { songStores.map(({ id }) => (
          <SongStoreItemContainer id={id} key={id} />
        ))
      }
      <SongStoreSyncContainer />
    </div>
  )
}

const mapStateToProps = ({ songStores }) => ({
  songStores: Object.keys(songStores)
    .map(key => ({
      ...songStores[key].swarmLogMeta
    }))
})

// const mapDispatchToProps = dispatch => bindActionCreators({
//   addSongStore
// }, dispatch)

const SongStoreListContainer = connect(
  mapStateToProps,
  null
) (SongStoreList)

export default SongStoreListContainer

const styles = {
  songStoreTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    fontFamily: 'sans-serif'
  },
  inputWrapper: {
    margin: '6px 0'
  }
}


================================================
FILE: examples/src/components/song-store-sync-container.js
================================================
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import {
  addSongStore
} from '../actions/index'


const SongStoreSync = ({
  addSongStore
}) => {
  let publicKey, privateKey, name
  const onSyncSongStore = e => {
    e.preventDefault()
    const pub = publicKey.value
    const priv = privateKey.value

    let swarmLogMeta = { name : name.value }
    if (pub !== '' && pub) {
      swarmLogMeta.keys = {
        public: pub
      }
      if (priv !== '' && priv) {
        swarmLogMeta.keys.private = priv
      }
    }
    addSongStore(swarmLogMeta)
  }
  return (
    <form onSubmit={onSyncSongStore} style={styles.outer}>
      <div style={styles.hr}>
        <span style={styles.songStoreTitle}>Add & Sync Stores</span>
      </div>
      <div style={styles.row}>
        <div style={styles.inputs}>
          <input key="1" type="text" ref={el => name = el}/> Name <br/>
          <input key="2" type="text" ref={el => publicKey = el}/> Public <br/>
          <input key="3" type="text" ref={el => privateKey = el}/> Private <br/>
          <button>Add Store</button>
        </div>
        <ul style={styles.ul}>
          <li>To add a new local store just fill in name</li>
          <li>To sync a remote store add a name (can be anything) then add private and public keys to give read write access or just the public key for read only access</li>
        </ul>
      </div>
    </form>
  )
}

// const mapStateToProps = ({ songStores }) => ({
//   songStores: Object.keys(songStores)
//     .map(key => ({
//       ...songStores[key].swarmLogMeta
//     }))
// })

const mapDispatchToProps = dispatch => bindActionCreators({
  addSongStore
}, dispatch)

const SongStoreSyncContainer = connect(
  null,
  mapDispatchToProps
) (SongStoreSync)

export default SongStoreSyncContainer

const styles = {
  songStoreTitle: {
    fontSize: 20,
    fontWeight: 'bold'
  },
  hr: {
    borderBottom: '2px solid #eee',
    margin: '0 0 7px'
  },
  outer: {
    border: '1px solid #ccc',
    borderRadius: 3,
    padding: 20,
    fontFamily: 'sans-serif',
    lineHeight: 1.8,
    marginBottom: '1em'
  },
  row: {
    display: 'flex',
    alignItems: 'center'
  },
  inputs: {
    flexBasis: 260
  },
  ul: {
    fontSize: 12,
    marginTop: 0,
    maxWidth: 300
  }
}


================================================
FILE: examples/src/consume.js
================================================
import swarmlog from 'swarmlog'
import memdb from 'memdb'

const log = swarmlog({
  publicKey: require('../keys.json').public,
  sodium: require('chloride/browser'),
  db: memdb(),
  valueEncoding: 'json',
  hubs: [ 'https://signalhub.mafintosh.com' ]
})

log.createReadStream({ live: true })
  .on('data', function (data) {
    //console.log('RECEIVED', data.key, data.value)
    const logEl = document.createElement('div')
    logEl.innerHTML = `<b>RECEIVED:</b> ${JSON.stringify(data.value)}`
    document.body.insertBefore(logEl, document.body.firstChild)
  })


================================================
FILE: examples/src/generate-keys.js
================================================
import supercop from '../supercop.js'

import crypto from 'crypto'

export default function generateKeys() {
  return require('../keys-old.json')
  const keys = supercop.createKeypair()
  return {
    curve: `ed25519`,
    public: `${keys.pubKey.toString('base64')}.ed25519`,
    private: `${keys.privKey.toString('base64')}.ed25519`,
    id: `@${keys.pubKey.toString('base64')}.ed25519`,
//    id: crypto.createHash('sha256').update(keys.pubKey).digest('base64')+'.sha256',
    hashKey: crypto.createHash('sha256').update(keys.pubKey).digest('hex'),
  }
}


================================================
FILE: examples/src/index.js
================================================
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers/index'
import { generateKeys } from './api'
import * as _actions from './actions/index'
import { sagaMiddleware } from './sagas'
import { bindActionCreators } from 'redux'

import {
  createStore,
  applyMiddleware,
  compose
} from 'redux'

import phil from '../../lib/index'

import {
  configureReduxSwarmLog,
  reduxSwarmLogMiddleware,
  getSwarmLogsFromDb
}
//from '../../src/redux-swarmlog'
from '../../lib/index'

console.log({
  configureReduxSwarmLog,
  reduxSwarmLogMiddleware,
  getSwarmLogsFromDb
},phil)

const store = createStore(
  rootReducer,
  compose(
    applyMiddleware(
      reduxSwarmLogMiddleware,
      sagaMiddleware
    ),
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )
)

const actions = bindActionCreators(_actions, store.dispatch)

window.actions = actions
window.dispatch = store.dispatch

configureReduxSwarmLog({
  reduxStore: store,
  generateKeys,
  logSampleActions,
  logLevel: 1
})

getSwarmLogsFromDb()
  .then(reduxSwarmLogs => {
    if (reduxSwarmLogs.length === 0) {
      actions.addSongStore({ name: 'My Songs' })
    } else {
      reduxSwarmLogs.forEach(actions.addSongStore)
    }
  })

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

function logSampleActions({ id, keys, name }) {
  console.log(
`
%cthe following actions can be dispatched from the console:

%c// clone song store over rtc on remote machine or in incognito window
%cactions.addSongStore({
  name: '${name} Clone',
  keys: {
    public: '${keys.public}',
    private: '${keys.private}'
  }
})

%c// add song
%cactions.putSongInSongStore('${id}', {id: 'hello', text: 'world'})

%c// remove song
%cactions.removeSongFromSongStore('${id}', 'hello')

%c// add new local song store
%cactions.addSongStore({ name: 'New Song Store' })

%c// remove song store
%cactions.removeSongStore('${id}')

`,
'font-weight: bold',
'font-style: italic; color: #888',
'color: #559',
'font-style: italic; color: #888',
'color: #559',
'font-style: italic; color: #888',
'color: #559',
'font-style: italic; color: #888',
'color: #559',
'font-style: italic; color: #888',
'color: #559'
)
}


================================================
FILE: examples/src/publish.js
================================================
import swarmlog from 'swarmlog'
import memdb from 'memdb'
import leveljs from 'level-js'
import levelup from 'levelup'
import sodium from 'chloride/browser'

//window.Key = Key

window.sodium = sodium

window.db = memdb()
//window.db = levelup('foo', { db: leveljs })
//indexedDB.deleteDatabase('IDBWrapper-foo')

const log = swarmlog({
  keys: require('../keys.json'),
  sodium,
  db: window.db,
  valueEncoding: 'json',
  hubs: [ 'https://signalhub.mafintosh.com' ]
})

let times = 0
setInterval(function () {
  const data = { message: 'HELLO!x' + times }
  log.append(data)
  times++
  const logEl = document.createElement('div')
  logEl.innerHTML = `<b>SENT:</b> ${JSON.stringify(data)}`
  document.body.insertBefore(logEl, document.body.firstChild)
}, 3000)

log.createReadStream({ live: true })
  .on('data', function (data) {
    const logEl = document.createElement('div')
    logEl.innerHTML = `<b>RECEIVED:</b> ${JSON.stringify(data.value)}`
    document.body.insertBefore(logEl, document.body.firstChild)
  })


================================================
FILE: examples/src/reducers/index.js
================================================
import { combineReducers } from 'redux'
import songStores from './song-stores'

export default combineReducers({
  songStores
})


================================================
FILE: examples/src/reducers/song-stores.js
================================================
import {
  ADD_SONG_STORE,
  ADD_SONG_STORE_SUCCEEDED,
  REMOVE_SONG_STORE_SUCCEEDED,
  PUT_SONG_IN_SONG_STORE,
  REMOVE_SONG_FROM_SONG_STORE
} from '../actions/index'

export function root(state = {}, action) {
  switch (action.type) {
    case ADD_SONG_STORE:
    case REMOVE_SONG_STORE:
    case PUT_SONG_IN_SONG_STORE:
    case REMOVE_SONG_FROM_SONG_STORE:
      return {
        ...state,
        songStores: songStores(state.songStores, action)
      }
    default:
      return state
  }
}

export default function songStores(state = {}, action) {
  switch (action.type) {
    case ADD_SONG_STORE_SUCCEEDED:
      console.log(action, action.swarmLogMeta.id,action.swarmLogMeta)
      return {
        ...state,
        [action.swarmLogMeta.id]: {
//          id: action.songStoreId,
          swarmLogMeta: { ...action.swarmLogMeta },
          songs: {}
        }
      }
    case REMOVE_SONG_STORE_SUCCEEDED: {
      const songStores = { ...state }
      delete songStores[action.songStoreId]
      return songStores
    }
    case PUT_SONG_IN_SONG_STORE:
    case REMOVE_SONG_FROM_SONG_STORE:
      return {
        ...state,
        [action.songStoreId]: songStore(state[action.songStoreId], action)
      }
    default:
      return state
  }
}

function songStore(state = {}, action) {
  const songs = { ...state.songs }
  switch (action.type) {
    case PUT_SONG_IN_SONG_STORE:
      return {
        ...state,
        songs: {
          ...songs,
          [action.song.id]: action.song
        }
      }
    case REMOVE_SONG_FROM_SONG_STORE: {
      delete songs[action.songId]
//      console.log(`delete ${action.id}` + songs)
      return { ...state, songs }
    }
    default:
      return state
  }
}

export function getSong(state, songStoreId, songId) {
  return state.songStores[songStoreId][songId]
}


================================================
FILE: examples/src/sagas/index.js
================================================
import { takeEvery } from 'redux-saga'
import { put, call } from 'redux-saga/effects'
import { generateKeys } from '../api'
import createSagaMiddleware from 'redux-saga'

import {
  keyToUriId,
  getSwarmLogsFromDb,
  addReduxSwarmLog,
  removeReduxSwarmLog
}
//from '../../../src/redux-swarmlog'
from '../../../lib/index'

import {
  ADD_SONG_STORE,
  REMOVE_SONG_STORE,
  addSongStoreSucceeded,
  removeSongStoreSucceeded
} from '../actions/index'

export const sagaMiddleware = createSagaMiddleware(
  removeSongStore,
  addSongStore
)

function *addSongStore() {
  yield* takeEvery(ADD_SONG_STORE, getKeys)
}

function *getKeys(action) {
  let swarmLogMeta = action.swarmLogMeta || {}
  if (!action.swarmLogMeta.keys) {
    swarmLogMeta.keys = yield call(generateKeys)
    //swarmLogMeta.id = `${swarmLogMeta.name.replace(/\s/g,'_')}-${swarmLogMeta.keys.public}`
  }
  yield addReduxSwarmLog(swarmLogMeta)
  yield put(addSongStoreSucceeded(swarmLogMeta))
}

function *removeSongStore() {
  yield* takeEvery(REMOVE_SONG_STORE, removeSongStoreFromDb)
}

function *removeSongStoreFromDb(action) {
  yield removeReduxSwarmLog(action.songStoreId)
  yield put(removeSongStoreSucceeded(action.songStoreId))
}



================================================
FILE: index.html
================================================
<!doctype html>
<html>
  <head>
    <title>React Transform Boilerplate</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/bundle.js"></script>
  </body>
</html>


================================================
FILE: keys.json
================================================
{
  "public":"qkN3S7AlBASCD6azVLqdPuWkp+TyU3g9RlfwCZA1yQ0=.ed25519",
  "publicSuper":"btb+H8Z//WHc7HS1+u54DD0jnRObljx5mskPVxi3VkI=.ed25519",
  "private":"O7GOCmou9JwXJtvDkhS17lSGbPokB3FoKB2FN67go5OqQ3dLsCUEBIIPprNUup0+5aSn5PJTeD1GV/AJkDXJDQ==.ed25519",
  "privateSuper":"UA3MhnzUijBQYmetVrXJzoN0vAF/BunXqw32aqGeyW+DPssqSA87/kyN6TzBkbyqksVrlm0Ookhr5rcYkH+HkA==.ed25519"
}


================================================
FILE: package.json
================================================
{
  "name": "@philholden/redux-swarmlog",
  "version": "0.0.1-semantic",
  "description": "nothing",
  "main": "lib/index.js",
  "scripts": {
    "clean": "rimraf lib dist",
    "build:webpack": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js",
    "build:babel": "NODE_ENV=lib babel src --out-dir lib",
    "build": "npm run clean && npm run build:webpack",
    "start": "node devServer.js",
    "lint": "eslint src",
    "commit": "git-cz",
    "open-coverage": "open ./coverage/lcov-report/index.html",
    "check-coverage": "nyc check-coverage --statements 0 --branches 0 --functions 0 --lines 0",
    "report-coverage": "nyc report --reporter=text-lcov | codecov",
    "test": "NODE_ENV=test ava src/**/*.test.js --require babel-register --require ./src/__tests__/null-compiler",
    "test:watch": "nodemon -w src --exec 'npm t -- --verbose'",
    "test:cover": "nyc --reporter=lcov --reporter=text npm t",
    "semantic-release": "semantic-release pre && npm publish --access=public && semantic-release post"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/philholden/redux-swarmlog.git"
  },
  "keywords": [
    "react",
    "reactjs",
    "boilerplate",
    "hot",
    "reload",
    "hmr",
    "live",
    "edit",
    "webpack",
    "babel",
    "react-transform"
  ],
  "author": "Phil Holden (http://github.com/philholden)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/philholden/redux-swarmlog/issues"
  },
  "homepage": "https://github.com/philholden/redux-swarmlog/",
  "devDependencies": {
    "ava": "^0.14.0",
    "babel-cli": "^6.4.0",
    "babel-core": "^6.3.15",
    "babel-eslint": "^5.0.0-beta4",
    "babel-loader": "^6.2.0",
    "babel-plugin-webpack-loaders": "^0.1.0",
    "babel-polyfill": "^6.7.4",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-react": "^6.3.13",
    "babel-preset-react-hmre": "^1.0.0",
    "babel-preset-stage-1": "^6.5.0",
    "babel-preset-stage-2": "^6.3.13",
    "babel-register": "^6.3.13",
    "brfs": "^1.4.3",
    "chloride": "^2.1.1",
    "codecov.io": "0.1.6",
    "coffee-script": "^1.10.0",
    "commitizen": "^2.7.6",
    "compression": "^1.6.0",
    "conventional-changelog": "^1.1.0",
    "cross-env": "^1.0.6",
    "cz-conventional-changelog": "^1.1.5",
    "eslint": "^1.10.3",
    "eslint-config-rackt": "^1.1.1",
    "eslint-plugin-babel": "^3.0.0",
    "eslint-plugin-react": "^3.11.3",
    "eventsource-polyfill": "^0.9.6",
    "expect": "^1.13.4",
    "expect-jsx": "^2.2.2",
    "express": "^4.13.3",
    "file-loader": "^0.8.5",
    "ghooks": "0.3.2",
    "guid": "0.0.12",
    "is_js": "^0.7.6",
    "json-loader": "^0.5.4",
    "keypair": "^1.0.0",
    "level-js": "^2.2.3",
    "levelup": "^1.3.1",
    "libsodium": "^0.3.0",
    "malloc": "^1.1.0",
    "memdb": "^1.3.1",
    "mock-fs": "^3.8.0",
    "nodemon": "^1.8.1",
    "nyc": "^5.3.0",
    "react": "^15.0.1",
    "react-addons-test-utils": "^15.0.1",
    "react-dom": "^15.0.1",
    "react-redux": "^4.4.2",
    "redux": "^3.4.0",
    "redux-saga": "^0.9.5",
    "rimraf": "^2.4.3",
    "semantic-release": "^4.3.5",
    "socket.io": "^1.4.0",
    "ssb-keys": "^5.0.1",
    "swarmlog": "^1.4.0",
    "transform-loader": "^0.2.3",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.9",
    "webpack-dev-middleware": "^1.4.0",
    "webpack-hot-middleware": "^2.6.0"
  },
  "config": {
    "ghooks": {
      "pre-commit": "npm run test"
    },
    "commitizen": {
      "path": "node_modules/cz-conventional-changelog"
    }
  },
  "release": {
    "debug": false
  }
}


================================================
FILE: publish.html
================================================
<!doctype html>
<html>
  <head>
    <title>React Transform Boilerplate</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/publish.js"></script>
  </body>
</html>


================================================
FILE: src/__tests__/index.test.js
================================================
import test from 'ava'
import is from 'is_js'
import React from 'react'
import { createRenderer } from 'react-addons-test-utils'
import expect from 'expect'
import expectJSX from 'expect-jsx'
//import { HelloWorld } from '../App'

expect.extend(expectJSX)

test('is an array of numbers', t => {
  t.true(
    [ 1, 2, 3 ].every(item => typeof item === 'number')
  )
})

test('1 is in the array', t => {
  t.true(
    is.inArray(1, [ 1, 2, 3 ])
  )
})

// test('MyComponent default render', () => {
//   const renderer = createRenderer()

//   renderer.render(
//     <HelloWorld />
//   )
//   expect(
//     renderer.getRenderOutput()
//   )
//   .toEqualJSX(
//     <div>Hello World.</div>
//   )
// })


================================================
FILE: src/__tests__/null-compiler.js
================================================
// Prevent Mocha from compiling class
function noop() {
  return null
}

require.extensions['.css'] = noop
require.extensions['.png'] = noop


================================================
FILE: src/index.js
================================================
import * as rds from './redux-swarmlog'

module.exports = rds


================================================
FILE: src/redux-swarmlog.js
================================================
import swarmlog from 'swarmlog'
import leveljs from 'level-js'
import levelup from 'levelup'
import { randomBytes } from 'crypto'

const sessionId = randomBytes(32).toString('base64')
const reduxSwarmLogsDb = levelup('swarmlogs', { db: leveljs })
let _reduxSwarmLogs = {}
let _reduxStore
let _logLevel
let _logSampleActions = (...args) => {
  if (_logLevel) console.log(args)
}

window.levelup = levelup
window.clearTables = clearTables

export function clearTables() {
  indexedDB.deleteDatabase('IDBWrapper-foo')
  indexedDB.deleteDatabase('IDBWrapper-swarmlogs')
  Object.keys(_reduxSwarmLogs).forEach(key => {
    indexedDB.deleteDatabase(`IDBWrapper-${key}`)
  })
}

export const keyToUriId = (key) =>
  key
    .replace(/\//g,'_')
    .replace(/\+/g,'-')
    .replace(/=+\.ed25519/,'')

export const uriIdToKey = (uriId) =>
  uriId
    .replace(/\_/g,'/')
    .replace(/\-/g,'+') +
    (uriId.length > 70 ? '=' : '') +
    '=.ed25519'

export function getSwarmLogsFromDb() {
  return new Promise((resolve, reject) => {
    const reduxSwarmLogs = []
    reduxSwarmLogsDb.createReadStream()
    .on('data', data => {
      const value = JSON.parse(data.value)
      logJson(`hydrating from indexedDB`, value.id)
      reduxSwarmLogs.push(value)
    })
    .on('error', err => reject(err))
    .on('end', () => resolve(reduxSwarmLogs))
  })
}

export function removeReduxSwarmLog(id) {
  return new Promise((resolve, reject) => {
    const reduxSwarmLog = _reduxSwarmLogs[id]
    //console.log(_reduxSwarmLogs, id, reduxSwarmLog.db.close)
    reduxSwarmLog.db.close()
    const req = indexedDB.deleteDatabase(`IDBWrapper-${id}`)
    req.onsuccess = () => {
      console.log(`database deleted`)
      reduxSwarmLogsDb.del(reduxSwarmLog.keys.public, err => {
        if (err) {
          console.log(`Couldn't delete database entry`)
          reject()
        } else {
          console.log(`database entry deleted`)
          resolve(`Database removed`)
        }
      })
    }

    req.onerror = () => {
      console.log(`Couldn't delete database`)
      reject()
    }

    req.onblocked = () => {
      console.log(`Couldn't delete database blocked`)
      reject()
    }
  })
}

export function addReduxSwarmLog(props) {
  return new Promise((resolve, reject) => {
    props.id = keyToUriId(props.keys.public)
    const { name, keys, id } = props
    const propsJson = JSON.stringify(props, null, 2)

    if (_reduxSwarmLogs[id]) {
      console.log(`store named ${name} already exists`)
      resolve(_reduxSwarmLogs[id])
      return
    }

    reduxSwarmLogsDb.get(id, (err, value) => {
      if (err && err.notFound) {
        reduxSwarmLogsDb.put(keys.public, propsJson, (err) => {
          if (err) return reject(err)
        })
      }
      _reduxSwarmLogs[id] = new ReduxSwarmLog({
        name,
        keys,
        id
      })
      _logSampleActions(props)
      resolve(_reduxSwarmLogs[id])
    })
  })
}

window.addReduxSwarmLog = addReduxSwarmLog

export function configureReduxSwarmLog({
  reduxStore,
  logSampleActions = _logSampleActions,
  logLevel = 1
}) {
  _reduxStore = reduxStore
  _logSampleActions = logSampleActions
  _logLevel = logLevel
}

export function reduxSwarmLogMiddleware() {
  return next => action => {
    const reduxSwarmLog = _reduxSwarmLogs[action.reduxSwarmLogId]
    if (!action.fromSwarm && reduxSwarmLog && reduxSwarmLog.keys.private) {
      action = {
        ...action,
        swarmLogSessionId: sessionId
      }
      if(reduxSwarmLog) {
        reduxSwarmLog.log.append(action)
        logJson('RTC SENT', action)
      }
    }
    next(action)
  }
}

export default class ReduxSwarmLog {
  constructor({ name, keys, id }) {
    this.db = levelup(id, { db: leveljs })
    this.keys = keys
    this.log = this.getSwarmLog()
    this.name = name
    this.startReadStream()
    this.id = id
    logJson(
      `CREATING ReduxSwarmLog ${name} and start listening`,
      this.keys,
      'green'
    )
  }

  getSwarmLog() {
    return swarmlog({
      keys: this.keys,
      sodium: require('chloride/browser'),
      db: this.db,
      valueEncoding: 'json',
      hubs: [ 'https://signalhub.mafintosh.com' ]
    })
  }

  startReadStream() {
    this.log.createReadStream({ live: true })
    .on('data', function (data) {
      const action = data.value
      if (action.swarmLogSessionId !== sessionId) {
        _reduxStore.dispatch({
          ...action,
          fromSwarm: true
        })
      }
      logJson('RTC RECEIVED', data.value)
    })
  }
}

function logJson(message, payload, color='black') {
  if (!_logLevel) return
  console.log(
`
%c${message}:

%c${
  typeof payload === 'string' ?
  payload:
  JSON.stringify(payload, null, 2)
}

`,
    `font-weight: bold; color: ${color}`,
    'font-weight: normal'
  )
}


================================================
FILE: webpack.config.dev.js
================================================
'use strict'

let path = require('path')
let webpack = require('webpack')

console.log(__dirname)

module.exports = {
  // node: {
  //   fs: 'empty'
  // },

  entry:{
    bundle: [
      'babel-polyfill',
      'eventsource-polyfill', // necessary for hot reloading with IE
      'webpack-hot-middleware/client',
      './examples/src/index'
    ],
    publish: [
      'eventsource-polyfill', // necessary for hot reloading with IE
      'webpack-hot-middleware/client',
      './examples/src/publish'
    ],
    consume: [
      'eventsource-polyfill', // necessary for hot reloading with IE
      'webpack-hot-middleware/client',
      './examples/src/consume'
    ]


  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/static/'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  resolve: {
    modulesDirectories: [
      path.join(__dirname, 'src'),
      path.join(__dirname, 'examples', 'src'),
      'node_modules',
      'node_modules/component-archetype/node_modules'
    ]
  },
  module: {
    postLoaders: [
      {
        loader: 'transform?brfs'
      }
    ],
    loaders: [
      {
        test: /\.jsx?/,
        loader: require.resolve('babel-loader'),
        include: [
          path.join(__dirname, 'src'),
          path.join(__dirname, 'examples', 'src')
        ]
      },
      {
        test: /\.png$/,
        loader: require.resolve('url-loader') + '?limit=100000'
      },
      {
        test: /\.json$/,
        loader: require.resolve('json-loader')
      },
      // {
      //   test: /\.js$/,
      //   loader: 'transform?brfs'
      // }

    ]
  }
}


================================================
FILE: webpack.config.lib.js
================================================
var path = require('path') // eslint-disable-line no-var
//var ExtractTextPlugin = require('extract-text-webpack-plugin') // eslint-disable-line no-var

module.exports = {
  output: {
    libraryTarget: 'commonjs2',
    path: path.join(__dirname, 'lib'),
    publicPath: '/lib/'
  },
//  plugins: [
//    new ExtractTextPlugin(path.parse(process.argv[2]).name + '.css')
//  ],
  module: {
    loaders: [
      {
        test: /\.png$/,
        loaders: [ 'url-loader?limit=7000' ]
      }
    ]
  }
}


================================================
FILE: webpack.config.prod.js
================================================
var path = require('path') // eslint-disable-line no-var
var webpack = require('webpack') // eslint-disable-line no-var

module.exports = {
  devtool: 'source-map',
  entry: [
    './src/index'
  ],
  output: {
    // export itself to a global var
    libraryTarget: 'commonjs2',
    // name of the global var: "Foo"
    library: 'redux-swarmlog',
    path: path.join(__dirname, 'lib'),
    filename: 'index.js',
    publicPath: '/static/'
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify('production')
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false
      }
    })
  ],
  resolve: {
    modulesDirectories: [
      path.join(__dirname, 'src'),
      'node_modules',
      'node_modules/component-archetype/node_modules'
    ]
  },
  module: {
    postLoaders: [
      {
        loader: 'transform?brfs'
      }
    ],
    loaders: [
      {
        test: /\.jsx?/,
        loader: require.resolve('babel-loader'),
        include: [
          path.join(__dirname, 'src'),
          path.join(__dirname, 'examples', 'src')
        ]
      },
      {
        test: /\.png$/,
        loader: require.resolve('url-loader') + '?limit=100000'
      },
      {
        test: /\.json$/,
        loader: require.resolve('json-loader')
      },
      // {
      //   test: /\.js$/,
      //   loader: 'transform?brfs'
      // }

    ]
  }
}
Download .txt
gitextract_retdfhs6/

├── .babelrc
├── .builderrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── .travis.yml-old
├── LICENSE
├── README.md
├── consume.html
├── devServer.js
├── examples/
│   ├── consume.html
│   ├── index.html
│   ├── keys.json
│   ├── publish.html
│   └── src/
│       ├── __tests__/
│       │   ├── index.test.js
│       │   └── null-compiler.js
│       ├── actions/
│       │   └── index.js
│       ├── api.js
│       ├── components/
│       │   ├── app.js
│       │   ├── song-item-container.js
│       │   ├── song-list-container.js
│       │   ├── song-store-item-container.js
│       │   ├── song-store-list-container.js
│       │   └── song-store-sync-container.js
│       ├── consume.js
│       ├── generate-keys.js
│       ├── index.js
│       ├── publish.js
│       ├── reducers/
│       │   ├── index.js
│       │   └── song-stores.js
│       └── sagas/
│           └── index.js
├── index.html
├── keys.json
├── package.json
├── publish.html
├── src/
│   ├── __tests__/
│   │   ├── index.test.js
│   │   └── null-compiler.js
│   ├── index.js
│   └── redux-swarmlog.js
├── webpack.config.dev.js
├── webpack.config.lib.js
└── webpack.config.prod.js
Download .txt
SYMBOL INDEX (34 symbols across 9 files)

FILE: examples/src/__tests__/null-compiler.js
  function noop (line 2) | function noop() {

FILE: examples/src/actions/index.js
  constant ADD_SONG_STORE (line 1) | const ADD_SONG_STORE = 'ADD_SONG_STORE'
  constant REMOVE_SONG_STORE (line 2) | const REMOVE_SONG_STORE = 'REMOVE_SONG_STORE'
  constant PUT_SONG_IN_SONG_STORE (line 3) | const PUT_SONG_IN_SONG_STORE = 'PUT_SONG_IN_SONG_STORE'
  constant REMOVE_SONG_FROM_SONG_STORE (line 4) | const REMOVE_SONG_FROM_SONG_STORE = 'REMOVE_SONG_FROM_SONG_STORE'
  constant ADD_SONG_STORE_SUCCEEDED (line 5) | const ADD_SONG_STORE_SUCCEEDED = 'ADD_SONG_STORE_SUCCEEDED'
  constant REMOVE_SONG_STORE_SUCCEEDED (line 6) | const REMOVE_SONG_STORE_SUCCEEDED = 'REMOVE_SONG_STORE_SUCCEEDED'
  function addSongStore (line 16) | function addSongStore(swarmLogMeta) {
  function addSongStoreSucceeded (line 23) | function addSongStoreSucceeded(swarmLogMeta) {
  function removeSongStore (line 30) | function removeSongStore(songStoreId) {
  function removeSongStoreSucceeded (line 37) | function removeSongStoreSucceeded(songStoreId) {
  function putSongInSongStore (line 44) | function putSongInSongStore(songStoreId, song) {
  function removeSongFromSongStore (line 53) | function removeSongFromSongStore(songStoreId, songId) {

FILE: examples/src/api.js
  function generateKeys (line 2) | function generateKeys() {

FILE: examples/src/components/app.js
  class App (line 6) | class App extends Component {
    method render (line 7) | render() {

FILE: examples/src/generate-keys.js
  function generateKeys (line 5) | function generateKeys() {

FILE: examples/src/index.js
  function logSampleActions (line 72) | function logSampleActions({ id, keys, name }) {

FILE: examples/src/reducers/song-stores.js
  function root (line 9) | function root(state = {}, action) {
  function songStores (line 24) | function songStores(state = {}, action) {
  function songStore (line 52) | function songStore(state = {}, action) {
  function getSong (line 73) | function getSong(state, songStoreId, songId) {

FILE: src/__tests__/null-compiler.js
  function noop (line 2) | function noop() {

FILE: src/redux-swarmlog.js
  function clearTables (line 18) | function clearTables() {
  function getSwarmLogsFromDb (line 39) | function getSwarmLogsFromDb() {
  function removeReduxSwarmLog (line 53) | function removeReduxSwarmLog(id) {
  function addReduxSwarmLog (line 84) | function addReduxSwarmLog(props) {
  function configureReduxSwarmLog (line 115) | function configureReduxSwarmLog({
  function reduxSwarmLogMiddleware (line 125) | function reduxSwarmLogMiddleware() {
  class ReduxSwarmLog (line 142) | class ReduxSwarmLog {
    method constructor (line 143) | constructor({ name, keys, id }) {
    method getSwarmLog (line 157) | getSwarmLog() {
    method startReadStream (line 167) | startReadStream() {
  function logJson (line 182) | function logJson(message, payload, color='black') {
Condensed preview — 44 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (52K chars).
[
  {
    "path": ".babelrc",
    "chars": 335,
    "preview": "{\n  \"presets\": [\"react\", \"es2015\", \"stage-1\"],\n  \"env\": {\n    \"development\": {\n      \"presets\": [\"react-hmre\"]\n    },\n  "
  },
  {
    "path": ".builderrc",
    "chars": 39,
    "preview": "---\narchetypes:\n  - component-archetype"
  },
  {
    "path": ".editorconfig",
    "chars": 414,
    "preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
  },
  {
    "path": ".eslintrc",
    "chars": 508,
    "preview": "{\n  \"extends\": \"rackt\",\n  \"ecmaFeatures\": {\n    \"jsx\": true,\n    \"modules\": true\n  },\n  \"env\": {\n    \"browser\": true,\n  "
  },
  {
    "path": ".gitignore",
    "chars": 66,
    "preview": ".DS_Store\nnode_modules\nnpm-debug.log\ndist\n.nyc_output\ncoverage\nlib"
  },
  {
    "path": ".npmignore",
    "chars": 91,
    "preview": ".DS_Store\n*.log\nsrc\ntest\nexamples\ndocs\ndemo\ncoverage\n*.sublime-project\n*.sublime-workspace\n"
  },
  {
    "path": ".travis.yml",
    "chars": 422,
    "preview": "sudo: false\nlanguage: node_js\ncache:\n  directories:\n    - node_modules\nbranches:\n  only:\n    - master\nnotifications:\n  e"
  },
  {
    "path": ".travis.yml-old",
    "chars": 422,
    "preview": "sudo: false\nlanguage: node_js\ncache:\n  directories:\n    - node_modules\nbranches:\n  only:\n    - master\nnotifications:\n  e"
  },
  {
    "path": "LICENSE",
    "chars": 7048,
    "preview": "Creative Commons Legal Code\n\nCC0 1.0 Universal\n\n    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE\n"
  },
  {
    "path": "README.md",
    "chars": 3427,
    "preview": "# Redux Swarmlog\n\n[![travis build](https://img.shields.io/travis/philholden/redux-swarmlog.svg?style=flat-square)](https"
  },
  {
    "path": "consume.html",
    "chars": 189,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <title>React Transform Boilerplate</title>\n  </head>\n  <body>\n    <div id=\"root\"></d"
  },
  {
    "path": "devServer.js",
    "chars": 2077,
    "preview": "var path = require('path')  // eslint-disable-line no-var\nvar express = require('express')  // eslint-disable-line no-va"
  },
  {
    "path": "examples/consume.html",
    "chars": 189,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <title>React Transform Boilerplate</title>\n  </head>\n  <body>\n    <div id=\"root\"></d"
  },
  {
    "path": "examples/index.html",
    "chars": 188,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <title>React Transform Boilerplate</title>\n  </head>\n  <body>\n    <div id=\"root\"></d"
  },
  {
    "path": "examples/keys.json",
    "chars": 271,
    "preview": "{\n  \"curve\": \"ed25519\",\n  \"public\": \"q8oQyaB0t9k8bAog6om+q86FRbBoYUklC0eQToR+nw8=.ed25519\",\n  \"private\": \"PmS89eOtLC35JJ"
  },
  {
    "path": "examples/publish.html",
    "chars": 189,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <title>React Transform Boilerplate</title>\n  </head>\n  <body>\n    <div id=\"root\"></d"
  },
  {
    "path": "examples/src/__tests__/index.test.js",
    "chars": 662,
    "preview": "import test from 'ava'\nimport is from 'is_js'\nimport React from 'react'\nimport { createRenderer } from 'react-addons-tes"
  },
  {
    "path": "examples/src/__tests__/null-compiler.js",
    "chars": 141,
    "preview": "// Prevent Mocha from compiling class\nfunction noop() {\n  return null\n}\n\nrequire.extensions['.css'] = noop\nrequire.exten"
  },
  {
    "path": "examples/src/actions/index.js",
    "chars": 1356,
    "preview": "export const ADD_SONG_STORE = 'ADD_SONG_STORE'\nexport const REMOVE_SONG_STORE = 'REMOVE_SONG_STORE'\nexport const PUT_SON"
  },
  {
    "path": "examples/src/api.js",
    "chars": 403,
    "preview": "\nexport function generateKeys() {\n  return fetch(`/keys`, {\n    method: 'get',\n    headers: {\n      'Accept': 'applicati"
  },
  {
    "path": "examples/src/components/app.js",
    "chars": 423,
    "preview": "import React, { Component } from 'react'\nimport { connect } from 'react-redux'\nimport SongStoreListContainer from './son"
  },
  {
    "path": "examples/src/components/song-item-container.js",
    "chars": 862,
    "preview": "import React from 'react'\nimport { connect } from 'react-redux'\n\nimport {\n  removeSongFromSongStore\n} from '../actions/i"
  },
  {
    "path": "examples/src/components/song-list-container.js",
    "chars": 1587,
    "preview": "import React from 'react'\nimport { connect } from 'react-redux'\nimport { bindActionCreators } from 'redux'\nimport SongIt"
  },
  {
    "path": "examples/src/components/song-store-item-container.js",
    "chars": 1431,
    "preview": "import React from 'react'\nimport { connect } from 'react-redux'\nimport SongListContainer from './song-list-container'\n\ni"
  },
  {
    "path": "examples/src/components/song-store-list-container.js",
    "chars": 1061,
    "preview": "import React from 'react'\nimport { connect } from 'react-redux'\nimport { bindActionCreators } from 'redux'\nimport SongSt"
  },
  {
    "path": "examples/src/components/song-store-sync-container.js",
    "chars": 2328,
    "preview": "import React from 'react'\nimport { connect } from 'react-redux'\nimport { bindActionCreators } from 'redux'\nimport {\n  ad"
  },
  {
    "path": "examples/src/consume.js",
    "chars": 565,
    "preview": "import swarmlog from 'swarmlog'\nimport memdb from 'memdb'\n\nconst log = swarmlog({\n  publicKey: require('../keys.json').p"
  },
  {
    "path": "examples/src/generate-keys.js",
    "chars": 557,
    "preview": "import supercop from '../supercop.js'\n\nimport crypto from 'crypto'\n\nexport default function generateKeys() {\n  return re"
  },
  {
    "path": "examples/src/index.js",
    "chars": 2327,
    "preview": "import React from 'react'\nimport { render } from 'react-dom'\nimport { Provider } from 'react-redux'\nimport App from './c"
  },
  {
    "path": "examples/src/publish.js",
    "chars": 1021,
    "preview": "import swarmlog from 'swarmlog'\nimport memdb from 'memdb'\nimport leveljs from 'level-js'\nimport levelup from 'levelup'\ni"
  },
  {
    "path": "examples/src/reducers/index.js",
    "chars": 129,
    "preview": "import { combineReducers } from 'redux'\nimport songStores from './song-stores'\n\nexport default combineReducers({\n  songS"
  },
  {
    "path": "examples/src/reducers/song-stores.js",
    "chars": 1826,
    "preview": "import {\n  ADD_SONG_STORE,\n  ADD_SONG_STORE_SUCCEEDED,\n  REMOVE_SONG_STORE_SUCCEEDED,\n  PUT_SONG_IN_SONG_STORE,\n  REMOVE"
  },
  {
    "path": "examples/src/sagas/index.js",
    "chars": 1207,
    "preview": "import { takeEvery } from 'redux-saga'\nimport { put, call } from 'redux-saga/effects'\nimport { generateKeys } from '../a"
  },
  {
    "path": "index.html",
    "chars": 188,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <title>React Transform Boilerplate</title>\n  </head>\n  <body>\n    <div id=\"root\"></d"
  },
  {
    "path": "keys.json",
    "chars": 371,
    "preview": "{\n  \"public\":\"qkN3S7AlBASCD6azVLqdPuWkp+TyU3g9RlfwCZA1yQ0=.ed25519\",\n  \"publicSuper\":\"btb+H8Z//WHc7HS1+u54DD0jnRObljx5ms"
  },
  {
    "path": "package.json",
    "chars": 3575,
    "preview": "{\n  \"name\": \"@philholden/redux-swarmlog\",\n  \"version\": \"0.0.1-semantic\",\n  \"description\": \"nothing\",\n  \"main\": \"lib/inde"
  },
  {
    "path": "publish.html",
    "chars": 189,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <title>React Transform Boilerplate</title>\n  </head>\n  <body>\n    <div id=\"root\"></d"
  },
  {
    "path": "src/__tests__/index.test.js",
    "chars": 704,
    "preview": "import test from 'ava'\nimport is from 'is_js'\nimport React from 'react'\nimport { createRenderer } from 'react-addons-tes"
  },
  {
    "path": "src/__tests__/null-compiler.js",
    "chars": 141,
    "preview": "// Prevent Mocha from compiling class\nfunction noop() {\n  return null\n}\n\nrequire.extensions['.css'] = noop\nrequire.exten"
  },
  {
    "path": "src/index.js",
    "chars": 62,
    "preview": "import * as rds from './redux-swarmlog'\n\nmodule.exports = rds\n"
  },
  {
    "path": "src/redux-swarmlog.js",
    "chars": 4797,
    "preview": "import swarmlog from 'swarmlog'\nimport leveljs from 'level-js'\nimport levelup from 'levelup'\nimport { randomBytes } from"
  },
  {
    "path": "webpack.config.dev.js",
    "chars": 1696,
    "preview": "'use strict'\n\nlet path = require('path')\nlet webpack = require('webpack')\n\nconsole.log(__dirname)\n\nmodule.exports = {\n  "
  },
  {
    "path": "webpack.config.lib.js",
    "chars": 501,
    "preview": "var path = require('path') // eslint-disable-line no-var\n//var ExtractTextPlugin = require('extract-text-webpack-plugin'"
  },
  {
    "path": "webpack.config.prod.js",
    "chars": 1494,
    "preview": "var path = require('path') // eslint-disable-line no-var\nvar webpack = require('webpack') // eslint-disable-line no-var\n"
  }
]

About this extraction

This page contains the full source code of the philholden/redux-swarmlog GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 44 files (46.4 KB), approximately 13.9k tokens, and a symbol index with 34 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!