├── .gitignore ├── .npmignore ├── README.md ├── bin └── peerchan.js ├── package-lock.json ├── package.json ├── screenshot.png └── src ├── index.js ├── log.js ├── network.js └── ui.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /data-* 3 | peerchan.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | screenshot.png 2 | data-*.level 3 | peerchan.log 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SYNOPSIS 2 | A terminal based client for the peerlinks protocol. 3 | 4 | ![screenshot](screenshot.png) 5 | 6 | - Fully peer to peer 7 | - Multiline input editing 8 | - Command autocomplete 9 | - Customizable color scheme 10 | - Buffer scrollback 11 | - Create channels 12 | - Request and accept invites 13 | 14 | # INSTALL 15 | 16 | ```sh 17 | npm install peerchan -g 18 | ``` 19 | 20 | # GETTING STARTED 21 | 22 | - Launch the app by typing `peerchan` from your terminal. 23 | - Create an identity by typing `/id `. 24 | - Make a request to join someone's channel by typing `/r` or 25 | create your own channel by typing `/create `. 26 | 27 | 28 | # CONFIG 29 | Optional example config stored in `~/.peerchan.json`. 30 | 31 | ```js 32 | { 33 | "bg": 255, 34 | "fg": 234, 35 | "comment": { // system comments 36 | "fg": 246, 37 | "bg": 255 38 | }, 39 | "prompt": { // the text message input 40 | "bg": 4, 41 | "fg": 15 42 | }, 43 | "timestamp": { // message timestamp 44 | "fg": 244, 45 | "bg": 255 46 | }, 47 | "status": { // the status bar at the top 48 | "fg": 4, 49 | "bg": 15 50 | }, 51 | "prefix": "▌", // prefix system comments 52 | "scrollback": 3, // scrollback rate 53 | "id": "heapwolf" // your default id 54 | } 55 | ``` 56 | 57 | # DEBUGGING 58 | This has been tested on node `12.16.3 LTS`, but latest, `14.1.0` seems 59 | to have some issues. 60 | 61 | In terminal window A... 62 | 63 | ```sh 64 | DEBUG_COLORS=false DEBUG='peerlinks:*' INST=0 node ./bin/peerchan.js 65 | ``` 66 | 67 | In terminal widow B... 68 | ```sh 69 | DEBUG_COLORS=false DEBUG='peerlinks:*' INST=1 node ./bin/peerchan.js 70 | ``` 71 | 72 | In terminal window C... 73 | 74 | ```sh 75 | tail -f ./peerchan.log 76 | ``` 77 | -------------------------------------------------------------------------------- /bin/peerchan.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const main = require('../src/index') 3 | 4 | main() 5 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peerchan", 3 | "version": "1.1.5", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@hyperswarm/dht": { 8 | "version": "3.6.3", 9 | "resolved": "https://registry.npmjs.org/@hyperswarm/dht/-/dht-3.6.3.tgz", 10 | "integrity": "sha512-ft+kjZK6YfxMZQ1rLVEKPug8ClesRbmSDBMY08N0h8LpBZ9ewHLPtf1W3rPeInPJQzcp3vLPHtX/YTngzNZbrA==", 11 | "requires": { 12 | "@hyperswarm/hypersign": "^2.0.0", 13 | "dht-rpc": "^4.8.0", 14 | "end-of-stream": "^1.4.1", 15 | "guard-timeout": "^2.0.0", 16 | "hashlru": "^2.3.0", 17 | "protocol-buffers-encodings": "^1.1.0", 18 | "record-cache": "^1.1.0", 19 | "sodium-universal": "^2.0.0", 20 | "uint64be": "^2.0.2" 21 | } 22 | }, 23 | "@hyperswarm/discovery": { 24 | "version": "1.11.4", 25 | "resolved": "https://registry.npmjs.org/@hyperswarm/discovery/-/discovery-1.11.4.tgz", 26 | "integrity": "sha512-IZ902SwvoV7CAnsNvCymV8CdFUOupNB6Shk207sUQfK7b3qHc/hP90bu0xsVDrao+YCQPC3J6Zdpb+Q047MBTg==", 27 | "requires": { 28 | "@hyperswarm/dht": "^3.2.0", 29 | "multicast-dns": "^7.2.0", 30 | "timeout-refresh": "^1.0.2" 31 | } 32 | }, 33 | "@hyperswarm/hypersign": { 34 | "version": "2.1.0", 35 | "resolved": "https://registry.npmjs.org/@hyperswarm/hypersign/-/hypersign-2.1.0.tgz", 36 | "integrity": "sha512-iHVUYUVo8zyzysXbWQsn6Yq0jRCKj5hkeY9qNJUq7xyps2npE8oc+Yzeq3qHQ6JD+IVbT/SOzIpE6wx0DWQ2IQ==", 37 | "requires": { 38 | "sodium-universal": "^2.0.0" 39 | } 40 | }, 41 | "@hyperswarm/network": { 42 | "version": "1.5.0", 43 | "resolved": "https://registry.npmjs.org/@hyperswarm/network/-/network-1.5.0.tgz", 44 | "integrity": "sha512-EtEGUew6+odJSMwbpzzSxtX7VXTDUQ16b/w7jl9MXegKPjQkLTVkGf18luO/sjWC8N81u5XEeabi6x4oKuP1tQ==", 45 | "requires": { 46 | "@hyperswarm/discovery": "^1.6.0", 47 | "nanoresource": "^1.0.0", 48 | "utp-native": "^2.1.3" 49 | } 50 | }, 51 | "@peerlinks/level-storage": { 52 | "version": "github:heapwolf/peerlinks-level-storage#f3da6afff186badb2aa9fe4c4b66610429776299", 53 | "from": "github:heapwolf/peerlinks-level-storage", 54 | "requires": { 55 | "charwise": "^3.0.1", 56 | "d64": "^1.0.0", 57 | "level": "^6.0.1" 58 | } 59 | }, 60 | "@peerlinks/protocol": { 61 | "version": "github:heapwolf/peerlinks#58cff177579cbd21601f372c484c2d873abda3c4", 62 | "from": "github:heapwolf/peerlinks#v7.4.5", 63 | "requires": { 64 | "debug": "^4.1.1", 65 | "promise-waitlist": "^1.5.0", 66 | "protobufjs": "^6.8.8", 67 | "quick-lru": "^4.0.1", 68 | "sodium-native": "^3.0.1", 69 | "sodium-universal": "^2.0.0" 70 | } 71 | }, 72 | "@peerlinks/swarm": { 73 | "version": "github:heapwolf/peerlinks-swarm#c4f20828c7a4f61e555f9f26045a8a0762d09638", 74 | "from": "github:heapwolf/peerlinks-swarm", 75 | "requires": { 76 | "debug": "^4.1.1", 77 | "hyperswarm": "^2.13.0", 78 | "promise-waitlist": "^1.5.0", 79 | "sodium-native": "^3.0.1" 80 | } 81 | }, 82 | "@protobufjs/aspromise": { 83 | "version": "1.1.2", 84 | "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", 85 | "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" 86 | }, 87 | "@protobufjs/base64": { 88 | "version": "1.1.2", 89 | "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", 90 | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" 91 | }, 92 | "@protobufjs/codegen": { 93 | "version": "2.0.4", 94 | "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", 95 | "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" 96 | }, 97 | "@protobufjs/eventemitter": { 98 | "version": "1.1.0", 99 | "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", 100 | "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" 101 | }, 102 | "@protobufjs/fetch": { 103 | "version": "1.1.0", 104 | "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", 105 | "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", 106 | "requires": { 107 | "@protobufjs/aspromise": "^1.1.1", 108 | "@protobufjs/inquire": "^1.1.0" 109 | } 110 | }, 111 | "@protobufjs/float": { 112 | "version": "1.0.2", 113 | "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", 114 | "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" 115 | }, 116 | "@protobufjs/inquire": { 117 | "version": "1.1.0", 118 | "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", 119 | "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" 120 | }, 121 | "@protobufjs/path": { 122 | "version": "1.1.2", 123 | "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", 124 | "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" 125 | }, 126 | "@protobufjs/pool": { 127 | "version": "1.1.0", 128 | "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", 129 | "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" 130 | }, 131 | "@protobufjs/utf8": { 132 | "version": "1.1.0", 133 | "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", 134 | "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" 135 | }, 136 | "@types/long": { 137 | "version": "4.0.1", 138 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", 139 | "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" 140 | }, 141 | "@types/node": { 142 | "version": "13.13.8", 143 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.8.tgz", 144 | "integrity": "sha512-WJoiKALUF5exZo0G3T5coauJR2Tmc6rdE9/kgppZVnV6rlUB2dl3gTu2GTNBKhKF6SZ/WFfpEUIGNC/0qvdMWA==" 145 | }, 146 | "abstract-leveldown": { 147 | "version": "6.2.3", 148 | "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", 149 | "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", 150 | "requires": { 151 | "buffer": "^5.5.0", 152 | "immediate": "^3.2.3", 153 | "level-concat-iterator": "~2.0.0", 154 | "level-supports": "~1.0.0", 155 | "xtend": "~4.0.0" 156 | } 157 | }, 158 | "arch": { 159 | "version": "2.1.1", 160 | "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", 161 | "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==" 162 | }, 163 | "base-x": { 164 | "version": "3.0.8", 165 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", 166 | "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", 167 | "requires": { 168 | "safe-buffer": "^5.0.1" 169 | } 170 | }, 171 | "base64-js": { 172 | "version": "1.3.1", 173 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 174 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" 175 | }, 176 | "blake2b": { 177 | "version": "2.1.3", 178 | "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.3.tgz", 179 | "integrity": "sha512-pkDss4xFVbMb4270aCyGD3qLv92314Et+FsKzilCLxDz5DuZ2/1g3w4nmBbu6nKApPspnjG7JcwTjGZnduB1yg==", 180 | "requires": { 181 | "blake2b-wasm": "^1.1.0", 182 | "nanoassert": "^1.0.0" 183 | } 184 | }, 185 | "blake2b-wasm": { 186 | "version": "1.1.7", 187 | "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-1.1.7.tgz", 188 | "integrity": "sha512-oFIHvXhlz/DUgF0kq5B1CqxIDjIJwh9iDeUUGQUcvgiGz7Wdw03McEO7CfLBy7QKGdsydcMCgO9jFNBAFCtFcA==", 189 | "requires": { 190 | "nanoassert": "^1.0.0" 191 | } 192 | }, 193 | "bs58": { 194 | "version": "4.0.1", 195 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", 196 | "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", 197 | "requires": { 198 | "base-x": "^3.0.2" 199 | } 200 | }, 201 | "buffer": { 202 | "version": "5.6.0", 203 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", 204 | "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", 205 | "requires": { 206 | "base64-js": "^1.0.2", 207 | "ieee754": "^1.1.4" 208 | } 209 | }, 210 | "buffer-alloc": { 211 | "version": "1.2.0", 212 | "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", 213 | "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", 214 | "requires": { 215 | "buffer-alloc-unsafe": "^1.1.0", 216 | "buffer-fill": "^1.0.0" 217 | } 218 | }, 219 | "buffer-alloc-unsafe": { 220 | "version": "1.1.0", 221 | "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", 222 | "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" 223 | }, 224 | "buffer-fill": { 225 | "version": "1.0.0", 226 | "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", 227 | "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" 228 | }, 229 | "charm": { 230 | "version": "1.0.2", 231 | "resolved": "https://registry.npmjs.org/charm/-/charm-1.0.2.tgz", 232 | "integrity": "sha1-it02cVOm2aWBMxBSxAkJkdqZXjU=", 233 | "requires": { 234 | "inherits": "^2.0.1" 235 | } 236 | }, 237 | "charwise": { 238 | "version": "3.0.1", 239 | "resolved": "https://registry.npmjs.org/charwise/-/charwise-3.0.1.tgz", 240 | "integrity": "sha512-RcdumNsM6fJZ5HHbYunqj2bpurVRGsXour3OR+SlLEHFhG6ALm54i6Osnh+OvO7kEoSBzwExpblYFH8zKQiEPw==" 241 | }, 242 | "clipboardy": { 243 | "version": "2.3.0", 244 | "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", 245 | "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", 246 | "requires": { 247 | "arch": "^2.1.1", 248 | "execa": "^1.0.0", 249 | "is-wsl": "^2.1.1" 250 | } 251 | }, 252 | "codecs": { 253 | "version": "2.0.0", 254 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-2.0.0.tgz", 255 | "integrity": "sha512-WXvpJRAgc693oqYvZte9uYEiL5YHtfrxyEq12uVny9oBJ1k37zSva5vVz7trsnt6R9Y15hEgOSC7VFZT2pfYnA==" 256 | }, 257 | "cross-spawn": { 258 | "version": "6.0.5", 259 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 260 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 261 | "requires": { 262 | "nice-try": "^1.0.4", 263 | "path-key": "^2.0.1", 264 | "semver": "^5.5.0", 265 | "shebang-command": "^1.2.0", 266 | "which": "^1.2.9" 267 | } 268 | }, 269 | "d64": { 270 | "version": "1.0.0", 271 | "resolved": "https://registry.npmjs.org/d64/-/d64-1.0.0.tgz", 272 | "integrity": "sha1-QAKofoUMv8n52XBrYPymE6MzbpA=" 273 | }, 274 | "debug": { 275 | "version": "4.1.1", 276 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 277 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 278 | "requires": { 279 | "ms": "^2.1.1" 280 | } 281 | }, 282 | "deferred-leveldown": { 283 | "version": "5.3.0", 284 | "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", 285 | "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", 286 | "requires": { 287 | "abstract-leveldown": "~6.2.1", 288 | "inherits": "^2.0.3" 289 | } 290 | }, 291 | "dht-rpc": { 292 | "version": "4.8.1", 293 | "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-4.8.1.tgz", 294 | "integrity": "sha512-5x+1nrAXIOaclICKI3hj1LpP2SW/aDw/Rzz76KWZNCtmqhPkxIHMdqLPL2ozw4DuqX2Uq/Wr9FE5dSFbNt/YyA==", 295 | "requires": { 296 | "codecs": "^2.0.0", 297 | "ipv4-peers": "^2.0.0", 298 | "k-bucket": "^5.0.0", 299 | "protocol-buffers-encodings": "^1.1.0", 300 | "sodium-universal": "^2.0.0", 301 | "speedometer": "^1.1.0", 302 | "stream-collector": "^1.0.1", 303 | "time-ordered-set": "^1.0.1", 304 | "xor-distance": "^2.0.0" 305 | } 306 | }, 307 | "dns-packet": { 308 | "version": "4.2.0", 309 | "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-4.2.0.tgz", 310 | "integrity": "sha512-bn1AKpfkFbm0MIioOMHZ5qJzl2uypdBwI4nYNsqvhjsegBhcKJUlCrMPWLx6JEezRjxZmxhtIz/FkBEur2l8Cw==", 311 | "requires": { 312 | "ip": "^1.1.5", 313 | "safe-buffer": "^5.1.1" 314 | } 315 | }, 316 | "encoding-down": { 317 | "version": "6.3.0", 318 | "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", 319 | "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", 320 | "requires": { 321 | "abstract-leveldown": "^6.2.1", 322 | "inherits": "^2.0.3", 323 | "level-codec": "^9.0.0", 324 | "level-errors": "^2.0.0" 325 | } 326 | }, 327 | "end-of-stream": { 328 | "version": "1.4.4", 329 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 330 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 331 | "requires": { 332 | "once": "^1.4.0" 333 | } 334 | }, 335 | "errno": { 336 | "version": "0.1.7", 337 | "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", 338 | "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", 339 | "requires": { 340 | "prr": "~1.0.1" 341 | } 342 | }, 343 | "execa": { 344 | "version": "1.0.0", 345 | "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", 346 | "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", 347 | "requires": { 348 | "cross-spawn": "^6.0.0", 349 | "get-stream": "^4.0.0", 350 | "is-stream": "^1.1.0", 351 | "npm-run-path": "^2.0.0", 352 | "p-finally": "^1.0.0", 353 | "signal-exit": "^3.0.0", 354 | "strip-eof": "^1.0.0" 355 | } 356 | }, 357 | "get-stream": { 358 | "version": "4.1.0", 359 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", 360 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", 361 | "requires": { 362 | "pump": "^3.0.0" 363 | } 364 | }, 365 | "guard-timeout": { 366 | "version": "2.0.0", 367 | "resolved": "https://registry.npmjs.org/guard-timeout/-/guard-timeout-2.0.0.tgz", 368 | "integrity": "sha512-35geHv72oal0cRUE5t1tZ5KHm3OVPXzFtiMG8AnRPV5FkkEf84RUpeQ0BCeCZunfSLGATW5ZVyALhJKgM7I/6A==" 369 | }, 370 | "hashlru": { 371 | "version": "2.3.0", 372 | "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", 373 | "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==" 374 | }, 375 | "hyperswarm": { 376 | "version": "2.13.0", 377 | "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-2.13.0.tgz", 378 | "integrity": "sha512-w9YKJJXvvjP7K4ywCH2mf53vDhbL57fH8dxd0UJ9HzTSFqY/KNJVNRKplPn/mVscVo4tfOkjpdaHh12y9RwrxQ==", 379 | "requires": { 380 | "@hyperswarm/network": "^1.5.0", 381 | "shuffled-priority-queue": "^2.1.0", 382 | "utp-native": "^2.1.7" 383 | } 384 | }, 385 | "ieee754": { 386 | "version": "1.1.13", 387 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 388 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 389 | }, 390 | "immediate": { 391 | "version": "3.2.3", 392 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", 393 | "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=" 394 | }, 395 | "inherits": { 396 | "version": "2.0.4", 397 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 398 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 399 | }, 400 | "ini": { 401 | "version": "1.3.5", 402 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 403 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" 404 | }, 405 | "ip": { 406 | "version": "1.1.5", 407 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", 408 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" 409 | }, 410 | "ipv4-peers": { 411 | "version": "2.0.0", 412 | "resolved": "https://registry.npmjs.org/ipv4-peers/-/ipv4-peers-2.0.0.tgz", 413 | "integrity": "sha512-6ZMWB3JnCWT0gISUkzChcUEkJS6+LpGRU3h10f9Mfc0usVmyIcbcTN9+QPMg29gLOY8WtaKFbM5ME8qNySoh8g==" 414 | }, 415 | "is-stream": { 416 | "version": "1.1.0", 417 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 418 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 419 | }, 420 | "is-wsl": { 421 | "version": "2.1.1", 422 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", 423 | "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==" 424 | }, 425 | "isexe": { 426 | "version": "2.0.0", 427 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 428 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 429 | }, 430 | "k-bucket": { 431 | "version": "5.0.0", 432 | "resolved": "https://registry.npmjs.org/k-bucket/-/k-bucket-5.0.0.tgz", 433 | "integrity": "sha512-r/q+wV/Kde62/tk+rqyttEJn6h0jR7x+incdMVSYTqK73zVxVrzJa70kJL49cIKen8XjIgUZKSvk8ktnrQbK4w==", 434 | "requires": { 435 | "randombytes": "^2.0.3" 436 | } 437 | }, 438 | "level": { 439 | "version": "6.0.1", 440 | "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", 441 | "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", 442 | "requires": { 443 | "level-js": "^5.0.0", 444 | "level-packager": "^5.1.0", 445 | "leveldown": "^5.4.0" 446 | } 447 | }, 448 | "level-codec": { 449 | "version": "9.0.1", 450 | "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.1.tgz", 451 | "integrity": "sha512-ajFP0kJ+nyq4i6kptSM+mAvJKLOg1X5FiFPtLG9M5gCEZyBmgDi3FkDrvlMkEzrUn1cWxtvVmrvoS4ASyO/q+Q==" 452 | }, 453 | "level-concat-iterator": { 454 | "version": "2.0.1", 455 | "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", 456 | "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" 457 | }, 458 | "level-errors": { 459 | "version": "2.0.1", 460 | "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", 461 | "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", 462 | "requires": { 463 | "errno": "~0.1.1" 464 | } 465 | }, 466 | "level-iterator-stream": { 467 | "version": "4.0.2", 468 | "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", 469 | "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", 470 | "requires": { 471 | "inherits": "^2.0.4", 472 | "readable-stream": "^3.4.0", 473 | "xtend": "^4.0.2" 474 | } 475 | }, 476 | "level-js": { 477 | "version": "5.0.2", 478 | "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", 479 | "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", 480 | "requires": { 481 | "abstract-leveldown": "~6.2.3", 482 | "buffer": "^5.5.0", 483 | "inherits": "^2.0.3", 484 | "ltgt": "^2.1.2" 485 | } 486 | }, 487 | "level-packager": { 488 | "version": "5.1.1", 489 | "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", 490 | "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", 491 | "requires": { 492 | "encoding-down": "^6.3.0", 493 | "levelup": "^4.3.2" 494 | } 495 | }, 496 | "level-supports": { 497 | "version": "1.0.1", 498 | "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", 499 | "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", 500 | "requires": { 501 | "xtend": "^4.0.2" 502 | } 503 | }, 504 | "leveldown": { 505 | "version": "5.6.0", 506 | "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", 507 | "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", 508 | "requires": { 509 | "abstract-leveldown": "~6.2.1", 510 | "napi-macros": "~2.0.0", 511 | "node-gyp-build": "~4.1.0" 512 | }, 513 | "dependencies": { 514 | "node-gyp-build": { 515 | "version": "4.1.1", 516 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", 517 | "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==" 518 | } 519 | } 520 | }, 521 | "levelup": { 522 | "version": "4.4.0", 523 | "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", 524 | "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", 525 | "requires": { 526 | "deferred-leveldown": "~5.3.0", 527 | "level-errors": "~2.0.0", 528 | "level-iterator-stream": "~4.0.0", 529 | "level-supports": "~1.0.0", 530 | "xtend": "~4.0.0" 531 | } 532 | }, 533 | "long": { 534 | "version": "4.0.0", 535 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 536 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 537 | }, 538 | "ltgt": { 539 | "version": "2.2.1", 540 | "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", 541 | "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" 542 | }, 543 | "ms": { 544 | "version": "2.1.2", 545 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 546 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 547 | }, 548 | "multicast-dns": { 549 | "version": "7.2.2", 550 | "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.2.tgz", 551 | "integrity": "sha512-XqSMeO8EWV/nOXOpPV8ztIpNweVfE1dSpz6SQvDPp71HD74lMXjt4m/mWB1YBMG0kHtOodxRWc5WOb/UNN1A5g==", 552 | "requires": { 553 | "dns-packet": "^4.0.0", 554 | "thunky": "^1.0.2" 555 | } 556 | }, 557 | "nan": { 558 | "version": "2.14.1", 559 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", 560 | "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", 561 | "optional": true 562 | }, 563 | "nanoassert": { 564 | "version": "1.1.0", 565 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", 566 | "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" 567 | }, 568 | "nanoresource": { 569 | "version": "1.3.0", 570 | "resolved": "https://registry.npmjs.org/nanoresource/-/nanoresource-1.3.0.tgz", 571 | "integrity": "sha512-OI5dswqipmlYfyL3k/YMm7mbERlh4Bd1KuKdMHpeoVD1iVxqxaTMKleB4qaA2mbQZ6/zMNSxCXv9M9P/YbqTuQ==", 572 | "requires": { 573 | "inherits": "^2.0.4" 574 | } 575 | }, 576 | "napi-macros": { 577 | "version": "2.0.0", 578 | "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", 579 | "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" 580 | }, 581 | "nice-try": { 582 | "version": "1.0.5", 583 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 584 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 585 | }, 586 | "node-gyp-build": { 587 | "version": "4.2.1", 588 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.1.tgz", 589 | "integrity": "sha512-XyCKXsqZfLqHep1hhsMncoXuUNt/cXCjg1+8CLbu69V1TKuPiOeSGbL9n+k/ByKH8UT0p4rdIX8XkTRZV0i7Sw==" 590 | }, 591 | "npm-run-path": { 592 | "version": "2.0.2", 593 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 594 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 595 | "requires": { 596 | "path-key": "^2.0.0" 597 | } 598 | }, 599 | "once": { 600 | "version": "1.4.0", 601 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 602 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 603 | "requires": { 604 | "wrappy": "1" 605 | } 606 | }, 607 | "p-finally": { 608 | "version": "1.0.0", 609 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 610 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" 611 | }, 612 | "path-key": { 613 | "version": "2.0.1", 614 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 615 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" 616 | }, 617 | "promise-waitlist": { 618 | "version": "1.5.0", 619 | "resolved": "https://registry.npmjs.org/promise-waitlist/-/promise-waitlist-1.5.0.tgz", 620 | "integrity": "sha512-PIwCP6oJhfJEdmwtr5m5rwYYFNK+kMcGCsHbvLMAq73XPCTg9hdWRZsa70RWooPY2sTLMWDtqi8qfkpg90ZoYQ==" 621 | }, 622 | "protobufjs": { 623 | "version": "6.9.0", 624 | "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", 625 | "integrity": "sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==", 626 | "requires": { 627 | "@protobufjs/aspromise": "^1.1.2", 628 | "@protobufjs/base64": "^1.1.2", 629 | "@protobufjs/codegen": "^2.0.4", 630 | "@protobufjs/eventemitter": "^1.1.0", 631 | "@protobufjs/fetch": "^1.1.0", 632 | "@protobufjs/float": "^1.0.2", 633 | "@protobufjs/inquire": "^1.1.0", 634 | "@protobufjs/path": "^1.1.2", 635 | "@protobufjs/pool": "^1.1.0", 636 | "@protobufjs/utf8": "^1.1.0", 637 | "@types/long": "^4.0.1", 638 | "@types/node": "^13.7.0", 639 | "long": "^4.0.0" 640 | } 641 | }, 642 | "protocol-buffers-encodings": { 643 | "version": "1.1.0", 644 | "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.1.0.tgz", 645 | "integrity": "sha512-SmjEuAf3hc3h3rWZ6V1YaaQw2MNJWK848gLJgzx/sefOJdNLujKinJVXIS0q2cBQpQn2Q32TinawZyDZPzm4kQ==", 646 | "requires": { 647 | "signed-varint": "^2.0.1", 648 | "varint": "^5.0.0" 649 | } 650 | }, 651 | "prr": { 652 | "version": "1.0.1", 653 | "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", 654 | "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" 655 | }, 656 | "pump": { 657 | "version": "3.0.0", 658 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 659 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 660 | "requires": { 661 | "end-of-stream": "^1.1.0", 662 | "once": "^1.3.1" 663 | } 664 | }, 665 | "quick-lru": { 666 | "version": "4.0.1", 667 | "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", 668 | "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" 669 | }, 670 | "randombytes": { 671 | "version": "2.1.0", 672 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 673 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 674 | "requires": { 675 | "safe-buffer": "^5.1.0" 676 | } 677 | }, 678 | "readable-stream": { 679 | "version": "3.6.0", 680 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 681 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 682 | "requires": { 683 | "inherits": "^2.0.3", 684 | "string_decoder": "^1.1.1", 685 | "util-deprecate": "^1.0.1" 686 | } 687 | }, 688 | "record-cache": { 689 | "version": "1.1.0", 690 | "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.1.0.tgz", 691 | "integrity": "sha512-u8rbtLEJV7HRacl/ZYwSBFD8NFyB3PfTTfGLP37IW3hftQCwu6z4Q2RLyxo1YJUNRTEzJfpLpGwVuEYdaIkG9Q==" 692 | }, 693 | "safe-buffer": { 694 | "version": "5.1.2", 695 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 696 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 697 | }, 698 | "semver": { 699 | "version": "5.7.1", 700 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 701 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 702 | }, 703 | "shebang-command": { 704 | "version": "1.2.0", 705 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 706 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 707 | "requires": { 708 | "shebang-regex": "^1.0.0" 709 | } 710 | }, 711 | "shebang-regex": { 712 | "version": "1.0.0", 713 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 714 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" 715 | }, 716 | "shuffled-priority-queue": { 717 | "version": "2.1.0", 718 | "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz", 719 | "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==", 720 | "requires": { 721 | "unordered-set": "^2.0.1" 722 | } 723 | }, 724 | "signal-exit": { 725 | "version": "3.0.3", 726 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 727 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 728 | }, 729 | "signed-varint": { 730 | "version": "2.0.1", 731 | "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", 732 | "integrity": "sha1-UKmYnafJjCxh2tEZvJdHDvhSgSk=", 733 | "requires": { 734 | "varint": "~5.0.0" 735 | } 736 | }, 737 | "siphash24": { 738 | "version": "1.1.1", 739 | "resolved": "https://registry.npmjs.org/siphash24/-/siphash24-1.1.1.tgz", 740 | "integrity": "sha512-dKKwjIoTOa587TARYLlBRXq2lkbu5Iz35XrEVWpelhBP1m8r2BGOy1QlaZe84GTFHG/BTucEUd2btnNc8QzIVA==", 741 | "requires": { 742 | "nanoassert": "^1.0.0" 743 | } 744 | }, 745 | "sodium-javascript": { 746 | "version": "0.5.6", 747 | "resolved": "https://registry.npmjs.org/sodium-javascript/-/sodium-javascript-0.5.6.tgz", 748 | "integrity": "sha512-Uk+JpqHEbzsEmiMxwL7TB/ndhMEpc52KdReYXXSIX2oRFPaI7ZDlDImF8KbkFWbYl9BJRtc82AZ/kNf4/0n9KA==", 749 | "requires": { 750 | "blake2b": "^2.1.1", 751 | "nanoassert": "^1.0.0", 752 | "siphash24": "^1.0.1", 753 | "xsalsa20": "^1.0.0" 754 | } 755 | }, 756 | "sodium-native": { 757 | "version": "3.0.1", 758 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-3.0.1.tgz", 759 | "integrity": "sha512-MHHgeK3JTx1j16V6SdO3ZD3aDbzWGCDd380gYi07a7HPR5TL2ipmI9BzVtckTkLTYMpAAnJ3BjsJ8F5O/GZ6eg==", 760 | "requires": { 761 | "ini": "^1.3.5", 762 | "node-gyp-build": "^4.2.0" 763 | } 764 | }, 765 | "sodium-universal": { 766 | "version": "2.0.0", 767 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-2.0.0.tgz", 768 | "integrity": "sha512-csdVyakzHJRyCevY4aZC2Eacda8paf+4nmRGF2N7KxCLKY2Ajn72JsExaQlJQ2BiXJncp44p3T+b80cU+2TTsg==", 769 | "requires": { 770 | "sodium-javascript": "~0.5.0", 771 | "sodium-native": "^2.0.0" 772 | }, 773 | "dependencies": { 774 | "sodium-native": { 775 | "version": "2.4.9", 776 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-2.4.9.tgz", 777 | "integrity": "sha512-mbkiyA2clyfwAyOFIzMvsV6ny2KrKEIhFVASJxWfsmgfUEymgLIS2MLHHcGIQMkrcKhPErRaMR5Dzv0EEn+BWg==", 778 | "optional": true, 779 | "requires": { 780 | "ini": "^1.3.5", 781 | "nan": "^2.14.0", 782 | "node-gyp-build": "^4.1.0" 783 | } 784 | } 785 | } 786 | }, 787 | "speedometer": { 788 | "version": "1.1.0", 789 | "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.1.0.tgz", 790 | "integrity": "sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==" 791 | }, 792 | "stream-collector": { 793 | "version": "1.0.1", 794 | "resolved": "https://registry.npmjs.org/stream-collector/-/stream-collector-1.0.1.tgz", 795 | "integrity": "sha1-TU5V8XE1YSGyxfZVn5RHBaso2xU=", 796 | "requires": { 797 | "once": "^1.3.1" 798 | } 799 | }, 800 | "string_decoder": { 801 | "version": "1.1.1", 802 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 803 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 804 | "requires": { 805 | "safe-buffer": "~5.1.0" 806 | } 807 | }, 808 | "strip-eof": { 809 | "version": "1.0.0", 810 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 811 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" 812 | }, 813 | "thunky": { 814 | "version": "1.1.0", 815 | "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", 816 | "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" 817 | }, 818 | "time-ordered-set": { 819 | "version": "1.0.2", 820 | "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-1.0.2.tgz", 821 | "integrity": "sha512-vGO99JkxvgX+u+LtOKQEpYf31Kj3i/GNwVstfnh4dyINakMgeZCpew1e3Aj+06hEslhtHEd52g7m5IV+o1K8Mw==" 822 | }, 823 | "timeout-refresh": { 824 | "version": "1.0.2", 825 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-1.0.2.tgz", 826 | "integrity": "sha512-lsO23gD/EeW53AvEoTOleUFqTIapZTu5NPzD6ndUGs0+G1IuN0+hu2kT6I3AmNX2fiOrcC6umtzMEv1XQmajgQ==" 827 | }, 828 | "uint64be": { 829 | "version": "2.0.2", 830 | "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-2.0.2.tgz", 831 | "integrity": "sha512-9QqdvpGQTXgxthP+lY4e/gIBy+RuqcBaC6JVwT5I3bDLgT/btL6twZMR0pI3/Fgah9G/pdwzIprE5gL6v9UvyQ==", 832 | "requires": { 833 | "buffer-alloc": "^1.1.0" 834 | } 835 | }, 836 | "unordered-set": { 837 | "version": "2.0.1", 838 | "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", 839 | "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==" 840 | }, 841 | "util-deprecate": { 842 | "version": "1.0.2", 843 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 844 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 845 | }, 846 | "utp-native": { 847 | "version": "2.1.7", 848 | "resolved": "https://registry.npmjs.org/utp-native/-/utp-native-2.1.7.tgz", 849 | "integrity": "sha512-PQmXCdZOkmADtIqmhoiFEIdNlvkPouO8ktXqThFDBAt850B752bjQMF5KAJeMBJ5gzuI70ZiP9Qsr9mwCwzSyg==", 850 | "requires": { 851 | "napi-macros": "^2.0.0", 852 | "node-gyp-build": "^4.2.0", 853 | "readable-stream": "^3.0.2", 854 | "timeout-refresh": "^1.0.0", 855 | "unordered-set": "^2.0.1" 856 | } 857 | }, 858 | "varint": { 859 | "version": "5.0.0", 860 | "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", 861 | "integrity": "sha1-2Ca4n3SQcy+rwMDtaT7Uddyynr8=" 862 | }, 863 | "which": { 864 | "version": "1.3.1", 865 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 866 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 867 | "requires": { 868 | "isexe": "^2.0.0" 869 | } 870 | }, 871 | "wrappy": { 872 | "version": "1.0.2", 873 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 874 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 875 | }, 876 | "xor-distance": { 877 | "version": "2.0.0", 878 | "resolved": "https://registry.npmjs.org/xor-distance/-/xor-distance-2.0.0.tgz", 879 | "integrity": "sha512-AsAqZfPAuWx7qB/0kyRDUEvoU3QKsHWzHU9smFlkaiprEpGfJ/NBbLze2Uq0rdkxCxkNM9uOLvz/KoNBCbZiLQ==" 880 | }, 881 | "xsalsa20": { 882 | "version": "1.1.0", 883 | "resolved": "https://registry.npmjs.org/xsalsa20/-/xsalsa20-1.1.0.tgz", 884 | "integrity": "sha512-zd3ytX2cm+tcSndRU+krm0eL4TMMpZE7evs5hLRAoOy6gviqLfe3qOlkjF3i5SeAkQUCeJk0lJZrEU56kHRfWw==" 885 | }, 886 | "xtend": { 887 | "version": "4.0.2", 888 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 889 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" 890 | } 891 | } 892 | } 893 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peerchan", 3 | "version": "1.1.6", 4 | "description": "Fully decentralized p2p IRC for your terminal", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node ./bin/peerchan.js" 8 | }, 9 | "author": "heapwolf", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@hyperswarm/network": "^1.5.0", 13 | "@peerlinks/level-storage": "github:heapwolf/peerlinks-level-storage", 14 | "@peerlinks/protocol": "github:heapwolf/peerlinks#v7.4.5", 15 | "@peerlinks/swarm": "github:heapwolf/peerlinks-swarm", 16 | "bs58": "^4.0.1", 17 | "charm": "^1.0.2", 18 | "clipboardy": "^2.3.0", 19 | "promise-waitlist": "^1.5.0", 20 | "sodium-native": "^3.0.1" 21 | }, 22 | "engines": { 23 | "node": ">=12.16.0" 24 | }, 25 | "engineStrict": true, 26 | "bin": { 27 | "peerchan": "bin/peerchan.js" 28 | }, 29 | "devDependencies": {}, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/heapwolf/peerchan.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/heapwolf/peerchan/issues" 36 | }, 37 | "homepage": "https://github.com/heapwolf/peerchan#readme" 38 | } 39 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heapwolf/peerchan/4b32fee7e6d8e7a7948242c7829434d21224a26d/screenshot.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug') 2 | const os = require('os') 3 | const fs = require('fs') 4 | const path = require('path') 5 | const util = require('util') 6 | const { EventEmitter } = require('events') 7 | 8 | const Network = require('./network') 9 | const log = require('./log') 10 | const ui = require('./ui') 11 | const Storage = require('@peerlinks/level-storage') 12 | 13 | // 14 | // Create the network 15 | // 16 | const USER_DATA_DIR = path.join(os.homedir(), '.peerchan') 17 | 18 | if (!fs.existsSync(USER_DATA_DIR)) { 19 | fs.mkdirSync(USER_DATA_DIR) 20 | } 21 | 22 | const ee = new EventEmitter() 23 | 24 | if (process.env.DEBUG) { 25 | const logfd = fs.openSync('peerchan.log', 'a+') 26 | 27 | debug.log = (...args) => { 28 | const s = util.format(...args) 29 | fs.write(logfd, s + '\n', () => {}) 30 | } 31 | } 32 | 33 | // 34 | // Startup 35 | // 36 | module.exports = async function main () { 37 | const instance = process.env.INST || 0 38 | 39 | log(ee) 40 | ui(ee) 41 | 42 | const storage = new Storage() 43 | 44 | await storage.open(`${USER_DATA_DIR}/data-${instance}.level`) 45 | 46 | const network = new Network(ee, { 47 | storage, 48 | log 49 | }) 50 | 51 | ee.on('quit', async () => { 52 | log.info('Quitting...') 53 | await storage.close() 54 | process.exit(0) 55 | }) 56 | 57 | try { 58 | await network.init() 59 | } catch (err) { 60 | log.error(err.stack) 61 | process.exit(1) 62 | } 63 | 64 | ee.emit('network:initialized') 65 | } 66 | -------------------------------------------------------------------------------- /src/log.js: -------------------------------------------------------------------------------- 1 | const log = ee => { 2 | log.emit = (...args) => ee.emit('log', ...args) 3 | } 4 | 5 | log.info = (...args) => log.emit({ status: 'OK', txt: args }) 6 | log.error = (...args) => log.emit({ status: 'NOT OK', txt: args }) 7 | log.warn = (...args) => log.emit({ status: 'WARN', txt: args }) 8 | 9 | module.exports = log 10 | -------------------------------------------------------------------------------- /src/network.js: -------------------------------------------------------------------------------- 1 | const sodium = require('sodium-native') 2 | const bs58 = require('bs58') 3 | const Swarm = require('@peerlinks/swarm') 4 | 5 | const { 6 | Protocol, 7 | Channel, 8 | Identity, 9 | Message 10 | } = require('@peerlinks/protocol') 11 | 12 | const config = { 13 | bufferSize: 5000 14 | } 15 | 16 | const INVITE_TIMEOUT = 15 * 60 * 1000 // 15 minutes 17 | 18 | module.exports = class Network { 19 | constructor (ee, options = {}) { 20 | this.options = options 21 | this.storage = options.storage 22 | this.log = options.log 23 | this.swarm = null 24 | this.events = ee 25 | 26 | this.protocol = new Protocol({ 27 | storage: options.storage, 28 | passphrase: 'test', 29 | sodium 30 | }) 31 | 32 | this.identity = null 33 | this.channel = null 34 | this.channelWait = null 35 | const rnum = Math.random().toString(16).slice(2, 8) 36 | this.randomName = `user${rnum}` 37 | 38 | this.events.on('network', (method, ...args) => { 39 | this[method] && this[method](...args) 40 | }) 41 | } 42 | 43 | async init () { 44 | await this.protocol.load() 45 | 46 | this.swarm = new Swarm(this.protocol) 47 | this.log.info('Initialized Hyperswarm') 48 | } 49 | 50 | async iam ({ name = this.randomName }) { 51 | let channel 52 | 53 | const existing = this.protocol.getIdentity(name) 54 | 55 | if (existing) { 56 | this.identity = existing 57 | channel = this.protocol.getChannel(name) 58 | } else { 59 | [this.identity, channel] = 60 | await this.protocol.createIdentityPair(name) 61 | } 62 | 63 | this.ch({ name: channel.name }) 64 | 65 | const message = existing 66 | ? `Using identity: "${name}"` 67 | : `Created identity: "${name}"` 68 | 69 | this.events.emit('network:identified') 70 | this.log.info(message) 71 | } 72 | 73 | async status () { 74 | if (!this.identity) { 75 | return this.log.warn('identity not set') 76 | } 77 | 78 | const chainMap = this.protocol.computeChainMap() 79 | const peers = [] 80 | 81 | for (const [, chains] of chainMap) { 82 | chains.forEach((chain) => { 83 | const path = chain.getDisplayPath() 84 | if (path.length) peers.push(path) 85 | }) 86 | } 87 | 88 | if (!peers.length) { 89 | peers.push(this.channel.name) 90 | } 91 | 92 | const status = { 93 | channelName: this.channel.name || this.identity.name, 94 | messageCount: await this.channel.getMessageCount(), 95 | peerCount: chainMap.size || 1, 96 | meta: JSON.stringify(this.channel.metadata), 97 | peers 98 | } 99 | 100 | this.events.emit('network:status', status) 101 | } 102 | 103 | async create ({ name }) { 104 | if (!this.identity) { 105 | return this.log.warn('identity not set') 106 | } 107 | 108 | const existing = this.protocol.getChannel(name) 109 | if (existing) { 110 | return this.log.warn('Channel already exists') 111 | } 112 | 113 | const identity = new Identity(name, { sodium }) 114 | const channel = await Channel.fromIdentity(identity, { 115 | name, 116 | sodium, 117 | storage: this.storage 118 | }) 119 | 120 | const { 121 | request, 122 | decrypt 123 | } = this.identity.requestInvite(this.protocol.id) 124 | 125 | const { 126 | encryptedInvite 127 | } = identity.issueInvite(channel, request, this.identity.name) 128 | 129 | const invite = decrypt(encryptedInvite) 130 | 131 | await this.protocol.channelFromInvite(invite, this.identity) 132 | 133 | await this.protocol.addChannel(channel) 134 | await this.protocol.addIdentity(identity) 135 | await this.ch({ name }) 136 | } 137 | 138 | async accept ({ inviteeName, request }) { 139 | if (!this.identity) { 140 | return this.log.warn('identity not set') 141 | } 142 | 143 | this.log.info(`trying to accept invite, ${inviteeName}, ${request}`) 144 | request = bs58.decode(request) 145 | 146 | const { 147 | encryptedInvite, 148 | peerId 149 | } = this.identity.issueInvite(this.channel, request, inviteeName) 150 | 151 | await this.swarm.sendInvite({ 152 | peerId, 153 | encryptedInvite 154 | }, INVITE_TIMEOUT) 155 | 156 | this.log.info('issued invite') 157 | } 158 | 159 | async request () { 160 | if (!this.identity) { 161 | return 162 | } 163 | 164 | const { 165 | requestId, 166 | request, 167 | decrypt 168 | } = this.identity.requestInvite(this.protocol.id) 169 | 170 | const request58 = JSON.stringify(bs58.encode(request)) 171 | const trusteeName = JSON.stringify(this.identity.name) 172 | 173 | this.events.emit('network:request', { 174 | trusteeName, 175 | request58 176 | }) 177 | 178 | this.log.info('Waiting for invite to be accepted') 179 | 180 | const encryptedInvite = 181 | await this.swarm.waitForInvite(requestId, INVITE_TIMEOUT) 182 | const invite = decrypt(encryptedInvite) 183 | 184 | const channel = 185 | await this.protocol.channelFromInvite(invite, this.identity) 186 | 187 | this.log.info(`Starting to sync (channel=${channel.name})`) 188 | 189 | // Join channel's swarm to start synchronization 190 | await this.ch({ name: channel.name }) 191 | 192 | this.events.emit('data', { 193 | joined: true, 194 | name: this.channel.name, 195 | message: `Joined ${this.channel.name}` 196 | }) 197 | 198 | this.log.info('Created request') 199 | } 200 | 201 | async post ({ text }) { 202 | if (!this.identity) { 203 | return 204 | } 205 | 206 | const body = Message.json({ text }) 207 | await this.channel.post(body, this.identity) 208 | await this.displayChannel() 209 | return true 210 | } 211 | 212 | channels () { 213 | this.events.emit('network:channels', this.protocol.getChannelNames()) 214 | } 215 | 216 | async identities () { 217 | const ids = this.protocol.getIdentityNames().map(id => String(id)) 218 | this.events.emit('network:identities', ids) 219 | } 220 | 221 | async ch ({ name }) { 222 | if (this.channelWait) { 223 | this.channelWait.cancel() 224 | this.channelWait = null 225 | } 226 | 227 | const channel = this.protocol.getChannel(name) 228 | 229 | if (!channel) { 230 | return this.log.error(`Unknown channel, '${name}'. Request an invite?`) 231 | } 232 | 233 | const loop = async () => { 234 | this.channelWait = channel.waitForIncomingMessage() 235 | 236 | this.events.emit('network:join', { 237 | name: channel.name 238 | }) 239 | 240 | try { 241 | await this.channelWait 242 | this.channelWait = null 243 | 244 | try { 245 | this.displayChannel() 246 | } catch (err) { 247 | // ignore 248 | } 249 | 250 | loop() 251 | } catch (err) { 252 | // ignore 253 | } 254 | } 255 | 256 | this.channel = channel 257 | this.swarm.joinChannel(channel) 258 | 259 | loop() 260 | 261 | this.log.info(`Joined channel ${name}`) 262 | 263 | await this.displayChannel() 264 | 265 | return { data: { joined: true } } 266 | } 267 | 268 | async displayChannel () { 269 | const ch = this.channel 270 | const count = await ch.getMessageCount() 271 | const min = Math.min(config.bufferSize, count) 272 | const messages = await ch.getReverseMessagesAtOffset(0, min) 273 | 274 | this.events.emit('messages', { 275 | channel: this.channel, 276 | publicKey: bs58.encode(this.channel.publicKey), 277 | lines: messages.slice().reverse() 278 | }) 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/ui.js: -------------------------------------------------------------------------------- 1 | const screen = require('charm')() 2 | const clipboardy = require('clipboardy') 3 | const readline = require('readline') 4 | const pkg = require('../package.json') 5 | const os = require('os') 6 | const path = require('path') 7 | const fs = require('fs') 8 | 9 | screen.pipe(process.stdout) 10 | screen.reset() 11 | 12 | screen.erase('screen') 13 | 14 | let config = { 15 | bg: 255, 16 | fg: 238, 17 | comment: { 18 | fg: 244, 19 | bg: 255 20 | }, 21 | timestamp: { 22 | fg: 250, 23 | bg: 255 24 | }, 25 | prompt: { 26 | fg: 15, 27 | bg: 238 28 | }, 29 | status: { 30 | fg: 15, 31 | bg: 248 32 | }, 33 | scrollback: 3, 34 | bufferSize: 5000 35 | } 36 | 37 | const colorKeys = {} 38 | let colorIndex = 3 39 | let paintCursorOffset = 0 40 | let readyBounce = null 41 | let status = '' 42 | 43 | const buffer = [ 44 | { txt: 'Hello. Try /help', status: 'OK' } 45 | ] 46 | 47 | const write = (o) => { 48 | paintCursorOffset = 0 49 | 50 | if (o.id) { 51 | const existing = buffer.find(line => line.id === o.id) 52 | 53 | if (existing) { 54 | existing.txt = o.txt 55 | return 56 | } 57 | } 58 | 59 | buffer.unshift(o) 60 | 61 | if (buffer.length === config.bufferSize) { 62 | buffer.pop() 63 | } 64 | } 65 | 66 | const exit = () => { 67 | screen.reset() 68 | process.exit(0) 69 | } 70 | 71 | const help = () => { 72 | const mod = process.platform === 'darwin' ? 'Fn' : 'Shift' 73 | 74 | const help = [ 75 | '', 76 | 'KEYS', 77 | '', 78 | '- Up and Down arrows to navigate command history', 79 | `- ${mod}+Up and ${mod}+Down arrows to scroll buffer`, 80 | '- Tab to complete commmand', 81 | '', 82 | 'COMMANDS', 83 | '', 84 | '/a accept invite', 85 | '/create [name] create a channel', 86 | '/exit leave', 87 | '/help this info', 88 | '/id [name] identify or list identities', 89 | '/join [name] join or list channels', 90 | '/r request invite', 91 | '/status status', 92 | '' 93 | ] 94 | 95 | help.forEach(txt => write({ txt })) 96 | } 97 | 98 | let completions = [] 99 | 100 | function completer (line) { 101 | const base = '/a /config /exit /help /id /join /q /r'.split(' ') 102 | const hits = [...base, ...completions].filter((c) => c.startsWith(line)) 103 | return [hits.length ? hits : completions, line] 104 | } 105 | 106 | const rl = readline.createInterface({ 107 | input: process.stdin, 108 | output: process.stdout, 109 | removeHistoryDuplicates: true, 110 | prompt: '', 111 | completer 112 | }) 113 | 114 | const setBackground = () => { 115 | screen.position(0, process.stdout.rows) 116 | screen.background(config.bg) 117 | screen.foreground(config.fg) 118 | } 119 | 120 | const setPrompt = () => { 121 | const { cols } = rl.getCursorPos() 122 | setBackground() 123 | screen.background(config.prompt.bg) 124 | screen.foreground(config.prompt.fg) 125 | screen.write(' '.repeat(process.stdout.columns)) 126 | screen.position(0, process.stdout.rows) 127 | screen.write(rl.line) 128 | screen.position(cols + 1, process.stdout.rows) 129 | } 130 | 131 | const paintRows = () => { 132 | screen.background(config.bg) 133 | screen.foreground(config.fg) 134 | screen.erase('screen') 135 | 136 | const height = process.stdout.rows - 1 137 | let index = paintCursorOffset 138 | 139 | for (let i = height; i > 0; i--) { 140 | screen.position(0, i) 141 | 142 | if (i === 1) { 143 | const cols = process.stdout.columns 144 | 145 | screen.background(config.status.bg) 146 | screen.foreground(config.status.fg) 147 | screen.write(' '.repeat(cols)) 148 | screen.position(0, 0) 149 | const padding = ' '.repeat((cols / 2) - (status.length / 2)) 150 | screen.write(padding + status) 151 | screen.background(config.bg) 152 | screen.foreground(config.fg) 153 | return 154 | } 155 | 156 | const line = buffer[index++] 157 | if (!line) continue 158 | 159 | const calcCost = len => { 160 | const w = process.stdout.columns 161 | const cost = Math.ceil(len / w) 162 | 163 | if (cost > 1) { 164 | i -= cost 165 | screen.position(0, i) 166 | } 167 | } 168 | 169 | if (line.type !== 'message') { 170 | screen.background(config.comment.bg) 171 | screen.foreground(config.comment.fg) 172 | const prefix = config.prefix || '▍' 173 | screen.write(prefix) 174 | screen.foreground(line.status === 'OK' ? 2 : 1) 175 | const status = line.status ? line.status + ' ' : '' 176 | screen.write(status) 177 | screen.background(config.comment.bg) 178 | screen.foreground(config.comment.fg) 179 | 180 | const lineLength = 181 | line.txt.length + 182 | prefix.length + 183 | status.length 184 | calcCost(lineLength) 185 | 186 | screen.write(String(line.txt) || '') 187 | } else { 188 | let text 189 | if (line.value.isRoot) { 190 | text = 'channel created' 191 | } else { 192 | text = line.value.json.text 193 | } 194 | 195 | const time = new Date(line.value.timestamp * 1000).toLocaleTimeString() 196 | const { publicKeys, displayPath } = line.value.getAuthor(line.channel) 197 | const author = displayPath.pop() 198 | const prefix = ' ' + (author || `<${line.channel}>`) + ' ' 199 | let color = 4 200 | 201 | if (publicKeys.length) { 202 | const key = publicKeys[publicKeys.length - 1].toString('hex').slice(0, 6) 203 | color = colorKeys[key] 204 | 205 | if (!color) { 206 | color = colorKeys[key] = colorIndex++ 207 | } 208 | } 209 | 210 | const lineLength = 211 | time.length + 212 | prefix.length + 213 | text.length 214 | 215 | calcCost(lineLength) 216 | 217 | screen.foreground(config.timestamp.fg) 218 | screen.background(config.timestamp.bg) 219 | screen.write(time) 220 | screen.background(config.bg) 221 | screen.foreground(color) 222 | screen.write(prefix) 223 | screen.foreground(config.fg) 224 | screen.write(text) 225 | } 226 | } 227 | } 228 | 229 | const historyDown = () => { 230 | if (paintCursorOffset > 0) { 231 | paintCursorOffset -= config.scrollback 232 | ready() 233 | } 234 | } 235 | 236 | const historyUp = () => { 237 | paintCursorOffset += config.scrollback 238 | ready() 239 | } 240 | 241 | const ready = () => { 242 | clearTimeout(readyBounce) 243 | readyBounce = setTimeout(() => { 244 | setBackground() 245 | paintRows() 246 | setPrompt() 247 | }, 16) 248 | } 249 | 250 | rl.on('SIGINT', () => { 251 | write({ txt: 'Use /exit to exit' }) 252 | ready() 253 | }) 254 | 255 | rl.on('close', exit) 256 | 257 | process.stdout.on('resize', ready) 258 | 259 | process.stdin.on('keypress', (c, k) => { 260 | if (!k) return 261 | 262 | const up = ((k.name === 'up') && k.shift) || (k.name === 'pageup') 263 | const down = ((k.name === 'down') && k.shift) || (k.name === 'pagedown') 264 | 265 | if (up) historyUp() 266 | if (down) historyDown() 267 | }) 268 | 269 | module.exports = (events) => { 270 | status = `Peerchan ${pkg.version}` 271 | const loadConfig = () => { 272 | const src = path.join(os.homedir(), '.peerchan.json') 273 | 274 | try { 275 | const str = fs.readFileSync(src) 276 | config = { 277 | ...config, 278 | ...JSON.parse(str) 279 | } 280 | } catch (err) { 281 | } 282 | 283 | events.emit('log', { status: 'OK', txt: 'Loaded config' }) 284 | ready() 285 | } 286 | 287 | events.on('messages', (data) => { 288 | for (const value of data.lines) { 289 | write({ 290 | type: 'message', 291 | id: value.hash, 292 | channel: data.channel.name, 293 | value 294 | }) 295 | } 296 | 297 | ready() 298 | }) 299 | 300 | events.on('log', (data) => { 301 | write(data) 302 | ready() 303 | }) 304 | 305 | const logError = err => { 306 | err.stack.split('\n').map(line => write({ txt: line })) 307 | ready() 308 | } 309 | 310 | process.on('uncaughtException', logError) 311 | process.on('unhandledRejection', logError) 312 | 313 | events.on('network:channels', data => { 314 | write({ txt: '' }) 315 | write({ txt: 'CHANNELS' }) 316 | write({ txt: '' }) 317 | 318 | completions = completions.filter(c => !c.includes('/join')) 319 | 320 | for (const channel of data) { 321 | completions.push(`/join ${channel}`) 322 | write({ txt: `${channel}` }) 323 | } 324 | 325 | write({ txt: '' }) 326 | ready() 327 | }) 328 | 329 | events.on('network:identities', data => { 330 | write({ txt: '' }) 331 | write({ txt: 'IDENTITIES' }) 332 | write({ txt: '' }) 333 | 334 | if (!data.length) { 335 | write({ txt: 'You have no identitiy. Use `/id coolusername`' }) 336 | write({ txt: 'Use `/help` for more commands.' }) 337 | write({ txt: '' }) 338 | return 339 | } 340 | 341 | completions = completions.filter(c => !c.includes('/id')) 342 | 343 | for (const id of data) { 344 | completions.push(`/id ${id}`) 345 | write({ txt: `${id}` }) 346 | } 347 | 348 | write({ txt: '' }) 349 | ready() 350 | }) 351 | 352 | events.on('network:join', ({ name }) => { 353 | status = name 354 | ready() 355 | }) 356 | 357 | events.on('network:status', status => { 358 | if (!status.channelName) return 359 | 360 | write({ txt: '' }) 361 | write({ txt: 'CHANNEL' }) 362 | write({ txt: '' }) 363 | write({ txt: `Channel: ${status.channelName}` }) 364 | write({ txt: `Messages: ${status.messageCount}` }) 365 | write({ txt: `Users: ${status.peerCount}` }) 366 | write({ txt: '' }) 367 | write({ txt: 'USERS' }) 368 | write({ txt: '' }) 369 | 370 | for (const user of status.peers) { 371 | write({ txt: `${user}` }) 372 | } 373 | 374 | write({ txt: '' }) 375 | ready() 376 | }) 377 | 378 | events.on('network:request', data => { 379 | const cmd = `/a ${data.trusteeName} ${data.request58}` 380 | 381 | const msg = [ 382 | '', 383 | 'Ask peer to use this command (copied to clipboard)', 384 | '', 385 | cmd, 386 | '' 387 | ] 388 | 389 | clipboardy.writeSync(cmd) 390 | 391 | for (const txt of msg) { 392 | write({ txt }) 393 | } 394 | }) 395 | 396 | rl.on('line', (line) => { 397 | const text = line.trim() 398 | 399 | if (text[0] === '/') { 400 | const parts = text.slice(1).split(' ') 401 | 402 | switch (parts[0]) { 403 | case '?': 404 | case 'h': 405 | case 'help': 406 | help() 407 | ready() 408 | break 409 | case 'create': { 410 | const name = parts[1] 411 | if (!name) break 412 | events.emit('network', 'create', { name }) 413 | break 414 | } 415 | case 'q': 416 | case 'exit': 417 | write({ txt: 'Exiting...', status: 'OK' }) 418 | events.emit('network', 'post', { text: '' }) 419 | setTimeout(exit, 1024) 420 | break 421 | case 'r': 422 | events.emit('network', 'request') 423 | ready() 424 | break 425 | case 'a': { 426 | events.emit('network', 'accept', { 427 | inviteeName: parts[1].replace(/"/g, ''), 428 | request: parts[2].replace(/"/g, '') 429 | }) 430 | break 431 | } 432 | case 'status': 433 | events.emit('network', 'status') 434 | break 435 | case 'config': 436 | loadConfig() 437 | break 438 | case 'join': { 439 | if (!parts[1]) { 440 | events.emit('network', 'channels') 441 | break 442 | } 443 | 444 | events.emit('network', 'ch', { 445 | name: parts[1] 446 | }) 447 | 448 | break 449 | } 450 | 451 | case 'id': { 452 | if (!parts[1]) { 453 | events.emit('network', 'identities') 454 | break 455 | } 456 | 457 | events.emit('network', 'iam', { 458 | name: parts[1] 459 | }) 460 | 461 | break 462 | } 463 | } 464 | 465 | ready() 466 | return 467 | } 468 | 469 | if (!text.trim()) { 470 | ready() 471 | return 472 | } 473 | 474 | events.emit('network', 'post', { text }) 475 | 476 | ready() 477 | rl.prompt(true) 478 | }) 479 | 480 | events.on('network:initialized', () => { 481 | if (config.id) { 482 | events.emit('network', 'iam', { name: config.id }) 483 | } 484 | }) 485 | 486 | loadConfig() 487 | rl.prompt(true) 488 | ready() 489 | } 490 | --------------------------------------------------------------------------------