├── .github └── stale.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── insert-bundle.sh ├── package-lock.json ├── package.json ├── patch-scuttlebot.js ├── react-native-node.d.ts ├── rnnodeapp ├── index.js ├── package.json └── serve-blobs.js ├── src └── index.ts └── tsconfig.json /.github/stale.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /lib 3 | /.vscode -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /tsconfig.json 2 | /src 3 | /node_modules 4 | /.vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present André Staltz (staltz.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scuttlebot for React Native apps 2 | 3 | ## install 4 | 5 | `npm install --save react-native-scuttlebot` 6 | 7 | ## usage 8 | 9 | In your React Native application (e.g. index.android.js): 10 | 11 | ```js 12 | import Scuttlebot from 'react-native-scuttlebot'; 13 | 14 | // ... 15 | 16 | // When you're ready, this will spawn a node.js process in the background: 17 | Scuttlebot.start(); 18 | ``` 19 | 20 | ### versions 21 | 22 | If this package is version A.B.C, then it uses `scuttlebot@A.B.x`. 23 | -------------------------------------------------------------------------------- /insert-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | INNER_BINS="$(pwd)/node_modules/.bin" 3 | OUTER_BINS="$(pwd)/../.bin" 4 | NODERIFY="$INNER_BINS/noderify" 5 | if [ ! -e "$NODERIFY" ]; then 6 | NODERIFY="$OUTER_BINS/noderify" 7 | fi 8 | RNNODE="$INNER_BINS/react-native-node" 9 | if [ ! -e "$RNNODE" ]; then 10 | RNNODE="$OUTER_BINS/react-native-node" 11 | fi 12 | PATCH_SCUTTLEBOT_BIN="$(pwd)/patch-scuttlebot.js" 13 | PROJECT="$(pwd)/../.." 14 | ORIG="$(pwd)/rnnodeapp" 15 | DEST="$PROJECT/rnnodeapp" 16 | 17 | # Set up destination 18 | rm -rf "$DEST"; 19 | mkdir -p "$DEST"; 20 | cp -r "$ORIG" "$DEST/../"; 21 | 22 | # Install dependencies there 23 | cd "$DEST"; 24 | npm i; 25 | 26 | # Setup native libraries and fix other libraries 27 | PREBUILT_PATH_1="$DEST/node_modules/leveldown-android-prebuilt/compiled" 28 | cp -R "$PREBUILT_PATH_1" "$DEST/"; 29 | node "$PATCH_SCUTTLEBOT_BIN" "$DEST"; 30 | 31 | # Minify the bundle 32 | cd "$DEST"; 33 | $NODERIFY \ 34 | --replace.chloride=sodium-browserify-tweetnacl \ 35 | --replace.sodium-chloride=sodium-browserify-tweetnacl \ 36 | --replace.node-extend=xtend \ 37 | --replace.leveldown=leveldown-android-prebuilt \ 38 | "$DEST/index.js" > "$DEST/_index.js"; 39 | rm "$DEST/index.js"; 40 | mv "$DEST/_index.js" "$DEST/index.js"; 41 | 42 | # Pre-insert clean up 43 | rm -rf "$DEST/node_modules"; 44 | 45 | # Insert bundle into the project 46 | cd $PROJECT; 47 | $RNNODE insert "$DEST"; 48 | 49 | # Post-insert clean up 50 | #`rm -rf ${dest}` 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-scuttlebot", 3 | "version": "10.4.23", 4 | "description": "Secure Scuttlebutt peer for React Native apps", 5 | "author": "Andre Staltz", 6 | "license": "MIT", 7 | "main": "lib/index.js", 8 | "typings": "lib/index.d.ts", 9 | "homepage": "https://github.com/ssbc/react-native-scuttlebot", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ssbc/react-native-scuttlebot" 13 | }, 14 | "scripts": { 15 | "compile": "tsc", 16 | "prepublishOnly": "npm run compile", 17 | "install": "./insert-bundle.sh && cd ../../ && react-native link react-native-node" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^8.0.9", 21 | "typescript": "^2.4.1" 22 | }, 23 | "dependencies": { 24 | "leveldown-android-prebuilt": "2.0.3", 25 | "bufferutil": "3.0.1", 26 | "mkdirp": "^0.5.1", 27 | "noderify": "3.0.2", 28 | "path": "0.12.7", 29 | "pull-identify-filetype": "1.1.0", 30 | "react-native-node": "3.3.0", 31 | "scuttlebot": "10.4.10", 32 | "sodium-browserify-tweetnacl": "0.2.3", 33 | "spawn-sync": "1.0.15", 34 | "ssb-about": "0.1.x", 35 | "ssb-backlinks": "0.4.x", 36 | "ssb-contacts": "0.0.x", 37 | "ssb-friends": "^2.3.0", 38 | "ssb-keys": "7.0.12", 39 | "ssb-private": "0.1.x", 40 | "ssb-query": "0.1.x", 41 | "try-thread-sleep": "^1.0.2", 42 | "utf-8-validate": "3.0.2", 43 | "yargs": "8.0.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /patch-scuttlebot.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var path = require("path"); 3 | 4 | var rnnodeappPath = process.argv[2]; 5 | var scuttlebotPath = path.join(rnnodeappPath, "node_modules/scuttlebot"); 6 | 7 | if (fs.existsSync(scuttlebotPath)) { 8 | patchApidocsFile(scuttlebotPath); 9 | } else { 10 | throw new Error("Cannot find file scuttlebot/lib/apidocs.js to patch"); 11 | } 12 | 13 | function patchApidocsFile(scuttlebotPath) { 14 | var apidocsPath = path.join(scuttlebotPath, "./lib/apidocs.js"); 15 | var prevContent = fs.readFileSync(apidocsPath, "utf-8"); 16 | 17 | var dirname = path.dirname(apidocsPath); 18 | 19 | function readMarkdownCompileTime(relPath) { 20 | return fs 21 | .readFileSync(path.join(dirname, relPath), "utf-8") 22 | .replace(/\`\`\`/g, "") 23 | .replace(/\`/g, '"'); 24 | } 25 | 26 | var nextContent = prevContent 27 | .replace( 28 | `fs.readFileSync(path.join(__dirname, '../api.md'), 'utf-8')`, 29 | `\`${readMarkdownCompileTime("../api.md")}\`` 30 | ) 31 | .replace( 32 | `fs.readFileSync(path.join(__dirname, '../plugins/block.md'), 'utf-8')`, 33 | `\`${readMarkdownCompileTime("../plugins/block.md")}\`` 34 | ) 35 | .replace( 36 | `fs.readFileSync(path.join(__dirname, '../plugins/friends.md'), 'utf-8')`, 37 | `\`${readMarkdownCompileTime("../plugins/friends.md")}\`` 38 | ) 39 | .replace( 40 | `fs.readFileSync(path.join(__dirname, '../plugins/gossip.md'), 'utf-8')`, 41 | `\`${readMarkdownCompileTime("../plugins/gossip.md")}\`` 42 | ) 43 | .replace( 44 | `fs.readFileSync(path.join(__dirname, '../plugins/invite.md'), 'utf-8')`, 45 | `\`${readMarkdownCompileTime("../plugins/invite.md")}\`` 46 | ) 47 | .replace( 48 | `fs.readFileSync(path.join(__dirname, '../plugins/plugins.md'), 'utf-8')`, 49 | `\`${readMarkdownCompileTime("../plugins/plugins.md")}\`` 50 | ) 51 | .replace( 52 | `fs.readFileSync(path.join(__dirname, '../plugins/private.md'), 'utf-8')`, 53 | `\`${readMarkdownCompileTime("../plugins/private.md")}\`` 54 | ) 55 | .replace( 56 | `fs.readFileSync(path.join(__dirname, '../plugins/replicate.md'), 'utf-8')`, 57 | `\`${readMarkdownCompileTime("../plugins/replicate.md")}\`` 58 | ); 59 | 60 | fs.writeFileSync(apidocsPath, nextContent, "utf-8"); 61 | } 62 | -------------------------------------------------------------------------------- /react-native-node.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-native-node' { 2 | export const RNNode: any; 3 | export default RNNode; 4 | } -------------------------------------------------------------------------------- /rnnodeapp/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const os = require("os"); 3 | const path = require("path"); 4 | const ssbKeys = require("ssb-keys"); 5 | const serveBlobs = require("./serve-blobs"); 6 | const mkdirp = require("mkdirp"); 7 | 8 | const ssbPath = path.resolve(os.homedir(), '.ssb'); 9 | if (!fs.existsSync(ssbPath)) { 10 | mkdirp.sync(ssbPath); 11 | } 12 | const secretPath = path.join(ssbPath, "/secret"); 13 | 14 | const keys = ssbKeys.loadOrCreateSync(secretPath); 15 | 16 | const config = require("ssb-config/inject")(); 17 | 18 | const manifest = { 19 | auth: "async", 20 | address: "sync", 21 | manifest: "sync", 22 | get: "async", 23 | createFeedStream: "source", 24 | createLogStream: "source", 25 | messagesByType: "source", 26 | createHistoryStream: "source", 27 | createUserStream: "source", 28 | links: "source", 29 | relatedMessages: "async", 30 | add: "async", 31 | publish: "async", 32 | getAddress: "sync", 33 | getLatest: "async", 34 | latest: "source", 35 | latestSequence: "async", 36 | whoami: "sync", 37 | usage: "sync", 38 | plugins: { 39 | install: "source", 40 | uninstall: "source", 41 | enable: "async", 42 | disable: "async" 43 | }, 44 | gossip: { 45 | peers: "sync", 46 | add: "sync", 47 | remove: "sync", 48 | ping: "duplex", 49 | connect: "async", 50 | changes: "source", 51 | reconnect: "sync" 52 | }, 53 | friends: { 54 | get: "async", 55 | createFriendStream: "source", 56 | hops: "async" 57 | }, 58 | replicate: { 59 | changes: "source", 60 | upto: "source" 61 | }, 62 | blobs: { 63 | get: "source", 64 | getSlice: "source", 65 | add: "sink", 66 | rm: "async", 67 | ls: "source", 68 | has: "async", 69 | size: "async", 70 | meta: "async", 71 | want: "async", 72 | push: "async", 73 | changes: "source", 74 | createWants: "source" 75 | }, 76 | backlinks: { 77 | read: "source" 78 | }, 79 | about: { 80 | stream: "source", 81 | get: "async" 82 | }, 83 | contacts: { 84 | stream: "source", 85 | get: "async" 86 | }, 87 | query: { 88 | read: "source" 89 | }, 90 | invite: { 91 | create: "async", 92 | accept: "async", 93 | use: "async" 94 | }, 95 | block: { 96 | isBlocked: "sync" 97 | }, 98 | private: { 99 | publish: "async", 100 | unbox: "sync", 101 | read: "source" 102 | } 103 | }; 104 | 105 | const createSbot = require("scuttlebot/index") 106 | .use(require("scuttlebot/plugins/plugins")) 107 | .use(require("scuttlebot/plugins/master")) 108 | .use(require("scuttlebot/plugins/gossip")) 109 | .use(require("scuttlebot/plugins/replicate")) 110 | .use(require("ssb-friends")) 111 | .use(require("ssb-blobs")) 112 | .use(require("ssb-backlinks")) 113 | .use(require("ssb-private")) 114 | .use(require("ssb-about")) 115 | .use(require("ssb-contacts")) 116 | .use(require("ssb-query")) 117 | .use(require("scuttlebot/plugins/invite")) 118 | .use(require("scuttlebot/plugins/block")) 119 | .use(require("scuttlebot/plugins/local")) 120 | .use(require("scuttlebot/plugins/logging")); 121 | 122 | // add third-party plugins 123 | require("scuttlebot/plugins/plugins").loadUserPlugins(createSbot, config); 124 | 125 | // start server 126 | config.keys = keys; 127 | config.path = ssbPath; 128 | const sbot = createSbot(config); 129 | serveBlobs(sbot); 130 | -------------------------------------------------------------------------------- /rnnodeapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rnnodeapp", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "dependencies": { 6 | "leveldown-android-prebuilt": "2.0.3", 7 | "bufferutil": "3.0.1", 8 | "pull-identify-filetype": "1.1.0", 9 | "scuttlebot": "10.4.10", 10 | "sodium-browserify-tweetnacl": "0.2.3", 11 | "ssb-about": "0.1.x", 12 | "ssb-backlinks": "0.4.x", 13 | "mime-types": "2.1.17", 14 | "ssb-contacts": "0.0.x", 15 | "ssb-friends": "^2.3.0", 16 | "ssb-keys": "7.0.12", 17 | "ssb-private": "0.1.x", 18 | "ssb-query": "0.1.x" 19 | } 20 | } -------------------------------------------------------------------------------- /rnnodeapp/serve-blobs.js: -------------------------------------------------------------------------------- 1 | var pull = require('pull-stream') 2 | var cat = require('pull-cat') 3 | var toPull = require('stream-to-pull-stream') 4 | var ident = require('pull-identify-filetype') 5 | var mime = require('mime-types') 6 | var URL = require('url') 7 | var http = require('http') 8 | 9 | module.exports = function (sbot, cb) { 10 | return http.createServer(ServeBlobs(sbot)).listen(7777, cb) 11 | } 12 | 13 | function ServeBlobs (sbot) { 14 | return function (req, res, next) { 15 | var parsed = URL.parse(req.url, true) 16 | var hash = decodeURIComponent(parsed.pathname.slice(1)) 17 | waitFor(hash, function (_, has) { 18 | if (!has) return respond(res, 404, 'File not found') 19 | // optional name override 20 | if (parsed.query.name) { 21 | res.setHeader('Content-Disposition', 'inline; filename=' + encodeURIComponent(parsed.query.name)) 22 | } 23 | 24 | // serve 25 | res.setHeader('Content-Security-Policy', BlobCSP()) 26 | respondSource(res, sbot.blobs.get(hash), false) 27 | }) 28 | } 29 | 30 | function waitFor (hash, cb) { 31 | sbot.blobs.has(hash, function (err, has) { 32 | if (err) return cb(err) 33 | if (has) { 34 | cb(null, has) 35 | } else { 36 | sbot.blobs.want(hash, cb) 37 | } 38 | }) 39 | } 40 | } 41 | 42 | function respondSource (res, source, wrap) { 43 | if (wrap) { 44 | res.writeHead(200, {'Content-Type': 'text/html'}) 45 | pull( 46 | cat([ 47 | pull.once('') 50 | ]), 51 | toPull.sink(res) 52 | ) 53 | } else { 54 | pull( 55 | source, 56 | ident(function (type) { 57 | if (type) res.writeHead(200, {'Content-Type': mime.lookup(type)}) 58 | }), 59 | toPull.sink(res) 60 | ) 61 | } 62 | } 63 | 64 | function respond (res, status, message) { 65 | res.writeHead(status) 66 | res.end(message) 67 | } 68 | 69 | function BlobCSP () { 70 | return 'default-src none; sandbox' 71 | } 72 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import RNNode from "react-native-node"; 2 | 3 | const Scuttlebot = { 4 | start(): void { 5 | RNNode.start(); 6 | } 7 | }; 8 | 9 | export default Scuttlebot; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "target": "es2016", 5 | "noImplicitAny": true, 6 | "noImplicitReturns": true, 7 | "noUnusedLocals": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "strictNullChecks": true, 11 | "skipLibCheck": true, 12 | "outDir": "./lib/", 13 | "lib": ["es2015"] 14 | }, 15 | "files": ["react-native-node.d.ts", "src/index.ts"] 16 | } 17 | --------------------------------------------------------------------------------