git ssb

3+

ev / decent



Commit 44fa44b60b078e9257408541c9c5824267deb7ca

decent 2.0 uses the main ssb network key and depends on minbase

Ev Bogue committed on 6/13/2017, 7:56:02 PM
Parent: c047dd23d6de58cf6cbac805acf5aad237a9fd58

Files changed

.gitignorechanged
decent.jschanged
package.jsonchanged
readme.mdchanged
serve.jschanged
yarn.lockchanged
build/index.htmldeleted
decent.cssadded
lib/api.mddeleted
lib/apidocs.jsdeleted
lib/cli-cmd-aliases.jsdeleted
lib/index.jsdeleted
lib/ssb-cap.jsdeleted
lib/util.jsdeleted
lib/validators.jsdeleted
index.jsadded
modules/app.jsadded
modules/index.jsadded
modules/tabs.jsadded
plugins/block.jsdeleted
plugins/block.mddeleted
plugins/friends.jsdeleted
plugins/friends.mddeleted
plugins/gossip.mddeleted
plugins/gossip/index.jsdeleted
plugins/gossip/init.jsdeleted
plugins/gossip/schedule.jsdeleted
plugins/invite.jsdeleted
plugins/invite.mddeleted
plugins/local.jsdeleted
plugins/logging.jsdeleted
plugins/master.jsdeleted
plugins/plugins.jsdeleted
plugins/plugins.mddeleted
plugins/private.jsdeleted
plugins/private.mddeleted
plugins/replicate.jsdeleted
plugins/replicate.mddeleted
plugins/sdash/.gitignoredeleted
plugins/sdash/bin.jsdeleted
plugins/sdash/index.jsdeleted
plugins/sdash/static/sdash.cssdeleted
plugins/sdash/style.cssdeleted
plugins/ssb-blobs/.npmignoredeleted
plugins/ssb-blobs/.travis.ymldeleted
plugins/ssb-blobs/LICENSEdeleted
plugins/ssb-blobs/README.mddeleted
plugins/ssb-blobs/create.jsdeleted
plugins/ssb-blobs/index.jsdeleted
plugins/ssb-blobs/inject.jsdeleted
plugins/ssb-blobs/package.jsondeleted
plugins/ssb-blobs/set.jsdeleted
plugins/ssb-blobs/test/async.jsdeleted
plugins/ssb-blobs/test/integration.jsdeleted
plugins/ssb-blobs/test/legacy.jsdeleted
plugins/ssb-blobs/test/mock/blobs.jsdeleted
plugins/ssb-blobs/test/mock/set.jsdeleted
plugins/ssb-blobs/test/push.jsdeleted
plugins/ssb-blobs/test/real.jsdeleted
plugins/ssb-blobs/test/secret-stack.jsdeleted
plugins/ssb-blobs/test/simple.jsdeleted
plugins/ssb-blobs/test/util.jsdeleted
plugins/ssb-client/README.mddeleted
plugins/ssb-client/index.jsdeleted
plugins/ssb-client/package.jsondeleted
plugins/ssb-client/test/index.jsdeleted
plugins/ssb-config/.npmignoredeleted
plugins/ssb-config/LICENSEdeleted
plugins/ssb-config/README.mddeleted
plugins/ssb-config/b.jsdeleted
plugins/ssb-config/index.jsdeleted
plugins/ssb-config/inject.jsdeleted
plugins/ssb-config/package.jsondeleted
plugins/ssb-config/test.jsdeleted
plugins/ssb-links/.npmignoredeleted
plugins/ssb-links/.travis.ymldeleted
plugins/ssb-links/LICENSEdeleted
plugins/ssb-links/README.mddeleted
plugins/ssb-links/index.jsdeleted
plugins/ssb-links/links.jsdeleted
plugins/ssb-links/package.jsondeleted
plugins/ssb-query/.npmignoredeleted
plugins/ssb-query/.travis.ymldeleted
plugins/ssb-query/LICENSEdeleted
plugins/ssb-query/README.mddeleted
plugins/ssb-query/index.jsdeleted
plugins/ssb-query/package.jsondeleted
plugins/ssb-ws/.npmignoredeleted
plugins/ssb-ws/.travis.ymldeleted
plugins/ssb-ws/LICENSEdeleted
plugins/ssb-ws/README.mddeleted
plugins/ssb-ws/index.jsdeleted
plugins/ssb-ws/json-api.jsdeleted
plugins/ssb-ws/npm-debug.logdeleted
plugins/ssb-ws/package.jsondeleted
plugins/viewer/README.mddeleted
plugins/viewer/bin.jsdeleted
plugins/viewer/example.htmldeleted
plugins/viewer/index.jsdeleted
plugins/viewer/lib/about.jsdeleted
plugins/viewer/package.jsondeleted
plugins/viewer/static/base.cssdeleted
plugins/viewer/static/nicer.cssdeleted
scripts/style.jsadded
.gitignoreView
@@ -1,1 +1,3 @@
1 +decent.css.json
2 +build
13 node_modules
decent.jsView
@@ -8,48 +8,51 @@
88 var stringify = require('pull-stringify')
99 var createHash = require('multiblob/util').createHash
1010 var minimist = require('minimist')
1111 var muxrpcli = require('muxrpcli')
12-var cmdAliases = require('./lib/cli-cmd-aliases')
12 +var cmdAliases = require('scuttlebot/lib/cli-cmd-aliases')
1313
1414 //get config as cli options after --, options before that are
1515 //options to the command.
1616 var argv = process.argv.slice(2)
1717 var i = argv.indexOf('--')
1818 var conf = argv.slice(i+1)
1919 argv = ~i ? argv.slice(0, i) : argv
2020
21-var config = require('./plugins/ssb-config/inject')(process.env.ssb_appname, minimist(conf))
21 +var config = require('ssb-config/inject')(process.env.ssb_appname, minimist(conf))
2222
2323 var keys = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret'))
24 +if(keys.curve === 'k256')
25 + throw new Error('k256 curves are no longer supported,'+
26 + 'please delete' + path.join(config.path, 'secret'))
2427
2528 var manifestFile = path.join(config.path, 'manifest.json')
2629
2730 if (argv[0] == 'server') {
2831
2932 // special server command:
3033 // import sbot and start the server
3134
32- var createSbot = require('./lib/')
33- .use(require('./plugins/plugins'))
34- .use(require('./plugins/master'))
35- .use(require('./plugins/gossip'))
36- .use(require('./plugins/friends'))
37- .use(require('./plugins/replicate'))
38- .use(require('./plugins/ssb-blobs'))
39- .use(require('./plugins/invite'))
40- .use(require('./plugins/block'))
41- .use(require('./plugins/local'))
42- .use(require('./plugins/logging'))
43- .use(require('./plugins/private'))
44- .use(require('./plugins/ssb-ws'))
45- .use(require('./plugins/ssb-links'))
46- .use(require('./plugins/ssb-query'))
47- .use(require('./plugins/sdash'))
48- .use(require('./plugins/viewer'))
35 + var createSbot = require('scuttlebot')
36 + .use(require('scuttlebot/plugins/plugins'))
37 + .use(require('scuttlebot/plugins/master'))
38 + .use(require('scuttlebot/plugins/gossip'))
39 + .use(require('scuttlebot/plugins/replicate'))
40 + .use(require('ssb-friends'))
41 + .use(require('ssb-blobs'))
42 + .use(require('scuttlebot/plugins/invite'))
43 + //.use(require('scuttlebot/plugins/block'))
44 + .use(require('scuttlebot/plugins/local'))
45 + .use(require('scuttlebot/plugins/logging'))
46 + .use(require('scuttlebot/plugins/private'))
47 + .use(require('ssb-ws'))
48 + .use(require('ssb-links'))
49 + .use(require('ssb-query'))
50 + .use(require('ssb-ebt'))
51 + // .use(require('ssb-fulltext'))
4952
5053 // add third-party plugins
51- // require('./plugins/plugins').loadUserPlugins(createSbot, config)
54 + //require('scuttlebot/plugins/plugins').loadUserPlugins(createSbot, config)
5255
5356 // start server
5457
5558 config.keys = keys
@@ -77,9 +80,9 @@
7780 )
7881 }
7982
8083 // connect
81- require('./plugins/ssb-client')(keys, {
84 + require('ssb-client')(keys, {
8285 manifest: manifest,
8386 port: config.port,
8487 host: config.host||'localhost',
8588 caps: config.caps,
package.jsonView
@@ -3,10 +3,10 @@
33 "version": "2.0.0",
44 "description": "A decent(tralized) lite client for secure scuttlebutt",
55 "homepage": "http://gitmx.com/%25Wq%2FvobdcDedC0FBO2UdowxhPcqokSwtf9Og1mjYvQGE%3D.sha256",
66 "scripts": {
7- "start": "node decent server --allowPrivate",
8- "build": "node client/scripts/style.js && mkdir -p build && browserify client/index.js | indexhtmlify > build/index.html"
7 + "build": "node scripts/style.js && mkdir -p build && browserify index.js | indexhtmlify > build/index.html",
8 + "start": "node decent server --allowPrivate"
99 },
1010 "author": "Ev Bogue",
1111 "license": "MIT",
1212 "dependencies": {
readme.mdView
@@ -1,15 +1,15 @@
11 # Decent
22
3-### A decent(ralized) network for business and development.
3 +### A decent(ralized) client for secure scuttlebutt.
44
55 In the beginning the web was distributed. Then companies in the valley centralized it for their own profit, impoverishing the creative class of the Internet. We're creating a decent alternative.
66
77 ![Decent on Nexus](decent-nexus.jpg)
88
99 ![Decent screenshot](decent-screenshot.png)
1010
11-Decent is based on [Scuttlebot](http://scuttlebot.io), but uses an alternative network key: `EVRctE2Iv8GrO/BpQCF34e2FMPsDJot9x0j846LjVtc=`.
11 +Decent is built on top of [Minbase](http://gitmx.com/%25%2BtyUthD1L689osLUj8LNLV4smRKpO7Wu07DB%2BLMd7TQ%3D.sha256) and now uses the main ssb network key.
1212
1313 Decent combines all of the necessary parts of Scuttlebot for a simpler install process
1414
1515 ```
@@ -18,11 +18,11 @@
1818 % npm build
1919 % npm start
2020 ```
2121
22-You'll need an invite from a Decent pub to join the network [email me](mailto:ev@evbogue.com), or contact me via a lite client.
22 +You'll need an invite from a pub to join the network or contact me via a lite client.
2323
24-You can get free local and lite client invites to Decent [here](http://sdash.evbogue.com/invite/):
24 +Try out lite client invites to Decent [here](http://sdash.evbogue.com/invite/).
2525
2626 Once you're on Decent, be sure to obey the first rule:
2727
2828 1. Be decent
serve.jsView
@@ -1,20 +1,20 @@
11 var http = require('http');
22 var serve = require('ecstatic');
3-var client = require('./plugins/ssb-client')
3 +var client = require('ssb-client')
44
55 exports.serve = function() {
66 http.createServer(
77 serve({ root: __dirname + '/build/'})
8- ).listen(3001);
8 + ).listen(3013);
99
1010 opts = {"modern": true}
1111
1212 client(function (err, sbot) {
1313 if(err) throw err
1414 sbot.invite.create(opts, function (err, invite) {
1515 if(err) throw err
1616 var lite = invite
17- console.log('Your lite client is now listening at http://localhost:3001\nHere\'s an invite\nhttp://localhost:3001#' + invite)
17 + console.log('Your lite client is now listening at http://localhost:3013\nHere\'s an invite\nhttp://localhost:3013#' + invite)
1818 })
1919 })
2020 }
yarn.lockView
The diff is too large to show. Use a local git client to view these changes.
Old file size: 100058 bytes
New file size: 116283 bytes
build/index.htmlView
The diff is too large to show. Use a local git client to view these changes.
Old file size: 787306 bytes
New file size: 0 bytes
decent.cssView
@@ -1,0 +1,393 @@
1 +body {
2 + font-family: 'Source Sans Pro', sans-serif;
3 +}
4 +
5 +hr {
6 + border: solid #eee;
7 + clear: both;
8 + border-width: 1px 0 0;
9 + height: 0;
10 + margin-bottom: .9em;
11 +}
12 +
13 +a:link, a:visited, a:active {
14 + color: #08c;
15 + text-decoration: none;
16 +}
17 +
18 +a:hover,
19 +a:focus {
20 + color: #005580;
21 + text-decoration: underline;
22 +}
23 +
24 +.screen {
25 + background: #f9f9f9;
26 +}
27 +
28 +pre {
29 + background: #eee;
30 + border: 1px solid #ddd;
31 + border-radius: 2px;
32 +}
33 +
34 +code {
35 + background: #eee;
36 +}
37 +
38 +.message {
39 + position: relative;
40 + flex-basis: 0;
41 + padding: .5em;
42 + border: 1px solid #ddd;
43 + margin-top: -1px;
44 + margin-bottom: 0;
45 + border-radius: 2px;
46 + background: #fff;
47 + width: 95%;
48 +}
49 +
50 +.message:focus, .message:hover {
51 + background: #f5f5f5;
52 +}
53 +
54 +.message_content--mini div > span {
55 + display: inline-block;
56 +}
57 +
58 +.message_meta {
59 + margin-left: auto;
60 +}
61 +
62 +.message_meta > * {
63 + margin-left: .5ex;
64 +}
65 +
66 +.message_actions {
67 + float: right;
68 +}
69 +
70 +.message img {
71 + max-width: 100%;
72 +}
73 +
74 +.message > .title > .avatar {
75 + margin-left: 0;
76 +}
77 +
78 +.message:first-child {
79 + margin-top: 1em;
80 +}
81 +
82 +.message_content {
83 + padding: .5ex;
84 +}
85 +
86 +.actions > :not(:last-child) {
87 + border-right: 2px solid #eee;
88 + padding-right: 5px;
89 +}
90 +
91 +.emoji {
92 + height: 1em;
93 + width: 1em;
94 + vertical-align: top;
95 +}
96 +
97 +
98 +/* -- suggest box */
99 +
100 +.suggest-box > * {
101 + display: block;
102 + font-weight: normal;
103 +}
104 +
105 +
106 +.suggest-box ul {
107 + padding: 0;
108 + list-style-type: none;
109 + padding-left: 0;
110 + margin: 0;
111 +}
112 +
113 +.suggest-box .selected {
114 + background: #ddd;
115 +}
116 +
117 +.suggest-box {
118 + width: max-content;
119 + background: white;
120 + border: 1px solid #eee;
121 + border-radius: 2px;
122 +}
123 +
124 +/* emoji */
125 +.suggest-box img {
126 + height: 20px;
127 + width: 20px;
128 +}
129 +
130 +/* avatar */
131 +
132 +.avatar--profile {
133 + width: 7em;
134 + height: 7em;
135 + float: left;
136 + border-radius: 5px;
137 + margin-right: .5em;
138 +}
139 +
140 +.avatar--full {
141 + margin: .5em;
142 + width: 100%;
143 + border-radius: 5px;
144 +}
145 +
146 +.avatar--large {
147 + width: 10em;
148 + height: 10em;
149 + border-radius: 5px;
150 +}
151 +
152 +.avatar--thumbnail {
153 + width: 2.5em;
154 + height: 2.5em;
155 + float: left;
156 + margin-right: .5ex;
157 + border-radius: 5px;
158 +}
159 +
160 +.avatar--fullsize {
161 + width: 50%;
162 + border-radius: 5px;
163 +}
164 +
165 +.profile {
166 + padding: .5ex;
167 + overflow: auto;
168 +}
169 +
170 +.profile input {
171 + width: 100%;
172 +}
173 +
174 +.profile__info {
175 + margin-left: .5em;
176 +}
177 +
178 +input, textarea {
179 + border: 1px solid #ddd;
180 + border-radius: 2px;
181 + font-family: sans-serif;
182 + font-size: .9em;
183 + padding: .5em;
184 +}
185 +
186 +textarea {
187 + padding: .5em;
188 +}
189 +
190 +.import {
191 + width: 97%
192 +}
193 +
194 +
195 +/* lightbox - used in message-confirm */
196 +
197 +.lightbox {
198 + overflow: auto;
199 + margin-top: 3em;
200 + margin-bottom: 3em;
201 + width: 512px;
202 + // background: white;
203 + z-index: 5;
204 +}
205 +
206 +/* searchprompt */
207 +
208 +.searchprompt {
209 + margin-top: 1px;
210 + margin-bottom: 1px;
211 + float: left;
212 + padding: .5em;
213 + width: 100%;
214 +}
215 +
216 +.header__search {
217 + position: absolute;
218 + bottom: .5em;
219 + left: .5em;
220 +}
221 +
222 +.logo {
223 + font-size: .8em;
224 + padding: .2em;
225 +}
226 +
227 +/* TextNodeSearcher highlights */
228 +
229 +.highlight {
230 + background: yellow;
231 +}
232 +
233 +/* avatar editor */
234 +
235 +.hypercrop__canvas {
236 + width: 100%;
237 +}
238 +
239 +/* gitssb */
240 +
241 +.git-table-wrapper {
242 + max-height: 12em;
243 + overflow: auto;
244 + word-break: break-all;
245 + margin: 1em 0;
246 +}
247 +
248 +.git-table-wrapper table {
249 + width: 100%;
250 +}
251 +
252 +/* --- network status --- */
253 +
254 +.status {
255 + width: 1em;
256 + height: 1em;
257 + margin: .5em;
258 + background: #08c;
259 +}
260 +
261 +.error {
262 + background: red;
263 +}
264 +
265 +/* tabs */
266 +
267 +.header {
268 + border-bottom: 1px inset #ddd;
269 + width: 100%;
270 + // flex-shrink: 0;
271 + z-index: 1;
272 +}
273 +
274 +.left {
275 + position: fixed;
276 + left: 0;
277 + width: 17%;
278 + max-width: 200px;
279 + height: 100%;
280 +}
281 +
282 +.hypertabs__content {
283 + margin-left: auto;
284 + width: 79%;
285 +}
286 +
287 +/*.header__tabs {
288 + width: 100%;
289 + min-width: 0px;
290 +}*/
291 +
292 +/* --- hypertabs ------- */
293 +
294 +.hypertabs__tabs {
295 + min-width: 0px;
296 + width: 95%;
297 + border-radius 2px;
298 +}
299 +
300 +.hypertabs__tab {
301 + overflow-x: hidden;
302 + min-width: 0px;
303 + border: 1px solid #ddd;
304 + border-radius: 2px;
305 + margin-top: -1px;
306 + background: linear-gradient(#fff, #eee);
307 +}
308 +
309 +.hypertabs__tab:hover {
310 + background: linear-gradient(#eee, #ddd);
311 + color: #fff;
312 +}
313 +
314 +.hypertabs__button {
315 + overflow-x: hidden;
316 + min-width: 0px;
317 + width: 100%;
318 +}
319 +
320 +.hypertabs__tab {
321 + color: black;
322 + // margin-left: -3px;
323 + padding-top: .5em;
324 + padding-left: 1em;
325 + // border-left: 1px solid #ccc;
326 + width: 100%;
327 + height: 1.5em;
328 +}
329 +
330 +.hypertabs__tab > a {
331 + color: #666;
332 + text-decoration: none;
333 + white-space: nowrap;
334 + font-size: .9em;
335 +}
336 +
337 +.hypertabs--selected {
338 + background: linear-gradient(#eee, #ddd);
339 + font-weight: bold;
340 + z-index: 3;
341 +}
342 +
343 +.hypertabs__x {
344 + display: none;
345 + transform: translate(-4px, -2px);
346 +}
347 +
348 +.hypertabs--selected .hypertabs__x {
349 + display: block;
350 +}
351 +
352 +/* progress bar */
353 +
354 +.hyperprogress__bar {
355 + background: darkgrey;
356 +}
357 +.hyperprogress__liquid {
358 + background: lightblue;
359 +}
360 +
361 +button {
362 + font-size: .9em;
363 + background: #eee;
364 + background: #eee linear-gradient(#eee, #ccc);
365 + border: 1px solid #aaa;
366 + border-top: 1px solid #ccc;
367 + border-left: 1px solid #ccc;
368 + border-radius: 5px;
369 + color: #444;
370 + display: inline-block;
371 + text-decoration: none;
372 + font-weight: bold;
373 + cursor: pointer;
374 + margin: .1em;
375 + padding-top: .4em;
376 + padding-left: .6em;
377 + padding-right: .6em;
378 + padding-bottom: .4em;
379 +}
380 +
381 +button:hover, button:focus {
382 + background: #ddd;
383 + background: #ddd linear-gradient(#ddd, #aaa);
384 + border: 1px solid #888;
385 + border-top: 1px solid #aaa;
386 + border-left: 1px solid #aaa;
387 + color: #222;
388 +}
389 +
390 +.edit {
391 + margin-left: .6em;
392 + font-size: .8em;
393 +}
lib/api.mdView
@@ -1,354 +1,0 @@
1-# scuttlebot
2-
3-Secure-scuttlebutt API server
4-
5-
6-
7-## get: async
8-
9-Get a message by its hash-id.
10-
11-```bash
12-get {msgid}
13-```
14-
15-```js
16-get(msgid, cb)
17-```
18-
19-
20-
21-## createFeedStream: source
22-
23-(feed) Fetch messages ordered by their claimed timestamps.
24-
25-```bash
26-feed [--live] [--gt index] [--gte index] [--lt index] [--lte index] [--reverse] [--keys] [--values] [--limit n]
27-```
28-
29-```js
30-createFeedStream({ live:, gt:, gte:, lt:, lte:, reverse:, keys:, values:, limit:, fillCache:, keyEncoding:, valueEncoding: })
31-```
32-
33-Create a stream of the data in the database, ordered by the timestamp claimed by the author.
34-NOTE - the timestamp is not verified, and may be incorrect.
35-The range queries (gt, gte, lt, lte) filter against this claimed timestap.
36-
37- - `live` (boolean, default: `false`): Keep the stream open and emit new messages as they are received.
38- - `gt` (greater than), `gte` (greater than or equal) define the lower bound of the range to be streamed. Only records where the key is greater than (or equal to) this option will be included in the range. When `reverse=true` the order will be reversed, but the records streamed will be the same.
39- - `lt` (less than), `lte` (less than or equal) define the higher bound of the range to be streamed. Only key/value pairs where the key is less than (or equal to) this option will be included in the range. When `reverse=true` the order will be reversed, but the records streamed will be the same.
40- - `reverse` (boolean, default: `false`): a boolean, set true and the stream output will be reversed. Beware that due to the way LevelDB works, a reverse seek will be slower than a forward seek.
41- - `keys` (boolean, default: `true`): whether the `data` event should contain keys. If set to `true` and `values` set to `false` then `data` events will simply be keys, rather than objects with a `key` property.
42- - `values` (boolean, default: `true`): whether the `data` event should contain values. If set to `true` and `keys` set to `false` then `data` events will simply be values, rather than objects with a `value` property.
43- - `limit` (number, default: `-1`): limit the number of results collected by this stream. This number represents a *maximum* number of results and may not be reached if you get to the end of the data first. A value of `-1` means there is no limit. When `reverse=true` the highest keys will be returned instead of the lowest keys.
44- - `fillCache` (boolean, default: `false`): wheather LevelDB's LRU-cache should be filled with data read.
45- - `keyEncoding` / `valueEncoding` (string): the encoding applied to each read piece of data.
46-
47-
48-
49-## createLogStream: source
50-
51-(log) Fetch messages ordered by the time received.
52-
53-```bash
54-log [--live] [--gt index] [--gte index] [--lt index] [--lte index] [--reverse] [--keys] [--values] [--limit n]
55-```
56-
57-```js
58-createLogStream({ live:, gt:, gte:, lt:, lte:, reverse:, keys:, values:, limit:, fillCache:, keyEncoding:, valueEncoding: })
59-```
60-
61-Creates a stream of the messages that have been written to this instance, in the order they arrived.
62-The objects in this stream will be of the form:
63-
64-```
65-{ key: Hash, value: Message, timestamp: timestamp }
66-```
67-
68-`timestamp` is the time which the message was received.
69-It is generated by [monotonic-timestamp](https://github.com/dominictarr/monotonic-timestamp).
70-The range queries (gt, gte, lt, lte) filter against this receive timestap.
71-
72-
73- - `live` (boolean, default: `false`): Keep the stream open and emit new messages as they are received.
74- - `gt` (greater than), `gte` (greater than or equal) define the lower bound of the range to be streamed. Only records where the key is greater than (or equal to) this option will be included in the range. When `reverse=true` the order will be reversed, but the records streamed will be the same.
75- - `lt` (less than), `lte` (less than or equal) define the higher bound of the range to be streamed. Only key/value pairs where the key is less than (or equal to) this option will be included in the range. When `reverse=true` the order will be reversed, but the records streamed will be the same.
76- - `reverse` (boolean, default: `false`): a boolean, set true and the stream output will be reversed. Beware that due to the way LevelDB works, a reverse seek will be slower than a forward seek.
77- - `keys` (boolean, default: `true`): whether the `data` event should contain keys. If set to `true` and `values` set to `false` then `data` events will simply be keys, rather than objects with a `key` property.
78- - `values` (boolean, default: `false`): whether the `data` event should contain values. If set to `true` and `keys` set to `false` then `data` events will simply be values, rather than objects with a `value` property.
79- - `limit` (number, default: `-1`): limit the number of results collected by this stream. This number represents a *maximum* number of results and may not be reached if you get to the end of the data first. A value of `-1` means there is no limit. When `reverse=true` the highest keys will be returned instead of the lowest keys.
80- - `fillCache` (boolean, default: `false`): wheather LevelDB's LRU-cache should be filled with data read.
81- - `keyEncoding` / `valueEncoding` (string): the encoding applied to each read piece of data.
82-
83-
84-
85-## messagesByType: source
86-
87-(logt) Retrieve messages with a given type, ordered by receive-time.
88-
89-
90-```bash
91-logt --type {type} [--live] [--gt index] [--gte index] [--lt index] [--lte index] [--reverse] [--keys] [--values] [--limit n]
92-```
93-
94-```js
95-messagesByType({ type:, live:, gt:, gte:, lt:, lte:, reverse:, keys:, values:, limit:, fillCache:, keyEncoding:, valueEncoding: })
96-```
97-
98-All messages must have a type, so this is a good way to select messages that an application might use.
99-Like in createLogStream, the range queries (gt, gte, lt, lte) filter against the receive timestap.
100-
101- - `type` (string): The type of the messages to emit.
102- - `live` (boolean, default: `false`): Keep the stream open and emit new messages as they are received.
103- - `gt` (greater than), `gte` (greater than or equal) define the lower bound of the range to be streamed. Only records where the key is greater than (or equal to) this option will be included in the range. When `reverse=true` the order will be reversed, but the records streamed will be the same.
104- - `lt` (less than), `lte` (less than or equal) define the higher bound of the range to be streamed. Only key/value pairs where the key is less than (or equal to) this option will be included in the range. When `reverse=true` the order will be reversed, but the records streamed will be the same.
105- - `reverse` (boolean, default: `false`): a boolean, set true and the stream output will be reversed. Beware that due to the way LevelDB works, a reverse seek will be slower than a forward seek.
106- - `keys` (boolean, default: `true`): whether the `data` event should contain keys. If set to `true` and `values` set to `false` then `data` events will simply be keys, rather than objects with a `key` property.
107- - `values` (boolean, default: `true`): whether the `data` event should contain values. If set to `true` and `keys` set to `false` then `data` events will simply be values, rather than objects with a `value` property.
108- - `limit` (number, default: `-1`): limit the number of results collected by this stream. This number represents a *maximum* number of results and may not be reached if you get to the end of the data first. A value of `-1` means there is no limit. When `reverse=true` the highest keys will be returned instead of the lowest keys.
109- - `fillCache` (boolean, default: `false`): wheather LevelDB's LRU-cache should be filled with data read.
110- - `keyEncoding` / `valueEncoding` (string): the encoding applied to each read piece of data.
111-
112-
113-
114-## createHistoryStream: source
115-
116-(hist) Fetch messages from a specific user, ordered by sequence numbers.
117-
118-```bash
119-hist {feedid} [seq] [live]
120-hist --id {feedid} [--seq n] [--live] [--limit n] [--keys] [--values]
121-```
122-
123-```js
124-createHistoryStream(id, seq, live)
125-createHistoryStream({ id:, seq:, live:, limit:, keys:, values: })
126-```
127-
128-`createHistoryStream` and `createUserStream` serve the same purpose.
129-`createHistoryStream` exists as a separate call because it provides fewer range parameters, which makes it safer for RPC between untrusted peers.
130-
131- - `id` (FeedID, required): The id of the feed to fetch.
132- - `seq` (number, default: `0`): If `seq > 0`, then only stream messages with sequence numbers greater than `seq`.
133- - `live` (boolean, default: `false`): Keep the stream open and emit new messages as they are received.
134- - `keys` (boolean, default: `true`): whether the `data` event should contain keys. If set to `true` and `values` set to `false` then `data` events will simply be keys, rather than objects with a `key` property.
135- - `values` (boolean, default: `true`): whether the `data` event should contain values. If set to `true` and `keys` set to `false` then `data` events will simply be values, rather than objects with a `value` property.
136- - `limit` (number, default: `-1`): limit the number of results collected by this stream. This number represents a *maximum* number of results and may not be reached if you get to the end of the data first. A value of `-1` means there is no limit. When `reverse=true` the highest keys will be returned instead of the lowest keys.
137-
138-
139-## createUserStream: source
140-
141-Fetch messages from a specific user, ordered by sequence numbers.
142-
143-```bash
144-createUserStream --id {feedid} [--live] [--gt index] [--gte index] [--lt index] [--lte index] [--reverse] [--keys] [--values] [--limit n]
145-```
146-
147-```js
148-createUserStream({ id:, live:, gt:, gte:, lt:, lte:, reverse:, keys:, values:, limit:, fillCache:, keyEncoding:, valueEncoding: })
149-```
150-
151-`createHistoryStream` and `createUserStream` serve the same purpose.
152-`createHistoryStream` exists as a separate call because it provides fewer range parameters, which makes it safer for RPC between untrusted peers.
153-
154-The range queries (gt, gte, lt, lte) filter against the sequence number.
155-
156- - `id` (FeedID, required): The id of the feed to fetch.
157- - `live` (boolean, default: `false`): Keep the stream open and emit new messages as they are received.
158- - `gt` (greater than), `gte` (greater than or equal) define the lower bound of the range to be streamed. Only records where the key is greater than (or equal to) this option will be included in the range. When `reverse=true` the order will be reversed, but the records streamed will be the same.
159- - `lt` (less than), `lte` (less than or equal) define the higher bound of the range to be streamed. Only key/value pairs where the key is less than (or equal to) this option will be included in the range. When `reverse=true` the order will be reversed, but the records streamed will be the same.
160- - `reverse` (boolean, default: `false`): a boolean, set true and the stream output will be reversed. Beware that due to the way LevelDB works, a reverse seek will be slower than a forward seek.
161- - `keys` (boolean, default: `true`): whether the `data` event should contain keys. If set to `true` and `values` set to `false` then `data` events will simply be keys, rather than objects with a `key` property.
162- - `values` (boolean, default: `true`): whether the `data` event should contain values. If set to `true` and `keys` set to `false` then `data` events will simply be values, rather than objects with a `value` property.
163- - `limit` (number, default: `-1`): limit the number of results collected by this stream. This number represents a *maximum* number of results and may not be reached if you get to the end of the data first. A value of `-1` means there is no limit. When `reverse=true` the highest keys will be returned instead of the lowest keys.
164- - `fillCache` (boolean, default: `false`): wheather LevelDB's LRU-cache should be filled with data read.
165- - `keyEncoding` / `valueEncoding` (string): the encoding applied to each read piece of data.
166-
167-
168-
169-## links: source
170-
171-Get a stream of messages, feeds, or blobs that are linked to/from an id.
172-
173-```bash
174-links [--source id|filter] [--dest id|filter] [--rel value] [--keys] [--values] [--live] [--reverse]
175-```
176-
177-```js
178-links({ source:, dest:, rel:, keys:, values:, live:, reverse: })
179-```
180-
181-The objects in this stream will be of the form:
182-
183-```
184-{ source: ID, rel: String, dest: ID, key: MsgID }
185-```
186-
187- - `source` (string, optional): An id or filter, specifying where the link should originate from. To filter, just use the sigil of the type you want: `@` for feeds, `%` for messages, and `&` for blobs.
188- - `dest` (string, optional): An id or filter, specifying where the link should point to. To filter, just use the sigil of the type you want: `@` for feeds, `%` for messages, and `&` for blobs.
189- - `rel` (string, optional): Filters the links by the relation string.
190- - `live` (boolean, default: `false`): Keep the stream open and emit new messages as they are received.
191- - `reverse` (boolean, default: `false`): a boolean, set true and the stream output will be reversed. Beware that due to the way LevelDB works, a reverse seek will be slower than a forward seek.
192- - `keys` (boolean, default: `true`): whether the `data` event should contain keys. If set to `true` and `values` set to `false` then `data` events will simply be keys, rather than objects with a `key` property.
193- - `values` (boolean, default: `true`): whether the `data` event should contain values. If set to `true` and `keys` set to `false` then `data` events will simply be values, rather than objects with a `value` property.
194-
195-
196-
197-## relatedMessages: async
198-
199-Retrieve the tree of messages related to the given id.
200-
201-```bash
202-relatedMessages --id {msgid} [--rel value] [--count] [--parent]
203-```
204-
205-```js
206-relatedMessages ({ id:, rel:, count:, parent: }, cb)
207-```
208-
209-This is ideal for collecting things like threaded replies.
210-The output is a recursive structure like this:
211-
212-``` js
213-{
214- key: <id>,
215- value: <msg>,
216- related: [
217- <recursive>,...
218- ],
219- //number of messages below this point. (when opts.count = true)
220- count: <int>,
221- //the message this message links to. this will not appear on the bottom level.
222- //(when opts.parent = true)
223- parent: <parent_id>
224-}
225-```
226-
227- - `id` (MsgID): Root message, fetches messages related message to its ID.
228- - `rel` (string, optional): Filters the links by the relation string.
229- - `count` (boolean, default: `false`): Include a `count` of each message's decendant messages.
230- - `parent` (boolean, default: `false`): Include the `parent` id of each message.
231-
232-
233-
234-## add: async
235-
236-Add a well-formed message to the database.
237-
238-```bash
239-cat ./message.json | add
240-add --author {feedid} --sequence {number} --previous {msgid} --timestamp {number} --hash sha256 --signature {sig} --content.type {type} --content.{...}
241-```
242-
243-```js
244-add({ author:, sequence:, previous: timestamp:, hash: 'sha256', signature:, content: { type:, ... } }, cb)
245-```
246-
247- - `author` (FeedID): Public key of the author of the message.
248- - `sequence` (number): Sequence number of the message. (Starts from 1.)
249- - `previous` (MsgID): Hash-id of the previous message in the feed (null for seq=1).
250- - `timestamp` (number): Unix timestamp for the publish time.
251- - `hash` (string): The hash algorithm used in the message, should always be `sha256`.
252- - `signature` (string): A signature computed using the author pubkey and the content of the message (less the `signature` attribute).
253- - `content` (object): The content of the message.
254- - `.type` (string): The object's type.
255-
256-
257-## publish: async
258-
259-Construct a message using sbot's current user, and add it to the DB.
260-
261-```bash
262-cat ./message-content.json | publish
263-publish --type {string} [--other-attributes...]
264-```
265-
266-```js
267-publish({ type:, ... }, cb)
268-```
269-
270-This is the recommended method for publishing new messages, as it handles the tasks of correctly setting the message's timestamp, sequence number, previous-hash, and signature.
271-
272- - `content` (object): The content of the message.
273- - `.type` (string): The object's type.
274-
275-
276-
277-
278-## getAddress: sync
279-
280-Get the address of the server.
281-
282-```bash
283-getAddress
284-```
285-
286-```js
287-getAddress(cb)
288-```
289-
290-
291-
292-## getLatest: async
293-
294-Get the latest message in the database by the given feedid.
295-
296-```bash
297-getLatest {feedid}
298-```
299-
300-```js
301-getLatest(id, cb)
302-```
303-
304-
305-
306-## latest: source
307-
308-Get the seq numbers of the latest messages of all users in the database.
309-
310-```bash
311-latest
312-```
313-
314-```js
315-latest()
316-```
317-
318-
319-
320-## latestSequence: async
321-
322-Get the sequence and local timestamp of the last received message from
323-a given `feedId`.
324-
325-```bash
326-latestSequence {feedId}
327-```
328-
329-```js
330-latest({feedId})
331-```
332-
333-
334-
335-## whoami: sync
336-
337-Get information about the current sbot user.
338-
339-```bash
340-whoami
341-```
342-
343-```js
344-whoami(cb)
345-```
346-
347-Outputs information in the following form:
348-
349-```
350-{ id: FeedID }
351-```
352-
353-
354-
lib/apidocs.jsView
@@ -1,13 +1,0 @@
1-var fs = require('fs')
2-var path = require('path')
3-module.exports = {
4- _: fs.readFileSync(path.join(__dirname, './api.md'), 'utf-8'),
5-// blobs: fs.readFileSync(path.join(__dirname, '../plugins/blobs.md'), 'utf-8'),
6- block: fs.readFileSync(path.join(__dirname, '../plugins/block.md'), 'utf-8'),
7- friends: fs.readFileSync(path.join(__dirname, '../plugins/friends.md'), 'utf-8'),
8- gossip: fs.readFileSync(path.join(__dirname, '../plugins/gossip.md'), 'utf-8'),
9- invite: fs.readFileSync(path.join(__dirname, '../plugins/invite.md'), 'utf-8'),
10- plugins: fs.readFileSync(path.join(__dirname, '../plugins/plugins.md'), 'utf-8'),
11- 'private': fs.readFileSync(path.join(__dirname, '../plugins/private.md'), 'utf-8'),
12- replicate: fs.readFileSync(path.join(__dirname, '../plugins/replicate.md'), 'utf-8')
13-}
lib/cli-cmd-aliases.jsView
@@ -1,10 +1,0 @@
1-module.exports = {
2- feed: 'createFeedStream',
3- history: 'createHistoryStream',
4- hist: 'createHistoryStream',
5- public: 'getPublicKey',
6- pub: 'getPublicKey',
7- log: 'createLogStream',
8- logt: 'messagesByType',
9- conf: 'config'
10-}
lib/index.jsView
@@ -1,126 +1,0 @@
1-var SecretStack = require('secret-stack')
2-var create = require('secure-scuttlebutt/create')
3-var ssbKeys = require('ssb-keys')
4-var path = require('path')
5-var osenv = require('osenv')
6-var mkdirp = require('mkdirp')
7-var rimraf = require('rimraf')
8-var mdm = require('mdmanifest')
9-var cmdAliases = require('./../lib/cli-cmd-aliases')
10-var valid = require('./../lib/validators')
11-var apidocs = require('./../lib/apidocs.js')
12-
13-function isString(s) { return 'string' === typeof s }
14-
15-// create SecretStack definition
16-var manifest = mdm.manifest(apidocs._)
17-manifest.usage = 'sync'
18-var SSB = {
19- manifest: manifest,
20- permissions: {
21- master: {allow: null, deny: null},
22- anonymous: {allow: ['createHistoryStream'], deny: null}
23- },
24- init: function (api, opts) {
25-
26- // .temp: use a /tmp data directory
27- // (useful for testing)
28- if(opts.temp) {
29- var name = isString(opts.temp) ? opts.temp : ''+Date.now()
30- opts.path = path.join(osenv.tmpdir(), name)
31- rimraf.sync(opts.path)
32- }
33-
34- // load/create secure scuttlebutt data directory
35- var dbPath = path.join(opts.path, 'db')
36- mkdirp.sync(dbPath)
37-
38- if(!opts.keys)
39- opts.keys = ssbKeys.generate('ed25519', opts.seed && new Buffer(opts.seed, 'base64'))
40-
41- if(!opts.path)
42- throw new Error('opts.path *must* be provided, or use opts.temp=name to create a test instance')
43-
44- // main interface
45- var ssb = create(path.join(opts.path, 'db'), opts, opts.keys)
46- //treat the main feed as remote, because it's likely handled like that by others.
47- var feed = ssb.createFeed(opts.keys, {remote: true})
48- var _close = api.close
49- var close = function (arg, cb) {
50- if('function' === typeof arg) cb = arg
51- // override to close the SSB database
52- ssb.close(function (err) {
53- if (err) throw err
54- _close()
55- cb && cb() //multiserver doesn't take a callback on close.
56- })
57- }
58- return {
59- id : feed.id,
60- keys : opts.keys,
61-
62- usage : valid.sync(usage, 'string?|boolean?'),
63- close : valid.async(close),
64-
65- publish : valid.async(feed.add, 'string|msgContent'),
66- add : valid.async(ssb.add, 'msg'),
67- get : valid.async(ssb.get, 'msgId'),
68-
69- pre : ssb.pre,
70- post : ssb.post,
71-
72- getPublicKey : ssb.getPublicKey,
73- latest : ssb.latest,
74- getLatest : valid.async(ssb.getLatest, 'feedId'),
75- latestSequence : valid.async(ssb.latestSequence, 'feedId'),
76- createFeed : ssb.createFeed,
77- whoami : function () { return { id: feed.id } },
78- relatedMessages : valid.async(ssb.relatedMessages, 'relatedMessagesOpts'),
79- query : ssb.query,
80- createFeedStream : valid.source(ssb.createFeedStream, 'readStreamOpts?'),
81- createHistoryStream : valid.source(ssb.createHistoryStream, ['createHistoryStreamOpts'], ['feedId', 'number?', 'boolean?']),
82- createLogStream : valid.source(ssb.createLogStream, 'readStreamOpts?'),
83- createUserStream : valid.source(ssb.createUserStream, 'createUserStreamOpts'),
84- links : valid.source(ssb.links, 'linksOpts'),
85- sublevel : ssb.sublevel,
86- messagesByType : valid.source(ssb.messagesByType, 'string|messagesByTypeOpts'),
87- createWriteStream : ssb.createWriteStream,
88-// createLatestLookupStream : ssb.createLatestLookupStream,
89- }
90- }
91-}
92-
93-// live help RPC method
94-function usage (cmd) {
95- var path = (cmd||'').split('.')
96- if ((path[0] && apidocs[path[0]]) || (cmd && apidocs[cmd])) {
97- // return usage for the plugin
98- cmd = path.slice(1).join('.')
99- return mdm.usage(apidocs[path[0]], cmd, { prefix: path[0] })
100- }
101- if (!cmd) {
102- // return usage for all docs
103- return Object.keys(apidocs).map(function (name) {
104- if (name == '_')
105- return mdm.usage(apidocs[name], null, { nameWidth: 20 })
106-
107- var text = mdm.usage(apidocs[name], null, { prefix: name, nameWidth: 20 })
108- return text.slice(text.indexOf('Commands:') + 10) // skip past the toplevel summary, straight to the cmd list
109- }).join('\n\n')
110- }
111- // toplevel cmd usage
112- cmd = cmdAliases[cmd] || cmd
113- return mdm.usage(apidocs._, cmd)
114-}
115-
116-module.exports = SecretStack({
117- //this is just the default app key.
118- //it can be overridden by passing a appKey as option
119- //when creating a Sbot instance.
120- appKey: require('./../lib/ssb-cap')
121-})
122-.use(SSB)
123-
124-
125-
126-
lib/ssb-cap.jsView
@@ -1,13 +1,0 @@
1-//this is the key for accessing the ssb protocol.
2-//this will be updated whenever breaking changes are made.
3-//(see secret-handshake paper for a full explaination)
4-module.exports =
5- new Buffer('EVRctE2Iv8GrO/BpQCF34e2FMPsDJot9x0j846LjVtc=', 'base64')
6-
7-//there is nothing special about this value.
8-//I generated it in the node repl with:
9-//
10-// > crypto.randomBytes(32).toString('base64')
11-//
12-//and copied it here.
13-
lib/util.jsView
@@ -1,69 +1,0 @@
1-var ref = require('ssb-ref')
2-
3-function isObject(o) {
4- return o && 'object' === typeof o
5-}
6-var DEFAULT_PORT = 3333
7-
8-var isArray = Array.isArray
9-
10-var isInteger = Number.isInteger
11-
12-function isString (s) {
13- return 'string' === typeof s
14-}
15-
16-var find = exports.find = function find(ary, test) {
17- for(var i in ary)
18- if(test(ary[i], i, ary)) return ary[i]
19-}
20-
21-var clone = exports.clone = function clone (obj, mapper) {
22- function map(v, k) {
23- return isObject(v) ? clone(v, mapper) : mapper(v, k)
24- }
25- if(isArray(obj))
26- return obj.map(map)
27- else if(isObject(obj)) {
28- var o = {}
29- for(var k in obj)
30- o[k] = map(obj[k], k)
31- return o
32- }
33- else
34- return map(obj)
35-}
36-
37-var mergeKeys = exports.mergeKeys = function (a, b, iter) {
38- var o = {}
39- for(var k in a) {
40- if(!isUndefined(a[k]))
41- o[k] = iter(v[k], b[k], k)
42- }
43- for(var k in b) {
44- if(isUndefined(a[a]))
45- o[k] = iter(undefined, b[k], k)
46- }
47- return o
48-}
49-
50-exports.merge = function (a, b) {
51-
52- //merge a and b objects
53-
54- if(isArray(a) != isArray(b))
55- throw new Error('cannot merge array with non-array')
56- if(isObject(a) != isObject(b))
57- throw new Error('cannot merge object with non-object')
58-
59- a = clone(a)
60-
61- var keys
62-
63- if(isObject(b)) {
64- for(var k in b)
65- a[k] = b
66- }
67-}
68-
69-
lib/validators.jsView
@@ -1,256 +1,0 @@
1-var valid = require('muxrpc-validation')
2-var zerr = require('zerr')
3-var ref = require('ssb-ref')
4-
5-// errors
6-var MissingAttr = zerr('Usage', 'Param % must have a .% of type "%"')
7-var AttrType = zerr('Usage', '.% of param % must be of type "%"')
8-
9-function isFilter (v) {
10- return (v == '@' || v == '%' || v == '&')
11-}
12-
13-module.exports = valid({
14- msgId: function (v) {
15- if (!ref.isMsg(v))
16- return 'type'
17- },
18- feedId: function (v) {
19- if (!ref.isFeed(v))
20- return 'type'
21- },
22- blobId: function (v) {
23- if (!ref.isBlob(v))
24- return 'type'
25- },
26-
27- msgContent: function (v, n) {
28- var err = this.get('object')(v, n)
29- if (err) return err
30- if (!v.type || typeof v.type != 'string')
31- return MissingAttr(n, 'type', 'string')
32- },
33-
34- msg: function (v, n) {
35- var err = this.get('object')(v, n)
36- if (err)
37- return err
38-
39- //allow content to be string. (i.e. for encrypted messages)
40- //or object with type string
41- if(!v.content)
42- return MissingAttr(n, 'content', 'object|string')
43- else if(typeof v.content === 'string')
44- ; //check if it's base64?
45- else if('object' === typeof v.content) {
46- if(!v.content.type || typeof v.content.type != 'string')
47- return MissingAttr(n, 'content.type', 'string')
48- }
49- else
50- return MissingAttr(n, 'content', 'object|string')
51-
52- // .author
53- if (!ref.isFeed(v.author))
54- return MissingAttr(n, 'author', 'feedId')
55-
56- // .sequence
57- if (typeof v.sequence != 'number')
58- return MissingAttr(n, 'sequence', 'number')
59-
60- // .previous
61- if (v.sequence > 1 && !ref.isMsg(v.previous))
62- return MissingAttr(n, 'previous', 'msgId')
63- else if(v.sequence == 1 && v.previous != null)
64- return MissingAttr(n, 'previous', 'null')
65-
66- // .timestamp
67- if (typeof v.timestamp != 'number')
68- return MissingAttr(n, 'timestamp', 'number')
69-
70- // .hash
71- if (v.hash != 'sha256')
72- return zerr('Usage', 'Param % must have .hash set to "sha256"')(n)
73-
74- // .signature
75- if (typeof v.signature != 'string')
76- return MissingAttr(n, 'signature', 'string')
77- },
78-
79- readStreamOpts: function (v, n) {
80- var err = this.get('object')(v, n)
81- if (err)
82- return err
83-
84- // .live
85- if (v.live && typeof v.live != 'boolean' && typeof v.live != 'number')
86- return AttrType(n, 'live', 'boolean')
87-
88- // .reverse
89- if (v.reverse && typeof v.reverse != 'boolean' && typeof v.reverse != 'number')
90- return AttrType(n, 'reverse', 'boolean')
91-
92- // .keys
93- if (v.keys && typeof v.keys != 'boolean' && typeof v.keys != 'number')
94- return AttrType(n, 'keys', 'boolean')
95-
96- // .values
97- if (v.values && typeof v.values != 'boolean' && typeof v.values != 'number')
98- return AttrType(n, 'values', 'boolean')
99-
100- // .limit
101- if (v.limit && typeof v.limit != 'number')
102- return AttrType(n, 'limit', 'number')
103-
104- // .fillCache
105- if (v.fillCache && typeof v.fillCache != 'boolean' && typeof v.fillCache != 'number')
106- return AttrType(n, 'fillCache', 'boolean')
107- },
108-
109- createHistoryStreamOpts: function (v, n) {
110- // .id
111- if (!ref.isFeed(v.id))
112- return MissingAttr(n, 'id', 'feedId')
113-
114- // .seq
115- if (v.seq && typeof v.seq != 'number')
116- return AttrType(n, 'seq', 'number')
117-
118- // .live
119- if (v.live && typeof v.live != 'boolean' && typeof v.live != 'number')
120- return AttrType(n, 'live', 'boolean')
121-
122- // .limit
123- if (v.limit && typeof v.limit != 'number')
124- return AttrType(n, 'limit', 'number')
125-
126- // .keys
127- if (v.keys && typeof v.keys != 'boolean' && typeof v.keys != 'number')
128- return AttrType(n, 'keys', 'boolean')
129-
130- // .values
131- if (v.values && typeof v.values != 'boolean' && typeof v.values != 'number')
132- return AttrType(n, 'values', 'boolean')
133- },
134-
135- createUserStreamOpts: function (v, n) {
136- var err = this.get('readStreamOpts')(v, n)
137- if (err)
138- return err
139-
140- // .id
141- if (!ref.isFeed(v.id))
142- return MissingAttr(n, 'id', 'feedId')
143- },
144-
145- messagesByTypeOpts: function (v, n) {
146- var err = this.get('readStreamOpts')(v, n)
147- if (err)
148- return err
149-
150- // .type
151- if (typeof v.type != 'string')
152- return MissingAttr(n, 'type', 'string')
153- },
154-
155- linksOpts: function (v, n) {
156- var err = this.get('object')(v, n)
157- if (err)
158- return err
159-
160- // .source
161- if (v.source && !ref.isLink(v.source) && !isFilter(v.source))
162- return AttrType(n, 'source', 'id|filter')
163-
164- // .dest
165- if (v.dest && !ref.isLink(v.dest) && !isFilter(v.dest))
166- return AttrType(n, 'dest', 'id|filter')
167-
168- // .rel
169- if (v.rel && typeof v.rel != 'string')
170- return AttrType(n, 'rel', 'string')
171-
172- // .live
173- if (v.live && typeof v.live != 'boolean' && typeof v.live != 'number')
174- return AttrType(n, 'live', 'boolean')
175-
176- // .reverse
177- if (v.reverse && typeof v.reverse != 'boolean' && typeof v.reverse != 'number')
178- return AttrType(n, 'reverse', 'boolean')
179-
180- // .keys
181- if (v.keys && typeof v.keys != 'boolean' && typeof v.keys != 'number')
182- return AttrType(n, 'keys', 'boolean')
183-
184- // .values
185- if (v.values && typeof v.values != 'boolean' && typeof v.values != 'number')
186- return AttrType(n, 'values', 'boolean')
187- },
188-
189- relatedMessagesOpts: function (v, n) {
190- var err = this.get('object')(v, n)
191- if (err)
192- return err
193-
194- // .id
195- if (!ref.isMsg(v.id))
196- return MissingAttr(n, 'id', 'msgId')
197-
198- // .rel
199- if (v.rel && typeof v.rel != 'string')
200- return AttrType(n, 'rel', 'string')
201-
202- // .count
203- if (v.count && typeof v.count != 'boolean' && typeof v.count != 'number')
204- return AttrType(n, 'count', 'boolean')
205-
206- // .parent
207- if (v.parent && typeof v.parent != 'boolean' && typeof v.parent != 'number')
208- return AttrType(n, 'parent', 'boolean')
209- },
210-
211- isBlockedOpts: function (v, n) {
212- var err = this.get('object')(v, n)
213- if (err)
214- return err
215-
216- // .source
217- if (v.source && !ref.isFeed(v.source))
218- return AttrType(n, 'source', 'feedId')
219-
220- // .dest
221- if (v.dest && !ref.isFeed(v.dest))
222- return AttrType(n, 'dest', 'feedId')
223- },
224-
225- createFriendStreamOpts: function (v, n) {
226- var err = this.get('object')(v, n)
227- if (err)
228- return err
229-
230- // .start
231- if (v.start && !ref.isFeed(v.start))
232- return AttrType(n, 'start', 'feedId')
233-
234- // .graph
235- if (v.graph && typeof v.graph != 'string')
236- return AttrType(n, 'graph', 'string')
237-
238- // .dunbar
239- if (v.dunbar && typeof v.dunbar != 'number')
240- return AttrType(n, 'dunbar', 'number')
241-
242- // .hops
243- if (v.hops && typeof v.hops != 'number')
244- return AttrType(n, 'hops', 'number')
245- }
246-})
247-
248-
249-
250-
251-
252-
253-
254-
255-
256-
index.jsView
@@ -1,0 +1,5 @@
1 +require('depject')(
2 + require('minbase/modules'),
3 + require('./modules')
4 +).app[0]()
5 +
modules/app.jsView
@@ -1,0 +1,40 @@
1 +var plugs = require('minbase/plugs')
2 +var h = require('hyperscript')
3 +
4 +module.exports = {
5 + needs: {screen_view: 'first'},
6 + gives: 'app',
7 + create: function (api) {
8 + return function () {
9 + document.head.appendChild(h('style', require('minbase/style.css.json')))
10 + document.head.appendChild(h('style', require('../decent.css.json')))
11 +
12 + window.addEventListener('error', window.onError = function (e) {
13 + document.body.appendChild(h('div.error',
14 + h('h1', e.message),
15 + h('big', h('code', e.filename + ':' + e.lineno)),
16 + h('pre', e.error ? (e.error.stack || e.error.toString()) : e.toString())))
17 + })
18 +
19 + function hash() {
20 + return window.location.hash.substring(1)
21 + }
22 +
23 + var view = api.screen_view(hash() || 'tabs')
24 +
25 + var screen = h('div.screen.column', view)
26 +
27 + window.onhashchange = function (ev) {
28 + var _view = view
29 + view = api.screen_view(hash() || 'tabs')
30 + if(_view) screen.replaceChild(view, _view)
31 + else document.body.appendChild(view)
32 + }
33 +
34 + document.body.appendChild(screen)
35 + return screen
36 + }
37 + }
38 +}
39 +
40 +
modules/index.jsView
@@ -1,0 +1,5 @@
1 +module.exports = {
2 + "app.js": require('./app.js'),
3 + "tabs.js": require('./tabs.js')
4 +}
5 +
modules/tabs.jsView
@@ -1,0 +1,229 @@
1 +var Tabs = require('hypertabs-vertical')
2 +var h = require('hyperscript')
3 +var pull = require('pull-stream')
4 +var u = require('minbase/util')
5 +var keyscroll = require('minbase/keyscroll')
6 +var open = require('open-external')
7 +var ref = require('ssb-ref')
8 +var visualize = require('visualize-buffer')
9 +var id = require('minbase/keys').id
10 +var getAvatar = require('ssb-avatar')
11 +
12 +function ancestor (el) {
13 + if(!el) return
14 + if(el.tagName !== 'A') return ancestor(el.parentElement)
15 + return el
16 +}
17 +
18 +exports.needs = {
19 + emoji_url: 'first',
20 + screen_view: 'first',
21 + search_box: 'first',
22 + blob_url: 'first',
23 + menu: 'first',
24 + sbot_links: 'first'
25 +}
26 +
27 +exports.gives = 'screen_view'
28 +
29 +
30 +exports.create = function (api) {
31 +
32 +
33 + return function (path) {
34 + if(path !== 'tabs')
35 + return
36 +
37 + function setSelected (indexes) {
38 + var ids = indexes.map(function (index) {
39 + return tabs.get(index).id
40 + })
41 + if(search)
42 + if(ids.length > 1)
43 + search.value = 'split('+ids.join(',')+')'
44 + else
45 + search.value = ids[0]
46 + }
47 +
48 + var search
49 + var tabs = Tabs(setSelected)
50 +
51 + search = api.search_box(function (path, change) {
52 +
53 + if(tabs.has(path)) {
54 + tabs.select(path)
55 + return true
56 + }
57 + var el = api.screen_view(path)
58 +
59 + if(el) {
60 + if(!el.title) el.title = path
61 + el.scroll = keyscroll(el.querySelector('.scroller__content'))
62 + tabs.add(el, change)
63 + // localStorage.openTabs = JSON.stringify(tabs.tabs)
64 + return change
65 + }
66 + })
67 +
68 + var img = visualize(new Buffer(id.substring(1), 'base64'), 256)
69 + img.classList.add('avatar--full')
70 + var selected = null, selected_data = null
71 +
72 + getAvatar({links: api.sbot_links}, id, id, function (err, avatar) {
73 + if (err) return console.error(err)
74 + //don't show user has already selected an avatar.
75 + if(selected) return
76 + if(ref.isBlob(avatar.image))
77 + img.src = api.blob_url(avatar.image)
78 + })
79 +
80 + //reposition hypertabs menu to inside a container...
81 + tabs.insertBefore(h('div.header.left',
82 + h('div',
83 + h('a', {href: '#' + id}, img)
84 + ),
85 + h('p.edit',
86 + h('a', {innerHTML: '<a href="#Edit">Edit your profile</a> <a href="#Key"><img src="' + api.emoji_url('key') + '" class="emoji" /></a>'})
87 + ),
88 + h('div.header__tabs', tabs.firstChild), //tabs
89 + h('div.header__search', h('div', search), api.menu())
90 + ), tabs.firstChild)
91 + // tabs.insertBefore(search, tabs.firstChild.nextSibling)
92 +
93 + var saved = []
94 + // try { saved = JSON.parse(localStorage.openTabs) }
95 + // catch (_) { }
96 +
97 + if(!saved || saved.length < 3)
98 + saved = ['Public', 'Direct', 'Mentions']
99 +
100 + saved.forEach(function (path) {
101 + var el = api.screen_view(path)
102 + if(!el) return
103 + el.id = el.id || path
104 + if (!el) return
105 + el.scroll = keyscroll(el.querySelector('.scroller__content'))
106 + if(el) tabs.add(el, false, false)
107 + })
108 +
109 + tabs.select(0)
110 +
111 + //handle link clicks
112 + window.onclick = function (ev) {
113 + var link = ancestor(ev.target)
114 + if(!link) return
115 + var path = link.hash.substring(1)
116 +
117 + ev.preventDefault()
118 + ev.stopPropagation()
119 +
120 + //let the application handle this link
121 + if (link.getAttribute('href') === '#') return
122 +
123 + //open external links.
124 + //this ought to be made into something more runcible
125 + if(open.isExternal(link.href)) return open(link.href)
126 +
127 + if(tabs.has(path))
128 + return tabs.select(path, !ev.ctrlKey, !!ev.shiftKey)
129 +
130 + var el = api.screen_view(path)
131 + if(el) {
132 + el.id = el.id || path
133 + el.scroll = keyscroll(el.querySelector('.scroller__content'))
134 + tabs.add(el, !ev.ctrlKey, !!ev.shiftKey)
135 + // localStorage.openTabs = JSON.stringify(tabs.tabs)
136 + }
137 +
138 + return false
139 + }
140 +
141 + window.addEventListener('keydown', function (ev) {
142 + if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA')
143 + return
144 + switch(ev.keyCode) {
145 +
146 + // scroll through tabs
147 + case 72: // h
148 + return tabs.selectRelative(-1)
149 + case 76: // l
150 + return tabs.selectRelative(1)
151 +
152 + // scroll through messages
153 + case 74: // j
154 + return tabs.get(tabs.selected[0]).scroll(1)
155 + case 75: // k
156 + return tabs.get(tabs.selected[0]).scroll(-1)
157 +
158 + // close a tab
159 + case 88: // x
160 + if (tabs.selected) {
161 + var sel = tabs.selected
162 + var i = sel.reduce(function (a, b) { return Math.min(a, b) })
163 + tabs.remove(sel)
164 + tabs.select(Math.max(i-1, 0))
165 + }
166 + return
167 +
168 + // activate the search field
169 + case 191: // /
170 + if (ev.shiftKey)
171 + search.activate('?', ev)
172 + else
173 + search.activate('/', ev)
174 + return
175 +
176 + // navigate to a feed
177 + case 50: // 2
178 + if (ev.shiftKey)
179 + search.activate('@', ev)
180 + return
181 +
182 + // navigate to a channel
183 + case 51: // 3
184 + if (ev.shiftKey)
185 + search.activate('#', ev)
186 + return
187 +
188 + // navigate to a message
189 + case 53: // 5
190 + if (ev.shiftKey)
191 + search.activate('%', ev)
192 + return
193 + }
194 + })
195 +
196 + // errors tab
197 + var errorsContent = h('div.column.scroller__content')
198 + var errors = h('div.column.scroller', {
199 + id: 'errors',
200 + style: {'overflow':'auto'}
201 + }, h('div.scroller__wrapper',
202 + errorsContent
203 + )
204 + )
205 +
206 + // remove loader error handler
207 + if (window.onError) {
208 + window.removeEventListener('error', window.onError)
209 + delete window.onError
210 + }
211 +
212 + // put errors in a tab
213 + window.addEventListener('error', function (ev) {
214 + var err = ev.error || ev
215 + if(!tabs.has('errors'))
216 + tabs.add(errors, false)
217 + var el = h('div.message',
218 + h('strong', err.message),
219 + h('pre', err.stack))
220 + if (errorsContent.firstChild)
221 + errorsContent.insertBefore(el, errorsContent.firstChild)
222 + else
223 + errorsContent.appendChild(el)
224 + })
225 +
226 + return tabs
227 + }
228 +
229 +}
plugins/block.jsView
@@ -1,69 +1,0 @@
1-var pull = require('pull-stream')
2-var valid = require('../lib/validators')
3-
4-exports.name = 'block'
5-exports.version = '1.0.0'
6-exports.manifest = {
7- isBlocked : 'sync',
8-}
9-
10-exports.init = function (sbot) {
11-
12- //TODO: move other blocking code in here,
13- // i think we'll need a hook system for this.
14-
15- //if a currently connected peer is blocked, disconnect them immediately.
16- pull(
17- sbot.friends.createFriendStream({graph: 'flag', live: true}),
18- pull.drain(function (blocked) {
19- if(sbot.peers[blocked]) {
20- sbot.peers[blocked].forEach(function (b) {
21- b.close(true, function () {})
22- })
23- }
24- })
25- )
26-
27- function isBlocked (_opts) {
28- var opts
29-
30- if('string' === typeof _opts)
31- opts = {
32- source: sbot.id, dest: _opts, graph:'flag'
33- }
34- else opts = {
35- source: _opts.source, dest: _opts.dest, graph: 'flag'
36- }
37- return sbot.friends.get(opts)
38- }
39-
40- sbot.createHistoryStream.hook(function (fn, args) {
41- var opts = args[0], id = this.id
42- if(opts.id !== this.id && isBlocked({source: opts.id, dest: this.id}))
43- return fn({id: null, sequence: 0})
44- else
45- return pull(
46- fn.apply(this, args),
47- //break off this feed if they suddenly block
48- //the recipient.
49- pull.take(function (msg) {
50- //handle when createHistoryStream is called with keys: true
51- if(!msg.content && msg.value.content)
52- msg = msg.value
53- if(msg.content.type !== 'contact') return true
54- return !(
55- msg.content.flagged &&
56- msg.content.contact === id
57- )
58- })
59- )
60- })
61-
62- sbot.auth.hook(function (fn, args) {
63- if(isBlocked(args[0])) args[1](new Error('client is blocked'))
64- else return fn.apply(this, args)
65- })
66-
67- return {isBlocked: valid.sync(isBlocked, 'feedId|isBlockedOpts') }
68-
69-}
plugins/block.mdView
@@ -1,20 +1,0 @@
1-# scuttlebot block plugin
2-
3-Disallow connections with people flagged by the local user, and avoid sending a feed to the users they've flag.
4-
5-
6-## isBlocked: sync
7-
8-Is the target user blocked?
9-
10-```bash
11-isBlocked {dest}
12-isBlocked --source {feedid} --dest {feedid}
13-```
14-
15-```js
16-isBlocked(dest, cb)
17-isBlocked({ source:, dest: }, cb)
18-```
19-
20-If `source` is not specified, defaults to the local user.
plugins/friends.jsView
@@ -1,178 +1,0 @@
1-var Graphmitter = require('graphmitter')
2-var pull = require('pull-stream')
3-var mlib = require('ssb-msgs')
4-var memview = require('level-memview')
5-var pushable = require('pull-pushable')
6-var mdm = require('mdmanifest')
7-var valid = require('../lib/validators')
8-var apidoc = require('../lib/apidocs').friends
9-
10-// friends plugin
11-// methods to analyze the social graph
12-// maintains a 'follow' and 'flag' graph
13-
14-function isFunction (f) {
15- return 'function' === typeof f
16-}
17-
18-function isString (s) {
19- return 'string' === typeof s
20-}
21-
22-function isFriend (friends, a, b) {
23- return friends[a] && friends[b] && friends[a][b] && friends[b][a]
24-}
25-
26-exports.name = 'friends'
27-exports.version = '1.0.0'
28-exports.manifest = mdm.manifest(apidoc)
29-
30-exports.init = function (sbot, config) {
31-
32- var graphs = {
33- follow: new Graphmitter(),
34- flag: new Graphmitter()
35- }
36-
37- // view processor
38- var syncCbs = []
39- function awaitSync (cb) {
40- if (syncCbs) syncCbs.push(cb)
41- else cb()
42- }
43-
44- // read/watch the log for changes to the social graph
45- pull(sbot.createLogStream({ live: true }), pull.drain(function (msg) {
46-
47- if (msg.sync) {
48- syncCbs.forEach(function (cb) { cb() })
49- syncCbs = null
50-
51- if (sbot.gossip) {
52- // prioritize friends
53- var friends = graphs['follow'].toJSON()
54- sbot.gossip.peers().forEach(function(peer) {
55- if (isFriend(friends, sbot.id, peer.key)) {
56- sbot.gossip.add(peer, 'friends')
57- }
58- })
59- }
60-
61- return
62- }
63-
64- var c = msg.value.content
65- if (c.type == 'contact') {
66- mlib.asLinks(c.contact, 'feed').forEach(function (link) {
67- if ('following' in c) {
68- if (c.following)
69- graphs.follow.edge(msg.value.author, link.link, true)
70- else
71- graphs.follow.del(msg.value.author, link.link)
72-
73- }
74- if ('flagged' in c) {
75- if (c.flagged)
76- graphs.flag.edge(msg.value.author, link.link, c.flagged)
77- else
78- graphs.flag.del(msg.value.author, link.link)
79- }
80- })
81- }
82- }))
83-
84- return {
85-
86- get: valid.sync(function (opts) {
87- var g = graphs[opts.graph || 'follow']
88- if(!g) throw new Error('opts.graph must be provided')
89- return g.get(opts.source, opts.dest)
90- }, 'object?'),
91-
92- all: valid.async(function (graph, cb) {
93- if (typeof graph == 'function') {
94- cb = graph
95- graph = null
96- }
97- if (!graph)
98- graph = 'follow'
99- awaitSync(function () {
100- cb(null, graphs[graph] ? graphs[graph].toJSON() : null)
101- })
102- }, 'string?'),
103-
104- path: valid.sync(function (opts) {
105- if(isString(opts))
106- opts = {source: sbot.id, dest: opts}
107- return graphs.follow.path(opts)
108-
109- }, 'string|object'),
110-
111- createFriendStream: valid.source(function (opts) {
112- opts = opts || {}
113- var live = opts.live === true
114- var meta = opts.meta === true
115- var start = opts.start || sbot.id
116- var graph = graphs[opts.graph || 'follow']
117- if(!graph)
118- return pull.error(new Error('unknown graph:' + opts.graph))
119- var cancel, ps = pushable(function () {
120- cancel && cancel()
121- })
122-
123- function push (to, hops) {
124- return ps.push(meta ? {id: to, hops: hops} : to)
125- }
126-
127- //by default, also emit your own key.
128- if(opts.self !== false)
129- push(start, 0)
130-
131- var conf = config.friends || {}
132- cancel = graph.traverse({
133- start: start,
134- hops: opts.hops || conf.hops || 3,
135- max: opts.dunbar || conf.dunbar || 150,
136- each: function (_, to, hops) {
137- if(to !== start) push(to, hops)
138- }
139- })
140-
141- if(!live) { cancel(); ps.end() }
142-
143- return ps
144- }, 'createFriendStreamOpts?'),
145-
146- hops: valid.async(function (start, graph, opts, cb) {
147- if (typeof opts == 'function') { // (start|opts, graph, cb)
148- cb = opts
149- opts = null
150- } else if (typeof graph == 'function') { // (start|opts, cb)
151- cb = graph
152- opts = graph = null
153- }
154- opts = opts || {}
155- if(isString(start)) { // (start, ...)
156- // first arg is id string
157- opts.start = start
158- } else if (start && typeof start == 'object') { // (opts, ...)
159- // first arg is opts
160- for (var k in start)
161- opts[k] = start[k]
162- }
163-
164- var conf = config.friends || {}
165- opts.start = opts.start || sbot.id
166- opts.dunbar = opts.dunbar || conf.dunbar || 150
167- opts.hops = opts.hops || conf.hops || 3
168-
169- var g = graphs[graph || 'follow']
170- if (!g)
171- return cb(new Error('Invalid graph type: '+graph))
172-
173- awaitSync(function () {
174- cb(null, g.traverse(opts))
175- })
176- }, ['feedId', 'string?', 'object?'], ['createFriendStreamOpts'])
177- }
178-}
plugins/friends.mdView
@@ -1,75 +1,0 @@
1-# scuttlebot friends plugin
2-
3-Query the follow and flag graphs.
4-
5-
6-## all: async
7-
8-Fetch the graph structure.
9-
10-```bash
11-all [graph]
12-```
13-
14-```js
15-all(graph, cb)
16-```
17-
18- - `graph` (string, default: `follow`): Which graph to view. May be `follow` or `flag`.
19-
20-
21-
22-## hops: async
23-
24-List the degrees-of-connection of all known feeds from the given feed.
25-
26-```bash
27-hops [start] [graph] [--dunbar number] [--hops number]
28-```
29-
30-```js
31-hops(start, graph, { dunbar:, hops: }, cb)
32-```
33-
34- - `start` (FeedID, default: local user): Which feed to start from.
35- - `graph` (string, default: `follow`): Which graph to view. May be `follow` or `flag`.
36- - `dunbar` (number, default: 150): Limit on how many feeds to include in the list.
37- - `hops` (number, default: 3): Limit on how many hops out the feed needs to be, to be included.
38-
39-
40-
41-## createFriendStream: source
42-
43-Live-stream the ids of feeds which meet the given hops query. If `meta`
44-option is set, then will return steam of `{id, hops}`
45-
46-```bash
47-createFriendStream [--start feedid] [--graph follow|flag] [--dunbar number] [--hops number] [--meta]
48-```
49-
50-```js
51-createFriendStream({ start:, graph:, dunbar:, hops: , meta: }, cb)
52-```
53-
54- - `start` (FeedID, default: local user): Which feed to start from.
55- - `graph` (string, default: `follow`): Which graph to view. May be `follow` or `flag`.
56- - `dunbar` (number, default: 150): Limit on how many feeds to include in the list.
57- - `hops` (number, default: 3): Limit on how many hops out the feed needs to be, to be included.
58-
59-
60-
61-## get: sync
62-
63-Get the edge between two different feeds.
64-
65-```bash
66-get --source {feedid} --dest {feedid} [--graph follow|flag]
67-```
68-
69-```js
70-get({ source:, dest:, graph: }, cb)
71-```
72-
73- - `source` (FeedID): Edge source.
74- - `dest` (FeedID): Edge destination.
75- - `graph` (string, default: `follow`): Which graph to query. May be `follow` or `flag`.
plugins/gossip.mdView
@@ -1,89 +1,0 @@
1-# scuttlebot gossip plugin
2-
3-Schedule connections randomly with a peerlist constructed from config, multicast UDP announcements, feed announcements, and API-calls.
4-
5-
6-
7-## peers: sync
8-
9-Get the current peerlist.
10-
11-```bash
12-peers
13-```
14-
15-```js
16-peers(cb)
17-```
18-
19-
20-
21-## add: sync
22-
23-Add an address to the peer table.
24-
25-```bash
26-add {addr}
27-add --host {string} --port {number} --key {feedid}
28-```
29-
30-```js
31-add(addr, cb)
32-add({ host:, port:, key: }, cb)
33-```
34-
35- - `addr` (address string): An address string, of the following format: `hostname:port:feedid`.
36- - `host` (host string): IP address or hostname.
37- - `port` (port number)
38- - `key` (feedid)
39-
40-## ping: duplex
41-
42-used internally by the gossip plugin to measure latency and clock skew
43-
44-## connect: async
45-
46-Add an address to the peer table, and connect immediately.
47-
48-```bash
49-connect {addr}
50-connect --host {string} --port {number} --key {feedid}
51-```
52-
53-```js
54-connect(addr, cb)
55-connect({ host:, port:, key: }, cb)
56-```
57-
58- - `addr` (address string): An address string, of the following format: `hostname:port:feedid`.
59- - `host` (host string): IP address or hostname.
60- - `port` (port number)
61- - `key` (feedid)
62-
63-
64-## changes: source
65-
66-Listen for gossip events.
67-
68-```bash
69-changes
70-```
71-
72-```js
73-changes()
74-```
75-
76-Events come in the following forms:
77-
78-```
79-{ type: 'discover', peer:, source: }
80-{ type: 'connect', peer: }
81-{ type: 'connect-failure', peer: }
82-{ type: 'disconnect', peer: }
83-```
84-
85-## reconnect: sync
86-
87-Tell sbot to reinitiate gossip connections now.
88-
89-
plugins/gossip/index.jsView
@@ -1,230 +1,0 @@
1-'use strict'
2-var pull = require('pull-stream')
3-var Notify = require('pull-notify')
4-var ref = require('ssb-ref')
5-var mdm = require('mdmanifest')
6-var valid = require('../../lib/validators')
7-var apidoc = require('../../lib/apidocs').gossip
8-var u = require('../../lib/util')
9-var ping = require('pull-ping')
10-var stats = require('statistics')
11-var Schedule = require('./schedule')
12-var Init = require('./init')
13-
14-function isFunction (f) {
15- return 'function' === typeof f
16-}
17-
18-function stringify(peer) {
19- return [peer.host, peer.port, peer.key].join(':')
20-}
21-
22-/*
23-Peers : [{
24- key: id,
25- host: ip,
26- port: int,
27- //to be backwards compatible with patchwork...
28- announcers: {length: int}
29- source: 'pub'|'manual'|'local'
30-}]
31-*/
32-
33-module.exports = {
34- name: 'gossip',
35- version: '1.0.0',
36- manifest: mdm.manifest(apidoc),
37- permissions: {
38- anonymous: {allow: ['ping']}
39- },
40- init: function (server, config) {
41- var notify = Notify()
42- var conf = config.gossip || {}
43- var home = ref.parseAddress(server.getAddress())
44-
45- //Known Peers
46- var peers = []
47-
48- function getPeer(id) {
49- return u.find(peers, function (e) {
50- return e && e.key === id
51- })
52- }
53-
54- var timer_ping = 5*6e4
55-
56- var gossip = {
57- wakeup: 0,
58- peers: function () {
59- return peers
60- },
61- get: function (addr) {
62- addr = ref.parseAddress(addr)
63- return u.find(peers, function (a) {
64- return (
65- addr.port === a.port
66- && addr.host === a.host
67- && addr.key === a.key
68- )
69- })
70- },
71- connect: valid.async(function (addr, cb) {
72- addr = ref.parseAddress(addr)
73- if (!addr || typeof addr != 'object')
74- return cb(new Error('first param must be an address'))
75-
76- if(!addr.key) return cb(new Error('address must have ed25519 key'))
77- // add peer to the table, incase it isn't already.
78- gossip.add(addr, 'manual')
79- var p = gossip.get(addr)
80- if(!p) return cb()
81-
82- p.stateChange = Date.now()
83- p.state = 'connecting'
84- server.connect(p, function (err, rpc) {
85- if (err) {
86- p.state = undefined
87- p.failure = (p.failure || 0) + 1
88- p.stateChange = Date.now()
89- notify({ type: 'connect-failure', peer: p })
90- server.emit('log:info', ['SBOT', p.host+':'+p.port+p.key, 'connection failed', err.message || err])
91- p.duration = stats(p.duration, 0)
92- return (cb && cb(err))
93- }
94- else {
95- p.state = 'connected'
96- p.failure = 0
97- }
98- cb && cb(null, rpc)
99- })
100-
101- }, 'string|object'),
102-
103- disconnect: valid.async(function (addr, cb) {
104- var peer = this.get(addr)
105-
106- peer.state = 'disconnecting'
107- peer.stateChange = Date.now()
108- if(!peer || !peer.disconnect) cb && cb()
109- else peer.disconnect(null, function (err) {
110- peer.stateChange = Date.now()
111- })
112-
113- }, 'string|object'),
114-
115- changes: function () {
116- return notify.listen()
117- },
118- //add an address to the peer table.
119- add: valid.sync(function (addr, source) {
120- addr = ref.parseAddress(addr)
121- if(!ref.isAddress(addr))
122- throw new Error('not a valid address:' + JSON.stringify(addr))
123- // check that this is a valid address, and not pointing at self.
124-
125- if(addr.key === home.key) return
126- if(addr.host === home.host && addr.port === home.port) return
127-
128- var f = gossip.get(addr)
129-
130- if(!f) {
131- // new peer
132- addr.source = source
133- addr.announcers = 1
134- addr.duration = addr.duration || null
135- peers.push(addr)
136- notify({ type: 'discover', peer: addr, source: source || 'manual' })
137- return addr
138- }
139- //don't count local over and over
140- else if(f.source != 'local')
141- f.announcers ++
142-
143- return f
144- }, 'string|object', 'string?'),
145- ping: function (opts) {
146- var timeout = config.timers && config.timers.ping || 5*60e3
147- //between 10 seconds and 30 minutes, default 5 min
148- timeout = Math.max(10e3, Math.min(timeout, 30*60e3))
149- return ping({timeout: timeout})
150- },
151- reconnect: function () {
152- for(var id in server.peers)
153- if(id !== server.id) //don't disconnect local client
154- server.peers[id].forEach(function (peer) {
155- peer.close(true)
156- })
157- return gossip.wakeup = Date.now()
158- }
159- }
160-
161- Schedule (gossip, config, server)
162- Init (gossip, config, server)
163- //get current state
164-
165- server.on('rpc:connect', function (rpc, isClient) {
166- var peer = getPeer(rpc.id)
167- //don't track clients that connect, but arn't considered peers.
168- //maybe we should though?
169- if(!peer) return
170- console.log('Connected', stringify(peer))
171- //means that we have created this connection, not received it.
172- peer.client = !!isClient
173- peer.state = 'connected'
174- peer.stateChange = Date.now()
175- peer.disconnect = function (err, cb) {
176- if(isFunction(err)) cb = err, err = null
177- rpc.close(err, cb)
178- }
179-
180- if(isClient) {
181- //default ping is 5 minutes...
182- var pp = ping({serve: true, timeout: timer_ping}, function (_) {})
183- peer.ping = {rtt: pp.rtt, skew: pp.skew}
184- pull(
185- pp,
186- rpc.gossip.ping({timeout: timer_ping}, function (err) {
187- if(err.name === 'TypeError') peer.ping.fail = true
188- }),
189- pp
190- )
191- }
192-
193- rpc.on('closed', function () {
194- console.log('Disconnected', stringify(peer))
195- //track whether we have successfully connected.
196- //or how many failures there have been.
197- var since = peer.stateChange
198- peer.stateChange = Date.now()
199- // if(peer.state === 'connected') //may be "disconnecting"
200- // peer.duration.value(peer.stateChange - since)
201- peer.duration = stats(peer.duration, peer.stateChange - since)
202- peer.state = undefined
203- notify({ type: 'disconnect', peer: peer })
204- server.emit('log:info', ['SBOT', rpc.id, 'disconnect'])
205- })
206-
207- notify({ type: 'connect', peer: peer })
208- })
209-
210- return gossip
211- }
212-}
213-
214-
215-
216-
217-
218-
219-
220-
221-
222-
223-
224-
225-
226-
227-
228-
229-
230-
plugins/gossip/init.jsView
@@ -1,38 +1,0 @@
1-//var isArray = Array.isArray
2-var pull = require('pull-stream')
3-var ref = require('ssb-ref')
4-
5-module.exports = function (gossip, config, server) {
6-
7- // populate peertable with configured seeds (mainly used in testing)
8- //var seeds = config.seeds
9- //
10- //;(isArray(seeds) ? seeds : [seeds]).filter(Boolean)
11- //.forEach(function (addr) { gossip.add(addr, 'seed') })
12-
13- // populate peertable with pub announcements on the feed
14- pull(
15- server.messagesByType({
16- type: 'pub', live: true, keys: false
17- }),
18- //pull.drain(function (msg) {
19- // if(!msg.content.address) return
20- // gossip.add(msg.content.address, 'pub')
21- //})
22- pull.drain(function (msg) {
23- if(msg.sync) return
24- if(!msg.content.address) return
25- if(ref.isAddress(msg.content.address))
26- gossip.add(msg.content.address, 'pub')
27- })
28- )
29-
30- // populate peertable with announcements on the LAN multicast
31- server.on('local', function (_peer) {
32- gossip.add(_peer, 'local')
33- })
34-
35-}
36-
37-
38-
plugins/gossip/schedule.jsView
@@ -1,175 +1,0 @@
1-var ip = require('ip')
2-var onWakeup = require('on-wakeup')
3-var onNetwork = require('on-change-network')
4-var hasNetwork = require('has-network')
5-var pull = require('pull-stream')
6-
7-function stringify(peer) {
8- return [peer.host, peer.port, peer.key].join(':')
9-}
10-
11-function not (fn) {
12- return function (e) { return !fn(e) }
13-}
14-
15-function and () {
16- var args = [].slice.call(arguments)
17- return function (value) {
18- return args.every(function (fn) { return fn.call(null, value) })
19- }
20-}
21-
22-function delay (failures, factor, max) {
23- return Math.min(Math.pow(2, failures)*factor, max || Infinity)
24-}
25-
26-function maxStateChange (M, e) {
27- return Math.max(M, e.stateChange || 0)
28-}
29-
30-function peerNext(peer, opts) {
31- return (peer.stateChange|0) + delay(peer.failure|0, opts.factor, opts.max)
32-}
33-
34-function isOffline (e) {
35- if(ip.isLoopback(e.host)) return false
36- return !hasNetwork()
37-}
38-
39-var isOnline = not(isOffline)
40-
41-function isLocal (e) {
42- //return ip.isPrivate(e.host) && e.type === 'local'
43- return ip.isPrivate(e.host) && e.type === 'local'
44-}
45-
46-function isUnattempted (e) {
47- return !e.stateChange
48-}
49-
50-function isInactive (e) {
51- //return e.stateChange && e.duration.mean == 0
52- //return e.stateChange && (!e.duration || e.duration.mean == 0)
53- return e.state !== 'connecting' && e.stateChange && (!e.duration || e.duration.mean == 0)
54-}
55-
56-function isLongterm (e) {
57- //return e.ping && e.ping.rtt.mean > 0
58- return e.ping && e.ping.rtt && e.ping.rtt.mean > 0
59-}
60-
61-function isLegacy (peer) {
62- // return peer.duration.mean > 0 && !exports.isLongterm(peer)
63- return peer.duration && (peer.duration && peer.duration.mean > 0) && !exports.isLongterm(peer)
64-}
65-
66-function isConnect (e) {
67- return 'connected' === e.state || 'connecting' === e.state
68-}
69-
70-function earliest(peers, n) {
71- return peers.sort(function (a, b) {
72- return a.stateChange - b.stateChange
73- }).slice(0, Math.max(n, 0))
74-}
75-
76-function select(peers, ts, filter, opts) {
77- if(opts.disable) return []
78- //opts: { quota, groupMin, min, factor, max }
79- var type = peers.filter(filter)
80- var unconnect = type.filter(not(isConnect))
81- var count = Math.max(opts.quota - type.filter(isConnect).length, 0)
82- var min = unconnect.reduce(maxStateChange, 0) + opts.groupMin
83- if(ts < min) return []
84-
85- return earliest(unconnect.filter(function (peer) {
86- return peerNext(peer, opts) < ts
87- }), count)
88-}
89-
90-var schedule = exports = module.exports =
91-function (gossip, config, server) {
92-
93- var min = 60e3, hour = 60*60e3
94-
95- onWakeup(gossip.reconnect)
96- onNetwork(gossip.reconnect)
97-
98- function conf(name, def) {
99- if(!config.gossip) return def
100- var value = config.gossip[name]
101- return (value === undefined || value === '') ? def : value
102- }
103-
104- function connect (peers, ts, name, filter, opts) {
105- var connected = peers.filter(isConnect).filter(filter)
106- .filter(function (peer) {
107- return peer.stateChange + 10e3 < ts
108- })
109-
110- if(connected.length > opts.quota) {
111- return earliest(connected, connected.length - opts.quota)
112- .forEach(function (peer) {
113- console.log('Disconnect', name, stringify(peer))
114- gossip.disconnect(peer)
115- })
116- }
117-
118- select(peers, ts, and(filter, isOnline), opts)
119- .forEach(function (peer) {
120- console.log('-Connect', name, stringify(peer))
121- gossip.connect(peer)
122- })
123- }
124- function connections () {
125- var ts = Date.now()
126- var peers = gossip.peers()
127-
128- //quota, groupMin, min, factor, max
129-
130- connect(peers, ts, 'attempt', exports.isUnattempted, {
131- min: 0, quota: 1, factor: 0, max: 0, groupMin: 0,
132- disable: !conf('global', true)
133- })
134-
135- connect(peers, ts, 'retry', exports.isInactive, {
136- min: 0, quota: 3, factor: 5*60e3, max: 3*60*60e3, groupMin: 5*50e3
137- })
138-
139- connect(peers, ts, 'legacy', exports.isLegacy, {
140- quota: 3, factor: 5*min, max: 3*hour, groupMin: 5*min,
141- disable: !conf('global', true)
142- })
143-
144- connect(peers, ts, 'longterm', exports.isLongterm, {
145- quota: 3, factor: 10e3, max: 10*min, groupMin: 5e3,
146- disable: !conf('global', true)
147- })
148-
149- connect(peers, ts, 'local', exports.isLocal, {
150- quota: 3, factor: 2e3, max: 10*min, groupMin: 1e3,
151- disable: !conf('local', true)
152- })
153- }
154-
155- pull(
156- gossip.changes(),
157- pull.drain(function (ev) {
158- if(ev.type == 'disconnect')
159- connections()
160- })
161- )
162-
163- var int = setInterval(connections, 2e3)
164- //if(int.unref) int.unref()
165-
166- connections()
167-}
168-
169-exports.isUnattempted = isUnattempted
170-exports.isInactive = isInactive
171-exports.isLongterm = isLongterm
172-exports.isLegacy = isLegacy
173-exports.isLocal = isLocal
174-exports.isConnectedOrConnecting = isConnect
175-exports.select = select
plugins/invite.jsView
@@ -1,217 +1,0 @@
1-var crypto = require('crypto')
2-var ssbKeys = require('ssb-keys')
3-var toAddress = require('../lib/util').toAddress
4-var cont = require('cont')
5-var explain = require('explain-error')
6-var ip = require('ip')
7-var mdm = require('mdmanifest')
8-var valid = require('../lib/validators')
9-var apidoc = require('../lib/apidocs').invite
10-var ref = require('ssb-ref')
11-
12-var ssbClient = require('./ssb-client')
13-
14-// invite plugin
15-// adds methods for producing invite-codes,
16-// which peers can use to command your server to follow them.
17-
18-function isFunction (f) {
19- return 'function' === typeof f
20-}
21-
22-function isString (s) {
23- return 'string' === typeof s
24-}
25-
26-function isObject(o) {
27- return o && 'object' === typeof o
28-}
29-
30-module.exports = {
31- name: 'invite',
32- version: '1.0.0',
33- manifest: mdm.manifest(apidoc),
34- permissions: {
35- master: {allow: ['create']},
36- //temp: {allow: ['use']}
37- },
38- init: function (server, config) {
39- var codes = {}
40- var codesDB = server.sublevel('codes')
41-
42- var createClient = this.createClient
43-
44- //add an auth hook.
45- server.auth.hook(function (fn, args) {
46- var pubkey = args[0], cb = args[1]
47-
48- // run normal authentication
49- fn(pubkey, function (err, auth) {
50- if(err || auth) return cb(err, auth)
51-
52- // if no rights were already defined for this pubkey
53- // check if the pubkey is one of our invite codes
54- codesDB.get(pubkey, function (_, code) {
55- //disallow if this invite has already been used.
56- if(code && (code.used >= code.total)) cb()
57- else cb(null, code && code.permissions)
58- })
59- })
60- })
61-
62- return {
63- create: valid.async(function (n, cb) {
64- var modern = false
65- if(isObject(n) && n.modern) {
66- n = 1
67- modern = true
68- }
69- var addr = server.getAddress()
70- var host = ref.parseAddress(addr).host
71- if(!config.allowPrivate && (ip.isPrivate(host) || 'localhost' === host))
72- return cb(new Error('Server has no public ip address, '
73- + 'cannot create useable invitation'))
74-
75- //this stuff is SECURITY CRITICAL
76- //so it should be moved into the main app.
77- //there should be something that restricts what
78- //permissions the plugin can create also:
79- //it should be able to diminish it's own permissions.
80-
81- // generate a key-seed and its key
82- var seed = crypto.randomBytes(32)
83- var keyCap = ssbKeys.generate('ed25519', seed)
84-
85- // store metadata under the generated pubkey
86- var owner = server.id
87- codesDB.put(keyCap.id, {
88- id: keyCap.id,
89- total: +n,
90- used: 0,
91- permissions: {allow: ['invite.use', 'getAddress'], deny: null}
92- }, function (err) {
93- // emit the invite code: our server address, plus the key-seed
94- if(err) cb(err)
95- else if(modern && server.ws && server.ws.getAddress) {
96- cb(null, server.ws.getAddress()+':'+seed.toString('base64'))
97- }
98- else {
99- addr = ref.parseAddress(addr)
100- cb(null, [addr.host, addr.port, addr.key].join(':') + '~' + seed.toString('base64'))
101- }
102- })
103- }, 'number|object'),
104- use: valid.async(function (req, cb) {
105- var rpc = this
106-
107- // fetch the code
108- codesDB.get(rpc.id, function(err, invite) {
109- if(err) return cb(err)
110-
111- // check if we're already following them
112- server.friends.all('follow', function(err, follows) {
113- if (follows && follows[server.id] && follows[server.id][req.feed])
114- return cb(new Error('already following'))
115-
116- // although we already know the current feed
117- // it's included so that request cannot be replayed.
118- if(!req.feed)
119- return cb(new Error('feed to follow is missing'))
120-
121- if(invite.used >= invite.total)
122- return cb(new Error('invite has expired'))
123-
124- invite.used ++
125-
126- //never allow this to be used again
127- if(invite.used >= invite.total) {
128- invite.permissions = {allow: [], deny: null}
129- }
130- //TODO
131- //okay so there is a small race condition here
132- //if people use a code massively in parallel
133- //then it may not be counted correctly...
134- //this is not a big enough deal to fix though.
135- //-dominic
136-
137- // update code metadata
138- codesDB.put(rpc.id, invite, function (err) {
139- server.emit('log:info', ['invite', rpc.id, 'use', req])
140-
141- // follow the user
142- server.publish({
143- type: 'contact',
144- contact: req.feed,
145- following: true,
146- pub: true
147- }, cb)
148- })
149- })
150- })
151- }, 'object'),
152- accept: valid.async(function (invite, cb) {
153- // remove surrounding quotes, if found
154- if (invite.charAt(0) === '"' && invite.charAt(invite.length - 1) === '"')
155- invite = invite.slice(1, -1)
156- var opts
157- // connect to the address in the invite code
158- // using a keypair generated from the key-seed in the invite code
159- var modern = false
160- if(ref.isInvite(invite)) { //legacy ivite
161- if(ref.isLegacyInvite(invite)) {
162- var parts = invite.split('~')
163- opts = ref.parseAddress(parts[0])//.split(':')
164- //convert legacy code to multiserver invite code.
165- invite = 'net:'+opts.host+':'+opts.port+'~shs:'+opts.key.slice(1, -8)+':'+parts[1]
166- }
167- else
168- modern = true
169- }
170-
171- opts = ref.parseAddress(ref.parseInvite(invite).remote)
172-
173- ssbClient(null, {
174- remote: invite,
175- manifest: {invite: {use: 'async'}, getAddress: 'async'}
176- }, function (err, rpc) {
177- if(err) return cb(explain(err, 'could not connect to server'))
178-
179- // command the peer to follow me
180- rpc.invite.use({ feed: server.id }, function (err, msg) {
181- if(err) return cb(explain(err, 'invite not accepted'))
182-
183- // follow and announce the pub
184- cont.para([
185- server.publish({
186- type: 'contact',
187- following: true,
188- autofollow: true,
189- contact: opts.key
190- }),
191- (
192- opts.host
193- ? server.publish({
194- type: 'pub',
195- address: opts
196- })
197- : function (cb) { cb() }
198- )
199- ])
200- (function (err, results) {
201- if(err) return cb(err)
202- rpc.getAddress(function (err, addr) {
203- rpc.close()
204- //ignore err if this is new style invite
205- if(modern && err) return cb(err, addr)
206- if(server.gossip) server.gossip.add(addr, 'seed')
207- cb(null, results)
208- })
209- })
210- })
211- })
212- }, 'string')
213- }
214- }
215-}
216-
217-
plugins/invite.mdView
@@ -1,62 +1,0 @@
1-# scuttlebot invite plugin
2-
3-Invite-token system, mainly used for pubs.
4-
5-
6-## create: async
7-
8-Create a new invite code.
9-
10-```bash
11-create {n}
12-```
13-
14-```js
15-create(n, cb)
16-```
17-
18-This produces an invite-code which encodes the sbot server's address, and a keypair seed.
19-The keypair seed is used to generate a keypair, which is then used to authenticate a connection with the sbot server.
20-The sbot server will then grant access to the `use` call.
21-
22-- `n` (number): How many times the invite can be used before it expires.
23-
24-
25-
26-## accept: async
27-
28-Use an invite code.
29-
30-```bash
31-accept {invitecode}
32-```
33-
34-```js
35-accept(invitecode, cb)
36-```
37-
38-This connects to the server address encoded in the invite-code, then calls `use()` on the server.
39-It will cause the server to follow the local user.
40-
41- - invitecode (string)
42-
43-
44-## use: async
45-
46-Use an invite code created by this sbot instance (advanced function).
47-
48-```bash
49-use --feed {feedid}
50-```
51-
52-```js
53-use({ feed: }, cb)
54-```
55-
56-This commands the receiving server to follow the given feed.
57-
58-An invite-code encodes the sbot server's address, and a keypair seed.
59-The keypair seed must be used to generate a keypair, then authenticate a connection with the sbot server, in order to use this function.
60-
61- - `feed` (feedid): The feed the server should follow.
62-
plugins/local.jsView
@@ -1,38 +1,0 @@
1-var broadcast = require('broadcast-stream')
2-var ref = require('ssb-ref')
3-// local plugin
4-// broadcasts the address:port:pubkey triple of the sbot server
5-// on the LAN, using multicast UDP
6-
7-function isFunction (f) {
8- return 'function' === typeof f
9-}
10-
11-module.exports = {
12- name: 'local',
13- version: '2.0.0',
14- init: function (sbot, config) {
15-
16- var local = broadcast(config.port)
17-
18- local.on('data', function (buf) {
19- if(buf.loopback) return
20- var data = buf.toString()
21- if(ref.parseAddress(data))
22- sbot.gossip.add(data, 'local')
23- })
24-
25- setInterval(function () {
26- // broadcast self
27- // TODO: sign beacons, so that receipient can be confidant
28- // that is really your id.
29- // (which means they can update their peer table)
30- // Oh if this includes your local address,
31- // then it becomes unforgeable.
32- local.write(sbot.getAddress())
33- }, 1000)
34- }
35-}
36-
37-
38-
plugins/logging.jsView
@@ -1,74 +1,0 @@
1-var color = require('bash-color')
2-
3-// logging plugin
4-// subscribes to 'log:*' events
5-// and emits using lovely colors
6-
7-var LOG_LEVELS = [
8- 'error',
9- 'warning',
10- 'notice',
11- 'info'
12-]
13-var DEFAULT_LEVEL = LOG_LEVELS.indexOf('notice')
14-
15-function indent (o) {
16- return o.split('\n').map(function (e) {
17- return ' ' + e
18- }).join('\n')
19-}
20-
21-function isString(s) {
22- return 'string' === s
23-}
24-
25-function formatter(id, level) {
26- var b = id.substring(0, 4)
27- return function (ary) {
28- var plug = ary[0].substring(0, 4).toUpperCase()
29- var id = ary[1]
30- var verb = ary[2]
31- var data = ary.length > 4 ? ary.slice(3) : ary[3]
32- var _data = (isString(data) ? data : JSON.stringify(data)) || ''
33-
34- var pre = [plug, id, color.cyan(verb)].join(' ')
35- var length = (5 + pre.length + 1 + _data.length)
36- var lines = isString(data) && data.split('\n').length > 1
37-
38- var c = process.stdout.columns
39- if((process.stdout.columns > length) && !lines)
40- console.log([level, b, pre, _data].join(' '))
41- else {
42- console.log([level, b, pre].join(' '))
43- if(lines)
44- console.log(indent(data))
45- else if(data && data.stack)
46- console.log(indent(data.stack))
47- else if(data) {
48- console.log(indent(JSON.stringify(data, null, 2)))
49- }
50- }
51- }
52-}
53-
54-module.exports = function logging (server, conf) {
55- var level = conf.logging && conf.logging.level && LOG_LEVELS.indexOf(conf.logging.level) || DEFAULT_LEVEL
56- if (level === -1) {
57- console.log('Warning, logging.level configured to an invalid value:', conf.logging.level)
58- console.log('Should be one of:', LOG_LEVELS.join(', '))
59- level = DEFAULT_LEVEL
60- }
61- console.log('Log level:', LOG_LEVELS[level])
62-
63- var id = server.id
64- if (level >= LOG_LEVELS.indexOf('info'))
65- server.on('log:info', formatter(id, color.green('info')))
66- if (level >= LOG_LEVELS.indexOf('notice'))
67- server.on('log:notice', formatter(id, color.blue('note')))
68- if (level >= LOG_LEVELS.indexOf('warning'))
69- server.on('log:warning', formatter(id, color.yellow('warn')))
70- if (level >= LOG_LEVELS.indexOf('error'))
71- server.on('log:error', formatter(id, color.red('err!')))
72-}
73-
74-module.exports.init = module.exports
plugins/master.jsView
@@ -1,11 +1,0 @@
1-// master plugin
2-// allows you to define "master" IDs in the config
3-// which are given the full rights of the local main ID
4-module.exports = function (api, opts) {
5- var masters = [api.id].concat(opts.master).filter(Boolean)
6- api.auth.hook(function (fn, args) {
7- var id = args[0]
8- var cb = args[1]
9- cb(null, ~masters.indexOf(id) ? {allow: null, deny: null} : null)
10- })
11-}
plugins/plugins.jsView
@@ -1,224 +1,0 @@
1-var assert = require('assert')
2-var path = require('path')
3-var fs = require('fs')
4-var pull = require('pull-stream')
5-var cat = require('pull-cat')
6-var many = require('pull-many')
7-var pushable = require('pull-pushable')
8-var toPull = require('stream-to-pull-stream')
9-var spawn = require('child_process').spawn
10-var mkdirp = require('mkdirp')
11-var osenv = require('osenv')
12-var rimraf = require('rimraf')
13-var mv = require('mv')
14-var mdm = require('mdmanifest')
15-var explain = require('explain-error')
16-var valid = require('../lib/validators')
17-var apidoc = require('../lib/apidocs').plugins
18-
19-module.exports = {
20- name: 'plugins',
21- version: '1.0.0',
22- manifest: mdm.manifest(apidoc),
23- permissions: {
24- master: {allow: ['install', 'uninstall', 'enable', 'disable']}
25- },
26- init: function (server, config) {
27- var installPath = config.path
28- config.plugins = config.plugins || {}
29- mkdirp.sync(path.join(installPath, 'node_modules'))
30-
31- // helper to enable/disable plugins
32- function configPluginEnabled (b) {
33- return function (pluginName, cb) {
34- checkInstalled(pluginName, function (err) {
35- if (err) return cb(err)
36-
37- config.plugins[pluginName] = b
38- writePluginConfig(pluginName, b)
39- if (b)
40- cb(null, '\''+pluginName+'\' has been enabled. Restart Scuttlebot server to use the plugin.')
41- else
42- cb(null, '\''+pluginName+'\' has been disabled. Restart Scuttlebot server to stop using the plugin.')
43- })
44- }
45- }
46-
47- // helper to check if a plugin is installed
48- function checkInstalled (pluginName, cb) {
49- if (!pluginName || typeof pluginName !== 'string')
50- return cb(new Error('plugin name is required'))
51- var modulePath = path.join(installPath, 'node_modules', pluginName)
52- fs.stat(modulePath, function (err) {
53- if (err)
54- cb(new Error('Plugin "'+pluginName+'" is not installed.'))
55- else
56- cb()
57- })
58- }
59-
60- // write the plugin config to ~/.ssb/config
61- function writePluginConfig (pluginName, value) {
62- var cfgPath = path.join(config.path, 'config')
63- var existingConfig = {}
64-
65- // load ~/.ssb/config
66- try { existingConfig = JSON.parse(fs.readFileSync(cfgPath, 'utf-8')) }
67- catch (e) {}
68-
69- // update the plugins config
70- existingConfig.plugins = existingConfig.plugins || {}
71- existingConfig.plugins[pluginName] = value
72-
73- // write to disc
74- fs.writeFileSync(cfgPath, JSON.stringify(existingConfig, null, 2), 'utf-8')
75- }
76-
77- return {
78- install: valid.source(function (pluginName, opts) {
79- var p = pushable()
80- var dryRun = opts && opts['dry-run']
81- var from = opts && opts.from
82-
83- if (!pluginName || typeof pluginName !== 'string')
84- return pull.error(new Error('plugin name is required'))
85-
86- // pull out the version, if given
87- if (pluginName.indexOf('@') !== -1) {
88- var pluginNameSplitted = pluginName.split('@')
89- pluginName = pluginNameSplitted[0]
90- var version = pluginNameSplitted[1]
91-
92- if (version && !from)
93- from = pluginName + '@' + version
94- }
95-
96- if (!validatePluginName(pluginName))
97- return pull.error(new Error('invalid plugin name: "'+pluginName+'"'))
98-
99- // create a tmp directory to install into
100- var tmpInstallPath = path.join(osenv.tmpdir(), pluginName)
101- rimraf.sync(tmpInstallPath); mkdirp.sync(tmpInstallPath)
102-
103- // build args
104- // --global-style: dont dedup at the top level, gives proper isolation between each plugin
105- // --loglevel error: dont output warnings, because npm just whines about the lack of a package.json in ~/.ssb
106- var args = ['install', from||pluginName, '--global-style', '--loglevel', 'error']
107- if (dryRun)
108- args.push('--dry-run')
109-
110- // exec npm
111- var child = spawn('npm', args, { cwd: tmpInstallPath })
112- .on('close', function (code) {
113- if (code == 0 && !dryRun) {
114- var tmpInstallNMPath = path.join(tmpInstallPath, 'node_modules')
115- var finalInstallNMPath = path.join(installPath, 'node_modules')
116-
117- // delete plugin, if it's already there
118- rimraf.sync(path.join(finalInstallNMPath, pluginName))
119-
120- // move the plugin from the tmpdir into our install path
121- // ...using our given plugin name
122- var dirs = fs.readdirSync(tmpInstallNMPath)
123- .filter(function (name) { return name.charAt(0) !== '.' }) // filter out dot dirs, like '.bin'
124- mv(
125- path.join(tmpInstallNMPath, dirs[0]),
126- path.join(finalInstallNMPath, pluginName),
127- function (err) {
128- if (err)
129- return p.end(explain(err, '"'+pluginName+'" failed to install. See log output above.'))
130-
131- // enable the plugin
132- // - use basename(), because plugins can be installed from the FS, in which case pluginName is a path
133- var name = path.basename(pluginName)
134- config.plugins[name] = true
135- writePluginConfig(name, true)
136- p.push(new Buffer('"'+pluginName+'" has been installed. Restart Scuttlebot server to enable the plugin.\n', 'utf-8'))
137- p.end()
138- }
139- )
140- } else
141- p.end(new Error('"'+pluginName+'" failed to install. See log output above.'))
142- })
143- return cat([
144- pull.values([new Buffer('Installing "'+pluginName+'"...\n', 'utf-8')]),
145- many([toPull(child.stdout), toPull(child.stderr)]),
146- p
147- ])
148- }, 'string', 'object?'),
149- uninstall: valid.source(function (pluginName, opts) {
150- var p = pushable()
151- if (!pluginName || typeof pluginName !== 'string')
152- return pull.error(new Error('plugin name is required'))
153-
154- var modulePath = path.join(installPath, 'node_modules', pluginName)
155-
156- rimraf(modulePath, function (err) {
157- if (!err) {
158- writePluginConfig(pluginName, false)
159- p.push(new Buffer('"'+pluginName+'" has been uninstalled. Restart Scuttlebot server to disable the plugin.\n', 'utf-8'))
160- p.end()
161- } else
162- p.end(err)
163- })
164- return p
165- }, 'string', 'object?'),
166- enable: valid.async(configPluginEnabled(true), 'string'),
167- disable: valid.async(configPluginEnabled(false), 'string')
168- }
169- }
170-}
171-
172-module.exports.loadUserPlugins = function (createSbot, config) {
173- // iterate all modules
174- var nodeModulesPath = path.join(config.path, 'node_modules')
175- try {
176- console.log('Loading plugins from', nodeModulesPath)
177- fs.readdirSync(nodeModulesPath).forEach(function (filename) {
178- if (filename.charAt(0) == '.')
179- return // ignore
180- if (!config.plugins[filename])
181- return console.log('Skipping disabled plugin "'+filename+'"')
182- console.log('Loading plugin "'+filename+'"')
183-
184- try {
185- // load module
186- var plugin = require(path.join(nodeModulesPath, filename))
187-
188- // check the signature
189- assertSbotPlugin(plugin)
190-
191- // load
192- createSbot.use(plugin)
193- } catch (e) {
194- console.error('Error loading plugin "'+filename+'":', e.message)
195- }
196- })
197- } catch (e) {
198- // node_modules dne, ignore
199- }
200-}
201-
202-// predictate to check if an object appears to be a sbot plugin
203-function assertSbotPlugin (obj) {
204- // function signature:
205- if (typeof obj == 'function')
206- return
207-
208- // object signature:
209- assert(obj && typeof obj == 'object', 'module.exports must be an object')
210- assert(typeof obj.name == 'string', 'module.exports.name must be a string')
211- assert(typeof obj.version == 'string', 'module.exports.version must be a string')
212- assert(obj.manifest &&
213- typeof obj.manifest == 'object', 'module.exports.manifest must be an object')
214- assert(typeof obj.init == 'function', 'module.exports.init must be a function')
215-}
216-
217-function validatePluginName (name) {
218- if (/^[._]/.test(name))
219- return false
220- // from npm-validate-package-name:
221- if (encodeURIComponent(name) !== name)
222- return false
223- return true
224-}
plugins/plugins.mdView
@@ -1,68 +1,0 @@
1-# scuttlebot plugins plugin
2-
3-Install and manage third-party plugins.
4-
5-
6-
7-## install: source
8-
9-Install a plugin to Scuttlebot.
10-
11-```bash
12-install {nodeModule} [--from path]
13-```
14-```js
15-install(nodeModule, { from: })
16-```
17-
18-Calls out to npm to install a package into `~/.ssb/node_modules`.
19-
20- - nodeModule (string): The name of the plugin to install. Uses npm's module package-name rules.
21- - from (string): A location to install from (directory path, url, or any location that npm accepts for its install command).
22-
23-
24-
25-## uninstall: source
26-
27-Uninstall a plugin from Scuttlebot.
28-
29-```bash
30-uninstall {nodeModule}
31-```
32-```js
33-uninstall(nodeModule)
34-```
35-
36-Calls out to npm to uninstall a package into `~/.ssb/node_modules`.
37-
38- - nodeModule (string): The name of the plugin to uninstall.
39-
40-
41-
42-## enable: async
43-
44-Update the config to enable a plugin.
45-
46-```bash
47-enable {nodeModule}
48-```
49-```js
50-enable(nodeModule, cb)
51-```
52-
53- - nodeModule (string): The name of the plugin to enable.
54-
55-
56-
57-## disable: async
58-
59-Update the config to disable a plugin.
60-
61-```bash
62-disable {nodeModule}
63-```
64-```js
65-disable(nodeModule, cb)
66-```
67-
68- - nodeModule (string): The name of the plugin to disable.
plugins/private.jsView
@@ -1,31 +1,0 @@
1-var ssbKeys = require('ssb-keys')
2-var explain = require('explain-error')
3-var mdm = require('mdmanifest')
4-var valid = require('../lib/validators')
5-var apidoc = require('../lib/apidocs').private
6-
7-module.exports = {
8- name: 'private',
9- version: '0.0.0',
10- manifest: mdm.manifest(apidoc),
11- permissions: {
12- anonymous: {},
13- },
14- init: function (sbot, opts) {
15- return {
16- publish: valid.async(function (data, recps, cb) {
17- var ciphertext
18- try { ciphertext = ssbKeys.box(data, recps) }
19- catch (e) { return cb(explain(e, 'failed to encrypt')) }
20-
21- sbot.publish(ciphertext, cb)
22- }, 'string|object', 'array'),
23- unbox: valid.sync(function (ciphertext) {
24- var data
25- try { data = ssbKeys.unbox(ciphertext, sbot.keys.private) }
26- catch (e) { throw explain(e, 'failed to decrypt') }
27- return data
28- }, 'string')
29- }
30- }
31-}
plugins/private.mdView
@@ -1,38 +1,0 @@
1-# scuttlebot private plugin
2-
3-Methods to publish and decrypt secret messages.
4-
5-
6-
7-## publish: async
8-
9-Publish an encrypted message.
10-
11-```bash
12-*this can not be used from the commandline*
13-```
14-
15-```js
16-publish(content, recps, cb)
17-```
18-
19-The content will be encrypted using the public keys passed into recps.
20-Limit 7 recipients.
21-
22- - `content` (object): The content of the message.
23- - `recps` (array of feedids): The recipients of the message (limit 7).
24-
25-
26-## unbox: sync
27-
28-Attempt to decrypt the content of an encrypted message.
29-
30-```
31-*this can not be used from the commandline*
32-```
33-
34-```js
35-unbox(ciphertext, cb)
36-```
37-
38- - `cyphertext` (string)
plugins/replicate.jsView
@@ -1,229 +1,0 @@
1-'use strict'
2-var pull = require('pull-stream')
3-var para = require('pull-paramap')
4-var Notify = require('pull-notify')
5-var many = require('pull-many')
6-var Cat = require('pull-cat')
7-var Abort = require('pull-abortable')
8-var Debounce = require('observ-debounce')
9-var mdm = require('mdmanifest')
10-var apidoc = require('../lib/apidocs').replicate
11-
12-var Pushable = require('pull-pushable')
13-
14-// compatibility function for old implementations of `latestSequence`
15-function toSeq (s) {
16- return 'number' === typeof s ? s : s.sequence
17-}
18-
19-function last (a) { return a[a.length - 1] }
20-
21-module.exports = {
22- name: 'replicate',
23- version: '2.0.0',
24- manifest: mdm.manifest(apidoc),
25- //replicate: replicate,
26- init: function (sbot, config) {
27- var debounce = Debounce(200)
28- var listeners = {}
29- var notify = Notify()
30- var newPeer = Notify()
31-
32- var total=0, progress=0, start, count = 0, rate=0
33- var to_send = {}
34- var to_recv = {}
35- var feeds = 0
36-
37- debounce(function () {
38- var _progress = progress, _total = total
39- progress = 0; total = 0
40- for(var k in to_send) progress += to_send[k]
41-
42- for(var k in to_recv)
43- if(to_send[k] !== null)
44- total += to_recv[k]
45-
46- if(_progress !== progress || _total !== total) {
47- notify({
48- id: sbot.id,
49- total: total, progress: progress, rate: rate,
50- feeds: feeds
51- })
52- }
53- })
54-
55- pull(
56- sbot.createLogStream({old: false, live: true, sync: false, keys: false}),
57- pull.drain(function (e) {
58- //track writes per second, mainly used for developing initial sync.
59- if(!start) start = Date.now()
60- var time = (Date.now() - start)/1000
61- if(time >= 1) {
62- rate = count / time
63- start = Date.now()
64- count = 0
65- }
66- var pushable = listeners[e.author]
67-
68- if(pushable && pushable.sequence == e.sequence) {
69- pushable.sequence ++
70- pushable.forEach(function (p) {
71- p.push(e)
72- })
73- }
74- count ++
75- addPeer({id: e.author, sequence: e.sequence})
76- })
77- )
78-
79- //keep track of maximum requested value, per feed.
80- sbot.createHistoryStream.hook(function (fn, args) {
81- var upto = args[0] || {}
82- var seq = upto.sequence || upto.seq
83- to_recv[upto.id] = Math.max(to_recv[upto.id] || 0, seq)
84- if(this._emit) this._emit('call:createHistoryStream', args[0])
85-
86- //if we are calling this locally, skip cleverness
87- if(this===sbot) return fn.call(this, upto)
88-
89- debounce.set()
90-
91- //handle creating lots of histor streams efficiently.
92- //maybe this could be optimized in map-filter-reduce queries instead?
93- if(to_send[upto.id] == null || (seq > to_send[upto.id])) {
94- upto.old = false
95- if(!upto.live) return pull.empty()
96- var pushable = listeners[upto.id] = listeners[upto.id] || []
97- var p = Pushable(function () {
98- var i = pushable.indexOf(p)
99- pushable.splice(i, 1)
100- })
101- pushable.push(p)
102- pushable.sequence = upto.sequence
103- return p
104- }
105- return fn.call(this, upto)
106- })
107-
108- // collect the IDs of feeds we want to request
109- var opts = config.replication || {}
110- opts.hops = opts.hops || 3
111- opts.dunbar = opts.dunbar || 150
112- opts.live = true
113- opts.meta = true
114-
115- function localPeers () {
116- if(!sbot.gossip) return
117- sbot.gossip.peers()
118- .forEach(function (e) {
119- if(to_send[e.key] == null)
120- addPeer({id: e.key, sequence: 0})
121- })
122- }
123-
124- //also request local peers.
125- if (sbot.gossip) {
126- // if we have the gossip plugin active, then include new local peers
127- // so that you can put a name to someone on your local network.
128- var int = setInterval(localPeers, 1000)
129- if(int.unref) int.unref()
130- localPeers()
131- }
132-
133- function addPeer (upto) {
134- if(upto.sync) return
135- if(!upto.id) return console.log('invalid', upto)
136-
137- if(to_send[upto.id] == null) {
138- to_send[upto.id] = Math.max(to_send[upto.id] || 0, upto.sequence || upto.seq || 0)
139- newPeer({id: upto.id, sequence: to_send[upto.id] , type: 'new' })
140- } else
141- to_send[upto.id] = Math.max(to_send[upto.id] || 0, upto.sequence || upto.seq || 0)
142-
143- debounce.set()
144- }
145-
146-
147- // create read-streams for the desired feeds
148- var S = false
149- pull(
150- sbot.friends.createFriendStream(opts),
151- // filter out duplicates, and also keep track of what we expect to receive
152- // lookup the latest sequence from each user
153- para(function (data, cb) {
154- if(data.sync) return cb(null, S = data)
155- var id = data.id || data
156- sbot.latestSequence(id, function (err, seq) {
157- cb(null, {
158- id: id, sequence: err ? 0 : toSeq(seq)
159- })
160- })
161- }, 32),
162- pull.drain(addPeer)
163- )
164-
165- function upto (opts) {
166- opts = opts || {}
167- var ary = Object.keys(to_send).map(function (k) {
168- return { id: k, sequence: to_send[k] }
169- })
170- if(opts.live)
171- return Cat([pull.values(ary), pull.once({sync: true}), newPeer.listen()])
172-
173- return pull.values(ary)
174- }
175-
176- sbot.on('rpc:connect', function(rpc) {
177- // this is the cli client, just ignore.
178- if(rpc.id === sbot.id) return
179- //check for local peers, or manual connections.
180- localPeers()
181- var drain
182- sbot.emit('replicate:start', rpc)
183- rpc.on('closed', function () {
184- sbot.emit('replicate:finish', to_send)
185- })
186- var SYNC = false
187- pull(
188- upto({live: opts.live}),
189- drain = pull.drain(function (upto) {
190- if(upto.sync) return
191- feeds++
192- debounce.set()
193- pull(
194- rpc.createHistoryStream({
195- id: upto.id,
196- seq: (upto.sequence || upto.seq || 0) + 1,
197- live: true,
198- keys: false
199- }),
200- sbot.createWriteStream(function (err) {
201- if(err) console.error(err.stack)
202-
203- feeds--
204- debounce.set()
205- })
206- )
207-
208- }, function (err) {
209- if(err)
210- sbot.emit('log:error', ['replication', rep.id, 'error', err])
211- })
212- )
213- })
214-
215- return {
216- changes: notify.listen,
217- upto: upto,
218- }
219- }
220-}
221-
222-
223-
224-
225-
226-
227-
228-
229-
plugins/replicate.mdView
@@ -1,27 +1,0 @@
1-# scuttlebot replicate plugin
2-
3-Sync feeds between peers.
4-
5-
6-## changes: source
7-
8-Listen to replicate events.
9-
10-```bash
11-changes
12-```
13-
14-```js
15-changes()
16-```
17-
18-Emits events of the following form:
19-
20-```
21-{ type: 'progress', peerid:, total:, progress:, feeds:, sync: }
22-```
23-
24-## upto: source
25-
26-returns {} of feeds to replicate, with sequences
27-
plugins/sdash/.gitignoreView
@@ -1,3 +1,0 @@
1-static/reserva
2-static/ssp
3-node_modules
plugins/sdash/bin.jsView
@@ -1,6 +1,0 @@
1-#!/usr/bin/env node
2-
3-require('../ssb-client')(function (err, sbot, config) {
4- if (err) throw err
5- require('.').init(sbot, config)
6-})
plugins/sdash/index.jsView
@@ -1,80 +1,0 @@
1-var http = require('http')
2-var fs = require('fs')
3-var h = require('hyperscript')
4-var pull = require('pull-stream')
5-var client = require('../ssb-client')
6-var md = require('ssb-markdown')
7-
8-var title = 'sdash'
9-var viewerUrl = 'http://localhost:3535/'
10-
11-var liteURL = 'http://decent.evbogue.com/'
12-var opts = {"modern":true,}
13-var lite
14-var port = 1333
15-
16-var style = fs.readFileSync(__dirname + '/style.css', 'utf8')
17-
18-exports.name = 'sdash'
19-exports.manifest = {}
20-// exports.version = require('./package').version
21-
22-exports.init = function (sbot, config) {
23-
24- var id = require ('../../client/keys').id
25- console.log(id)
26-
27- http.createServer(function (req, res){
28- if (req.url === '/') {
29- client(function (err, sbot) {
30- pull(
31- sbot.query.read({query: [{$filter: { value: { author: id, content: {type: 'post'}}}}], limit: 1, reverse: true}),
32- pull.drain(function (data) {
33- post = data
34- gotPost()
35- sbot.close()
36- })
37- )
38- })
39- function gotPost() {
40- res.end(
41- h('html',
42- h('head',
43- h('title', title),
44- h('style', style)
45- ),
46- h('body',
47- h('div.msg',
48- h('script', {src: viewerUrl + encodeURI(post.key) + '.js'})
49- )
50- )
51- ).outerHTML)
52- }
53- }
54- if (req.url === '/invite/') {
55- client(function (err, sbot) {
56- sbot.invite.create(opts, function (err, invite) {
57- if(err) throw err
58- lite = invite
59- gotInvite()
60- sbot.close()
61- })
62- })
63- function gotInvite() {
64- res.end(
65- h('html',
66- h('head',
67- h('title', title),
68- h('style', style)
69- ),
70- h('body',
71- h('div.msg',
72- h('p', {innerHTML: '<a href="' + liteURL + '#' + lite + '" rel="nofollow" target="_blank">'+ liteURL + '#' + lite + '</a>'})
73- )
74- )
75- ).outerHTML)
76- }
77- }
78- }).listen(port)
79- console.log('sdash is running at http://localhost:' + port + '/')
80-}
plugins/sdash/static/sdash.cssView
@@ -1,31 +1,0 @@
1-p {
2- font-family: 'Source Sans Pro', sans-serif;
3-}
4-
5-.avatar {
6- width: 2em;
7- float: left;
8- margin-right: 1em;
9-}
10-
11-.message {
12- border: 1px solid #ccc;
13- padding: 1em;
14-}
15-
16-.pre {
17- margin-bottom: 0;
18-}
19-
20-.date {
21- font-size: .8em;
22- color: #666;
23-}
24-
25-.small {
26- font-size: .8em;
27-}
28-
29-.ri {
30- float: right;
31-}
plugins/sdash/style.cssView
@@ -1,79 +1,0 @@
1-body {
2- font-family: 'Source Sans Pro', sans-serif;
3- color: #333;
4- width: 100%;
5- margin-right: auto;
6- margin-left: auto;
7-}
8-
9-p, pre, code {
10- margin-top: .35ex;
11- word-wrap: break-word;
12- white-space: pre-wrap;
13-}
14-
15-.msg {
16- border-bottom: 1px solid #eee;
17- padding: .5em;
18- margin-bottom: .5em;
19-}
20-
21-.ad {
22- float: right;
23- width: 35%;
24- margin-left: 1em;
25- border: 1px solid #eee;
26- margin-bottom: 1em;
27- padding: .5em;
28- font-size: .8em;
29-}
30-
31-.avatar {
32- width: 2em;
33- float: left;
34- margin-right: .5em;
35- padding: 2px;
36- border: 1px solid #eee;
37-}
38-
39-.profile {
40- width: 6em;
41- float: left;
42- padding: 4px;
43- border: 1px solid #eee;
44- margin-right: 1em;
45- margin-bottom: 1em;
46-}
47-
48-hr {
49- border: solid #eee;
50- clear: both;
51- border-width: 1px 0 0;
52- height: 0;
53- margin-bottom: .9em;
54-}
55-
56-.small {
57- font-size: .8em;
58- font-weight: bold;
59-}
60-
61-.ri {
62- float: right;
63-}
64-
65-.date {
66- font-size: .8em;
67- color: #666;
68-}
69-
70-a:link, a:visited, a:active {
71- color: #0088cc;
72- text-decoration: underline;
73-}
74-
75-a:hover,
76-a:focus {
77- color: #005580;
78-}
79-
plugins/ssb-blobs/.npmignoreView
@@ -1,3 +1,0 @@
1-node_modules
2-node_modules/*
3-npm-debug.log
plugins/ssb-blobs/.travis.ymlView
@@ -1,4 +1,0 @@
1-language: node_js
2-node_js:
3- - 0.6
4- - 0.8
plugins/ssb-blobs/LICENSEView
@@ -1,22 +1,0 @@
1-Copyright (c) 2016 Dominic Tarr
2-
3-Permission is hereby granted, free of charge,
4-to any person obtaining a copy of this software and
5-associated documentation files (the "Software"), to
6-deal in the Software without restriction, including
7-without limitation the rights to use, copy, modify,
8-merge, publish, distribute, sublicense, and/or sell
9-copies of the Software, and to permit persons to whom
10-the Software is furnished to do so,
11-subject to the following conditions:
12-
13-The above copyright notice and this permission notice
14-shall be included in all copies or substantial portions of the Software.
15-
16-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20-ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
plugins/ssb-blobs/README.mdView
@@ -1,55 +1,0 @@
1-# ssb-blobs
2-
3-protocol for gossiping "blobs", in no particular order.
4-
5-## protocol
6-
7-when two peers connect, they request the blobs the other peer "wants"
8-they do this by sending a JSON object that is a map of
9-`{<blob_id>: -hop_count}` (note, the hop count is negative)
10-if they have a blob wanted by the peer, they respond
11-with the size they have for that blob: `{<blob_id>: size}` (note,
12-size is positive - this is how sizes and blobs are distinguished)
13-
14-Peers can only request blobs they know about. In the legacy
15-[scuttlebot/plugins/blobs](https://github.com/ssbc/scuttlebot/tree/99fad7c5f6e436cbd670346b4da20c57222a1419/plugins/blobs)
16-peers would request blobs that where mentioned in an
17-[ssb-feed](https://github.com/ssbc/ssb-feed) but this approach
18-means they cannot know about (encrypted) blobs shared in private
19-messages, or about blobs recursively linked from other blobs.
20-
21-This protocol addresses that by implementing _sympathetic wants_,
22-if a peer requests a blob that you do not have, you start to want it too.
23-so that wants to not flood the entire network, you signal how much
24-you want it with a hop count. If you want it for your self,
25-you use a `hop_count` of -1, if you want it for a friend,
26-you use a `hop_count` of -2 (and so on..)
27-
28-This allows you to publish a secret blob, without creating
29-a permanent cryptographic record of that blob.
30-
31-However, this alone would mean that to upload a blob,
32-someone else needs to request it from you, which requries
33-both peers to be online at the same time.
34-
35-To address this, we have a `push` method. "pushing" a blob,
36-just makes a peer "pretend" that they want a blob,
37-triggering sympathetic wants from intermediate nodes,
38-ensuring that there are at least some peers that will have the blob.
39-(currently, your peer will continue to pretend they want the
40-blob until at least 3 peers report having it)
41-
42-
43-## License
44-
45-MIT
46-
47-
48-
49-
50-
51-
52-
53-
54-
55-
plugins/ssb-blobs/create.jsView
@@ -1,27 +1,0 @@
1-var util = require('multiblob/util')
2-var isBlob = require('ssb-ref').isBlob
3-var MultiBlob = require('multiblob')
4-
5-function desigil (hash) {
6- return isBlob(hash) ? hash.substring(1) : hash
7-}
8-
9-function resigil (hash) {
10- return isBlob(hash) ? hash : '&'+hash
11-}
12-
13-module.exports = function (dir) {
14- return MultiBlob({
15- dir: dir,
16- alg: 'sha256',
17- encode: function (buf, alg) {
18- return resigil(util.encode(buf, alg))
19- },
20- decode: function (str) {
21- return util.decode(desigil(str))
22- },
23- isHash: isBlob
24- })
25-}
26-
27-
plugins/ssb-blobs/index.jsView
@@ -1,40 +1,0 @@
1-var create = require('./create')
2-var path = require('path')
3-var Inject = require('./inject')
4-var Set = require('./set')
5-var Level = require('level')
6-
7-exports.manifest = {
8- get: 'source',
9- add: 'sink',
10- ls: 'source',
11- has: 'async',
12- size: 'async',
13- meta: 'async',
14- want: 'async',
15- push: 'async',
16- changes: 'source',
17- createWants: 'source'
18-}
19-
20-exports.name = 'blobs'
21-
22-exports.version = require('./package.json').version
23-
24-exports.permissions = {
25- anonymous: {allow: ['has', 'get', 'changes', 'createWants']},
26-}
27-
28-exports.init = function (sbot, config) {
29- var blobs = Inject(
30- create(path.join(config.path, 'blobs')),
31- Set(Level(path.join(config.path, 'blobs_push'), {valueEncoding: 'json'})),
32- sbot.id
33- )
34-
35- sbot.on('rpc:connect', function (rpc) {
36- blobs._onConnect(rpc, rpc.id)
37- })
38- return blobs
39-}
40-
plugins/ssb-blobs/inject.jsView
@@ -1,319 +1,0 @@
1-'use strict'
2-function isEmpty (o) {
3- for(var k in o) return false
4- return true
5-}
6-
7-function isInteger (i) {
8- return Number.isInteger(i)
9-}
10-
11-var isArray = Array.isArray
12-
13-var Notify = require('pull-notify')
14-var pull = require('pull-stream')
15-var isBlobId = require('ssb-ref').isBlob
16-
17-var MB = 1024*1024
18-var MAX_SIZE = 5*MB
19-var MIN_PUSH_PEERS = 3
20-function noop () {}
21-
22-function clone (obj) {
23- var o = {}
24- for(var k in obj)
25- o[k] = obj[k]
26- return o
27-}
28-
29-module.exports = function inject (blobs, set, name) {
30- var notify = Notify()
31- var pushed = Notify()
32-
33- var peers = {}
34- var want = {}, push = {}, waiting = {}, getting = {}
35- var available = {}, streams = {}
36- var send = {}, timer
37-
38- function queue (hash, hops) {
39- if(hops < 0)
40- want[hash] = hops
41- else
42- delete want[hash]
43-
44- send[hash] = hops
45- var _send = send;
46- send = {}
47- notify(_send)
48- }
49-
50- function isAvailable(id) {
51- for(var peer in peers)
52- if(available[peer] && available[peer][id] && peers[peer])
53- return peer
54- }
55-
56- function get (peer, id, name) {
57- if(getting[id] || !peers[peer]) return
58-
59- getting[id] = peer
60- //XXX REINSTATE MAXIMUM SIZE BLOBS!
61-// var source = peers[peer].blobs.get({id: id, max: 5*1024*1024})
62- var source = peers[peer].blobs.get(id)
63- pull(source, blobs.add(id, function (err, _id) {
64- delete getting[id]
65- if(err) {
66- if(available[peer]) delete available[peer][id]
67- //check if another peer has this.
68- //if so get it from them.
69- if(peer = isAvailable(id)) get(peer, id, name)
70- }
71- }))
72- }
73-
74- function wants (peer, id, hops) {
75- if(!want[id] || want[id] < hops) {
76- want[id] = hops
77- queue(id, hops)
78- if(peer = isAvailable(id)) {
79- get(peer, id)
80- }
81- }
82- }
83-
84- pull(
85- blobs.ls({old: false, meta: true}),
86- pull.drain(function (data) {
87- queue(data.id, data.size)
88- delete want[data.id]
89- if(waiting[data.id])
90- while(waiting[data.id].length)
91- waiting[data.id].shift()(null, true)
92- })
93- )
94-
95- function has(peer_id, id, size) {
96- if('string' !== typeof peer_id) throw new Error('peer must be string id')
97- available[peer_id] = available[peer_id] || {}
98- available[peer_id][id] = size
99- //if we are broadcasting this blob,
100- //mark this peer has it.
101- //if N peers have it, we can stop broadcasting.
102- if(push[id]) {
103- push[id][peer_id] = size
104- if(Object.keys(push[id]).length >= MIN_PUSH_PEERS) {
105- var data = {key: id, peers: push[id]}
106- set.remove(id)
107- delete push[id]; pushed(data)
108- }
109- }
110- if(want[id] && !getting[id] && size < MAX_SIZE) get(peer_id, id)
111- }
112-
113- function process (data, peer, cb) {
114- var n = 0, res = {}
115- for(var id in data) {
116- if(isBlobId(id) && isInteger(data[id])) {
117- if(data[id] <= 0) { //interpret as "WANT"
118- n++
119- //check whether we already *HAVE* this file.
120- //respond with it's size, if we do.
121- blobs.size(id, function (err, size) { //XXX
122- if(size) res[id] = size
123- else wants(peer, id, data[id] - 1)
124- next()
125- })
126- }
127- else if(data[id] > 0) { //interpret as "HAS"
128- has(peer, id, data[id])
129- }
130- }
131- }
132-
133- function next () {
134- if(--n) return
135- cb(null, res)
136- }
137- }
138-
139- function dead (peer_id) {
140- delete peers[peer_id]
141- delete available[peer_id]
142- delete streams[peer_id]
143- }
144-
145- //LEGACY LEGACY LEGACY
146-
147- function legacySync (peer) {
148- var drain //we need to keep a reference to drain
149- //so we can abort it when we get an error.
150- function hasLegacy (hashes) {
151- var ary = Object.keys(hashes).filter(function (k) {
152- return hashes[k] < 0
153- })
154- if(ary.length)
155- peer.blobs.has(ary, function (err, haves) {
156- if(err) drain.abort(err) //ERROR: abort this stream.
157- else haves.forEach(function (have, i) {
158- if(have) has(peer.id, ary[i], have)
159- })
160- })
161- }
162-
163- function notPeer (err) {
164- if(err) dead(peer.id)
165- }
166-
167- drain = pull.drain(function (hash) {
168- has(peer.id, hash, true)
169- }, notPeer)
170-
171-
172- pull(peer.blobs.changes(), drain)
173-
174- hasLegacy(want)
175-
176- //a stream of hashes
177- pull(notify.listen(), pull.drain(hasLegacy, notPeer))
178- }
179- //LEGACY LEGACY LEGACY
180-
181- function createWantStream (id) {
182- if(!streams[id]) {
183- streams[id] = notify.listen()
184-
185- //merge in ids we are pushing.
186- var w = clone(want)
187- for(var k in push) w[k] = -1
188- streams[id].push(w)
189-
190- return streams[id]
191- }
192- return streams[id]
193- }
194-
195- function wantSink (peer) {
196- createWantStream(peer.id) //set streams[peer.id]
197-
198- var modern = false
199- return pull.drain(function (data) {
200- modern = true
201- //respond with list of blobs you already have,
202- process(data, peer.id, function (err, has_data) {
203- //(if you have any)
204- if(!isEmpty(has_data) && streams[peer.id]) streams[peer.id].push(has_data)
205- })
206- }, function (err) {
207- if(err && !modern) {
208- streams[peer.id] = false
209- legacySync(peer)
210- }
211- //if can handle unpeer another way,
212- //then can refactor legacy handling out of sight.
213-
214- //handle error and fallback to legacy mode.
215- else if(peers[peer.id] == peer) {
216- delete peers[peer.id]
217- delete available[peer.id]
218- delete streams[peer.id]
219- }
220- })
221- }
222-
223- var self
224- return self = {
225- //id: name,
226- has: function (hash, cb) {
227- if(isArray(hash)) {
228- for(var i = 0; i < hash.length; i++)
229- if(!isBlobId(hash[i]))
230- return cb(new Error('invalid hash:'+hash[i]))
231- }
232- else if(!isBlobId(hash))
233- return cb(new Error('invalid hash:'+hash))
234-
235- if(this === self || !this || this === global) { // a local call
236- return blobs.has.call(this, hash, cb)
237- }
238- //LEGACY LEGACY LEGACY
239-
240- //ELSE, interpret remote calls to has as a WANT request.
241- //handling this by calling process (which calls size())
242- //avoids a race condition in the tests.
243- //(and avoids doubling the number of calls)
244- var a = Array.isArray(hash) ? hash : [hash]
245- var o = {}
246- a.forEach(function (h) { o[h] = -1 })
247- //since this is always "has" process will never use the second arg.
248- process(o, null, function (err, res) {
249- var a = []; for(var k in o) a.push(res[k] > 0)
250- cb(null, Array.isArray(hash) ? a : a[0])
251- })
252-
253- //LEGACY LEGACY LEGACY
254-
255- },
256- size: blobs.size,
257- get: blobs.get,
258- add: blobs.add,
259- ls: blobs.ls,
260- changes: function () {
261- return blobs.ls({old: false, meta: false})
262- },
263- want: function (hash, cb) {
264- if(!isBlobId(hash))
265- return cb(new Error('invalid hash:'+hash))
266- //always broadcast wants immediately, because of race condition
267- //between has and adding a blob (needed to pass test/async.js)
268- var id = isAvailable(hash)
269- if(!id) queue(hash, -1)
270-
271- if(waiting[hash])
272- waiting[hash].push(cb)
273- else {
274- waiting[hash] = [cb]
275- blobs.size(hash, function (err, has) {
276- if(has) {
277- while(waiting[hash].length)
278- waiting[hash].shift()(null, true)
279- delete waiting[hash]
280- }
281- })
282- }
283- if(id) return get(id, hash)
284- },
285- push: function (id, cb) {
286- //also store the id to push.
287- if(!isBlobId(id))
288- return cb(new Error('invalid hash:'+id))
289-
290- push[id] = push[id] || {}
291- queue(id, -1)
292- set.add(id, cb)
293- },
294- pushed: function () {
295- return pushed.listen()
296- },
297- createWants: function () {
298- return createWantStream(this.id)
299- },
300- //private api. used for testing. not exposed over rpc.
301- _wantSink: wantSink,
302- _onConnect: function (other, name) {
303- peers[other.id] = other
304- //sending of your wants starts when you we know
305- //that they are not legacy style.
306- //process is called when wantSync
307- //doesn't immediately get an error.
308- pull(other.blobs.createWants(), wantSink(other))
309- }
310- }
311-}
312-
313-
314-
315-
316-
317-
318-
319-
plugins/ssb-blobs/package.jsonView
@@ -1,34 +1,0 @@
1-{
2- "name": "ssb-blobs",
3- "description": "blobs and blob replication for ssb",
4- "version": "0.1.7",
5- "homepage": "https://github.com/dominictarr/ssb-blobs",
6- "repository": {
7- "type": "git",
8- "url": "git://github.com/dominictarr/ssb-blobs.git"
9- },
10- "dependencies": {
11- "cont": "^1.0.3",
12- "level": "^1.4.0",
13- "multiblob": "^1.10.0",
14- "pull-level": "^1.5.2",
15- "pull-notify": "^0.1.0",
16- "pull-stream": "^3.3.0",
17- "ssb-ref": "^2.3.0"
18- },
19- "devDependencies": {
20- "interleavings": "^0.3.1",
21- "mkdirp": "^0.5.1",
22- "multiblob": "^1.9.3",
23- "osenv": "^0.1.3",
24- "pull-bitflipper": "^0.1.0",
25- "rimraf": "^2.5.2",
26- "secret-stack": "^2.5.1",
27- "tape": "^4.5.1"
28- },
29- "scripts": {
30- "test": "set -e; for t in test/*.js; do node $t; done"
31- },
32- "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
33- "license": "MIT"
34-}
plugins/ssb-blobs/set.jsView
@@ -1,28 +1,0 @@
1-var pull = require('pull-stream')
2-var pl = require('pull-level')
3-
4-module.exports = function (db) {
5- var set = {}
6-
7- pull(
8- pl.read(db, {live: true}),
9- pull.drain(function (e) {
10- if(!e.sync)
11- if(e.type === 'del')
12- delete set[e.key]
13- else set[e.key] = e.value
14- })
15- )
16-
17- return {
18- set: set,
19- add: function (key, cb) {
20- db.put(key, -1, cb)
21- },
22- remove: function (key, cb) {
23- db.del(key, cb)
24- }
25- }
26-}
27-
28-
plugins/ssb-blobs/test/async.jsView
@@ -1,18 +1,0 @@
1-var interleavings = require('interleavings')
2-
3-var Mock = require('./mock/blobs')
4-var MockSet = require('./mock/set')
5-var Blobs = require('../inject')
6-
7-function create(name, async) {
8- return Blobs(Mock(name, async), MockSet(async), name)
9-}
10-
11-
12-require('./simple')(create, interleavings.test)
13-require('./integration')(create, interleavings.test)
14-require('./legacy')(create, interleavings.test)
15-require('./push')(create, interleavings.test)
16-
17-
18-
plugins/ssb-blobs/test/integration.jsView
@@ -1,233 +1,0 @@
1-var tape = require('tape')
2-var Blobs = require('../inject')
3-var pull = require('pull-stream')
4-var bitflipper = require('pull-bitflipper')
5-var assert = require('assert')
6-
7-var u = require('./util')
8-var Fake = u.fake
9-var hash = u.hash
10-
11-module.exports = function (createBlobs, createAsync) {
12-
13-
14- function log (name) {
15- if(LOGGING)
16- return pull.through(function (e) {
17- console.log(name, e)
18- })
19- else
20- return pull.through()
21- }
22-
23- tape('want - has', function (t) {
24- createAsync(function (async) {
25- var alice = createBlobs('alice', async)
26- var bob = createBlobs('bob', async)
27- var blob = Fake('foobar', 64)
28- var h = hash(blob)
29-
30- u.peers('alice', alice, 'bob', bob)//, async)
31-
32- alice.want(h, function (err, has) {
33- if(err) throw err
34- console.log('ALICE has?', h, has)
35- alice.has(h, function (err, has) {
36- console.log('ALICE has!', h, has)
37- if(err) throw err
38- assert.ok(has)
39- async.done()
40- })
41- })
42-
43- pull(pull.once(blob), bob.add())
44- }, function (err) {
45- if(err) throw err
46- t.end()
47- })
48- })
49-
50- tape('want - has 2', function (t) {
51- createAsync(function (async) {
52- var alice = createBlobs('alice', async)
53- var bob = createBlobs('bob', async)
54- var blob = Fake('foobar', 64)
55- var h = hash(blob)
56-
57- u.peers('bob', bob, 'alice', alice)
58- pull(pull.once(blob), bob.add())
59-
60- alice.want(h, function (err, has) {
61- if(err) throw err
62- alice.has(h, function (err, has) {
63- if(err) throw err
64- assert.ok(has)
65- async.done()
66- })
67- })
68- }, function (err) {
69- if(err) throw err
70- t.end()
71- })
72- })
73-
74- tape('want - want -has', function (t) {
75- createAsync(function (async) {
76- var alice = createBlobs('alice', async)
77- var bob = createBlobs('bob', async)
78- var carol = createBlobs('carol', async)
79-
80- var blob = Fake('baz', 64)
81- var h = hash(blob)
82-
83- u.peers('alice', alice, 'bob', bob)
84- u.peers('bob', bob, 'carol', carol)
85-
86- alice.want(h, function (err, has) {
87- if(err) throw err
88- alice.has(h, function (err, has) {
89- if(err) throw err
90- assert.ok(has)
91- async.done()
92- })
93- })
94-
95- pull(pull.once(blob), carol.add())
96- }, function (err) {
97- if(err) throw err
98- t.end()
99- })
100- })
101-
102-
103- tape('peers want what you have', function (t) {
104- createAsync(function (async) {
105- if(Array.isArray(process._events['exit']))
106- console.log(process._events['exit'].reverse())
107- var alice = createBlobs('alice', async)
108- var bob = createBlobs('bob', async)
109- var carol = createBlobs('carol', async)
110-
111- var blob = Fake('baz', 64)
112- var h = hash(blob)
113-
114- u.peers('alice', alice, 'bob', bob)
115- u.peers('bob', bob, 'carol', carol)
116-
117- pull(
118- carol.changes(),
119- pull.drain(function (_h) {
120- assert.equal(_h, h)
121- async.done()
122- })
123- )
124-
125- alice.want(h, function () {})
126- pull(pull.once(blob), alice.add())
127- }, function (err) {
128- if(err) throw err
129- t.end()
130- })
131- })
132-
133-
134- tape('triangle', function (t) {
135- createAsync(function (async) {
136- var n = 0
137- var alice = createBlobs('alice', async)
138- var bob = createBlobs('bob', async)
139- var carol = createBlobs('carol', async)
140-
141- var blob = Fake('baz', 64)
142- var h = hash(blob)
143-
144- u.peers('alice', alice, 'bob', bob)
145- u.peers('bob', bob, 'carol', carol)
146-
147- pull(
148- bob.changes(),
149- pull.drain(function (_h) {
150- assert.equal(_h, h)
151- async.done()
152- })
153- )
154-
155- pull(pull.once(blob), alice.add())
156- pull(pull.once(blob), carol.add())
157-
158- bob.want(h, function () {})
159- }, function (err) {
160- if(err) throw err
161- t.end()
162- })
163- })
164-
165- tape('corrupt', function (t) {
166- createAsync(function (async) {
167- var n = 0
168- var alice = createBlobs('alice', async)
169- var bob = createBlobs('bob', async)
170- var carol = createBlobs('carol', async)
171-
172- //everything that comes from bob is corrupt
173- var get = alice.get
174- alice.get = function (id) {
175- return pull(get(id), bitflipper(1))
176- }
177-
178- var blob = Fake('baz', 64)
179- var h = hash(blob)
180-
181- u.peers('alice', alice, 'bob', bob)
182- u.peers('bob', bob, 'carol', carol)
183-
184- pull(
185- bob.changes(),
186- pull.drain(function (_h) {
187- console.log('HAS', _h)
188- assert.equal(_h, h)
189- async.done()
190- })
191- )
192-
193- bob.want(h, function () {})
194- pull(pull.once(blob), alice.add())
195- pull(pull.once(blob), carol.add())
196- }, function (err) {
197- if(err) throw err
198- t.end()
199- })
200- })
201-
202- tape('cycle', function (t) {
203- createAsync(function (async) {
204- var n = 0
205- var alice = createBlobs('alice', async)
206- var bob = createBlobs('bob', async)
207- var carol = createBlobs('carol', async)
208- var dan = createBlobs('dan', async)
209- u.peers('alice', alice, 'bob', bob)
210- u.peers('bob', bob, 'carol', carol)
211- u.peers('carol', carol, 'dan', dan)
212- u.peers('dan', dan, 'alice', alice)
213-
214- var blob = Fake('gurg', 64)
215- var h = hash(blob)
216- alice.want(h, function (err, has) {
217- async.done()
218- })
219-
220- pull(pull.once(blob), dan.add(h))
221- }, function (err) {
222- if(err) throw err
223- t.end()
224- })
225-
226- })
227-}
228-
229-if(!module.parent)
230- u.tests(module.exports)
231-
232-
233-
plugins/ssb-blobs/test/legacy.jsView
@@ -1,113 +1,0 @@
1-var tape = require('tape')
2-var Blobs = require('../inject')
3-var pull = require('pull-stream')
4-var bitflipper = require('pull-bitflipper')
5-var assert = require('assert')
6-
7-var u = require('./util')
8-var Fake = u.fake
9-var hash = u.hash
10-
11-module.exports = function (createBlobs, createAsync) {
12-
13- //client is legacy. call has on a peer, should emit want({<id>: -2})
14-
15- tape('legacy calls modern', function (t) {
16- createAsync(function (async) {
17- //legacy tests
18-
19- var n = 0
20-
21- var modern = createBlobs('modern', async)
22-
23- var blob = Fake('foo', 100)
24- var h = hash(blob)
25-
26- var first = {}
27- first[h] = -2
28- var second = {}
29- second[h] = blob.length
30- var expected = [{}, first, second]
31-
32- //the most important thing is that a modern blobs
33- //plugin emits 2nd hand hops when someone calls has(hash)
34-
35- pull(
36- modern.createWants(),
37- pull.drain(function (req) {
38- n++
39- assert.deepEqual(req, expected.shift())
40- })
41- )
42-
43- pull(modern.changes(), pull.drain(function (hash) {
44- assert.equal(hash, h)
45- pull(modern.get(hash), pull.collect(function (err, ary) {
46- assert.deepEqual(Buffer.concat(ary), blob)
47- assert.equal(n, 3)
48- async.done()
49- }))
50- }))
51-
52- modern.has.call({id: 'other'}, h, function (err, value) {
53- if(err) throw err
54- t.equal(value, false)
55- pull(pull.once(blob), modern.add(function (err, hash) {
56- if(err) throw err
57- }))
58- })
59-
60- }, function (err) {
61- if(err) throw err
62- t.end()
63- })
64-
65- })
66-
67- tape('modern calls legacy', function (t) {
68- createAsync(function (async) {
69-
70- var modern = createBlobs('modern', async)
71- var legacy = createBlobs('legacy', async)
72-
73- var size = legacy.size
74- legacy.size = function (hashes, cb) {
75- console.log("CALLED_SIZE", hashes)
76- size.call(this, hashes, function (err, value) {
77- console.log('SIZES', err, value)
78- cb(err, value)
79- })
80- }
81-
82- legacy.createWants = function () {
83- var err = new Error('cannot call apply of null')
84- err.name = 'TypeError'
85- return pull.error(err)
86- }
87-
88- u.peers('modern', modern, 'legacy', legacy)
89-
90- var blob = Fake('bar', 101)
91- var h = hash(blob)
92-
93- modern.want(h, function (err, has) {
94- async.done()
95- })
96-
97- pull(pull.once(blob), legacy.add(function (err, _h) {
98- assert.equal(_h, h)
99- console.log('ADDED', _h)
100- }))
101-
102- }, function (err) {
103- console.log(err)
104- if(err) throw err
105- t.end()
106- })
107- })
108-
109-}
110-
111-if(!module.parent) u.tests(module.exports)
112-
113-
plugins/ssb-blobs/test/mock/blobs.jsView
@@ -1,98 +1,0 @@
1-var pull = require('pull-stream')
2-var crypto = require('crypto')
3-var cont = require('cont')
4-var Notify = require('pull-notify')
5-var assert = require('assert')
6-
7-function hash(buf) {
8- buf = 'string' == typeof buf ? new Buffer(buf) : buf
9- return '&'+crypto.createHash('sha256')
10- .update(buf).digest('base64')+'.sha256'
11-}
12-
13-function single (fn) {
14- var waiting = {}
15- return function (value, cb) {
16- if(!waiting[value]) {
17- waiting[value] = [cb]
18- fn(value, function done (err, result) {
19- var cbs = waiting[value]
20- delete waiting[value]
21- while(cbs.length) cbs.shift()(err, result)
22- })
23- }
24- else
25- waiting[value].push(cb)
26- }
27-}
28-
29-module.exports = function MockBlobStore (name, async) {
30-
31- var notify = Notify()
32-
33- var store = {}
34- function add (buf, _h) {
35- var h = hash(buf)
36- if(_h && _h != h) return false
37- store[h] = buf
38- return h
39- }
40-
41- function all(fn) {
42- return function (value) {
43- return Array.isArray(value) ? cont.para(value.map(function (e) { return fn(e) })) : fn(value)
44- }
45- }
46-
47- function toAsync (fn, name) {
48- return async(function (value, cb) {
49- fn(value)(function (err, value) {
50- async(cb, name+'-cb')(err, value)
51- })
52- }, name)
53- }
54-
55- return {
56- store: store,
57- get: function (blobId) {
58- if(!store[blobId])
59- return pull(pull.error(new Error('no blob:'+blobId)), async.through('get-error'))
60- return pull(pull.values([store[blobId]]), async.through('get'))
61- },
62- has: single(toAsync(all(cont(function (blobId, cb) {
63- cb(null, store[blobId] ? true : false)
64- })), 'has')),
65- size: single(toAsync(all(cont(function (blobId, cb) {
66- cb(null, store[blobId] ? store[blobId].length : null)
67- })), 'size')),
68- ls: function (opts) {
69- //don't implement all the options, just the ones needed by ssb-blobs
70- assert.equal(opts.old, false, 'must have old')
71- if(opts.meta) //used internally.
72- return notify.listen()
73- else //used as blobs.changes (legacy api)
74- return pull(notify.listen(), pull.map(function (e) { return e.id }))
75- },
76- add: function (_hash, cb) {
77- if('function' == typeof _hash)
78- cb = _hash, _hash = null
79- if(!cb) cb = function (err) {
80- if(err) throw err
81- }
82- return pull(async.through('add'), pull.collect(async(function (err, data) {
83- if(err) return cb(err)
84- data = Buffer.concat(data)
85- var h = add(data, _hash)
86- console.log('..ADDED', name, h)
87- if(!h) cb(new Error('wrong hash'))
88- else {
89- notify({id: h, size: data.length, ts: Date.now()})
90- cb(null, h)
91- }
92- }, 'add-cb')))
93- }
94- }
95-}
96-
97-
98-
plugins/ssb-blobs/test/mock/set.jsView
@@ -1,20 +1,0 @@
1-module.exports = function (async) {
2- console.log(async)
3- var set = {}
4- return {
5- set: set,
6- add: async(function (key, cb) {
7- set[key] = true
8- cb && async(cb)()
9- }),
10- remove: async(function (key, cb) {
11- delete set[key]
12- cb && async(cb)()
13- })
14- }
15-}
16-
17-
18-
19-
20-
plugins/ssb-blobs/test/push.jsView
@@ -1,65 +1,0 @@
1-var tape = require('tape')
2-var Blobs = require('../inject')
3-var pull = require('pull-stream')
4-var assert = require('assert')
5-var cont = require('cont')
6-var u = require('./util')
7-var Fake = u.fake
8-var hash = u.hash
9-
10-module.exports = function (createBlobs, createAsync) {
11- /*
12- push
13- tell peers about a blob that you have,
14- that you want other peers to want.
15-
16- stores pushed blobs in a durable log (eg: leveldb)
17- so that after a restart, push will still happen.
18- (incase you write a message offline, then restart)
19-
20- */
21-
22- tape('push 3', function (t) {
23- createAsync(function (async) {
24- var n = 0
25- var alice = createBlobs('alice', async)
26- var bob = createBlobs('bob', async)
27- var carol = createBlobs('carol', async)
28- var dan = createBlobs('dan', async)
29-
30- var blob = Fake('baz', 64)
31- var h = hash(blob)
32-
33- u.peers('alice', alice, 'bob', bob)
34- u.peers('alice', alice, 'carol', carol)
35- u.peers('alice', alice, 'dan', dan)
36-
37- pull(alice.pushed(), pull.drain(function (data) {
38- assert.deepEqual(data, {key: h, peers: {bob: 64, carol: 64, dan: 64}})
39- console.log("PUSHED", data)
40- cont.para([bob, carol, dan].map(function (p) {
41- return cont(p.has)(h)
42- }))
43- (function (err, ary) {
44- console.log('HAS', err, ary)
45- if(err) throw err
46- assert.deepEqual(ary, [true, true, true])
47- async.done()
48- })
49- }))
50-
51- pull(pull.once(blob), alice.add())
52- alice.push(h)
53-
54- }, function (err) {
55- if(err) throw err
56- t.end()
57- })
58- })
59-
60-}
61-
62-if(!module.parent) u.tests(module.exports)
63-
64-
65-
plugins/ssb-blobs/test/real.jsView
@@ -1,36 +1,0 @@
1-
2-var rimraf = require('rimraf')
3-var osenv = require('osenv')
4-var path = require('path')
5-var ref = require('ssb-ref')
6-var mkdirp = require('mkdirp')
7-var level = require('level')
8-
9-var Blobs = require('../inject')
10-var create = require('../create')
11-
12-function test_create(name, async) {
13- var dir = path.join(
14- osenv.tmpdir(),
15- 'test-blobstore_'+Date.now()+'_'+name
16- )
17- rimraf.sync(dir)
18- mkdirp.sync(dir)
19- return Blobs(
20- create(dir),
21- require('../set')(level(dir, {valueEncoding: 'json'})),
22- name
23- )
24-}
25-
26-//since we are using the real FS this time,
27-//we don't need to apply fake async.
28-var sync = require('./util').sync
29-require('./simple')(test_create, sync)
30-require('./integration')(test_create, sync)
31-require('./legacy')(test_create, sync)
32-require('./push')(test_create, sync)
33-
34-
35-
36-
plugins/ssb-blobs/test/secret-stack.jsView
@@ -1,54 +1,0 @@
1-var SecretStack = require('secret-stack')
2-var crypto = require('crypto')
3-var tape = require('tape')
4-var path = require('path')
5-var osenv = require('osenv')
6-var mkdirp = require('mkdirp')
7-var pull = require('pull-stream')
8-
9-//deterministic keys make testing easy.
10-function hash (s) {
11- return crypto.createHash('sha256').update(s).digest()
12-}
13-
14-var appkey = hash('TESTBLOBS')
15-
16-var create = SecretStack({ appKey: appkey }).use(require('../'))
17-
18-function tmp (name) {
19- var dir = path.join(osenv.tmpdir(), 'testblobs-'+Date.now()+'-'+name)
20- mkdirp.sync(dir)
21- return dir
22-}
23-
24-var alice = create({ seed: hash('ALICE'), path: tmp('alice') })
25-var bob = create({ seed: hash('BOB'), path: tmp('bob') })
26-
27-tape('alice pushes to bob', function (t) {
28-
29- alice.connect(bob.address(), function (err, rpc) {
30- if(err) throw err
31- })
32-
33- var hello = new Buffer('Hello World'), _hash
34-
35- pull(
36- bob.blobs.ls({live: true, long: true}),
37- pull.take(1),
38- pull.collect(function (err, ary) {
39- t.equal(ary[0].id, _hash)
40- t.end()
41- alice.close()
42- bob.close()
43- })
44- )
45-
46- pull(
47- pull.values([hello]),
48- alice.blobs.add(function (err, hash) {
49- _hash = hash
50- alice.blobs.push(hash)
51- })
52- )
53-})
54-
plugins/ssb-blobs/test/simple.jsView
@@ -1,157 +1,0 @@
1-var tape = require('tape')
2-var pull = require('pull-stream')
3-var assert = require('assert')
4-
5-var u = require('./util')
6-var Fake = u.fake
7-var hash = u.hash
8-
9-module.exports = function (createBlobs, createAsync) {
10-
11- //if a peer is recieves a WANT for a blob they have,
12- //it responds with a HAS
13- tape('simple', function (t) {
14- createAsync(function (async) {
15- var blobs = createBlobs('simple', async)
16- console.log(blobs)
17- var b = Fake('hello', 256), h = hash(b)
18- pull(pull.once(b), async.through(), blobs.add(h, function (err, _h) {
19- if(err) throw err
20- console.log('added', _h)
21- t.equal(_h, h)
22-
23- var req = {}
24- req[h] = 0
25- var res = {}
26- res[h] = 256
27-
28- pull(
29- pull.once(req),
30- async.through(),
31- blobs._wantSink({id: 'test'})
32- )
33-
34- pull(
35- blobs.createWants.call({id: 'test'}),
36- async.through(),
37- pull.take(2),
38- pull.collect(function (err, _res) {
39- if(err) throw err
40- console.log("_RES", _res)
41- assert.deepEqual(_res, [{}, res])
42- async.done()
43- })
44- )
45- }))
46- }, function (err, results) {
47- if(err) throw err
48- t.end()
49- })
50- })
51-
52- //if you receive a want, sympathetically want that too.
53- //but only once.
54- tape('simple wants', function (t) {
55- createAsync(function (async) {
56- var blobs = createBlobs('simple', async)
57-
58- var b = Fake('hello', 256), h = hash(b)
59-
60- var req = {}
61- req[h] = -1
62- var res = {}
63- res[h] = -2
64-
65- pull(
66- //also send {<hash>: -1} which simulates a cycle.
67- pull.values([req, res]),
68- async.through(),
69- blobs._wantSink({id: 'test'})
70- )
71-
72- var c = 0
73-
74- pull(
75- blobs.createWants.call({id: 'test'}),
76- async.through(),
77- pull.take(2),
78- pull.collect(function (err, _res) {
79- // c++
80- console.log('END', c, _res)
81- assert.deepEqual(_res, [{}, res])
82- async.done()
83-// throw new Error('called thrice')
84- })
85- )
86-
87- }, function (err, results) {
88- console.log(err)
89- if(err) throw err
90- t.end()
91- })
92- })
93-
94- //if you want something, tell your peer.
95- tape('want', function (t) {
96- createAsync(function (async) {
97- var blobs = createBlobs('want', async)
98- var h = hash(Fake('foobar', 64))
99- var res = {}
100- res[h] = -1
101-
102- pull(
103- blobs.createWants.call({id: 'test'}),
104- async.through(),
105- pull.take(2),
106- pull.collect(function (err, _res) {
107- if(err) throw err
108- //requests
109- assert.deepEqual(_res, [{}, res])
110- async.done()
111- })
112- )
113-
114- blobs.want(h)
115-
116- }, function (err, results) {
117- if(err) throw err
118- //t.deepEqual(_res, res)
119- t.end()
120- })
121- })
122-
123- //if you want something, tell your peer.
124- tape('already wanted, before connection', function (t) {
125- createAsync(function (async) {
126- var blobs = createBlobs('want', async)
127- var h = hash(Fake('foobar', 64))
128- var res = {}
129- res[h] = -1
130-
131- blobs.want(h)
132-
133- pull(
134- blobs.createWants.call({id: 'test'}),
135- async.through(),
136- pull.find(null, function (err, _res) {
137- if(err) throw err
138- //requests
139- t.deepEqual(_res, res)
140- async.done()
141- })
142- )
143-
144- }, function (err, results) {
145- if(err) throw err
146- //t.deepEqual()
147- t.end()
148- })
149- })
150-
151-}
152-
153-
154-if(!module.parent) u.tests(module.exports)
155-
156-
157-
plugins/ssb-blobs/test/util.jsView
@@ -1,61 +1,0 @@
1-var pull = require('pull-stream')
2-var crypto = require('crypto')
3-
4-var log = exports.log = function log (name) {
5- if(process.env.DEBUG)
6- return pull.through(function (e) {
7- console.log(name, e)
8- })
9- else
10- return pull.through()
11-}
12-
13-function bindAll(obj, context) {
14- var o = {}
15- for(var k in obj)
16- o[k] = obj[k].bind(context)
17- return o
18-}
19-
20-exports.peers = function (nameA, a, nameB, b, async) {
21- var na = nameA[0].toUpperCase(), nb = nameB[0].toUpperCase()
22- //this is just a hack to fake RPC. over rpc each method is called
23- //with the remote id in the current this context.
24- a._onConnect({id: nameB, blobs: bindAll(b, {id: nameA})}, nb+na)
25- b._onConnect({id: nameA, blobs: bindAll(a, {id: nameB})}, na+nb)
26-}
27-
28-
29-exports.hash = function (buf) {
30- buf = 'string' == typeof buf ? new Buffer(buf) : buf
31- return '&'+crypto.createHash('sha256')
32- .update(buf).digest('base64')+'.sha256'
33-}
34-
35-exports.fake = function (string, length) {
36- var b = new Buffer(length)
37- var n = Buffer.byteLength(string)
38- for(var i = 0; i < length; i += n)
39- b.write(string, i)
40- return b
41-}
42-
43-
44-exports.sync = function noAsync (test, done) {
45- function async(fn) {
46- return fn
47- }
48- async.through = function () { return pull.through() }
49- async.done = done
50- test(async)
51-}
52-
53-exports.tests = function (tests) {
54- tests(function (name, async) {
55- return require('../inject')(
56- require('./mock/blobs')(name, async),
57- require('./mock/set')(async),
58- name
59- )
60- }, exports.sync)
61-}
plugins/ssb-client/README.mdView
@@ -1,31 +1,0 @@
1-# ssb-client v2
2-
3-[Scuttlebot](https://github.com/ssbc/scuttlebot) client.
4-
5-```js
6-var ssbClient = require('ssb-client')
7-
8-// simplest usage, connect to localhost sbot
9-ssbClient(function (err, sbot) {
10- // ...
11-})
12-
13-// configuration:
14-var keys = ssbKeys.loadOrCreateSync('./app-private.key')
15-ssbClient(
16- keys, // optional, defaults to ~/.ssb/secret
17- {
18- host: 'localhost', // optional, defaults to localhost
19- port: 8008, // optional, defaults to 8008
20- key: keys.id // optional, defaults to keys.id
21- },
22- function (err, sbot) {
23- // ...
24- }
25-)
26-
27-```
28-
29-## License
30-
31-MIT, Copyright 2015 Paul Frazee and Dominic Tarr
plugins/ssb-client/index.jsView
@@ -1,101 +1,0 @@
1-'use strict'
2-var path = require('path')
3-var ssbKeys = require('ssb-keys')
4-var explain = require('explain-error')
5-var path = require('path')
6-var fs = require('fs')
7-
8-var MultiServer = require('multiserver')
9-var WS = require('multiserver/plugins/ws')
10-var Net = require('multiserver/plugins/net')
11-var Onion = require('multiserver/plugins/onion')
12-var Shs = require('multiserver/plugins/shs')
13-
14-var muxrpc = require('muxrpc')
15-var pull = require('pull-stream')
16-
17-function toSodiumKeys(keys) {
18- if(!keys || !keys.public) return null
19- return {
20- publicKey:
21- new Buffer(keys.public.replace('.ed25519',''), 'base64'),
22- secretKey:
23- new Buffer(keys.private.replace('.ed25519',''), 'base64'),
24- }
25-}
26-
27-//load cap from config instead!
28-var cap = 'EVRctE2Iv8GrO/BpQCF34e2FMPsDJot9x0j846LjVtc='
29-
30-var createConfig = require('../ssb-config/inject')
31-
32-module.exports = function (keys, opts, cb) {
33- var config
34- if (typeof keys == 'function') {
35- cb = keys
36- keys = null
37- opts = null
38- }
39- else if (typeof opts == 'function') {
40- cb = opts
41- opts = keys
42- keys = null
43- }
44- if(typeof opts === 'string' || opts == null || !keys)
45- config = createConfig((typeof opts === 'string' ? opts : null) || process.env.ssb_appname)
46- else if(opts && 'object' === typeof opts)
47- config = opts
48-
49- keys = keys || ssbKeys.loadOrCreateSync(path.join(config.path, 'secret'))
50- opts = opts || {}
51-
52- var appKey = new Buffer((opts.caps && opts.caps.shs) || cap, 'base64')
53-
54- var remote
55- if(opts.remote)
56- remote = opts.remote
57- else {
58- var host = opts.host || 'localhost'
59- var port = opts.port || config.port || 8008
60- var key = opts.key || keys.id
61-
62- var protocol = 'net:'
63- if (host.endsWith(".onion"))
64- protocol = 'onion:'
65- remote = protocol+host+':'+port+'~shs:'+key.substring(1).replace('.ed25519', '')
66- }
67-
68- var manifest = opts.manifest || (function () {
69- try {
70- return JSON.parse(fs.readFileSync(
71- path.join(config.path, 'manifest.json')
72- ))
73- } catch (err) {
74- throw explain(err, 'could not load manifest file')
75- }
76- })()
77-
78- var shs = Shs({
79- keys: toSodiumKeys(keys),
80- appKey: opts.appKey || appKey,
81-
82- //no client auth. we can't receive connections anyway.
83- auth: function (cb) { cb(null, false) },
84- timeout: config.timers && config.timers.handshake || 3000
85- })
86-
87- var ms = MultiServer([
88- [Net({}), shs],
89- [Onion({}), shs],
90- [WS({}), shs]
91- ])
92-
93- ms.client(remote, function (err, stream) {
94- if(err) return cb(explain(err, 'could not connect to sbot'))
95- var sbot = muxrpc(manifest, false)()
96- sbot.id = '@'+stream.remote.toString('base64')+'.ed25519'
97- pull(stream, sbot.createStream(), stream)
98- cb(null, sbot, config)
99- })
100-}
101-
plugins/ssb-client/package.jsonView
@@ -1,30 +1,0 @@
1-{
2- "name": "ssb-client",
3- "version": "4.4.0",
4- "description": "scuttlebot client",
5- "main": "index.js",
6- "scripts": {
7- "test": "set -e; for t in test/*.js; do node $t; done"
8- },
9- "repository": {
10- "type": "git",
11- "url": "https://github.com/ssbc/ssb-client.git"
12- },
13- "dependencies": {
14- "explain-error": "^1.0.1",
15- "multiserver": "^1.7.0",
16- "muxrpc": "^6.3.3",
17- "ssb-config": "^2.0.0",
18- "ssb-keys": "^6.0.0"
19- },
20- "devDependencies": {
21- "scuttlebot": "^7.3.4",
22- "tape": "^4.2.0"
23- },
24- "author": "Paul Frazee <pfrazee@gmail.com>",
25- "license": "MIT",
26- "bugs": {
27- "url": "https://github.com/ssbc/ssb-client/issues"
28- },
29- "homepage": "https://github.com/ssbc/ssb-client"
30-}
plugins/ssb-client/test/index.jsView
@@ -1,34 +1,0 @@
1-var tape = require('tape')
2-var ssbKeys = require('ssb-keys')
3-var ssbServer = require('scuttlebot')
4- .use(require('scuttlebot/plugins/master'))
5-
6-var ssbClient = require('../')
7-
8-var keys = ssbKeys.generate()
9-var server = ssbServer({
10- port: 45451, timeout: 2001,
11- temp: 'connect',
12- host: 'localhost',
13- master: keys.id,
14- keys: keys
15-})
16-
17-tape('connect', function (t) {
18-
19- ssbClient(keys, { port: 45451, manifest: server.manifest() }, function (err, client) {
20- if (err) throw err
21-
22- client.whoami(function (err, info) {
23- if (err) throw err
24-
25- console.log('whoami', info)
26- t.equal(info.id, keys.id)
27- t.end()
28- client.close(true)
29- server.close(true)
30- process.exit(0)
31- })
32- })
33-
34-})
plugins/ssb-config/.npmignoreView
@@ -1,1 +1,0 @@
1-node_modules
plugins/ssb-config/LICENSEView
@@ -1,24 +1,0 @@
1-The MIT License (MIT)
2-
3-Copyright (c) 2015 Dominic Tarr
4-
5-Permission is hereby granted, free of charge,
6-to any person obtaining a copy of this software and
7-associated documentation files (the "Software"), to
8-deal in the Software without restriction, including
9-without limitation the rights to use, copy, modify,
10-merge, publish, distribute, sublicense, and/or sell
11-copies of the Software, and to permit persons to whom
12-the Software is furnished to do so,
13-subject to the following conditions:
14-
15-The above copyright notice and this permission notice
16-shall be included in all copies or substantial portions of the Software.
17-
18-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
22-ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
plugins/ssb-config/README.mdView
@@ -1,35 +1,0 @@
1-# ssb-config
2-
3-Configuration module used by [`scuttlebot`](https://github.com/ssbc/scuttlebot).
4-
5-## example
6-
7-``` js
8-var config = require('ssb-config')
9-
10-//if you want to set up a test network, that
11-//doesn't collide with main ssb pass the name of that network in.
12-
13-var test_config = require('ssb-config/inject')('testnet', {port: 9999})
14-//you can also pass a second argument, which overrides the default defaults.
15-```
16-
17-## Configuration
18-
19-* `host` *(string)* The domain or ip address for `sbot`. Defaults to your public ip address.
20-* `port` *(string|number)* The port for `sbot`. Defaults to `8008`.
21-* `timeout`: *(number)* Number of milliseconds a replication stream can idle before it's automatically disconnected. Defaults to `30000`.
22-* `pub` *(boolean)* Replicate with pub servers. Defaults to `true`.
23-* `local` *(boolean)* Replicate with local servers found on the same network via `udp`. Defaults to `true`.
24-* `friends.dunbar` *(number)* [`Dunbar's number`](https://en.wikipedia.org/wiki/Dunbar%27s_number). Number of nodes your instance will replicate. Defaults to `150`.
25-* `friends.hops` *(number)* How many friend of friend hops to replicate. Defaults to `3`.
26-* `gossip.connections` *(number)* How many other nodes to connect with at one time. Defaults to `2`.
27-* `path` *(string)* Path to the application data folder, which contains the private key, message attachment data (blobs) and the leveldb backend. Defaults to `$HOME/.ssb`.
28-* `master` *(array)* Pubkeys of users who, if they connect to the Scuttlebot instance, are allowed to command the primary user with full rights. Useful for remotely operating a pub. Defaults to `[]`.
29-* `logging.level` *(string)* How verbose should the logging be. Possible values are error, warning, notice, and info. Defaults to `notice`.
30-
31-There are some configuration options for the sysadmins out there. All configuration is loaded via [`rc`](https://github.com/dominictarr/rc). You can pass any configuration value in as cli arg, env var, or in a file.
32-
33-## License
34-
35-MIT
plugins/ssb-config/b.jsView
@@ -1,3083 +1,0 @@
1-(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2-
3-},{}],2:[function(require,module,exports){
4-'use strict'
5-
6-exports.toByteArray = toByteArray
7-exports.fromByteArray = fromByteArray
8-
9-var lookup = []
10-var revLookup = []
11-var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
12-
13-function init () {
14- var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
15- for (var i = 0, len = code.length; i < len; ++i) {
16- lookup[i] = code[i]
17- revLookup[code.charCodeAt(i)] = i
18- }
19-
20- revLookup['-'.charCodeAt(0)] = 62
21- revLookup['_'.charCodeAt(0)] = 63
22-}
23-
24-init()
25-
26-function toByteArray (b64) {
27- var i, j, l, tmp, placeHolders, arr
28- var len = b64.length
29-
30- if (len % 4 > 0) {
31- throw new Error('Invalid string. Length must be a multiple of 4')
32- }
33-
34- // the number of equal signs (place holders)
35- // if there are two placeholders, than the two characters before it
36- // represent one byte
37- // if there is only one, then the three characters before it represent 2 bytes
38- // this is just a cheap hack to not do indexOf twice
39- placeHolders = b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0
40-
41- // base64 is 4/3 + up to two characters of the original data
42- arr = new Arr(len * 3 / 4 - placeHolders)
43-
44- // if there are placeholders, only get up to the last complete 4 chars
45- l = placeHolders > 0 ? len - 4 : len
46-
47- var L = 0
48-
49- for (i = 0, j = 0; i < l; i += 4, j += 3) {
50- tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)]
51- arr[L++] = (tmp >> 16) & 0xFF
52- arr[L++] = (tmp >> 8) & 0xFF
53- arr[L++] = tmp & 0xFF
54- }
55-
56- if (placeHolders === 2) {
57- tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4)
58- arr[L++] = tmp & 0xFF
59- } else if (placeHolders === 1) {
60- tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2)
61- arr[L++] = (tmp >> 8) & 0xFF
62- arr[L++] = tmp & 0xFF
63- }
64-
65- return arr
66-}
67-
68-function tripletToBase64 (num) {
69- return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
70-}
71-
72-function encodeChunk (uint8, start, end) {
73- var tmp
74- var output = []
75- for (var i = start; i < end; i += 3) {
76- tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
77- output.push(tripletToBase64(tmp))
78- }
79- return output.join('')
80-}
81-
82-function fromByteArray (uint8) {
83- var tmp
84- var len = uint8.length
85- var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
86- var output = ''
87- var parts = []
88- var maxChunkLength = 16383 // must be multiple of 3
89-
90- // go through the array every three bytes, we'll deal with trailing stuff later
91- for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
92- parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
93- }
94-
95- // pad the end with zeros, but make sure to not forget the extra bytes
96- if (extraBytes === 1) {
97- tmp = uint8[len - 1]
98- output += lookup[tmp >> 2]
99- output += lookup[(tmp << 4) & 0x3F]
100- output += '=='
101- } else if (extraBytes === 2) {
102- tmp = (uint8[len - 2] << 8) + (uint8[len - 1])
103- output += lookup[tmp >> 10]
104- output += lookup[(tmp >> 4) & 0x3F]
105- output += lookup[(tmp << 2) & 0x3F]
106- output += '='
107- }
108-
109- parts.push(output)
110-
111- return parts.join('')
112-}
113-
114-},{}],3:[function(require,module,exports){
115-(function (global){
116-/*!
117- * The buffer module from node.js, for the browser.
118- *
119- * @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
120- * @license MIT
121- */
122-/* eslint-disable no-proto */
123-
124-'use strict'
125-
126-var base64 = require('base64-js')
127-var ieee754 = require('ieee754')
128-var isArray = require('isarray')
129-
130-exports.Buffer = Buffer
131-exports.SlowBuffer = SlowBuffer
132-exports.INSPECT_MAX_BYTES = 50
133-
134-/**
135- * If `Buffer.TYPED_ARRAY_SUPPORT`:
136- * === true Use Uint8Array implementation (fastest)
137- * === false Use Object implementation (most compatible, even IE6)
138- *
139- * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
140- * Opera 11.6+, iOS 4.2+.
141- *
142- * Due to various browser bugs, sometimes the Object implementation will be used even
143- * when the browser supports typed arrays.
144- *
145- * Note:
146- *
147- * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
148- * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
149- *
150- * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
151- *
152- * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
153- * incorrect length in some situations.
154-
155- * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
156- * get the Object implementation, which is slower but behaves correctly.
157- */
158-Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined
159- ? global.TYPED_ARRAY_SUPPORT
160- : typedArraySupport()
161-
162-/*
163- * Export kMaxLength after typed array support is determined.
164- */
165-exports.kMaxLength = kMaxLength()
166-
167-function typedArraySupport () {
168- try {
169- var arr = new Uint8Array(1)
170- arr.foo = function () { return 42 }
171- return arr.foo() === 42 && // typed array instances can be augmented
172- typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
173- arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
174- } catch (e) {
175- return false
176- }
177-}
178-
179-function kMaxLength () {
180- return Buffer.TYPED_ARRAY_SUPPORT
181- ? 0x7fffffff
182- : 0x3fffffff
183-}
184-
185-function createBuffer (that, length) {
186- if (kMaxLength() < length) {
187- throw new RangeError('Invalid typed array length')
188- }
189- if (Buffer.TYPED_ARRAY_SUPPORT) {
190- // Return an augmented `Uint8Array` instance, for best performance
191- that = new Uint8Array(length)
192- that.__proto__ = Buffer.prototype
193- } else {
194- // Fallback: Return an object instance of the Buffer class
195- if (that === null) {
196- that = new Buffer(length)
197- }
198- that.length = length
199- }
200-
201- return that
202-}
203-
204-/**
205- * The Buffer constructor returns instances of `Uint8Array` that have their
206- * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
207- * `Uint8Array`, so the returned instances will have all the node `Buffer` methods
208- * and the `Uint8Array` methods. Square bracket notation works as expected -- it
209- * returns a single octet.
210- *
211- * The `Uint8Array` prototype remains unmodified.
212- */
213-
214-function Buffer (arg, encodingOrOffset, length) {
215- if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {
216- return new Buffer(arg, encodingOrOffset, length)
217- }
218-
219- // Common case.
220- if (typeof arg === 'number') {
221- if (typeof encodingOrOffset === 'string') {
222- throw new Error(
223- 'If encoding is specified then the first argument must be a string'
224- )
225- }
226- return allocUnsafe(this, arg)
227- }
228- return from(this, arg, encodingOrOffset, length)
229-}
230-
231-Buffer.poolSize = 8192 // not used by this implementation
232-
233-// TODO: Legacy, not needed anymore. Remove in next major version.
234-Buffer._augment = function (arr) {
235- arr.__proto__ = Buffer.prototype
236- return arr
237-}
238-
239-function from (that, value, encodingOrOffset, length) {
240- if (typeof value === 'number') {
241- throw new TypeError('"value" argument must not be a number')
242- }
243-
244- if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
245- return fromArrayBuffer(that, value, encodingOrOffset, length)
246- }
247-
248- if (typeof value === 'string') {
249- return fromString(that, value, encodingOrOffset)
250- }
251-
252- return fromObject(that, value)
253-}
254-
255-/**
256- * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
257- * if value is a number.
258- * Buffer.from(str[, encoding])
259- * Buffer.from(array)
260- * Buffer.from(buffer)
261- * Buffer.from(arrayBuffer[, byteOffset[, length]])
262- **/
263-Buffer.from = function (value, encodingOrOffset, length) {
264- return from(null, value, encodingOrOffset, length)
265-}
266-
267-if (Buffer.TYPED_ARRAY_SUPPORT) {
268- Buffer.prototype.__proto__ = Uint8Array.prototype
269- Buffer.__proto__ = Uint8Array
270- if (typeof Symbol !== 'undefined' && Symbol.species &&
271- Buffer[Symbol.species] === Buffer) {
272- // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
273- Object.defineProperty(Buffer, Symbol.species, {
274- value: null,
275- configurable: true
276- })
277- }
278-}
279-
280-function assertSize (size) {
281- if (typeof size !== 'number') {
282- throw new TypeError('"size" argument must be a number')
283- }
284-}
285-
286-function alloc (that, size, fill, encoding) {
287- assertSize(size)
288- if (size <= 0) {
289- return createBuffer(that, size)
290- }
291- if (fill !== undefined) {
292- // Only pay attention to encoding if it's a string. This
293- // prevents accidentally sending in a number that would
294- // be interpretted as a start offset.
295- return typeof encoding === 'string'
296- ? createBuffer(that, size).fill(fill, encoding)
297- : createBuffer(that, size).fill(fill)
298- }
299- return createBuffer(that, size)
300-}
301-
302-/**
303- * Creates a new filled Buffer instance.
304- * alloc(size[, fill[, encoding]])
305- **/
306-Buffer.alloc = function (size, fill, encoding) {
307- return alloc(null, size, fill, encoding)
308-}
309-
310-function allocUnsafe (that, size) {
311- assertSize(size)
312- that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)
313- if (!Buffer.TYPED_ARRAY_SUPPORT) {
314- for (var i = 0; i < size; i++) {
315- that[i] = 0
316- }
317- }
318- return that
319-}
320-
321-/**
322- * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
323- * */
324-Buffer.allocUnsafe = function (size) {
325- return allocUnsafe(null, size)
326-}
327-/**
328- * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
329- */
330-Buffer.allocUnsafeSlow = function (size) {
331- return allocUnsafe(null, size)
332-}
333-
334-function fromString (that, string, encoding) {
335- if (typeof encoding !== 'string' || encoding === '') {
336- encoding = 'utf8'
337- }
338-
339- if (!Buffer.isEncoding(encoding)) {
340- throw new TypeError('"encoding" must be a valid string encoding')
341- }
342-
343- var length = byteLength(string, encoding) | 0
344- that = createBuffer(that, length)
345-
346- that.write(string, encoding)
347- return that
348-}
349-
350-function fromArrayLike (that, array) {
351- var length = checked(array.length) | 0
352- that = createBuffer(that, length)
353- for (var i = 0; i < length; i += 1) {
354- that[i] = array[i] & 255
355- }
356- return that
357-}
358-
359-function fromArrayBuffer (that, array, byteOffset, length) {
360- array.byteLength // this throws if `array` is not a valid ArrayBuffer
361-
362- if (byteOffset < 0 || array.byteLength < byteOffset) {
363- throw new RangeError('\'offset\' is out of bounds')
364- }
365-
366- if (array.byteLength < byteOffset + (length || 0)) {
367- throw new RangeError('\'length\' is out of bounds')
368- }
369-
370- if (length === undefined) {
371- array = new Uint8Array(array, byteOffset)
372- } else {
373- array = new Uint8Array(array, byteOffset, length)
374- }
375-
376- if (Buffer.TYPED_ARRAY_SUPPORT) {
377- // Return an augmented `Uint8Array` instance, for best performance
378- that = array
379- that.__proto__ = Buffer.prototype
380- } else {
381- // Fallback: Return an object instance of the Buffer class
382- that = fromArrayLike(that, array)
383- }
384- return that
385-}
386-
387-function fromObject (that, obj) {
388- if (Buffer.isBuffer(obj)) {
389- var len = checked(obj.length) | 0
390- that = createBuffer(that, len)
391-
392- if (that.length === 0) {
393- return that
394- }
395-
396- obj.copy(that, 0, 0, len)
397- return that
398- }
399-
400- if (obj) {
401- if ((typeof ArrayBuffer !== 'undefined' &&
402- obj.buffer instanceof ArrayBuffer) || 'length' in obj) {
403- if (typeof obj.length !== 'number' || isnan(obj.length)) {
404- return createBuffer(that, 0)
405- }
406- return fromArrayLike(that, obj)
407- }
408-
409- if (obj.type === 'Buffer' && isArray(obj.data)) {
410- return fromArrayLike(that, obj.data)
411- }
412- }
413-
414- throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
415-}
416-
417-function checked (length) {
418- // Note: cannot use `length < kMaxLength` here because that fails when
419- // length is NaN (which is otherwise coerced to zero.)
420- if (length >= kMaxLength()) {
421- throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
422- 'size: 0x' + kMaxLength().toString(16) + ' bytes')
423- }
424- return length | 0
425-}
426-
427-function SlowBuffer (length) {
428- if (+length != length) { // eslint-disable-line eqeqeq
429- length = 0
430- }
431- return Buffer.alloc(+length)
432-}
433-
434-Buffer.isBuffer = function isBuffer (b) {
435- return !!(b != null && b._isBuffer)
436-}
437-
438-Buffer.compare = function compare (a, b) {
439- if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
440- throw new TypeError('Arguments must be Buffers')
441- }
442-
443- if (a === b) return 0
444-
445- var x = a.length
446- var y = b.length
447-
448- for (var i = 0, len = Math.min(x, y); i < len; ++i) {
449- if (a[i] !== b[i]) {
450- x = a[i]
451- y = b[i]
452- break
453- }
454- }
455-
456- if (x < y) return -1
457- if (y < x) return 1
458- return 0
459-}
460-
461-Buffer.isEncoding = function isEncoding (encoding) {
462- switch (String(encoding).toLowerCase()) {
463- case 'hex':
464- case 'utf8':
465- case 'utf-8':
466- case 'ascii':
467- case 'binary':
468- case 'base64':
469- case 'raw':
470- case 'ucs2':
471- case 'ucs-2':
472- case 'utf16le':
473- case 'utf-16le':
474- return true
475- default:
476- return false
477- }
478-}
479-
480-Buffer.concat = function concat (list, length) {
481- if (!isArray(list)) {
482- throw new TypeError('"list" argument must be an Array of Buffers')
483- }
484-
485- if (list.length === 0) {
486- return Buffer.alloc(0)
487- }
488-
489- var i
490- if (length === undefined) {
491- length = 0
492- for (i = 0; i < list.length; i++) {
493- length += list[i].length
494- }
495- }
496-
497- var buffer = Buffer.allocUnsafe(length)
498- var pos = 0
499- for (i = 0; i < list.length; i++) {
500- var buf = list[i]
501- if (!Buffer.isBuffer(buf)) {
502- throw new TypeError('"list" argument must be an Array of Buffers')
503- }
504- buf.copy(buffer, pos)
505- pos += buf.length
506- }
507- return buffer
508-}
509-
510-function byteLength (string, encoding) {
511- if (Buffer.isBuffer(string)) {
512- return string.length
513- }
514- if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&
515- (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {
516- return string.byteLength
517- }
518- if (typeof string !== 'string') {
519- string = '' + string
520- }
521-
522- var len = string.length
523- if (len === 0) return 0
524-
525- // Use a for loop to avoid recursion
526- var loweredCase = false
527- for (;;) {
528- switch (encoding) {
529- case 'ascii':
530- case 'binary':
531- // Deprecated
532- case 'raw':
533- case 'raws':
534- return len
535- case 'utf8':
536- case 'utf-8':
537- case undefined:
538- return utf8ToBytes(string).length
539- case 'ucs2':
540- case 'ucs-2':
541- case 'utf16le':
542- case 'utf-16le':
543- return len * 2
544- case 'hex':
545- return len >>> 1
546- case 'base64':
547- return base64ToBytes(string).length
548- default:
549- if (loweredCase) return utf8ToBytes(string).length // assume utf8
550- encoding = ('' + encoding).toLowerCase()
551- loweredCase = true
552- }
553- }
554-}
555-Buffer.byteLength = byteLength
556-
557-function slowToString (encoding, start, end) {
558- var loweredCase = false
559-
560- // No need to verify that "this.length <= MAX_UINT32" since it's a read-only
561- // property of a typed array.
562-
563- // This behaves neither like String nor Uint8Array in that we set start/end
564- // to their upper/lower bounds if the value passed is out of range.
565- // undefined is handled specially as per ECMA-262 6th Edition,
566- // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
567- if (start === undefined || start < 0) {
568- start = 0
569- }
570- // Return early if start > this.length. Done here to prevent potential uint32
571- // coercion fail below.
572- if (start > this.length) {
573- return ''
574- }
575-
576- if (end === undefined || end > this.length) {
577- end = this.length
578- }
579-
580- if (end <= 0) {
581- return ''
582- }
583-
584- // Force coersion to uint32. This will also coerce falsey/NaN values to 0.
585- end >>>= 0
586- start >>>= 0
587-
588- if (end <= start) {
589- return ''
590- }
591-
592- if (!encoding) encoding = 'utf8'
593-
594- while (true) {
595- switch (encoding) {
596- case 'hex':
597- return hexSlice(this, start, end)
598-
599- case 'utf8':
600- case 'utf-8':
601- return utf8Slice(this, start, end)
602-
603- case 'ascii':
604- return asciiSlice(this, start, end)
605-
606- case 'binary':
607- return binarySlice(this, start, end)
608-
609- case 'base64':
610- return base64Slice(this, start, end)
611-
612- case 'ucs2':
613- case 'ucs-2':
614- case 'utf16le':
615- case 'utf-16le':
616- return utf16leSlice(this, start, end)
617-
618- default:
619- if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
620- encoding = (encoding + '').toLowerCase()
621- loweredCase = true
622- }
623- }
624-}
625-
626-// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect
627-// Buffer instances.
628-Buffer.prototype._isBuffer = true
629-
630-function swap (b, n, m) {
631- var i = b[n]
632- b[n] = b[m]
633- b[m] = i
634-}
635-
636-Buffer.prototype.swap16 = function swap16 () {
637- var len = this.length
638- if (len % 2 !== 0) {
639- throw new RangeError('Buffer size must be a multiple of 16-bits')
640- }
641- for (var i = 0; i < len; i += 2) {
642- swap(this, i, i + 1)
643- }
644- return this
645-}
646-
647-Buffer.prototype.swap32 = function swap32 () {
648- var len = this.length
649- if (len % 4 !== 0) {
650- throw new RangeError('Buffer size must be a multiple of 32-bits')
651- }
652- for (var i = 0; i < len; i += 4) {
653- swap(this, i, i + 3)
654- swap(this, i + 1, i + 2)
655- }
656- return this
657-}
658-
659-Buffer.prototype.toString = function toString () {
660- var length = this.length | 0
661- if (length === 0) return ''
662- if (arguments.length === 0) return utf8Slice(this, 0, length)
663- return slowToString.apply(this, arguments)
664-}
665-
666-Buffer.prototype.equals = function equals (b) {
667- if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
668- if (this === b) return true
669- return Buffer.compare(this, b) === 0
670-}
671-
672-Buffer.prototype.inspect = function inspect () {
673- var str = ''
674- var max = exports.INSPECT_MAX_BYTES
675- if (this.length > 0) {
676- str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
677- if (this.length > max) str += ' ... '
678- }
679- return '<Buffer ' + str + '>'
680-}
681-
682-Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
683- if (!Buffer.isBuffer(target)) {
684- throw new TypeError('Argument must be a Buffer')
685- }
686-
687- if (start === undefined) {
688- start = 0
689- }
690- if (end === undefined) {
691- end = target ? target.length : 0
692- }
693- if (thisStart === undefined) {
694- thisStart = 0
695- }
696- if (thisEnd === undefined) {
697- thisEnd = this.length
698- }
699-
700- if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
701- throw new RangeError('out of range index')
702- }
703-
704- if (thisStart >= thisEnd && start >= end) {
705- return 0
706- }
707- if (thisStart >= thisEnd) {
708- return -1
709- }
710- if (start >= end) {
711- return 1
712- }
713-
714- start >>>= 0
715- end >>>= 0
716- thisStart >>>= 0
717- thisEnd >>>= 0
718-
719- if (this === target) return 0
720-
721- var x = thisEnd - thisStart
722- var y = end - start
723- var len = Math.min(x, y)
724-
725- var thisCopy = this.slice(thisStart, thisEnd)
726- var targetCopy = target.slice(start, end)
727-
728- for (var i = 0; i < len; ++i) {
729- if (thisCopy[i] !== targetCopy[i]) {
730- x = thisCopy[i]
731- y = targetCopy[i]
732- break
733- }
734- }
735-
736- if (x < y) return -1
737- if (y < x) return 1
738- return 0
739-}
740-
741-function arrayIndexOf (arr, val, byteOffset, encoding) {
742- var indexSize = 1
743- var arrLength = arr.length
744- var valLength = val.length
745-
746- if (encoding !== undefined) {
747- encoding = String(encoding).toLowerCase()
748- if (encoding === 'ucs2' || encoding === 'ucs-2' ||
749- encoding === 'utf16le' || encoding === 'utf-16le') {
750- if (arr.length < 2 || val.length < 2) {
751- return -1
752- }
753- indexSize = 2
754- arrLength /= 2
755- valLength /= 2
756- byteOffset /= 2
757- }
758- }
759-
760- function read (buf, i) {
761- if (indexSize === 1) {
762- return buf[i]
763- } else {
764- return buf.readUInt16BE(i * indexSize)
765- }
766- }
767-
768- var foundIndex = -1
769- for (var i = 0; byteOffset + i < arrLength; i++) {
770- if (read(arr, byteOffset + i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
771- if (foundIndex === -1) foundIndex = i
772- if (i - foundIndex + 1 === valLength) return (byteOffset + foundIndex) * indexSize
773- } else {
774- if (foundIndex !== -1) i -= i - foundIndex
775- foundIndex = -1
776- }
777- }
778- return -1
779-}
780-
781-Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
782- if (typeof byteOffset === 'string') {
783- encoding = byteOffset
784- byteOffset = 0
785- } else if (byteOffset > 0x7fffffff) {
786- byteOffset = 0x7fffffff
787- } else if (byteOffset < -0x80000000) {
788- byteOffset = -0x80000000
789- }
790- byteOffset >>= 0
791-
792- if (this.length === 0) return -1
793- if (byteOffset >= this.length) return -1
794-
795- // Negative offsets start from the end of the buffer
796- if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0)
797-
798- if (typeof val === 'string') {
799- val = Buffer.from(val, encoding)
800- }
801-
802- if (Buffer.isBuffer(val)) {
803- // special case: looking for empty string/buffer always fails
804- if (val.length === 0) {
805- return -1
806- }
807- return arrayIndexOf(this, val, byteOffset, encoding)
808- }
809- if (typeof val === 'number') {
810- if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') {
811- return Uint8Array.prototype.indexOf.call(this, val, byteOffset)
812- }
813- return arrayIndexOf(this, [ val ], byteOffset, encoding)
814- }
815-
816- throw new TypeError('val must be string, number or Buffer')
817-}
818-
819-Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
820- return this.indexOf(val, byteOffset, encoding) !== -1
821-}
822-
823-function hexWrite (buf, string, offset, length) {
824- offset = Number(offset) || 0
825- var remaining = buf.length - offset
826- if (!length) {
827- length = remaining
828- } else {
829- length = Number(length)
830- if (length > remaining) {
831- length = remaining
832- }
833- }
834-
835- // must be an even number of digits
836- var strLen = string.length
837- if (strLen % 2 !== 0) throw new Error('Invalid hex string')
838-
839- if (length > strLen / 2) {
840- length = strLen / 2
841- }
842- for (var i = 0; i < length; i++) {
843- var parsed = parseInt(string.substr(i * 2, 2), 16)
844- if (isNaN(parsed)) return i
845- buf[offset + i] = parsed
846- }
847- return i
848-}
849-
850-function utf8Write (buf, string, offset, length) {
851- return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
852-}
853-
854-function asciiWrite (buf, string, offset, length) {
855- return blitBuffer(asciiToBytes(string), buf, offset, length)
856-}
857-
858-function binaryWrite (buf, string, offset, length) {
859- return asciiWrite(buf, string, offset, length)
860-}
861-
862-function base64Write (buf, string, offset, length) {
863- return blitBuffer(base64ToBytes(string), buf, offset, length)
864-}
865-
866-function ucs2Write (buf, string, offset, length) {
867- return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
868-}
869-
870-Buffer.prototype.write = function write (string, offset, length, encoding) {
871- // Buffer#write(string)
872- if (offset === undefined) {
873- encoding = 'utf8'
874- length = this.length
875- offset = 0
876- // Buffer#write(string, encoding)
877- } else if (length === undefined && typeof offset === 'string') {
878- encoding = offset
879- length = this.length
880- offset = 0
881- // Buffer#write(string, offset[, length][, encoding])
882- } else if (isFinite(offset)) {
883- offset = offset | 0
884- if (isFinite(length)) {
885- length = length | 0
886- if (encoding === undefined) encoding = 'utf8'
887- } else {
888- encoding = length
889- length = undefined
890- }
891- // legacy write(string, encoding, offset, length) - remove in v0.13
892- } else {
893- throw new Error(
894- 'Buffer.write(string, encoding, offset[, length]) is no longer supported'
895- )
896- }
897-
898- var remaining = this.length - offset
899- if (length === undefined || length > remaining) length = remaining
900-
901- if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
902- throw new RangeError('Attempt to write outside buffer bounds')
903- }
904-
905- if (!encoding) encoding = 'utf8'
906-
907- var loweredCase = false
908- for (;;) {
909- switch (encoding) {
910- case 'hex':
911- return hexWrite(this, string, offset, length)
912-
913- case 'utf8':
914- case 'utf-8':
915- return utf8Write(this, string, offset, length)
916-
917- case 'ascii':
918- return asciiWrite(this, string, offset, length)
919-
920- case 'binary':
921- return binaryWrite(this, string, offset, length)
922-
923- case 'base64':
924- // Warning: maxLength not taken into account in base64Write
925- return base64Write(this, string, offset, length)
926-
927- case 'ucs2':
928- case 'ucs-2':
929- case 'utf16le':
930- case 'utf-16le':
931- return ucs2Write(this, string, offset, length)
932-
933- default:
934- if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
935- encoding = ('' + encoding).toLowerCase()
936- loweredCase = true
937- }
938- }
939-}
940-
941-Buffer.prototype.toJSON = function toJSON () {
942- return {
943- type: 'Buffer',
944- data: Array.prototype.slice.call(this._arr || this, 0)
945- }
946-}
947-
948-function base64Slice (buf, start, end) {
949- if (start === 0 && end === buf.length) {
950- return base64.fromByteArray(buf)
951- } else {
952- return base64.fromByteArray(buf.slice(start, end))
953- }
954-}
955-
956-function utf8Slice (buf, start, end) {
957- end = Math.min(buf.length, end)
958- var res = []
959-
960- var i = start
961- while (i < end) {
962- var firstByte = buf[i]
963- var codePoint = null
964- var bytesPerSequence = (firstByte > 0xEF) ? 4
965- : (firstByte > 0xDF) ? 3
966- : (firstByte > 0xBF) ? 2
967- : 1
968-
969- if (i + bytesPerSequence <= end) {
970- var secondByte, thirdByte, fourthByte, tempCodePoint
971-
972- switch (bytesPerSequence) {
973- case 1:
974- if (firstByte < 0x80) {
975- codePoint = firstByte
976- }
977- break
978- case 2:
979- secondByte = buf[i + 1]
980- if ((secondByte & 0xC0) === 0x80) {
981- tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
982- if (tempCodePoint > 0x7F) {
983- codePoint = tempCodePoint
984- }
985- }
986- break
987- case 3:
988- secondByte = buf[i + 1]
989- thirdByte = buf[i + 2]
990- if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
991- tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
992- if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
993- codePoint = tempCodePoint
994- }
995- }
996- break
997- case 4:
998- secondByte = buf[i + 1]
999- thirdByte = buf[i + 2]
1000- fourthByte = buf[i + 3]
1001- if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
1002- tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
1003- if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
1004- codePoint = tempCodePoint
1005- }
1006- }
1007- }
1008- }
1009-
1010- if (codePoint === null) {
1011- // we did not generate a valid codePoint so insert a
1012- // replacement char (U+FFFD) and advance only 1 byte
1013- codePoint = 0xFFFD
1014- bytesPerSequence = 1
1015- } else if (codePoint > 0xFFFF) {
1016- // encode to utf16 (surrogate pair dance)
1017- codePoint -= 0x10000
1018- res.push(codePoint >>> 10 & 0x3FF | 0xD800)
1019- codePoint = 0xDC00 | codePoint & 0x3FF
1020- }
1021-
1022- res.push(codePoint)
1023- i += bytesPerSequence
1024- }
1025-
1026- return decodeCodePointsArray(res)
1027-}
1028-
1029-// Based on http://stackoverflow.com/a/22747272/680742, the browser with
1030-// the lowest limit is Chrome, with 0x10000 args.
1031-// We go 1 magnitude less, for safety
1032-var MAX_ARGUMENTS_LENGTH = 0x1000
1033-
1034-function decodeCodePointsArray (codePoints) {
1035- var len = codePoints.length
1036- if (len <= MAX_ARGUMENTS_LENGTH) {
1037- return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
1038- }
1039-
1040- // Decode in chunks to avoid "call stack size exceeded".
1041- var res = ''
1042- var i = 0
1043- while (i < len) {
1044- res += String.fromCharCode.apply(
1045- String,
1046- codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
1047- )
1048- }
1049- return res
1050-}
1051-
1052-function asciiSlice (buf, start, end) {
1053- var ret = ''
1054- end = Math.min(buf.length, end)
1055-
1056- for (var i = start; i < end; i++) {
1057- ret += String.fromCharCode(buf[i] & 0x7F)
1058- }
1059- return ret
1060-}
1061-
1062-function binarySlice (buf, start, end) {
1063- var ret = ''
1064- end = Math.min(buf.length, end)
1065-
1066- for (var i = start; i < end; i++) {
1067- ret += String.fromCharCode(buf[i])
1068- }
1069- return ret
1070-}
1071-
1072-function hexSlice (buf, start, end) {
1073- var len = buf.length
1074-
1075- if (!start || start < 0) start = 0
1076- if (!end || end < 0 || end > len) end = len
1077-
1078- var out = ''
1079- for (var i = start; i < end; i++) {
1080- out += toHex(buf[i])
1081- }
1082- return out
1083-}
1084-
1085-function utf16leSlice (buf, start, end) {
1086- var bytes = buf.slice(start, end)
1087- var res = ''
1088- for (var i = 0; i < bytes.length; i += 2) {
1089- res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
1090- }
1091- return res
1092-}
1093-
1094-Buffer.prototype.slice = function slice (start, end) {
1095- var len = this.length
1096- start = ~~start
1097- end = end === undefined ? len : ~~end
1098-
1099- if (start < 0) {
1100- start += len
1101- if (start < 0) start = 0
1102- } else if (start > len) {
1103- start = len
1104- }
1105-
1106- if (end < 0) {
1107- end += len
1108- if (end < 0) end = 0
1109- } else if (end > len) {
1110- end = len
1111- }
1112-
1113- if (end < start) end = start
1114-
1115- var newBuf
1116- if (Buffer.TYPED_ARRAY_SUPPORT) {
1117- newBuf = this.subarray(start, end)
1118- newBuf.__proto__ = Buffer.prototype
1119- } else {
1120- var sliceLen = end - start
1121- newBuf = new Buffer(sliceLen, undefined)
1122- for (var i = 0; i < sliceLen; i++) {
1123- newBuf[i] = this[i + start]
1124- }
1125- }
1126-
1127- return newBuf
1128-}
1129-
1130-/*
1131- * Need to make sure that buffer isn't trying to write out of bounds.
1132- */
1133-function checkOffset (offset, ext, length) {
1134- if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
1135- if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
1136-}
1137-
1138-Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
1139- offset = offset | 0
1140- byteLength = byteLength | 0
1141- if (!noAssert) checkOffset(offset, byteLength, this.length)
1142-
1143- var val = this[offset]
1144- var mul = 1
1145- var i = 0
1146- while (++i < byteLength && (mul *= 0x100)) {
1147- val += this[offset + i] * mul
1148- }
1149-
1150- return val
1151-}
1152-
1153-Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
1154- offset = offset | 0
1155- byteLength = byteLength | 0
1156- if (!noAssert) {
1157- checkOffset(offset, byteLength, this.length)
1158- }
1159-
1160- var val = this[offset + --byteLength]
1161- var mul = 1
1162- while (byteLength > 0 && (mul *= 0x100)) {
1163- val += this[offset + --byteLength] * mul
1164- }
1165-
1166- return val
1167-}
1168-
1169-Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
1170- if (!noAssert) checkOffset(offset, 1, this.length)
1171- return this[offset]
1172-}
1173-
1174-Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
1175- if (!noAssert) checkOffset(offset, 2, this.length)
1176- return this[offset] | (this[offset + 1] << 8)
1177-}
1178-
1179-Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
1180- if (!noAssert) checkOffset(offset, 2, this.length)
1181- return (this[offset] << 8) | this[offset + 1]
1182-}
1183-
1184-Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
1185- if (!noAssert) checkOffset(offset, 4, this.length)
1186-
1187- return ((this[offset]) |
1188- (this[offset + 1] << 8) |
1189- (this[offset + 2] << 16)) +
1190- (this[offset + 3] * 0x1000000)
1191-}
1192-
1193-Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
1194- if (!noAssert) checkOffset(offset, 4, this.length)
1195-
1196- return (this[offset] * 0x1000000) +
1197- ((this[offset + 1] << 16) |
1198- (this[offset + 2] << 8) |
1199- this[offset + 3])
1200-}
1201-
1202-Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
1203- offset = offset | 0
1204- byteLength = byteLength | 0
1205- if (!noAssert) checkOffset(offset, byteLength, this.length)
1206-
1207- var val = this[offset]
1208- var mul = 1
1209- var i = 0
1210- while (++i < byteLength && (mul *= 0x100)) {
1211- val += this[offset + i] * mul
1212- }
1213- mul *= 0x80
1214-
1215- if (val >= mul) val -= Math.pow(2, 8 * byteLength)
1216-
1217- return val
1218-}
1219-
1220-Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
1221- offset = offset | 0
1222- byteLength = byteLength | 0
1223- if (!noAssert) checkOffset(offset, byteLength, this.length)
1224-
1225- var i = byteLength
1226- var mul = 1
1227- var val = this[offset + --i]
1228- while (i > 0 && (mul *= 0x100)) {
1229- val += this[offset + --i] * mul
1230- }
1231- mul *= 0x80
1232-
1233- if (val >= mul) val -= Math.pow(2, 8 * byteLength)
1234-
1235- return val
1236-}
1237-
1238-Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
1239- if (!noAssert) checkOffset(offset, 1, this.length)
1240- if (!(this[offset] & 0x80)) return (this[offset])
1241- return ((0xff - this[offset] + 1) * -1)
1242-}
1243-
1244-Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
1245- if (!noAssert) checkOffset(offset, 2, this.length)
1246- var val = this[offset] | (this[offset + 1] << 8)
1247- return (val & 0x8000) ? val | 0xFFFF0000 : val
1248-}
1249-
1250-Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
1251- if (!noAssert) checkOffset(offset, 2, this.length)
1252- var val = this[offset + 1] | (this[offset] << 8)
1253- return (val & 0x8000) ? val | 0xFFFF0000 : val
1254-}
1255-
1256-Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
1257- if (!noAssert) checkOffset(offset, 4, this.length)
1258-
1259- return (this[offset]) |
1260- (this[offset + 1] << 8) |
1261- (this[offset + 2] << 16) |
1262- (this[offset + 3] << 24)
1263-}
1264-
1265-Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
1266- if (!noAssert) checkOffset(offset, 4, this.length)
1267-
1268- return (this[offset] << 24) |
1269- (this[offset + 1] << 16) |
1270- (this[offset + 2] << 8) |
1271- (this[offset + 3])
1272-}
1273-
1274-Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
1275- if (!noAssert) checkOffset(offset, 4, this.length)
1276- return ieee754.read(this, offset, true, 23, 4)
1277-}
1278-
1279-Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
1280- if (!noAssert) checkOffset(offset, 4, this.length)
1281- return ieee754.read(this, offset, false, 23, 4)
1282-}
1283-
1284-Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
1285- if (!noAssert) checkOffset(offset, 8, this.length)
1286- return ieee754.read(this, offset, true, 52, 8)
1287-}
1288-
1289-Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
1290- if (!noAssert) checkOffset(offset, 8, this.length)
1291- return ieee754.read(this, offset, false, 52, 8)
1292-}
1293-
1294-function checkInt (buf, value, offset, ext, max, min) {
1295- if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
1296- if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
1297- if (offset + ext > buf.length) throw new RangeError('Index out of range')
1298-}
1299-
1300-Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
1301- value = +value
1302- offset = offset | 0
1303- byteLength = byteLength | 0
1304- if (!noAssert) {
1305- var maxBytes = Math.pow(2, 8 * byteLength) - 1
1306- checkInt(this, value, offset, byteLength, maxBytes, 0)
1307- }
1308-
1309- var mul = 1
1310- var i = 0
1311- this[offset] = value & 0xFF
1312- while (++i < byteLength && (mul *= 0x100)) {
1313- this[offset + i] = (value / mul) & 0xFF
1314- }
1315-
1316- return offset + byteLength
1317-}
1318-
1319-Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
1320- value = +value
1321- offset = offset | 0
1322- byteLength = byteLength | 0
1323- if (!noAssert) {
1324- var maxBytes = Math.pow(2, 8 * byteLength) - 1
1325- checkInt(this, value, offset, byteLength, maxBytes, 0)
1326- }
1327-
1328- var i = byteLength - 1
1329- var mul = 1
1330- this[offset + i] = value & 0xFF
1331- while (--i >= 0 && (mul *= 0x100)) {
1332- this[offset + i] = (value / mul) & 0xFF
1333- }
1334-
1335- return offset + byteLength
1336-}
1337-
1338-Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
1339- value = +value
1340- offset = offset | 0
1341- if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
1342- if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
1343- this[offset] = (value & 0xff)
1344- return offset + 1
1345-}
1346-
1347-function objectWriteUInt16 (buf, value, offset, littleEndian) {
1348- if (value < 0) value = 0xffff + value + 1
1349- for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) {
1350- buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
1351- (littleEndian ? i : 1 - i) * 8
1352- }
1353-}
1354-
1355-Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
1356- value = +value
1357- offset = offset | 0
1358- if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
1359- if (Buffer.TYPED_ARRAY_SUPPORT) {
1360- this[offset] = (value & 0xff)
1361- this[offset + 1] = (value >>> 8)
1362- } else {
1363- objectWriteUInt16(this, value, offset, true)
1364- }
1365- return offset + 2
1366-}
1367-
1368-Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
1369- value = +value
1370- offset = offset | 0
1371- if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
1372- if (Buffer.TYPED_ARRAY_SUPPORT) {
1373- this[offset] = (value >>> 8)
1374- this[offset + 1] = (value & 0xff)
1375- } else {
1376- objectWriteUInt16(this, value, offset, false)
1377- }
1378- return offset + 2
1379-}
1380-
1381-function objectWriteUInt32 (buf, value, offset, littleEndian) {
1382- if (value < 0) value = 0xffffffff + value + 1
1383- for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) {
1384- buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
1385- }
1386-}
1387-
1388-Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
1389- value = +value
1390- offset = offset | 0
1391- if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
1392- if (Buffer.TYPED_ARRAY_SUPPORT) {
1393- this[offset + 3] = (value >>> 24)
1394- this[offset + 2] = (value >>> 16)
1395- this[offset + 1] = (value >>> 8)
1396- this[offset] = (value & 0xff)
1397- } else {
1398- objectWriteUInt32(this, value, offset, true)
1399- }
1400- return offset + 4
1401-}
1402-
1403-Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
1404- value = +value
1405- offset = offset | 0
1406- if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
1407- if (Buffer.TYPED_ARRAY_SUPPORT) {
1408- this[offset] = (value >>> 24)
1409- this[offset + 1] = (value >>> 16)
1410- this[offset + 2] = (value >>> 8)
1411- this[offset + 3] = (value & 0xff)
1412- } else {
1413- objectWriteUInt32(this, value, offset, false)
1414- }
1415- return offset + 4
1416-}
1417-
1418-Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
1419- value = +value
1420- offset = offset | 0
1421- if (!noAssert) {
1422- var limit = Math.pow(2, 8 * byteLength - 1)
1423-
1424- checkInt(this, value, offset, byteLength, limit - 1, -limit)
1425- }
1426-
1427- var i = 0
1428- var mul = 1
1429- var sub = 0
1430- this[offset] = value & 0xFF
1431- while (++i < byteLength && (mul *= 0x100)) {
1432- if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
1433- sub = 1
1434- }
1435- this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
1436- }
1437-
1438- return offset + byteLength
1439-}
1440-
1441-Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
1442- value = +value
1443- offset = offset | 0
1444- if (!noAssert) {
1445- var limit = Math.pow(2, 8 * byteLength - 1)
1446-
1447- checkInt(this, value, offset, byteLength, limit - 1, -limit)
1448- }
1449-
1450- var i = byteLength - 1
1451- var mul = 1
1452- var sub = 0
1453- this[offset + i] = value & 0xFF
1454- while (--i >= 0 && (mul *= 0x100)) {
1455- if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
1456- sub = 1
1457- }
1458- this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
1459- }
1460-
1461- return offset + byteLength
1462-}
1463-
1464-Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
1465- value = +value
1466- offset = offset | 0
1467- if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
1468- if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
1469- if (value < 0) value = 0xff + value + 1
1470- this[offset] = (value & 0xff)
1471- return offset + 1
1472-}
1473-
1474-Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
1475- value = +value
1476- offset = offset | 0
1477- if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
1478- if (Buffer.TYPED_ARRAY_SUPPORT) {
1479- this[offset] = (value & 0xff)
1480- this[offset + 1] = (value >>> 8)
1481- } else {
1482- objectWriteUInt16(this, value, offset, true)
1483- }
1484- return offset + 2
1485-}
1486-
1487-Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
1488- value = +value
1489- offset = offset | 0
1490- if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
1491- if (Buffer.TYPED_ARRAY_SUPPORT) {
1492- this[offset] = (value >>> 8)
1493- this[offset + 1] = (value & 0xff)
1494- } else {
1495- objectWriteUInt16(this, value, offset, false)
1496- }
1497- return offset + 2
1498-}
1499-
1500-Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
1501- value = +value
1502- offset = offset | 0
1503- if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
1504- if (Buffer.TYPED_ARRAY_SUPPORT) {
1505- this[offset] = (value & 0xff)
1506- this[offset + 1] = (value >>> 8)
1507- this[offset + 2] = (value >>> 16)
1508- this[offset + 3] = (value >>> 24)
1509- } else {
1510- objectWriteUInt32(this, value, offset, true)
1511- }
1512- return offset + 4
1513-}
1514-
1515-Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
1516- value = +value
1517- offset = offset | 0
1518- if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
1519- if (value < 0) value = 0xffffffff + value + 1
1520- if (Buffer.TYPED_ARRAY_SUPPORT) {
1521- this[offset] = (value >>> 24)
1522- this[offset + 1] = (value >>> 16)
1523- this[offset + 2] = (value >>> 8)
1524- this[offset + 3] = (value & 0xff)
1525- } else {
1526- objectWriteUInt32(this, value, offset, false)
1527- }
1528- return offset + 4
1529-}
1530-
1531-function checkIEEE754 (buf, value, offset, ext, max, min) {
1532- if (offset + ext > buf.length) throw new RangeError('Index out of range')
1533- if (offset < 0) throw new RangeError('Index out of range')
1534-}
1535-
1536-function writeFloat (buf, value, offset, littleEndian, noAssert) {
1537- if (!noAssert) {
1538- checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
1539- }
1540- ieee754.write(buf, value, offset, littleEndian, 23, 4)
1541- return offset + 4
1542-}
1543-
1544-Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
1545- return writeFloat(this, value, offset, true, noAssert)
1546-}
1547-
1548-Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
1549- return writeFloat(this, value, offset, false, noAssert)
1550-}
1551-
1552-function writeDouble (buf, value, offset, littleEndian, noAssert) {
1553- if (!noAssert) {
1554- checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
1555- }
1556- ieee754.write(buf, value, offset, littleEndian, 52, 8)
1557- return offset + 8
1558-}
1559-
1560-Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
1561- return writeDouble(this, value, offset, true, noAssert)
1562-}
1563-
1564-Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
1565- return writeDouble(this, value, offset, false, noAssert)
1566-}
1567-
1568-// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
1569-Buffer.prototype.copy = function copy (target, targetStart, start, end) {
1570- if (!start) start = 0
1571- if (!end && end !== 0) end = this.length
1572- if (targetStart >= target.length) targetStart = target.length
1573- if (!targetStart) targetStart = 0
1574- if (end > 0 && end < start) end = start
1575-
1576- // Copy 0 bytes; we're done
1577- if (end === start) return 0
1578- if (target.length === 0 || this.length === 0) return 0
1579-
1580- // Fatal error conditions
1581- if (targetStart < 0) {
1582- throw new RangeError('targetStart out of bounds')
1583- }
1584- if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')
1585- if (end < 0) throw new RangeError('sourceEnd out of bounds')
1586-
1587- // Are we oob?
1588- if (end > this.length) end = this.length
1589- if (target.length - targetStart < end - start) {
1590- end = target.length - targetStart + start
1591- }
1592-
1593- var len = end - start
1594- var i
1595-
1596- if (this === target && start < targetStart && targetStart < end) {
1597- // descending copy from end
1598- for (i = len - 1; i >= 0; i--) {
1599- target[i + targetStart] = this[i + start]
1600- }
1601- } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
1602- // ascending copy from start
1603- for (i = 0; i < len; i++) {
1604- target[i + targetStart] = this[i + start]
1605- }
1606- } else {
1607- Uint8Array.prototype.set.call(
1608- target,
1609- this.subarray(start, start + len),
1610- targetStart
1611- )
1612- }
1613-
1614- return len
1615-}
1616-
1617-// Usage:
1618-// buffer.fill(number[, offset[, end]])
1619-// buffer.fill(buffer[, offset[, end]])
1620-// buffer.fill(string[, offset[, end]][, encoding])
1621-Buffer.prototype.fill = function fill (val, start, end, encoding) {
1622- // Handle string cases:
1623- if (typeof val === 'string') {
1624- if (typeof start === 'string') {
1625- encoding = start
1626- start = 0
1627- end = this.length
1628- } else if (typeof end === 'string') {
1629- encoding = end
1630- end = this.length
1631- }
1632- if (val.length === 1) {
1633- var code = val.charCodeAt(0)
1634- if (code < 256) {
1635- val = code
1636- }
1637- }
1638- if (encoding !== undefined && typeof encoding !== 'string') {
1639- throw new TypeError('encoding must be a string')
1640- }
1641- if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
1642- throw new TypeError('Unknown encoding: ' + encoding)
1643- }
1644- } else if (typeof val === 'number') {
1645- val = val & 255
1646- }
1647-
1648- // Invalid ranges are not set to a default, so can range check early.
1649- if (start < 0 || this.length < start || this.length < end) {
1650- throw new RangeError('Out of range index')
1651- }
1652-
1653- if (end <= start) {
1654- return this
1655- }
1656-
1657- start = start >>> 0
1658- end = end === undefined ? this.length : end >>> 0
1659-
1660- if (!val) val = 0
1661-
1662- var i
1663- if (typeof val === 'number') {
1664- for (i = start; i < end; i++) {
1665- this[i] = val
1666- }
1667- } else {
1668- var bytes = Buffer.isBuffer(val)
1669- ? val
1670- : utf8ToBytes(new Buffer(val, encoding).toString())
1671- var len = bytes.length
1672- for (i = 0; i < end - start; i++) {
1673- this[i + start] = bytes[i % len]
1674- }
1675- }
1676-
1677- return this
1678-}
1679-
1680-// HELPER FUNCTIONS
1681-// ================
1682-
1683-var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g
1684-
1685-function base64clean (str) {
1686- // Node strips out invalid characters like \n and \t from the string, base64-js does not
1687- str = stringtrim(str).replace(INVALID_BASE64_RE, '')
1688- // Node converts strings with length < 2 to ''
1689- if (str.length < 2) return ''
1690- // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
1691- while (str.length % 4 !== 0) {
1692- str = str + '='
1693- }
1694- return str
1695-}
1696-
1697-function stringtrim (str) {
1698- if (str.trim) return str.trim()
1699- return str.replace(/^\s+|\s+$/g, '')
1700-}
1701-
1702-function toHex (n) {
1703- if (n < 16) return '0' + n.toString(16)
1704- return n.toString(16)
1705-}
1706-
1707-function utf8ToBytes (string, units) {
1708- units = units || Infinity
1709- var codePoint
1710- var length = string.length
1711- var leadSurrogate = null
1712- var bytes = []
1713-
1714- for (var i = 0; i < length; i++) {
1715- codePoint = string.charCodeAt(i)
1716-
1717- // is surrogate component
1718- if (codePoint > 0xD7FF && codePoint < 0xE000) {
1719- // last char was a lead
1720- if (!leadSurrogate) {
1721- // no lead yet
1722- if (codePoint > 0xDBFF) {
1723- // unexpected trail
1724- if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
1725- continue
1726- } else if (i + 1 === length) {
1727- // unpaired lead
1728- if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
1729- continue
1730- }
1731-
1732- // valid lead
1733- leadSurrogate = codePoint
1734-
1735- continue
1736- }
1737-
1738- // 2 leads in a row
1739- if (codePoint < 0xDC00) {
1740- if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
1741- leadSurrogate = codePoint
1742- continue
1743- }
1744-
1745- // valid surrogate pair
1746- codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
1747- } else if (leadSurrogate) {
1748- // valid bmp char, but last char was a lead
1749- if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
1750- }
1751-
1752- leadSurrogate = null
1753-
1754- // encode utf8
1755- if (codePoint < 0x80) {
1756- if ((units -= 1) < 0) break
1757- bytes.push(codePoint)
1758- } else if (codePoint < 0x800) {
1759- if ((units -= 2) < 0) break
1760- bytes.push(
1761- codePoint >> 0x6 | 0xC0,
1762- codePoint & 0x3F | 0x80
1763- )
1764- } else if (codePoint < 0x10000) {
1765- if ((units -= 3) < 0) break
1766- bytes.push(
1767- codePoint >> 0xC | 0xE0,
1768- codePoint >> 0x6 & 0x3F | 0x80,
1769- codePoint & 0x3F | 0x80
1770- )
1771- } else if (codePoint < 0x110000) {
1772- if ((units -= 4) < 0) break
1773- bytes.push(
1774- codePoint >> 0x12 | 0xF0,
1775- codePoint >> 0xC & 0x3F | 0x80,
1776- codePoint >> 0x6 & 0x3F | 0x80,
1777- codePoint & 0x3F | 0x80
1778- )
1779- } else {
1780- throw new Error('Invalid code point')
1781- }
1782- }
1783-
1784- return bytes
1785-}
1786-
1787-function asciiToBytes (str) {
1788- var byteArray = []
1789- for (var i = 0; i < str.length; i++) {
1790- // Node's code seems to be doing this and not & 0x7F..
1791- byteArray.push(str.charCodeAt(i) & 0xFF)
1792- }
1793- return byteArray
1794-}
1795-
1796-function utf16leToBytes (str, units) {
1797- var c, hi, lo
1798- var byteArray = []
1799- for (var i = 0; i < str.length; i++) {
1800- if ((units -= 2) < 0) break
1801-
1802- c = str.charCodeAt(i)
1803- hi = c >> 8
1804- lo = c % 256
1805- byteArray.push(lo)
1806- byteArray.push(hi)
1807- }
1808-
1809- return byteArray
1810-}
1811-
1812-function base64ToBytes (str) {
1813- return base64.toByteArray(base64clean(str))
1814-}
1815-
1816-function blitBuffer (src, dst, offset, length) {
1817- for (var i = 0; i < length; i++) {
1818- if ((i + offset >= dst.length) || (i >= src.length)) break
1819- dst[i + offset] = src[i]
1820- }
1821- return i
1822-}
1823-
1824-function isnan (val) {
1825- return val !== val // eslint-disable-line no-self-compare
1826-}
1827-
1828-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
1829-},{"base64-js":2,"ieee754":4,"isarray":5}],4:[function(require,module,exports){
1830-exports.read = function (buffer, offset, isLE, mLen, nBytes) {
1831- var e, m
1832- var eLen = nBytes * 8 - mLen - 1
1833- var eMax = (1 << eLen) - 1
1834- var eBias = eMax >> 1
1835- var nBits = -7
1836- var i = isLE ? (nBytes - 1) : 0
1837- var d = isLE ? -1 : 1
1838- var s = buffer[offset + i]
1839-
1840- i += d
1841-
1842- e = s & ((1 << (-nBits)) - 1)
1843- s >>= (-nBits)
1844- nBits += eLen
1845- for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
1846-
1847- m = e & ((1 << (-nBits)) - 1)
1848- e >>= (-nBits)
1849- nBits += mLen
1850- for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
1851-
1852- if (e === 0) {
1853- e = 1 - eBias
1854- } else if (e === eMax) {
1855- return m ? NaN : ((s ? -1 : 1) * Infinity)
1856- } else {
1857- m = m + Math.pow(2, mLen)
1858- e = e - eBias
1859- }
1860- return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
1861-}
1862-
1863-exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
1864- var e, m, c
1865- var eLen = nBytes * 8 - mLen - 1
1866- var eMax = (1 << eLen) - 1
1867- var eBias = eMax >> 1
1868- var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
1869- var i = isLE ? 0 : (nBytes - 1)
1870- var d = isLE ? 1 : -1
1871- var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
1872-
1873- value = Math.abs(value)
1874-
1875- if (isNaN(value) || value === Infinity) {
1876- m = isNaN(value) ? 1 : 0
1877- e = eMax
1878- } else {
1879- e = Math.floor(Math.log(value) / Math.LN2)
1880- if (value * (c = Math.pow(2, -e)) < 1) {
1881- e--
1882- c *= 2
1883- }
1884- if (e + eBias >= 1) {
1885- value += rt / c
1886- } else {
1887- value += rt * Math.pow(2, 1 - eBias)
1888- }
1889- if (value * c >= 2) {
1890- e++
1891- c /= 2
1892- }
1893-
1894- if (e + eBias >= eMax) {
1895- m = 0
1896- e = eMax
1897- } else if (e + eBias >= 1) {
1898- m = (value * c - 1) * Math.pow(2, mLen)
1899- e = e + eBias
1900- } else {
1901- m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
1902- e = 0
1903- }
1904- }
1905-
1906- for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
1907-
1908- e = (e << mLen) | m
1909- eLen += mLen
1910- for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
1911-
1912- buffer[offset + i - d] |= s * 128
1913-}
1914-
1915-},{}],5:[function(require,module,exports){
1916-var toString = {}.toString;
1917-
1918-module.exports = Array.isArray || function (arr) {
1919- return toString.call(arr) == '[object Array]';
1920-};
1921-
1922-},{}],6:[function(require,module,exports){
1923-exports.endianness = function () { return 'LE' };
1924-
1925-exports.hostname = function () {
1926- if (typeof location !== 'undefined') {
1927- return location.hostname
1928- }
1929- else return '';
1930-};
1931-
1932-exports.loadavg = function () { return [] };
1933-
1934-exports.uptime = function () { return 0 };
1935-
1936-exports.freemem = function () {
1937- return Number.MAX_VALUE;
1938-};
1939-
1940-exports.totalmem = function () {
1941- return Number.MAX_VALUE;
1942-};
1943-
1944-exports.cpus = function () { return [] };
1945-
1946-exports.type = function () { return 'Browser' };
1947-
1948-exports.release = function () {
1949- if (typeof navigator !== 'undefined') {
1950- return navigator.appVersion;
1951- }
1952- return '';
1953-};
1954-
1955-exports.networkInterfaces
1956-= exports.getNetworkInterfaces
1957-= function () { return {} };
1958-
1959-exports.arch = function () { return 'javascript' };
1960-
1961-exports.platform = function () { return 'browser' };
1962-
1963-exports.tmpdir = exports.tmpDir = function () {
1964- return '/tmp';
1965-};
1966-
1967-exports.EOL = '\n';
1968-
1969-},{}],7:[function(require,module,exports){
1970-(function (process){
1971-// Copyright Joyent, Inc. and other Node contributors.
1972-//
1973-// Permission is hereby granted, free of charge, to any person obtaining a
1974-// copy of this software and associated documentation files (the
1975-// "Software"), to deal in the Software without restriction, including
1976-// without limitation the rights to use, copy, modify, merge, publish,
1977-// distribute, sublicense, and/or sell copies of the Software, and to permit
1978-// persons to whom the Software is furnished to do so, subject to the
1979-// following conditions:
1980-//
1981-// The above copyright notice and this permission notice shall be included
1982-// in all copies or substantial portions of the Software.
1983-//
1984-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1985-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1986-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
1987-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
1988-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
1989-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
1990-// USE OR OTHER DEALINGS IN THE SOFTWARE.
1991-
1992-// resolves . and .. elements in a path array with directory names there
1993-// must be no slashes, empty elements, or device names (c:\) in the array
1994-// (so also no leading and trailing slashes - it does not distinguish
1995-// relative and absolute paths)
1996-function normalizeArray(parts, allowAboveRoot) {
1997- // if the path tries to go above the root, `up` ends up > 0
1998- var up = 0;
1999- for (var i = parts.length - 1; i >= 0; i--) {
2000- var last = parts[i];
2001- if (last === '.') {
2002- parts.splice(i, 1);
2003- } else if (last === '..') {
2004- parts.splice(i, 1);
2005- up++;
2006- } else if (up) {
2007- parts.splice(i, 1);
2008- up--;
2009- }
2010- }
2011-
2012- // if the path is allowed to go above the root, restore leading ..s
2013- if (allowAboveRoot) {
2014- for (; up--; up) {
2015- parts.unshift('..');
2016- }
2017- }
2018-
2019- return parts;
2020-}
2021-
2022-// Split a filename into [root, dir, basename, ext], unix version
2023-// 'root' is just a slash, or nothing.
2024-var splitPathRe =
2025- /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
2026-var splitPath = function(filename) {
2027- return splitPathRe.exec(filename).slice(1);
2028-};
2029-
2030-// path.resolve([from ...], to)
2031-// posix version
2032-exports.resolve = function() {
2033- var resolvedPath = '',
2034- resolvedAbsolute = false;
2035-
2036- for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
2037- var path = (i >= 0) ? arguments[i] : process.cwd();
2038-
2039- // Skip empty and invalid entries
2040- if (typeof path !== 'string') {
2041- throw new TypeError('Arguments to path.resolve must be strings');
2042- } else if (!path) {
2043- continue;
2044- }
2045-
2046- resolvedPath = path + '/' + resolvedPath;
2047- resolvedAbsolute = path.charAt(0) === '/';
2048- }
2049-
2050- // At this point the path should be resolved to a full absolute path, but
2051- // handle relative paths to be safe (might happen when process.cwd() fails)
2052-
2053- // Normalize the path
2054- resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
2055- return !!p;
2056- }), !resolvedAbsolute).join('/');
2057-
2058- return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
2059-};
2060-
2061-// path.normalize(path)
2062-// posix version
2063-exports.normalize = function(path) {
2064- var isAbsolute = exports.isAbsolute(path),
2065- trailingSlash = substr(path, -1) === '/';
2066-
2067- // Normalize the path
2068- path = normalizeArray(filter(path.split('/'), function(p) {
2069- return !!p;
2070- }), !isAbsolute).join('/');
2071-
2072- if (!path && !isAbsolute) {
2073- path = '.';
2074- }
2075- if (path && trailingSlash) {
2076- path += '/';
2077- }
2078-
2079- return (isAbsolute ? '/' : '') + path;
2080-};
2081-
2082-// posix version
2083-exports.isAbsolute = function(path) {
2084- return path.charAt(0) === '/';
2085-};
2086-
2087-// posix version
2088-exports.join = function() {
2089- var paths = Array.prototype.slice.call(arguments, 0);
2090- return exports.normalize(filter(paths, function(p, index) {
2091- if (typeof p !== 'string') {
2092- throw new TypeError('Arguments to path.join must be strings');
2093- }
2094- return p;
2095- }).join('/'));
2096-};
2097-
2098-
2099-// path.relative(from, to)
2100-// posix version
2101-exports.relative = function(from, to) {
2102- from = exports.resolve(from).substr(1);
2103- to = exports.resolve(to).substr(1);
2104-
2105- function trim(arr) {
2106- var start = 0;
2107- for (; start < arr.length; start++) {
2108- if (arr[start] !== '') break;
2109- }
2110-
2111- var end = arr.length - 1;
2112- for (; end >= 0; end--) {
2113- if (arr[end] !== '') break;
2114- }
2115-
2116- if (start > end) return [];
2117- return arr.slice(start, end - start + 1);
2118- }
2119-
2120- var fromParts = trim(from.split('/'));
2121- var toParts = trim(to.split('/'));
2122-
2123- var length = Math.min(fromParts.length, toParts.length);
2124- var samePartsLength = length;
2125- for (var i = 0; i < length; i++) {
2126- if (fromParts[i] !== toParts[i]) {
2127- samePartsLength = i;
2128- break;
2129- }
2130- }
2131-
2132- var outputParts = [];
2133- for (var i = samePartsLength; i < fromParts.length; i++) {
2134- outputParts.push('..');
2135- }
2136-
2137- outputParts = outputParts.concat(toParts.slice(samePartsLength));
2138-
2139- return outputParts.join('/');
2140-};
2141-
2142-exports.sep = '/';
2143-exports.delimiter = ':';
2144-
2145-exports.dirname = function(path) {
2146- var result = splitPath(path),
2147- root = result[0],
2148- dir = result[1];
2149-
2150- if (!root && !dir) {
2151- // No dirname whatsoever
2152- return '.';
2153- }
2154-
2155- if (dir) {
2156- // It has a dirname, strip trailing slash
2157- dir = dir.substr(0, dir.length - 1);
2158- }
2159-
2160- return root + dir;
2161-};
2162-
2163-
2164-exports.basename = function(path, ext) {
2165- var f = splitPath(path)[2];
2166- // TODO: make this comparison case-insensitive on windows?
2167- if (ext && f.substr(-1 * ext.length) === ext) {
2168- f = f.substr(0, f.length - ext.length);
2169- }
2170- return f;
2171-};
2172-
2173-
2174-exports.extname = function(path) {
2175- return splitPath(path)[3];
2176-};
2177-
2178-function filter (xs, f) {
2179- if (xs.filter) return xs.filter(f);
2180- var res = [];
2181- for (var i = 0; i < xs.length; i++) {
2182- if (f(xs[i], i, xs)) res.push(xs[i]);
2183- }
2184- return res;
2185-}
2186-
2187-// String.prototype.substr - negative index don't work in IE8
2188-var substr = 'ab'.substr(-1) === 'b'
2189- ? function (str, start, len) { return str.substr(start, len) }
2190- : function (str, start, len) {
2191- if (start < 0) start = str.length + start;
2192- return str.substr(start, len);
2193- }
2194-;
2195-
2196-}).call(this,require('_process'))
2197-},{"_process":8}],8:[function(require,module,exports){
2198-// shim for using process in browser
2199-
2200-var process = module.exports = {};
2201-var queue = [];
2202-var draining = false;
2203-var currentQueue;
2204-var queueIndex = -1;
2205-
2206-function cleanUpNextTick() {
2207- if (!draining || !currentQueue) {
2208- return;
2209- }
2210- draining = false;
2211- if (currentQueue.length) {
2212- queue = currentQueue.concat(queue);
2213- } else {
2214- queueIndex = -1;
2215- }
2216- if (queue.length) {
2217- drainQueue();
2218- }
2219-}
2220-
2221-function drainQueue() {
2222- if (draining) {
2223- return;
2224- }
2225- var timeout = setTimeout(cleanUpNextTick);
2226- draining = true;
2227-
2228- var len = queue.length;
2229- while(len) {
2230- currentQueue = queue;
2231- queue = [];
2232- while (++queueIndex < len) {
2233- if (currentQueue) {
2234- currentQueue[queueIndex].run();
2235- }
2236- }
2237- queueIndex = -1;
2238- len = queue.length;
2239- }
2240- currentQueue = null;
2241- draining = false;
2242- clearTimeout(timeout);
2243-}
2244-
2245-process.nextTick = function (fun) {
2246- var args = new Array(arguments.length - 1);
2247- if (arguments.length > 1) {
2248- for (var i = 1; i < arguments.length; i++) {
2249- args[i - 1] = arguments[i];
2250- }
2251- }
2252- queue.push(new Item(fun, args));
2253- if (queue.length === 1 && !draining) {
2254- setTimeout(drainQueue, 0);
2255- }
2256-};
2257-
2258-// v8 likes predictible objects
2259-function Item(fun, array) {
2260- this.fun = fun;
2261- this.array = array;
2262-}
2263-Item.prototype.run = function () {
2264- this.fun.apply(null, this.array);
2265-};
2266-process.title = 'browser';
2267-process.browser = true;
2268-process.env = {};
2269-process.argv = [];
2270-process.version = ''; // empty string to avoid regexp issues
2271-process.versions = {};
2272-
2273-function noop() {}
2274-
2275-process.on = noop;
2276-process.addListener = noop;
2277-process.once = noop;
2278-process.off = noop;
2279-process.removeListener = noop;
2280-process.removeAllListeners = noop;
2281-process.emit = noop;
2282-
2283-process.binding = function (name) {
2284- throw new Error('process.binding is not supported');
2285-};
2286-
2287-process.cwd = function () { return '/' };
2288-process.chdir = function (dir) {
2289- throw new Error('process.chdir is not supported');
2290-};
2291-process.umask = function() { return 0; };
2292-
2293-},{}],9:[function(require,module,exports){
2294-var path = require('path')
2295-var home = require('osenv').home
2296-var nonPrivate = require('non-private-ip')
2297-var merge = require('deep-extend')
2298-
2299-var RC = require('rc')
2300-
2301-var SEC = 1e3
2302-var MIN = 60*SEC
2303-
2304-module.exports = function (name, override) {
2305- name = name || 'ssb'
2306- console.log("BROWSER?", home())
2307- var HOME = home() || 'browser' //most probably browser
2308- return RC(name || 'ssb', merge({
2309- //just use an ipv4 address by default.
2310- //there have been some reports of seemingly non-private
2311- //ipv6 addresses being returned and not working.
2312- //https://github.com/ssbc/scuttlebot/pull/102
2313- party: true,
2314- host: nonPrivate.v4 || '',
2315- port: 8008,
2316- timeout: 0,
2317- pub: true,
2318- local: true,
2319- friends: {
2320- dunbar: 150,
2321- hops: 3
2322- },
2323- ws: {
2324- port: 8989
2325- },
2326- gossip: {
2327- connections: 3
2328- },
2329- path: path.join(HOME, '.' + name),
2330- timers: {
2331- connection: 0,
2332- reconnect: 5*SEC,
2333- ping: 5*MIN,
2334- handshake: 5*SEC
2335- },
2336- master: [],
2337- logging: { level: 'notice' },
2338- party: true //disable quotas
2339- }, override || {}))
2340-}
2341-
2342-
2343-},{"deep-extend":10,"non-private-ip":12,"osenv":15,"path":7,"rc":16}],10:[function(require,module,exports){
2344-(function (Buffer){
2345-/*!
2346- * @description Recursive object extending
2347- * @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
2348- * @license MIT
2349- *
2350- * The MIT License (MIT)
2351- *
2352- * Copyright (c) 2013-2015 Viacheslav Lotsmanov
2353- *
2354- * Permission is hereby granted, free of charge, to any person obtaining a copy of
2355- * this software and associated documentation files (the "Software"), to deal in
2356- * the Software without restriction, including without limitation the rights to
2357- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
2358- * the Software, and to permit persons to whom the Software is furnished to do so,
2359- * subject to the following conditions:
2360- *
2361- * The above copyright notice and this permission notice shall be included in all
2362- * copies or substantial portions of the Software.
2363- *
2364- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2365- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
2366- * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
2367- * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
2368- * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
2369- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2370- */
2371-
2372-'use strict';
2373-
2374-function isSpecificValue(val) {
2375- return (
2376- val instanceof Buffer
2377- || val instanceof Date
2378- || val instanceof RegExp
2379- ) ? true : false;
2380-}
2381-
2382-function cloneSpecificValue(val) {
2383- if (val instanceof Buffer) {
2384- var x = new Buffer(val.length);
2385- val.copy(x);
2386- return x;
2387- } else if (val instanceof Date) {
2388- return new Date(val.getTime());
2389- } else if (val instanceof RegExp) {
2390- return new RegExp(val);
2391- } else {
2392- throw new Error('Unexpected situation');
2393- }
2394-}
2395-
2396-/**
2397- * Recursive cloning array.
2398- */
2399-function deepCloneArray(arr) {
2400- var clone = [];
2401- arr.forEach(function (item, index) {
2402- if (typeof item === 'object' && item !== null) {
2403- if (Array.isArray(item)) {
2404- clone[index] = deepCloneArray(item);
2405- } else if (isSpecificValue(item)) {
2406- clone[index] = cloneSpecificValue(item);
2407- } else {
2408- clone[index] = deepExtend({}, item);
2409- }
2410- } else {
2411- clone[index] = item;
2412- }
2413- });
2414- return clone;
2415-}
2416-
2417-/**
2418- * Extening object that entered in first argument.
2419- *
2420- * Returns extended object or false if have no target object or incorrect type.
2421- *
2422- * If you wish to clone source object (without modify it), just use empty new
2423- * object as first argument, like this:
2424- * deepExtend({}, yourObj_1, [yourObj_N]);
2425- */
2426-var deepExtend = module.exports = function (/*obj_1, [obj_2], [obj_N]*/) {
2427- if (arguments.length < 1 || typeof arguments[0] !== 'object') {
2428- return false;
2429- }
2430-
2431- if (arguments.length < 2) {
2432- return arguments[0];
2433- }
2434-
2435- var target = arguments[0];
2436-
2437- // convert arguments to array and cut off target object
2438- var args = Array.prototype.slice.call(arguments, 1);
2439-
2440- var val, src, clone;
2441-
2442- args.forEach(function (obj) {
2443- // skip argument if it is array or isn't object
2444- if (typeof obj !== 'object' || Array.isArray(obj)) {
2445- return;
2446- }
2447-
2448- Object.keys(obj).forEach(function (key) {
2449- src = target[key]; // source value
2450- val = obj[key]; // new value
2451-
2452- // recursion prevention
2453- if (val === target) {
2454- return;
2455-
2456- /**
2457- * if new value isn't object then just overwrite by new value
2458- * instead of extending.
2459- */
2460- } else if (typeof val !== 'object' || val === null) {
2461- target[key] = val;
2462- return;
2463-
2464- // just clone arrays (and recursive clone objects inside)
2465- } else if (Array.isArray(val)) {
2466- target[key] = deepCloneArray(val);
2467- return;
2468-
2469- // custom cloning and overwrite for specific objects
2470- } else if (isSpecificValue(val)) {
2471- target[key] = cloneSpecificValue(val);
2472- return;
2473-
2474- // overwrite by new value if source isn't object or array
2475- } else if (typeof src !== 'object' || src === null || Array.isArray(src)) {
2476- target[key] = deepExtend({}, val);
2477- return;
2478-
2479- // source value and new value is objects both, extending...
2480- } else {
2481- target[key] = deepExtend(src, val);
2482- return;
2483- }
2484- });
2485- });
2486-
2487- return target;
2488-}
2489-
2490-}).call(this,require("buffer").Buffer)
2491-},{"buffer":3}],11:[function(require,module,exports){
2492-var ip = exports,
2493- Buffer = require('buffer').Buffer,
2494- os = require('os');
2495-
2496-ip.toBuffer = function toBuffer(ip, buff, offset) {
2497- offset = ~~offset;
2498-
2499- var result;
2500-
2501- if (/^(\d{1,3}\.){3,3}\d{1,3}$/.test(ip)) {
2502- result = buff || new Buffer(offset + 4);
2503- ip.split(/\./g).map(function(byte) {
2504- result[offset++] = parseInt(byte, 10) & 0xff;
2505- });
2506- } else if (/^[a-f0-9:]+$/.test(ip)) {
2507- var s = ip.split(/::/g, 2),
2508- head = (s[0] || '').split(/:/g, 8),
2509- tail = (s[1] || '').split(/:/g, 8);
2510-
2511- if (tail.length === 0) {
2512- // xxxx::
2513- while (head.length < 8) head.push('0000');
2514- } else if (head.length === 0) {
2515- // ::xxxx
2516- while (tail.length < 8) tail.unshift('0000');
2517- } else {
2518- // xxxx::xxxx
2519- while (head.length + tail.length < 8) head.push('0000');
2520- }
2521-
2522- result = buff || new Buffer(offset + 16);
2523- head.concat(tail).map(function(word) {
2524- word = parseInt(word, 16);
2525- result[offset++] = (word >> 8) & 0xff;
2526- result[offset++] = word & 0xff;
2527- });
2528- } else {
2529- throw Error('Invalid ip address: ' + ip);
2530- }
2531-
2532- return result;
2533-};
2534-
2535-ip.toString = function toString(buff, offset, length) {
2536- offset = ~~offset;
2537- length = length || (buff.length - offset);
2538-
2539- var result = [];
2540- if (length === 4) {
2541- // IPv4
2542- for (var i = 0; i < length; i++) {
2543- result.push(buff[offset + i]);
2544- }
2545- result = result.join('.');
2546- } else if (length === 16) {
2547- // IPv6
2548- for (var i = 0; i < length; i += 2) {
2549- result.push(buff.readUInt16BE(offset + i).toString(16));
2550- }
2551- result = result.join(':');
2552- result = result.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3');
2553- result = result.replace(/:{3,4}/, '::');
2554- }
2555-
2556- return result;
2557-};
2558-
2559-ip.fromPrefixLen = function fromPrefixLen(prefixlen, family) {
2560- if (prefixlen > 32) {
2561- family = 'ipv6';
2562- } else {
2563- family = _normalizeFamily(family);
2564- }
2565-
2566- var len = 4;
2567- if (family === 'ipv6') {
2568- len = 16;
2569- }
2570- var buff = new Buffer(len);
2571-
2572- for (var i = 0, n = buff.length; i < n; ++i) {
2573- var bits = 8;
2574- if (prefixlen < 8) {
2575- bits = prefixlen;
2576- }
2577- prefixlen -= bits;
2578-
2579- buff[i] = ~(0xff >> bits);
2580- }
2581-
2582- return ip.toString(buff);
2583-};
2584-
2585-ip.mask = function mask(addr, mask) {
2586- addr = ip.toBuffer(addr);
2587- mask = ip.toBuffer(mask);
2588-
2589- var result = new Buffer(Math.max(addr.length, mask.length));
2590-
2591- // Same protocol - do bitwise and
2592- if (addr.length === mask.length) {
2593- for (var i = 0; i < addr.length; i++) {
2594- result[i] = addr[i] & mask[i];
2595- }
2596- } else if (mask.length === 4) {
2597- // IPv6 address and IPv4 mask
2598- // (Mask low bits)
2599- for (var i = 0; i < mask.length; i++) {
2600- result[i] = addr[addr.length - 4 + i] & mask[i];
2601- }
2602- } else {
2603- // IPv6 mask and IPv4 addr
2604- for (var i = 0; i < result.length - 6; i++) {
2605- result[i] = 0;
2606- }
2607-
2608- // ::ffff:ipv4
2609- result[10] = 0xff;
2610- result[11] = 0xff;
2611- for (var i = 0; i < addr.length; i++) {
2612- result[i + 12] = addr[i] & mask[i + 12];
2613- }
2614- }
2615-
2616- return ip.toString(result);
2617-};
2618-
2619-ip.cidr = function cidr(cidrString) {
2620- var cidrParts = cidrString.split('/');
2621-
2622- if (cidrParts.length != 2)
2623- throw new Error('invalid CIDR subnet: ' + addr);
2624-
2625- var addr = cidrParts[0];
2626- var mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10));
2627-
2628- return ip.mask(addr, mask);
2629-}
2630-
2631-ip.subnet = function subnet(addr, mask) {
2632- var networkAddress = ip.toLong(ip.mask(addr, mask));
2633-
2634- // Calculate the mask's length.
2635- var maskBuffer = ip.toBuffer(mask);
2636- var maskLength = 0;
2637-
2638- for (var i = 0; i < maskBuffer.length; i++) {
2639- if (maskBuffer[i] == 0xff) {
2640- maskLength += 8;
2641- } else {
2642- var octet = maskBuffer[i] & 0xff;
2643- while (octet) {
2644- octet = (octet << 1) & 0xff;
2645- maskLength++;
2646- }
2647- }
2648- }
2649-
2650- var numberOfAddresses = Math.pow(2, 32 - maskLength);
2651-
2652- return {
2653- networkAddress: ip.fromLong(networkAddress),
2654- firstAddress: numberOfAddresses <= 2 ?
2655- ip.fromLong(networkAddress) :
2656- ip.fromLong(networkAddress + 1),
2657- lastAddress: numberOfAddresses <= 2 ?
2658- ip.fromLong(networkAddress + numberOfAddresses - 1) :
2659- ip.fromLong(networkAddress + numberOfAddresses - 2),
2660- broadcastAddress: ip.fromLong(networkAddress + numberOfAddresses - 1),
2661- subnetMask: mask,
2662- subnetMaskLength: maskLength,
2663- numHosts: numberOfAddresses <= 2 ?
2664- numberOfAddresses : numberOfAddresses - 2,
2665- length: numberOfAddresses
2666- };
2667-}
2668-
2669-ip.cidrSubnet = function cidrSubnet(cidrString) {
2670- var cidrParts = cidrString.split('/');
2671-
2672- if (cidrParts.length !== 2)
2673- throw new Error('invalid CIDR subnet: ' + addr);
2674-
2675- var addr = cidrParts[0];
2676- var mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10));
2677-
2678- return ip.subnet(addr, mask);
2679-}
2680-
2681-ip.not = function not(addr) {
2682- var buff = ip.toBuffer(addr);
2683- for (var i = 0; i < buff.length; i++) {
2684- buff[i] = 0xff ^ buff[i];
2685- }
2686- return ip.toString(buff);
2687-};
2688-
2689-ip.or = function or(a, b) {
2690- a = ip.toBuffer(a);
2691- b = ip.toBuffer(b);
2692-
2693- // same protocol
2694- if (a.length == b.length) {
2695- for (var i = 0; i < a.length; ++i) {
2696- a[i] |= b[i];
2697- }
2698- return ip.toString(a);
2699-
2700- // mixed protocols
2701- } else {
2702- var buff = a;
2703- var other = b;
2704- if (b.length > a.length) {
2705- buff = b;
2706- other = a;
2707- }
2708-
2709- var offset = buff.length - other.length;
2710- for (var i = offset; i < buff.length; ++i) {
2711- buff[i] |= other[i - offset];
2712- }
2713-
2714- return ip.toString(buff);
2715- }
2716-};
2717-
2718-ip.isEqual = function isEqual(a, b) {
2719- a = ip.toBuffer(a);
2720- b = ip.toBuffer(b);
2721-
2722- // Same protocol
2723- if (a.length === b.length) {
2724- for (var i = 0; i < a.length; i++) {
2725- if (a[i] !== b[i]) return false;
2726- }
2727- return true;
2728- }
2729-
2730- // Swap
2731- if (b.length === 4) {
2732- var t = b;
2733- b = a;
2734- a = t;
2735- }
2736-
2737- // a - IPv4, b - IPv6
2738- for (var i = 0; i < 10; i++) {
2739- if (b[i] !== 0) return false;
2740- }
2741-
2742- var word = b.readUInt16BE(10);
2743- if (word !== 0 && word !== 0xffff) return false;
2744-
2745- for (var i = 0; i < 4; i++) {
2746- if (a[i] !== b[i + 12]) return false;
2747- }
2748-
2749- return true;
2750-};
2751-
2752-ip.isPrivate = function isPrivate(addr) {
2753- return addr.match(/^10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
2754- addr.match(/^192\.168\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
2755- addr.match(
2756- /^172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
2757- addr.match(/^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
2758- addr.match(/^169\.254\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
2759- addr.match(/^fc00:/) != null || addr.match(/^fe80:/) != null ||
2760- addr.match(/^::1$/) != null || addr.match(/^::$/) != null;
2761-};
2762-
2763-ip.isPublic = function isPublic(addr) {
2764- return !ip.isPrivate(addr);
2765-}
2766-
2767-ip.isLoopback = function isLoopback(addr) {
2768- return /^127\.0\.0\.1$/.test(addr)
2769- || /^fe80::1$/.test(addr)
2770- || /^::1$/.test(addr)
2771- || /^::$/.test(addr);
2772-};
2773-
2774-ip.loopback = function loopback(family) {
2775- //
2776- // Default to `ipv4`
2777- //
2778- family = _normalizeFamily(family);
2779-
2780- if (family !== 'ipv4' && family !== 'ipv6') {
2781- throw new Error('family must be ipv4 or ipv6');
2782- }
2783-
2784- return family === 'ipv4'
2785- ? '127.0.0.1'
2786- : 'fe80::1';
2787-};
2788-
2789-//
2790-// ### function address (name, family)
2791-// #### @name {string|'public'|'private'} **Optional** Name or security
2792-// of the network interface.
2793-// #### @family {ipv4|ipv6} **Optional** IP family of the address (defaults
2794-// to ipv4).
2795-//
2796-// Returns the address for the network interface on the current system with
2797-// the specified `name`:
2798-// * String: First `family` address of the interface.
2799-// If not found see `undefined`.
2800-// * 'public': the first public ip address of family.
2801-// * 'private': the first private ip address of family.
2802-// * undefined: First address with `ipv4` or loopback addres `127.0.0.1`.
2803-//
2804-ip.address = function address(name, family) {
2805- var interfaces = os.networkInterfaces(),
2806- all;
2807-
2808- //
2809- // Default to `ipv4`
2810- //
2811- family = _normalizeFamily(family);
2812-
2813- //
2814- // If a specific network interface has been named,
2815- // return the address.
2816- //
2817- if (name && !~['public', 'private'].indexOf(name)) {
2818- return interfaces[name].filter(function (details) {
2819- details.family = details.family.toLowerCase();
2820- return details.family === family;
2821- })[0].address;
2822- }
2823-
2824- var all = Object.keys(interfaces).map(function (nic) {
2825- //
2826- // Note: name will only be `public` or `private`
2827- // when this is called.
2828- //
2829- var addresses = interfaces[nic].filter(function (details) {
2830- details.family = details.family.toLowerCase();
2831- if (details.family !== family || ip.isLoopback(details.address)) {
2832- return false;
2833- }
2834- else if (!name) {
2835- return true;
2836- }
2837-
2838- return name === 'public'
2839- ? !ip.isPrivate(details.address)
2840- : ip.isPrivate(details.address)
2841- });
2842-
2843- return addresses.length
2844- ? addresses[0].address
2845- : undefined;
2846- }).filter(Boolean);
2847-
2848- return !all.length
2849- ? ip.loopback(family)
2850- : all[0];
2851-};
2852-
2853-ip.toLong = function toInt(ip){
2854- var ipl=0;
2855- ip.split('.').forEach(function( octet ) {
2856- ipl<<=8;
2857- ipl+=parseInt(octet);
2858- });
2859- return(ipl >>>0);
2860-};
2861-
2862-ip.fromLong = function fromInt(ipl){
2863- return ( (ipl>>>24) +'.' +
2864- (ipl>>16 & 255) +'.' +
2865- (ipl>>8 & 255) +'.' +
2866- (ipl & 255) );
2867-};
2868-
2869-function _normalizeFamily(family) {
2870- return family ? family.toLowerCase() : 'ipv4';
2871-}
2872-
2873-},{"buffer":3,"os":6}],12:[function(require,module,exports){
2874-var os = require('os')
2875-var ip = require('ip')
2876-//pick the first reasonable looking host.
2877-//this should *just work* when running on a vps.
2878-
2879-var isPrivate = ip.isPrivate
2880-
2881-function isNonPrivate (e) {
2882- return !isPrivate(e)
2883-}
2884-
2885-
2886-var address = module.exports = function (inter, filter) {
2887- inter = inter || os.networkInterfaces()
2888- filter = filter || isNonPrivate
2889- for(var k in inter) {
2890- for(var i in inter[k]) {
2891- var e = inter[k][i]
2892- // find a reasonable looking address
2893- if(!e.internal && filter(e.address, e))
2894- return e.address
2895- }
2896- }
2897-}
2898-
2899-function isV4 (e) {
2900- return e.family === 'IPv4'
2901-}
2902-
2903-function isV6 (e) {
2904- return e.family === 'IPv6'
2905-}
2906-
2907-var private = module.exports.private = function (inter) {
2908- return address(inter, isPrivate)
2909-}
2910-
2911-module.exports.v4 = address(null, function (addr, e) {
2912- return isV4(e) && isNonPrivate(addr)
2913-})
2914-
2915-module.exports.v6 = address(null, function (addr, e) {
2916- return isV6(e) && isNonPrivate(addr)
2917-})
2918-
2919-private.v4 = address(null, function (addr, e) {
2920- return isV4(e) && isPrivate(addr)
2921-})
2922-
2923-private.v6 = address(null, function (addr, e) {
2924- return isV6(e) && isPrivate(addr)
2925-})
2926-
2927-module.exports.all = {
2928- public: {
2929- v4: module.exports.v4, v6: module.exports.v6
2930- },
2931- private: {
2932- v4: private.v4, v6: private.v6
2933- }
2934-}
2935-
2936-
2937-if(!module.parent) {
2938- console.log(module.exports.all)
2939-}
2940-
2941-},{"ip":11,"os":6}],13:[function(require,module,exports){
2942-(function (process){
2943-'use strict';
2944-var os = require('os');
2945-
2946-function homedir() {
2947- var env = process.env;
2948- var home = env.HOME;
2949- var user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME;
2950-
2951- if (process.platform === 'win32') {
2952- return env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null;
2953- }
2954-
2955- if (process.platform === 'darwin') {
2956- return home || (user ? '/Users/' + user : null);
2957- }
2958-
2959- if (process.platform === 'linux') {
2960- return home || (process.getuid() === 0 ? '/root' : (user ? '/home/' + user : null));
2961- }
2962-
2963- return home || null;
2964-}
2965-
2966-module.exports = typeof os.homedir === 'function' ? os.homedir : homedir;
2967-
2968-}).call(this,require('_process'))
2969-},{"_process":8,"os":6}],14:[function(require,module,exports){
2970-(function (process){
2971-'use strict';
2972-var isWindows = process.platform === 'win32';
2973-var trailingSlashRe = isWindows ? /[^:]\\$/ : /.\/$/;
2974-
2975-// https://github.com/nodejs/io.js/blob/3e7a14381497a3b73dda68d05b5130563cdab420/lib/os.js#L25-L43
2976-module.exports = function () {
2977- var path;
2978-
2979- if (isWindows) {
2980- path = process.env.TEMP ||
2981- process.env.TMP ||
2982- (process.env.SystemRoot || process.env.windir) + '\\temp';
2983- } else {
2984- path = process.env.TMPDIR ||
2985- process.env.TMP ||
2986- process.env.TEMP ||
2987- '/tmp';
2988- }
2989-
2990- if (trailingSlashRe.test(path)) {
2991- path = path.slice(0, -1);
2992- }
2993-
2994- return path;
2995-};
2996-
2997-}).call(this,require('_process'))
2998-},{"_process":8}],15:[function(require,module,exports){
2999-(function (process){
3000-var isWindows = process.platform === 'win32'
3001-var path = require('path')
3002-var exec = require('child_process').exec
3003-var osTmpdir = require('os-tmpdir')
3004-var osHomedir = require('os-homedir')
3005-
3006-// looking up envs is a bit costly.
3007-// Also, sometimes we want to have a fallback
3008-// Pass in a callback to wait for the fallback on failures
3009-// After the first lookup, always returns the same thing.
3010-function memo (key, lookup, fallback) {
3011- var fell = false
3012- var falling = false
3013- exports[key] = function (cb) {
3014- var val = lookup()
3015- if (!val && !fell && !falling && fallback) {
3016- fell = true
3017- falling = true
3018- exec(fallback, function (er, output, stderr) {
3019- falling = false
3020- if (er) return // oh well, we tried
3021- val = output.trim()
3022- })
3023- }
3024- exports[key] = function (cb) {
3025- if (cb) process.nextTick(cb.bind(null, null, val))
3026- return val
3027- }
3028- if (cb && !falling) process.nextTick(cb.bind(null, null, val))
3029- return val
3030- }
3031-}
3032-
3033-memo('user', function () {
3034- return ( isWindows
3035- ? process.env.USERDOMAIN + '\\' + process.env.USERNAME
3036- : process.env.USER
3037- )
3038-}, 'whoami')
3039-
3040-memo('prompt', function () {
3041- return isWindows ? process.env.PROMPT : process.env.PS1
3042-})
3043-
3044-memo('hostname', function () {
3045- return isWindows ? process.env.COMPUTERNAME : process.env.HOSTNAME
3046-}, 'hostname')
3047-
3048-memo('tmpdir', function () {
3049- return osTmpdir()
3050-})
3051-
3052-memo('home', function () {
3053- return osHomedir()
3054-})
3055-
3056-memo('path', function () {
3057- return (process.env.PATH ||
3058- process.env.Path ||
3059- process.env.path).split(isWindows ? ';' : ':')
3060-})
3061-
3062-memo('editor', function () {
3063- return process.env.EDITOR ||
3064- process.env.VISUAL ||
3065- (isWindows ? 'notepad.exe' : 'vi')
3066-})
3067-
3068-memo('shell', function () {
3069- return isWindows ? process.env.ComSpec || 'cmd'
3070- : process.env.SHELL || 'bash'
3071-})
3072-
3073-}).call(this,require('_process'))
3074-},{"_process":8,"child_process":1,"os-homedir":13,"os-tmpdir":14,"path":7}],16:[function(require,module,exports){
3075-
3076-// when this is loaded into the browser,
3077-// just use the defaults...
3078-
3079-module.exports = function (name, defaults) {
3080- return defaults
3081-}
3082-
3083-},{}]},{},[9]);
plugins/ssb-config/index.jsView
@@ -1,3 +1,0 @@
1-
2-
3-module.exports = require('./inject')()
plugins/ssb-config/inject.jsView
@@ -1,70 +1,0 @@
1-var path = require('path')
2-var home = require('os-homedir')
3-
4-var nonPrivate = require('non-private-ip')
5-var merge = require('deep-extend')
6-
7-var RC = require('rc')
8-
9-var SEC = 1e3
10-var MIN = 60*SEC
11-
12-module.exports = function (name, override) {
13- name = name || 'decent'
14- var HOME = home() || 'browser' //most probably browser
15- return RC(name || 'decent', merge({
16- //just use an ipv4 address by default.
17- //there have been some reports of seemingly non-private
18- //ipv6 addresses being returned and not working.
19- //https://github.com/ssbc/scuttlebot/pull/102
20- party: true,
21- host: nonPrivate.v4 || '',
22- port: 3333,
23- timeout: 0,
24- pub: true,
25- local: true,
26- friends: {
27- dunbar: 150,
28- hops: 3
29- },
30- ws: {
31- port: 3939
32- },
33- gossip: {
34- connections: 3
35- },
36- path: path.join(HOME, '.' + name),
37- timers: {
38- connection: 0,
39- reconnect: 5*SEC,
40- ping: 5*MIN,
41- handshake: 5*SEC
42- },
43- //change these to make a test network that will not connect to the main network.
44- caps: {
45- //this is the key for accessing the ssb protocol.
46- //this will be updated whenever breaking changes are made.
47- //(see secret-handshake paper for a full explaination)
48- //(generated by crypto.randomBytes(32).toString('base64'))
49- shs: 'EVRctE2Iv8GrO/BpQCF34e2FMPsDJot9x0j846LjVtc=',
50-
51- //used to sign messages
52- sign: null
53- },
54- master: [],
55- logging: { level: 'notice' },
56- party: true //disable quotas
57- }, override || {}))
58-}
59-
60-
61-
62-
63-
64-
65-
66-
67-
68-
69-
70-
plugins/ssb-config/package.jsonView
@@ -1,19 +1,0 @@
1-{
2- "name": "ssb-config",
3- "description": "load ssb config",
4- "version": "2.2.0",
5- "homepage": "https://github.com/dominictarr/ssb-config",
6- "repository": {
7- "type": "git",
8- "url": "git://github.com/dominictarr/ssb-config.git"
9- },
10- "dependencies": {
11- "deep-extend": "^0.4.0",
12- "non-private-ip": "^1.2.1",
13- "os-homedir": "^1.0.1",
14- "rc": "^1.1.6"
15- },
16- "devDependencies": {},
17- "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
18- "license": "MIT"
19-}
plugins/ssb-config/test.jsView
@@ -1,3 +1,0 @@
1-
2-
3-console.log(require('./')('testnet', {port: 9999, friends: {dunbar: 1500}}))
plugins/ssb-links/.npmignoreView
@@ -1,3 +1,0 @@
1-node_modules
2-node_modules/*
3-npm-debug.log
plugins/ssb-links/.travis.ymlView
@@ -1,4 +1,0 @@
1-language: node_js
2-node_js:
3- - 0.6
4- - 0.8
plugins/ssb-links/LICENSEView
@@ -1,22 +1,0 @@
1-Copyright (c) 2016 Dominic Tarr
2-
3-Permission is hereby granted, free of charge,
4-to any person obtaining a copy of this software and
5-associated documentation files (the "Software"), to
6-deal in the Software without restriction, including
7-without limitation the rights to use, copy, modify,
8-merge, publish, distribute, sublicense, and/or sell
9-copies of the Software, and to permit persons to whom
10-the Software is furnished to do so,
11-subject to the following conditions:
12-
13-The above copyright notice and this permission notice
14-shall be included in all copies or substantial portions of the Software.
15-
16-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20-ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
plugins/ssb-links/README.mdView
@@ -1,110 +1,0 @@
1-# ssb-links
2-
3-ssb-plugin that indexes all the links!
4-
5-## Example
6-
7-if you have a handle on the sbot server:
8-``` js
9-sbot.use(require('ssb-links')
10-sbot.links2.read({query: [{$filter: {rel: ['mentions', {$prefix: '@d'}]}}]})
11-```
12-OR if sbot is a client...
13-
14-``` js
15-links2 = require('ssb-links').init(sbot, {path: '~/.ssb'})
16-links2.read({query: [{$filter: {rel: ['mentions', {$prefix: '@d'}]}}]})
17-```
18-
19-## config
20-
21-a leveldb instance will be created at `config.path+'/links'`
22-
23-## api: links2.read ({query: QUERY, live, reverse, limit})
24-
25-If there is no query provided, a full scan of the links database
26-will be performed. this will return a stream of data that looks like
27-
28-``` js
29-{
30- source: @{author_of_link},
31- rel: [{rel}, {rel_data}...],
32- dest: @/%/&{link} //the dest can be any type of ssb object.
33- ts: {local_timestamp}
34-}
35-```
36-
37-this format will be the input to the query.
38-
39-### relation data.
40-
41-this module introduces the concept of "rel data",
42-the rel is now stored as an array, and the data associated
43-with the rel stored with it. by indexing it as an array,
44-it becomes easy to query it when that data is a sortable range
45-(for example, mention names, which may be aphabetically sorted)
46-
47-see `./links.js` to see how data is mapped.
48-
49-TODO: map all markdown links, including http links.
50-
51-### query syntax
52-
53-the query must be a valid [map-filter-reduce](https://github.com/dominictarr/map-filter-reduce)
54-
55-## example queries
56-
57-these queries are in JSON format so you can use them via the cli,
58-sbot `links2.read '{QUERY}'`
59-be sure to use single quotes around the query so that the json is property
60-escaped. otherwise, run these queries by passing them to `sbot.links2.read({query: QUERY})`
61-and taking the output as a pull-stream.
62-
63-### all feeds mentioned
64-
65-returns an stream of `{name, feed, uses}`
66-``` js
67-[
68- {"$filter": {
69- "rel": ["mentions", {"$prefix": "@"}]
70- }},
71-
72- {"$reduce":{
73- "name": ["rel", 1], "feed": "dest", "uses": {"$count": true}
74- }}
75-]
76-```
77-
78-### thread contributors
79-
80-returns feeds in each thread, and how many posts they have made.
81-return object in form of `{[thread_id]:{[poster]:count}}`
82-``` js
83-[
84- {"$filter": {
85- "rel": ["root"]
86- }},
87- {"$reduce":{
88- "thread": "dest", "participant": "source", "posts": {"$count": true}
89- }}
90-]
91-```
92-
93-### names used for "@EMovhfIr...=.ed25519" and who mentioned them.
94-
95-``` js
96-[
97- {"$filter": {
98- "rel": ["mentions", {"$prefix": "@"}],
99- "dest": "@EMovhfIrFk4NihAKnRNhrfRaqIhBv1Wj8pTxJNgvCCY=.ed25519"
100- }},
101- {"$reduce": {
102- "name": ["rel", 1], "by": "source", "uses": {"$count": true}
103- }}
104-]
105-```
106-
107-## License
108-
109-MIT
110-
plugins/ssb-links/index.jsView
@@ -1,62 +1,0 @@
1-var pull = require('pull-stream')
2-var Links = require('streamview-links')
3-var path = require('path')
4-
5-var extractLinks = require('./links')
6-
7-//we could have up to six indexes for links,
8-//but these are the three that we really need.
9-//(queries are fast if the fields you already know
10-//are left most, and the ranges are to the right of that.
11-
12-var indexes = [
13- { key: 'SRD', value: ['source', 'rel', 'dest', 'ts'] },
14- { key: 'DRS', value: ['dest', 'rel', 'source', 'ts'] },
15- { key: 'RDS', value: ['rel', 'dest', 'source', 'ts'] }
16-]
17-
18-exports.name = 'links2'
19-exports.version = require('./package.json').version
20-exports.manifest = {
21- read: 'source',
22- dump: 'source'
23-}
24-exports.init = function (ssb, config) {
25-
26- var dir = path.join(config.path, 'links')
27-
28- var version = 5
29- //it's really nice to tweak a few things
30- //and then change the version number,
31- //restart the server and have it regenerate the indexes,
32- //all consistent again.
33- var links = Links(dir, indexes, extractLinks, version)
34-
35- links.init(function (err, since) {
36- console.error('LOAD LINKS SINCE', err, since)
37- pull(
38- ssb.createLogStream({gt: since || 0, live: true, limit: -1}),
39- pull.through(function (e) {
40- process.stderr.write('.')
41- }),
42- links.write(function (err) {
43- if(err) throw err
44- })
45- )
46- })
47-
48- return {
49- dump: function () {
50- return links.dump()
51- },
52- read: function (opts) {
53- if(opts && 'string' == typeof opts)
54- try { opts = {query: JSON.parse(opts) } } catch (err) {
55- return pull.error(err)
56- }
57- return links.read(opts)
58- }
59- }
60-}
61-
62-
plugins/ssb-links/links.jsView
@@ -1,65 +1,0 @@
1-var msgs = require('ssb-msgs')
2-
3-module.exports = function (data, iter) {
4- if(data.sync) return
5- var content = data.value.content
6- var source = data.value.author
7-
8- //TODO parse the links from markdown
9- //and index those also. most of these are http links,
10- //some ipfs.
11-
12- //it would be easy to tag another message,
13- //and query that, once markdown links are ready
14- //[#hashtag](msgId) would tag msg with #hashtag
15-
16- //TODO: what about a syntax for self-links?
17- //interpret a lone hashtag in a message as a selflink,
18- //and then that will work.
19-
20- msgs.indexLinks(data.value, function (ln, rel) {
21- var dest = ln.link
22-
23- //take all the already existing links and put
24- //the relavant aspects into the index,
25- //as part of the rel, so that we don't need to lookup
26- //the message to get them, and even better,
27- //we can query by these attributes! enabling search.
28-
29- if(rel == 'vote')
30- rel = ['vote', ln.value]
31- else if(rel == 'flag')
32- rel = ['flag', ln.reason]
33- else if(rel == 'mentions') {
34- if(ln.link[0] === '@')
35- rel = ['mentions', '@'+(ln.name||'').toLowerCase()]
36- else if(ln.link[0] == '&') {
37- rel = ['mentions', ln.filename || ln.name, ln.size]
38- }
39- else {
40- //TODO: check whether they included some text in the markdown link.
41- rel = ['mentions']
42- }
43- }
44- else if(rel == 'about') {
45- rel = ['about', content.name]
46- }
47- else if(rel == 'image')
48- rel = ['image', ln.type, ln.size]
49- else if(rel == 'contact')
50- rel = ['contact', content.following, content.blocking]
51- else
52- rel = [rel]
53-
54- iter({
55- source: source, dest: dest,
56- rel: rel,
57- ts: data.timestamp
58- })
59- })
60-}
61-
62-
63-
64-
65-
plugins/ssb-links/package.jsonView
@@ -1,21 +1,0 @@
1-{
2- "name": "ssb-links",
3- "description": "",
4- "version": "2.0.0",
5- "homepage": "https://github.com/dominictarr/ssb-links",
6- "repository": {
7- "type": "git",
8- "url": "git://github.com/dominictarr/ssb-links.git"
9- },
10- "dependencies": {
11- "pull-stream": "^3.1.0",
12- "ssb-msgs": "^5.2.0",
13- "streamview-links": "^2.0.0"
14- },
15- "devDependencies": {},
16- "scripts": {
17- "test": "set -e; for t in test/*.js; do node $t; done"
18- },
19- "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
20- "license": "MIT"
21-}
plugins/ssb-query/.npmignoreView
@@ -1,3 +1,0 @@
1-node_modules
2-node_modules/*
3-npm-debug.log
plugins/ssb-query/.travis.ymlView
@@ -1,4 +1,0 @@
1-language: node_js
2-node_js:
3- - 0.6
4- - 0.8
plugins/ssb-query/LICENSEView
@@ -1,22 +1,0 @@
1-Copyright (c) 2016 Dominic Tarr
2-
3-Permission is hereby granted, free of charge,
4-to any person obtaining a copy of this software and
5-associated documentation files (the "Software"), to
6-deal in the Software without restriction, including
7-without limitation the rights to use, copy, modify,
8-merge, publish, distribute, sublicense, and/or sell
9-copies of the Software, and to permit persons to whom
10-the Software is furnished to do so,
11-subject to the following conditions:
12-
13-The above copyright notice and this permission notice
14-shall be included in all copies or substantial portions of the Software.
15-
16-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20-ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
plugins/ssb-query/README.mdView
@@ -1,6 +1,0 @@
1-# ssb-query
2-
3-
4-## License
5-
6-MIT
plugins/ssb-query/index.jsView
@@ -1,78 +1,0 @@
1-
2-var pull = require('pull-stream')
3-var path = require('path')
4-var Links = require('streamview-links')
5-var explain = require('explain-error')
6-
7-exports.name = 'query'
8-exports.version = require('./package.json').version
9-exports.manifest = {
10- read: 'source', dump: 'source'
11-}
12-
13-var indexes = [
14- {key: 'clk', value: [['value', 'author'], ['value', 'sequence'], 'timestamp'] },
15- {key: 'typ', value: [['value', 'content', 'type'], 'timestamp'] },
16- {key: 'hsh', value: ['key', 'timestamp']},
17- {key: 'cha', value: [['value', 'content', 'channel'], 'timestamp'] },
18-// {key: 'aty', value: [['value', 'author'], ['value', 'content', 'type'], 'ts']}
19-]
20-
21-//createHistoryStream( id, seq )
22-//[{$filter: {author: <id>, sequence: {$gt: <seq>}}}, {$map: true}]
23-
24-//messagesByType (type)
25-
26-//[{$filter: {content: {type: <type>}}}, {$map: true}]
27-
28-exports.init = function (ssb, config) {
29-
30- var dir = path.join(config.path, 'query')
31-
32- var version = 13
33- //it's really nice to tweak a few things
34- //and then change the version number,
35- //restart the server and have it regenerate the indexes,
36- //all consistent again.
37- function id (e, emit) {
38- return emit(e)
39- }
40-
41- var links = Links(dir, indexes, id, version)
42-
43- links.init(function (err, since) {
44- pull(
45- ssb.createLogStream({gt: since || 0, live: true, sync: false}),
46- pull.through(function () {
47- process.stdout.write('x')
48- }),
49- links.write(function (err) {
50- if(err) throw err
51- })
52- )
53- })
54-
55- return {
56- dump: function () {
57- return links.dump()
58- },
59-
60- read: function (opts) {
61- if(opts && 'string' == typeof opts)
62- try { opts = {query: JSON.parse(opts) } } catch (err) {
63- return pull.error(err)
64- }
65- return links.read(opts, function (ts, cb) {
66- ssb.sublevel('log').get(ts, function (err, key) {
67- if(err) return cb(explain(err, 'missing timestamp:'+ts))
68- ssb.get(key, function (err, value) {
69- if(err) return cb(explain(err, 'missing key:'+key))
70- cb(null, {key: key, value: value, timestamp: ts})
71- })
72- })
73- })
74- }
75- }
76-}
77-
78-
plugins/ssb-query/package.jsonView
@@ -1,21 +1,0 @@
1-{
2- "name": "ssb-query",
3- "description": "",
4- "version": "0.1.2",
5- "homepage": "https://github.com/dominictarr/ssb-query",
6- "repository": {
7- "type": "git",
8- "url": "git://github.com/dominictarr/ssb-query.git"
9- },
10- "dependencies": {
11- "explain-error": "^1.0.1",
12- "pull-stream": "^3.4.2",
13- "streamview-links": "^2.0.0"
14- },
15- "devDependencies": {},
16- "scripts": {
17- "test": "set -e; for t in test/*.js; do node $t; done"
18- },
19- "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
20- "license": "MIT"
21-}
plugins/ssb-ws/.npmignoreView
@@ -1,3 +1,0 @@
1-node_modules
2-node_modules/*
3-npm-debug.log
plugins/ssb-ws/.travis.ymlView
@@ -1,4 +1,0 @@
1-language: node_js
2-node_js:
3- - 0.6
4- - 0.8
plugins/ssb-ws/LICENSEView
@@ -1,22 +1,0 @@
1-Copyright (c) 2016 'Dominic Tarr'
2-
3-Permission is hereby granted, free of charge,
4-to any person obtaining a copy of this software and
5-associated documentation files (the "Software"), to
6-deal in the Software without restriction, including
7-without limitation the rights to use, copy, modify,
8-merge, publish, distribute, sublicense, and/or sell
9-copies of the Software, and to permit persons to whom
10-the Software is furnished to do so,
11-subject to the following conditions:
12-
13-The above copyright notice and this permission notice
14-shall be included in all copies or substantial portions of the Software.
15-
16-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20-ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
plugins/ssb-ws/README.mdView
@@ -1,21 +1,0 @@
1-# ssb-ws
2-
3-ssb-ws & http server for ssb.
4-
5-Work In Progress
6-
7-``` js
8-sbot plugins.install ssb-ws
9-```
10-
11-make sure you set a port in your config file (~/.ssb/config).
12-
13-``` json
14-{
15- "ws": {"port": 8989}
16-}
17-```
18-
19-## License
20-
21-MIT
plugins/ssb-ws/index.jsView
@@ -1,122 +1,0 @@
1-var MultiServer = require('multiserver')
2-var WS = require('multiserver/plugins/ws')
3-var SHS = require('multiserver/plugins/shs')
4-var http = require('http')
5-var muxrpc = require('muxrpc')
6-var pull = require('pull-stream')
7-var JSONApi = require('./json-api')
8-
9-var cap =
10- new Buffer('EVRctE2Iv8GrO/BpQCF34e2FMPsDJot9x0j846LjVtc=', 'base64')
11-
12-function toSodiumKeys(keys) {
13- return {
14- publicKey:
15- new Buffer(keys.public.replace('.ed25519',''), 'base64'),
16- secretKey:
17- new Buffer(keys.private.replace('.ed25519',''), 'base64'),
18- }
19-}
20-
21-var READ_AND_ADD = [ //except for add, of course
22- 'get',
23- 'getLatest',
24- 'createLogStream',
25- 'createUserStream',
26-
27- 'createHistoryStream',
28- 'getAddress',
29-
30- 'links',
31-
32- 'blobs.add',
33- 'blobs.size',
34- 'blobs.has',
35- 'blobs.get',
36- 'blobs.changes',
37- 'blobs.createWants',
38-
39-
40- 'add',
41-
42- 'query.read',
43- 'links2.read'
44-]
45-
46-
47-exports.name = 'ws'
48-exports.version = require('./package.json').version
49-exports.manifest = {
50- getAddress: 'sync'
51-}
52-
53-function toId(id) {
54- return '@'+id.toString('base64')+'.ed25519'
55-}
56-
57-exports.init = function (sbot, config) {
58-
59- var port
60- if(config.ws)
61- port = config.ws.port
62- if(!port)
63- port = 1024+(~~(Math.random()*(65536-1024)))
64-
65- var server = http.createServer(JSONApi(sbot)).listen(port)
66-
67- //allow friends to
68- sbot.auth.hook(function (fn, args) {
69- var id = args[0]
70- var cb = args[1]
71- var self = this
72- fn.apply(self, [id, function (err, allowed) {
73- if(err) return cb(err)
74- if(allowed) return cb(null, allowed)
75- sbot.friends.get({source: sbot.id, dest: id}, function (err, follows) {
76- if(err) return cb(err)
77- else if(follows) cb(null, {allow: READ_AND_ADD, deny: null})
78- else cb()
79- })
80- }])
81-
82- })
83-
84- var ms = MultiServer([
85- [
86- WS({server: server, port: port, host: config.host || 'localhost'}),
87- SHS({
88- keys: toSodiumKeys(config.keys),
89- appKey: (config.caps && config.caps.shs) || cap,
90- auth: function (id, cb) {
91- sbot.auth(toId(id), cb)
92- },
93- timeout: config.timeout
94- })
95- ]
96- ])
97-
98- var close = ms.server(function (stream) {
99- var manifest = sbot.getManifest()
100- var rpc = muxrpc({}, manifest)(sbot, stream.auth)
101- rpc.id = toId(stream.remote)
102- pull(stream, rpc.createStream(), stream)
103- })
104-
105- //close when the server closes.
106- sbot.close.hook(function (fn, args) {
107- close()
108- fn.apply(this, args)
109- })
110-
111- return {
112- getAddress: function () {
113- return ms.stringify()
114- }
115-
116- }
117-}
118-
119-
120-
121-
122-
plugins/ssb-ws/json-api.jsView
@@ -1,87 +1,0 @@
1-'use strict'
2-var ref = require('ssb-ref')
3-var Stack = require('stack')
4-var BlobsHttp = require('multiblob-http')
5-var sort = require('ssb-sort')
6-var pull = require('pull-stream')
7-var URL = require('url')
8-var Emoji = require('emoji-server')
9-
10-function msgHandler(path, handler) {
11- return function (req, res, next) {
12- //console.log(req.method, req.url)
13- if(req.method !== 'GET') return next()
14- if(req.url.indexOf(path) === 0) {
15- var id = req.url.substring(path.length)
16- console.log("MSG?", id)
17- if(!ref.isMsg(id))
18- next(new Error('not a valid message id:'+id))
19- else {
20- req.id = id
21- handler(req, res, next)
22- }
23- }
24- else
25- next()
26- }
27-}
28-
29-function send(res, obj) {
30- res.writeHead(200, {'Content-Type': 'application/json'})
31- res.end(JSON.stringify(obj, null, 2))
32-}
33-
34-module.exports = function (sbot) {
35- var prefix = '/blobs'
36- return Stack(
37- Emoji('/img/emoji'),
38- function (req, res, next) {
39- res.setHeader('Access-Control-Allow-Origin', '*')
40- res.setHeader("Access-Control-Allow-Headers",
41- "Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since");
42- res.setHeader("Access-Control-Allow-Methods", "GET", "HEAD");
43- next()
44- },
45- msgHandler('/msg/', function (req, res, next) {
46- sbot.get(req.id, function (err, msg) {
47- if(err) return next(err)
48- send(res, {key: req.id, value: msg})
49- })
50- }),
51- msgHandler('/thread/', function (req, res, next) {
52- sbot.get(req.id, function (err, value) {
53- if(err) return next(err)
54- var msg = {key: req.id, value: value}
55-
56- pull(
57- sbot.links({rel: 'root', dest: req.id, values: true, keys: true}),
58- pull.collect(function (err, ary) {
59- if(err) return next(err)
60- ary.unshift(msg)
61- send(res, sort(ary))
62- })
63- )
64- })
65-
66- }),
67- function (req, res, next) {
68- if(!(req.method === "GET" || req.method == 'HEAD')) return next()
69-
70- var u = URL.parse('http://makeurlparseright.com'+req.url)
71- var hash = decodeURIComponent(u.pathname.substring((prefix+'/get/').length))
72- //check if we don't already have this, tell blobs we want it, if necessary.
73- sbot.blobs.has(hash, function (err, has) {
74- if(has) next()
75- else sbot.blobs.want(hash, function (err, has) { next() })
76- })
77- },
78- BlobsHttp(sbot.blobs, prefix)
79- )
80-}
81-
82-
83-
84-
85-
86-
87-
plugins/ssb-ws/npm-debug.logView
@@ -1,24 +1,0 @@
1-0 info it worked if it ends with ok
2-1 verbose cli [ '/usr/bin/node', '/usr/bin/npm', 'start' ]
3-2 info using npm@4.1.1
4-3 info using node@v7.4.0
5-4 verbose stack Error: missing script: start
6-4 verbose stack at run (/usr/lib/node_modules/npm/lib/run-script.js:151:19)
7-4 verbose stack at /usr/lib/node_modules/npm/lib/run-script.js:61:5
8-4 verbose stack at /usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:356:5
9-4 verbose stack at checkBinReferences_ (/usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:320:45)
10-4 verbose stack at final (/usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:354:3)
11-4 verbose stack at then (/usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:124:5)
12-4 verbose stack at /usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:243:12
13-4 verbose stack at /usr/lib/node_modules/npm/node_modules/graceful-fs/graceful-fs.js:78:16
14-4 verbose stack at tryToString (fs.js:426:3)
15-4 verbose stack at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:413:12)
16-5 verbose cwd /home/ev/decent/plugins/ssb-ws
17-6 error Linux 4.8.13-1-ARCH
18-7 error argv "/usr/bin/node" "/usr/bin/npm" "start"
19-8 error node v7.4.0
20-9 error npm v4.1.1
21-10 error missing script: start
22-11 error If you need help, you may report this error at:
23-11 error <https://github.com/npm/npm/issues>
24-12 verbose exit [ 1, true ]
plugins/ssb-ws/package.jsonView
@@ -1,27 +1,0 @@
1-{
2- "name": "ssb-ws",
3- "description": "websocket & http server for ssb",
4- "version": "1.0.1",
5- "homepage": "https://github.com/dominictarr/ssb-ws",
6- "repository": {
7- "type": "git",
8- "url": "git://github.com/dominictarr/ssb-ws.git"
9- },
10- "dependencies": {
11- "emoji-server": "^1.0.0",
12- "multiblob-http": "^0.2.0",
13- "multiserver": "^1.2.0",
14- "muxrpc": "^6.3.3",
15- "pull-stream": "^3.4.3",
16- "ssb-ref": "^2.3.0",
17- "ssb-sort": "0.0.0",
18- "stack": "^0.1.0",
19- "web-bootloader": "^0.1.0"
20- },
21- "devDependencies": {},
22- "scripts": {
23- "test": "set -e; for t in test/*.js; do node $t; done"
24- },
25- "author": "'Dominic Tarr' <dominic.tarr@gmail.com> (dominictarr.com)",
26- "license": "MIT"
27-}
plugins/viewer/README.mdView
@@ -1,108 +1,0 @@
1-# ssb-viewer
2-
3-HTTP server for read-only views of SSB content. Serves content as web pages or as scripts for embedding in other web pages.
4-
5-## Install & Run
6-
7-As a sbot plugin:
8-```sh
9-mkdir -p ~/.ssb/node_modules
10-cd ~/.ssb/node_modules
11-git clone ssb://%MeCTQrz9uszf9EZoTnKCeFeIedhnKWuB3JHW2l1g9NA=.sha256 ssb-viewer && cd ssb-viewer
12-npm install
13-sbot plugins.enable ssb-viewer
14-# restart sbot
15-```
16-
17-Or standalone:
18-```sh
19-git clone ssb://%MeCTQrz9uszf9EZoTnKCeFeIedhnKWuB3JHW2l1g9NA=.sha256 ssb-viewer && cd ssb-viewer
20-npm install
21-./bin.js
22-```
23-
24-## Usage
25-
26-To view a thread as a web page, navigate to a url like `http://localhost:8807/%MSGID`.
27-
28-To embed a thread into another web page, load it as follows:
29-
30-```html
31-<script src="http://localhost:8807/%MSGID.js"></script>
32-```
33-
34-To add more than the base styles, you can also load `http://localhost:8807/static/nicer.css`.
35-
36-## Routes
37-
38-- `/%msgid`: web page showing a message thread
39-- `/%msgid.js`: script to embed a message thread
40-- `/%msgid.json`: message thread as JSON
41-- `/&feedid`: web page showing a complete feed
42-- `/user-feed/&feedid`: web page showing messages from followed users and channels of a feed
43-- `/channel/#channel`: web page showing messages in a specific channel
44-
45-### Query options
46-
47-- `noroot`: don't include the root message in the thread
48-- `base=...`: base url for links that ssb-viewer can handle
49-- `msg_base=...`: base url for links to messages
50-- `feed_base=...`: base url for links to feeds
51-- `blob_base=...`: base url for links to blobs
52-- `img_base=...`: base url for embedded blobs (images)
53-- `emoji_base=...`: base url for emoji images
54-
55-The `*_base` query options overwrite the defaults set in the config.
56-The `base` option is a fallback instead of specifying the URLs separately.
57-The base options are mostly useful for embedding, where the script is embedded
58-on a different origin than where ssb-viewer is running. However, you may not
59-need them, as the ssb-viewer embed script will detect the base where it is
60-included from.
61-
62-## Config
63-
64-To change `ssb-viewer`'s default options, edit your `~/.ssb/config`, to have
65-properties like the following:
66-```json
67-{
68- "viewer": {
69- "port": 8807,
70- "host": "::"
71- }
72-}
73-```
74-You can also pass these as command-line options to `./bin.js` or `sbot` as,
75-e.g. `--viewer.port 8807`.
76-
77-- `viewer.port`: port for the server to listen on. default: `8807`
78-- `viewer.host`: host address for the server to listen on. default: `::`
79-- `viewer.base`: default base url for links that ssb-viewer can handle
80-- `viewer.msg_base`: base url for links to ssb messages
81-- `viewer.feed_base`: base url for links to ssb feeds
82-- `viewer.blob_base`: base url for links to ssb blobs
83-- `viewer.img_base`: base url for embedded blobs (images)
84-- `viewer.emoji_base`: base url for emoji images
85-
86-## References
87-
88-- Concept: [ssb-porthole][]
89-- UI ideas: [sdash][], [patchbay][]
90-- Server techniques: [ssb-web-server][], [ssb-ws][], [git-ssb-web][]
91-
92-
93-[ssb-porthole]: %cgkDJXsh6pO5m458B3ngEro+U0qUMGTY1TRGTZOP6lQ=.sha256
94-[patchbay]: %s9mSFATE4RGyJx9wgH22lBrvD4CgUQW4yeguSWWjtqc=.sha256
95-[sdash]: %qrU04j9vfUJKfq1rGZrQ5ihtSfA4ilfY3wLy7xFv0xk=.sha256
96-[git-ssb-web]: %q5d5Du+9WkaSdjc8aJPZm+jMrqgo0tmfR+RcX5ZZ6H4=.sha256
97-[ssb-web-server]: %gYctTCrA06BhAGGvQ6PJ0H2eCCQLj1iEsmfn8SD5+nk=.sha256
98-[ssb-ws]: %tFjo5SoD+Y0SaB5vqZYppmoPmv9LKB5wMPl96qtu4qk=.sha256
99-
100-## License
101-
102-Copyright (c) 2016-2017 Secure Scuttlebutt Consortium
103-
104-Usage of the works is permitted provided that this instrument is
105-retained with the works, so that any entity that uses the works is
106-notified of this instrument.
107-
108-DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
plugins/viewer/bin.jsView
@@ -1,6 +1,0 @@
1-#!/usr/bin/env node
2-
3-require('ssb-client')(function (err, sbot, config) {
4- if (err) throw err
5- require('.').init(sbot, config)
6-})
plugins/viewer/example.htmlView
@@ -1,28 +1,0 @@
1-<!DOCTYPE html>
2-<html>
3-<head>
4-<meta charset=utf-8>
5-<title>ssb-viewer</title>
6-<meta name=viewport content="width=device-width,initial-scale=1">
7-<link rel="stylesheet" href="static/nicer.css">
8-<style>
9-body {
10- background-color: #f3f3f3;
11-}
12-#demo .ssb-thread {
13- background-color: white;
14- padding: 1ex 2em;
15-}
16-</style>
17-</head>
18-<body id="demo">
19- <center>
20- <h1>Embed Example</h1>
21- </center>
22- <script src="http://localhost:8807/%fU0K0uC4orV6258+dGuAcA95TSfc9PrTtKIG9uL3rWI=.sha256.js"></script>
23- <center>
24- <p>the end</p>
25- </center>
26-</div>
27-</body>
28-</html>
plugins/viewer/index.jsView
@@ -1,638 +1,0 @@
1-var fs = require('fs')
2-var http = require('http')
3-var qs = require('querystring')
4-var path = require('path')
5-var crypto = require('crypto')
6-var cat = require('pull-cat')
7-var pull = require('pull-stream')
8-var paramap = require('pull-paramap')
9-var marked = require('ssb-marked')
10-var sort = require('ssb-sort')
11-var toPull = require('stream-to-pull-stream')
12-var memo = require('asyncmemo')
13-var lru = require('lrucache')
14-var htime = require('human-time')
15-var emojis = require('emoji-named-characters')
16-var serveEmoji = require('emoji-server')()
17-
18-var emojiDir = path.join(require.resolve('emoji-named-characters'), '../pngs')
19-var appHash = hash([fs.readFileSync(__filename)])
20-
21-var urlIdRegex = /^(?:\/(([%&@]|%25|%26|%40)(?:[A-Za-z0-9\/+]|%2[Ff]|%2[Bb]){43}(?:=|%3[Dd])\.(?:sha256|ed25519))(?:\.([^?]*))?|(\/.*?))(?:\?(.*))?$/
22-
23-function MdRenderer(opts) {
24- marked.Renderer.call(this, {})
25- this.opts = opts
26-}
27-MdRenderer.prototype = new marked.Renderer()
28-
29-MdRenderer.prototype.urltransform = function (href) {
30- if (!href) return false
31- switch (href[0]) {
32- case '#': return '/channel/' + href.slice(1)
33- case '%': return this.opts.msg_base + encodeURIComponent(href)
34- case '@': return this.opts.feed_base + encodeURIComponent(href)
35- case '&': return this.opts.blob_base + encodeURIComponent(href)
36- }
37- if (href.indexOf('javascript:') === 0) return false
38- return href
39-}
40-
41-MdRenderer.prototype.image = function (href, title, text) {
42- return '<img src="' + this.opts.img_base + escape(href) + '"'
43- + ' alt="' + text + '"'
44- + (title ? ' title="' + title + '"' : '')
45- + (this.options.xhtml ? '/>' : '>')
46-}
47-
48-function renderEmoji(emoji) {
49- var opts = this.renderer.opts
50- return emoji in emojis ?
51- '<img src="' + opts.emoji_base + escape(emoji) + '.png"'
52- + ' alt=":' + escape(emoji) + ':"'
53- + ' title=":' + escape(emoji) + ':"'
54- + ' class="ssb-emoji" height="16" width="16">'
55- : ':' + emoji + ':'
56-}
57-
58-exports.name = 'viewer'
59-exports.manifest = {}
60-exports.version = require('./package').version
61-
62-exports.init = function (sbot, config) {
63- var conf = config.viewer || {}
64- var port = conf.port || 3535
65- var host = conf.host || config.host || '::'
66-
67- var base = conf.base || '/'
68- var defaultOpts = {
69- msg_base: conf.msg_base || base,
70- feed_base: conf.feed_base || base,
71- blob_base: conf.blob_base || base,
72- img_base: conf.img_base || base,
73- emoji_base: conf.emoji_base || (base + 'emoji/'),
74- }
75-
76- var getMsg = memo({cache: lru(100)}, getMsgWithValue, sbot)
77- var getAbout = memo({cache: lru(100)}, require('./lib/about'), sbot)
78-
79- http.createServer(serve).listen(port, host, function () {
80- console.log('[viewer] Listening on http://' + host + ':' + port)
81- })
82-
83- function serve(req, res) {
84- if (req.method !== 'GET' && req.method !== 'HEAD') {
85- return respond(res, 405, 'Method must be GET or HEAD')
86- }
87-
88- var m = urlIdRegex.exec(req.url)
89-
90- if (req.url.startsWith('/user-feed/')) return serveUserFeed(req, res, m[4])
91- else if (req.url.startsWith('/channel/')) return serveChannel(req, res, m[4])
92-
93- if (m[2] && m[2].length === 3) {
94- m[1] = decodeURIComponent(m[1])
95- m[2] = m[1][0]
96- }
97- switch (m[2]) {
98- case '%': return serveId(req, res, m[1], m[3], m[5])
99- case '@': return serveFeed(req, res, m[1], m[3], m[5])
100- case '&': return serveBlob(req, res, sbot, m[1])
101- default: return servePath(req, res, m[4])
102- }
103- }
104-
105- function serveFeed(req, res, feedId) {
106- console.log("serving feed: " + feedId)
107-
108- var opts = defaultOpts
109-
110- opts.marked = {
111- gfm: true,
112- mentions: true,
113- tables: true,
114- breaks: true,
115- pedantic: false,
116- sanitize: true,
117- smartLists: true,
118- smartypants: false,
119- emoji: renderEmoji,
120- renderer: new MdRenderer(opts)
121- }
122-
123- getAbout(feedId, function (err, about) {
124- if (err) return cb(err)
125-
126- pull(
127- sbot.createUserStream({ id: feedId, reverse: true }),
128- pull.collect(function (err, logs) {
129- if (err) return respond(res, 500, err.stack || err)
130- res.writeHead(200, {
131- 'Content-Type': ctype("html")
132- })
133- pull(
134- pull.values(logs),
135- paramap(addAuthorAbout, 8),
136- paramap(addFollowAbout, 8),
137- paramap(addVoteMessage, 8),
138- pull(renderThread(opts), wrapPage(about.name)),
139- toPull(res, function (err) {
140- if (err) console.error('[viewer]', err)
141- })
142- )
143- })
144- )
145- })
146- }
147-
148- function serveUserFeed(req, res, url) {
149- var feedId = url.substring(url.lastIndexOf('user-feed/')+10, 100)
150- console.log("serving user feed: " + feedId)
151-
152- var following = []
153- var channelSubscriptions = []
154-
155- getAbout(feedId, function (err, about) {
156- pull(
157- sbot.createUserStream({ id: feedId }),
158- pull.filter((msg) => {
159- return !msg.value ||
160- msg.value.content.type == 'contact' ||
161- (msg.value.content.type == 'channel' &&
162- typeof msg.value.content.subscribed != 'undefined')
163- }),
164- pull.collect(function (err, msgs) {
165- msgs.forEach((msg) => {
166- if (msg.value.content.type == 'contact')
167- {
168- if (msg.value.content.following)
169- following[msg.value.content.contact] = 1
170- else
171- delete following[msg.value.content.contact]
172- }
173- else // channel subscription
174- {
175- if (msg.value.content.subscribed)
176- channelSubscriptions[msg.value.content.channel] = 1
177- else
178- delete following[msg.value.content.channel]
179- }
180- })
181-
182- serveFeeds(req, res, following, channelSubscriptions, feedId, 'user feed ' + about.name)
183- })
184- )
185- })
186- }
187-
188- function serveFeeds(req, res, following, channelSubscriptions, feedId, name) {
189- var opts = defaultOpts
190-
191- opts.marked = {
192- gfm: true,
193- mentions: true,
194- tables: true,
195- breaks: true,
196- pedantic: false,
197- sanitize: true,
198- smartLists: true,
199- smartypants: false,
200- emoji: renderEmoji,
201- renderer: new MdRenderer(opts)
202- }
203-
204- pull(
205- sbot.createLogStream({ reverse: true, limit: 2500 }),
206- pull.filter((msg) => {
207- return !msg.value ||
208- (msg.value.author in following ||
209- msg.value.content.channel in channelSubscriptions)
210- }),
211- pull.take(100),
212- pull.collect(function (err, logs) {
213- if (err) return respond(res, 500, err.stack || err)
214- res.writeHead(200, {
215- 'Content-Type': ctype("html")
216- })
217- pull(
218- pull.values(logs),
219- paramap(addAuthorAbout, 8),
220- paramap(addFollowAbout, 8),
221- paramap(addVoteMessage, 8),
222- pull(renderThread(opts), wrapPage(name)),
223- toPull(res, function (err) {
224- if (err) console.error('[viewer]', err)
225- })
226- )
227- })
228- )
229- }
230-
231- function serveChannel(req, res, url) {
232- var channelId = url.substring(url.lastIndexOf('channel/')+8, 100)
233- console.log("serving channel: " + channelId)
234-
235- var opts = defaultOpts
236-
237- opts.marked = {
238- gfm: true,
239- mentions: true,
240- tables: true,
241- breaks: true,
242- pedantic: false,
243- sanitize: true,
244- smartLists: true,
245- smartypants: false,
246- emoji: renderEmoji,
247- renderer: new MdRenderer(opts)
248- }
249-
250- pull(
251- sbot.query.read({ limit: 500, reverse: true, query: [{$filter: { value: { content: { channel: channelId }}}}]}),
252- pull.collect(function (err, logs) {
253- if (err) return respond(res, 500, err.stack || err)
254- res.writeHead(200, {
255- 'Content-Type': ctype("html")
256- })
257- pull(
258- pull.values(logs),
259- paramap(addAuthorAbout, 8),
260- paramap(addVoteMessage, 8),
261- pull(renderThread(opts), wrapPage('#' + channelId)),
262- toPull(res, function (err) {
263- if (err) console.error('[viewer]', err)
264- })
265- )
266- })
267- )
268- }
269-
270- function addFollowAbout(msg, cb) {
271- if (msg.value.content.contact)
272- getAbout(msg.value.content.contact, function (err, about) {
273- if (err) return cb(err)
274- msg.value.content.contactAbout = about
275- cb(null, msg)
276- })
277- else
278- cb(null, msg)
279- }
280-
281- function addVoteMessage(msg, cb) {
282- if (msg.value.content.type == 'vote' && msg.value.content.vote.link[0] == '%')
283- getMsg(msg.value.content.vote.link, function (err, linkedMsg) {
284- if (linkedMsg)
285- msg.value.content.vote.linkedText = linkedMsg.value.content.text
286- cb(null, msg)
287- })
288- else
289- cb(null, msg)
290- }
291-
292- function serveId(req, res, id, ext, query) {
293- var q = query ? qs.parse(query) : {}
294- var includeRoot = !('noroot' in q)
295- var base = q.base || conf.base
296- var baseToken
297- if (!base) {
298- if (ext === 'js') base = baseToken = '__BASE_' + Math.random() + '_'
299- else base = '/'
300- }
301- var opts = {
302- base: base,
303- base_token: baseToken,
304- msg_base: q.msg_base || conf.msg_base || base,
305- feed_base: q.feed_base || conf.feed_base || base,
306- blob_base: q.blob_base || conf.blob_base || base,
307- img_base: q.img_base || conf.img_base || base,
308- emoji_base: q.emoji_base || conf.emoji_base || (base + 'emoji/'),
309- }
310- opts.marked = {
311- gfm: true,
312- mentions: true,
313- tables: true,
314- breaks: true,
315- pedantic: false,
316- sanitize: true,
317- smartLists: true,
318- smartypants: false,
319- emoji: renderEmoji,
320- renderer: new MdRenderer(opts)
321- }
322-
323- var format = formatMsgs(id, ext, opts)
324- if (format === null) return respond(res, 415, 'Invalid format')
325-
326- pull(
327- sbot.links({dest: id, values: true, rel: 'root'}),
328- includeRoot && prepend(getMsg, id),
329- pull.unique('key'),
330- pull.collect(function (err, links) {
331- if (err) return respond(res, 500, err.stack || err)
332- var etag = hash(sort.heads(links).concat(appHash, ext, qs))
333- if (req.headers['if-none-match'] === etag) return respond(res, 304)
334- res.writeHead(200, {
335- 'Content-Type': ctype(ext),
336- 'etag': etag
337- })
338- pull(
339- pull.values(sort(links)),
340- paramap(addAuthorAbout, 8),
341- format,
342- toPull(res, function (err) {
343- if (err) console.error('[viewer]', err)
344- })
345- )
346- })
347- )
348- }
349-
350- function addAuthorAbout(msg, cb) {
351- getAbout(msg.value.author, function (err, about) {
352- if (err) return cb(err)
353- msg.author = about
354- cb(null, msg)
355- })
356- }
357-}
358-
359-function serveBlob(req, res, sbot, id) {
360- if (req.headers['if-none-match'] === id) return respond(res, 304)
361- sbot.blobs.has(id, function (err, has) {
362- if (err) {
363- if (/^invalid/.test(err.message)) return respond(res, 400, err.message)
364- else return respond(res, 500, err.message || err)
365- }
366- if (!has) return respond(res, 404, 'Not found')
367- res.writeHead(200, {
368- 'Cache-Control': 'public, max-age=315360000',
369- 'etag': id
370- })
371- pull(
372- sbot.blobs.get(id),
373- toPull(res, function (err) {
374- if (err) console.error('[viewer]', err)
375- })
376- )
377- })
378-}
379-
380-function getMsgWithValue(sbot, id, cb) {
381- sbot.get(id, function (err, value) {
382- if (err) return cb(err)
383- cb(null, {key: id, value: value})
384- })
385-}
386-
387-function escape(str) {
388- return String(str)
389- .replace(/&/g, '&amp;')
390- .replace(/</g, '&lt;')
391- .replace(/>/g, '&gt;')
392- .replace(/"/g, '&quot;')
393-}
394-
395-function respond(res, status, message) {
396- res.writeHead(status)
397- res.end(message)
398-}
399-
400-function ctype(name) {
401- switch (name && /[^.\/]*$/.exec(name)[0] || 'html') {
402- case 'html': return 'text/html'
403- case 'js': return 'text/javascript'
404- case 'css': return 'text/css'
405- case 'json': return 'application/json'
406- }
407-}
408-
409-function servePath(req, res, url) {
410- switch (url) {
411- case '/robots.txt': return res.end('User-agent: *')
412- }
413- var m = /^(\/?[^\/]*)(\/.*)?$/.exec(url)
414- switch (m[1]) {
415- case '/static': return serveStatic(req, res, m[2])
416- case '/emoji': return serveEmoji(req, res, m[2])
417- }
418- return respond(res, 404, 'Not found')
419-}
420-
421-function ifModified(req, lastMod) {
422- var ifModSince = req.headers['if-modified-since']
423- if (!ifModSince) return false
424- var d = new Date(ifModSince)
425- return d && Math.floor(d/1000) >= Math.floor(lastMod/1000)
426-}
427-
428-function serveStatic(req, res, file) {
429- serveFile(req, res, path.join(__dirname, 'static', file))
430-}
431-
432-function serveFile(req, res, file) {
433- fs.stat(file, function (err, stat) {
434- if (err && err.code === 'ENOENT') return respond(res, 404, 'Not found')
435- if (err) return respond(res, 500, err.stack || err)
436- if (!stat.isFile()) return respond(res, 403, 'May only load files')
437- if (ifModified(req, stat.mtime)) return respond(res, 304, 'Not modified')
438- res.writeHead(200, {
439- 'Content-Type': ctype(file),
440- 'Content-Length': stat.size,
441- 'Last-Modified': stat.mtime.toGMTString()
442- })
443- fs.createReadStream(file).pipe(res)
444- })
445-}
446-
447-function prepend(fn, arg) {
448- return function (read) {
449- return function (abort, cb) {
450- if (fn && !abort) {
451- var _fn = fn
452- fn = null
453- return _fn(arg, function (err, value) {
454- if (err) return read(err, function (err) {
455- cb(err || true)
456- })
457- cb(null, value)
458- })
459- }
460- read(abort, cb)
461- }
462- }
463-}
464-
465-function formatMsgs(id, ext, opts) {
466- switch (ext || 'html') {
467- case 'html': return pull(renderThread(opts), wrapPage(id))
468- case 'js': return pull(renderThread(opts), wrapJSEmbed(opts))
469- case 'json': return wrapJSON()
470- default: return null
471- }
472-}
473-
474-function wrap(before, after) {
475- return function (read) {
476- return cat([pull.once(before), read, pull.once(after)])
477- }
478-}
479-
480-function renderThread(opts) {
481- return pull(
482- pull.map(renderMsg.bind(this, opts)),
483- wrap('<div class="ssb-thread">', '</div>')
484- )
485-}
486-
487-function wrapPage(id) {
488- return wrap('<!doctype html><html><head>'
489- + '<meta charset=utf-8>'
490- + '<title>' + id + ' | ssb-viewer</title>'
491- + '<meta name=viewport content="width=device-width,initial-scale=1">'
492- + '<link rel=stylesheet href="/static/base.css">'
493- + '<link rel=stylesheet href="/static/nicer.css">'
494- + '</head><body>',
495- '</body></html>'
496- )
497-}
498-
499-function wrapJSON() {
500- var first = true
501- return pull(
502- pull.map(JSON.stringify),
503- join(','),
504- wrap('[', ']')
505- )
506-}
507-
508-function wrapJSEmbed(opts) {
509- return pull(
510- wrap('<link rel=stylesheet href="' + opts.base + 'static/base.css">', ''),
511- pull.map(docWrite),
512- opts.base_token && rewriteBase(new RegExp(opts.base_token, 'g'))
513- )
514-}
515-
516-
517-function rewriteBase(token) {
518- // detect the origin of the script and rewrite the js/html to use it
519- return pull(
520- replace(token, '" + SSB_VIEWER_ORIGIN + "/'),
521- wrap('var SSB_VIEWER_ORIGIN = (function () {'
522- + 'var scripts = document.getElementsByTagName("script")\n'
523- + 'var script = scripts[scripts.length-1]\n'
524- + 'if (!script) return location.origin\n'
525- + 'return script.src.replace(/\\/%.*$/, "")\n'
526- + '}())\n', '')
527- )
528-}
529-
530-function join(delim) {
531- var first = true
532- return pull.map(function (val) {
533- if (!first) return delim + String(val)
534- first = false
535- return val
536- })
537-}
538-
539-function replace(re, rep) {
540- return pull.map(function (val) {
541- return String(val).replace(re, rep)
542- })
543-}
544-
545-function docWrite(str) {
546- return 'document.write(' + JSON.stringify(str) + ')\n'
547-}
548-
549-function hash(arr) {
550- return arr.reduce(function (hash, item) {
551- return hash.update(String(item))
552- }, crypto.createHash('sha256')).digest('base64')
553-}
554-
555-function renderMsg(opts, msg) {
556- var c = msg.value.content || {}
557- var name = encodeURIComponent(msg.key)
558- return '<div class="ssb-message" id="' + name + '">'
559- + '<img class="ssb-avatar-image" alt=""'
560- + ' src="' + opts.img_base + escape(msg.author.image) + '"'
561- + ' height="32" width="32">'
562- + '<a class="ssb-avatar-name"'
563- + ' href="/' + escape(msg.value.author) + '"'
564- + '>' + msg.author.name + '</a>'
565- + msgTimestamp(msg, name)
566- + render(opts, c)
567- + '</div>'
568-}
569-
570-function msgTimestamp(msg, name) {
571- var date = new Date(msg.value.timestamp)
572- return '<time class="ssb-timestamp" datetime="' + date.toISOString() + '">'
573- + '<a href="#' + name + '">'
574- + formatDate(date) + '</a></time>'
575-}
576-
577-function formatDate(date) {
578- // return date.toISOString().replace('T', ' ')
579- return htime(date)
580-}
581-
582-function render(opts, c)
583-{
584- if (c.type === 'post') {
585- var channel = c.channel ? ' in <a href="/channel/' + c.channel + '">#' + c.channel + '</a>' : ''
586- return channel + renderPost(opts, c)
587- } else if (c.type == 'vote' && c.vote.expression == 'Dig') {
588- var channel = c.channel ? ' in <a href="/channel/' + c.channel + '">#' + c.channel + '</a>' : ''
589- var linkedText = 'this'
590- if (typeof c.vote.linkedText != 'undefined')
591- linkedText = c.vote.linkedText.substring(0, 75)
592- return ' dug ' + '<a href="/' + c.vote.link + '">' + linkedText + '</a>' + channel
593- }
594- else if (c.type == 'vote') {
595- var linkedText = 'this'
596- if (typeof c.vote.linkedText != 'undefined')
597- linkedText = c.vote.linkedText.substring(0, 75)
598- return ' voted <a href="/' + c.vote.link + '">' + linkedText + '</a>'
599- }
600- else if (c.type == 'contact' && c.following) {
601- var name = c.contact
602- if (typeof c.contactAbout != 'undefined')
603- name = c.contactAbout.name
604- return ' followed <a href="/' + c.contact + '">' + name + "</a>"
605- }
606- else if (c.type == 'contact' && !c.following) {
607- var name = c.contact
608- if (typeof c.contactAbout != 'undefined')
609- name = c.contactAbout.name
610- return ' unfollowed <a href="/' + c.contact + '">' + name + "</a>"
611- }
612- else if (typeof c == 'string')
613- return ' wrote something private '
614- else if (c.type == 'about')
615- return ' changed something in about'
616- else if (c.type == 'issue')
617- return ' created an issue'
618- else if (c.type == 'git-update')
619- return ' did a git update'
620- else if (c.type == 'ssb-dns')
621- return ' updated dns'
622- else if (c.type == 'pub')
623- return ' connected to a pub'
624- else if (c.type == 'channel' && c.subscribed)
625- return ' subscribed to channel <a href="/channel/' + c.channel + '">#' + c.channel + "</a>"
626- else if (c.type == 'channel' && !c.subscribed)
627- return ' unsubscribed from channel <a href="/channel/' + c.channel + '">#' + c.channel + "</a>"
628- else
629- return renderDefault(c)
630-}
631-
632-function renderPost(opts, c) {
633- return '<div class="ssb-post">' + marked(c.text, opts.marked) + '</div>'
634-}
635-
636-function renderDefault(c) {
637- return '<pre>' + JSON.stringify(c, 0, 2) + '</pre>'
638-}
plugins/viewer/lib/about.jsView
@@ -1,31 +1,0 @@
1-var pull = require('pull-stream')
2-var sort = require('ssb-sort')
3-
4-function linkDest(val) {
5- return typeof val === 'string' ? val : val && val.link
6-}
7-
8-function reduceAbout(about, msg) {
9- var c = msg.value.content
10- if (!c) return about
11- if (c.name) about.name = c.name.replace(/^@?/, '@')
12- if (c.image) about.image = linkDest(c.image)
13- return about
14-}
15-
16-module.exports = function (sbot, id, cb) {
17- var about = {}
18- pull(
19- sbot.links({
20- rel: 'about',
21- dest: id,
22- values: true,
23- }),
24- pull.collect(function (err, msgs) {
25- if (err) return cb(err)
26- cb(null, sort(msgs).reduce(reduceAbout, {
27- name: String(id).substr(0, 10) + '…',
28- }))
29- })
30- )
31-}
plugins/viewer/package.jsonView
@@ -1,27 +1,0 @@
1-{
2- "name": "ssb-viewer",
3- "version": "1.0.0",
4- "description": "serve ssb threads as (embeddable) web pages",
5- "main": "index.js",
6- "bin": "bin.js",
7- "dependencies": {
8- "asyncmemo": "^1.0.0",
9- "emoji-named-characters": "^1.0.2",
10- "emoji-server": "^1.0.0",
11- "human-time": "^0.0.1",
12- "lrucache": "^1.0.2",
13- "pull-cat": "^1.1.11",
14- "pull-paramap": "^1.2.1",
15- "pull-stream": "^3.5.0",
16- "ssb-client": "^4.4.0",
17- "ssb-marked": "^0.7.2",
18- "ssb-ref": "^2.6.2",
19- "ssb-sort": "^1.0.0",
20- "stream-to-pull-stream": "^1.7.2"
21- },
22- "devDependencies": {
23- "tape": "^4.6.2"
24- },
25- "author": "cel",
26- "license": "Fair"
27-}
plugins/viewer/static/base.cssView
@@ -1,15 +1,0 @@
1-.ssb-timestamp {
2- float: right;
3-}
4-
5-.ssb-avatar-image {
6- vertical-align: top;
7-}
8-
9-.ssb-avatar-name {
10- margin-left: 1ex;
11-}
12-
13-.ssb-post img {
14- max-width: 100%;
15-}
plugins/viewer/static/nicer.cssView
@@ -1,32 +1,0 @@
1-.ssb-message {
2- border-bottom: 1px solid #ddd;
3- margin: 1em 0;
4-}
5-
6-h1, h2, h3, h4 {
7- line-height: 1.2;
8-}
9-
10-.ssb-thread {
11- width: 80ex;
12- max-width: 100%;
13- min-width: 57%;
14- margin: 0 auto;
15- line-height: 1.5;
16- font-family: sans-serif;
17-}
18-
19-.ssb-message:target {
20- background-color: #fcfae8;
21- padding: 1em 1em 0;
22- margin: -1em -1em 0;
23-}
24-.ssb-message:target:first-child {
25- margin-top: 0;
26-}
27-
28-.ssb-emoji {
29- height: 1em;
30- width: 1em;
31- vertical-align: top;
32-}
scripts/style.jsView
@@ -1,0 +1,8 @@
1 +var fs = require('fs')
2 +var path = require('path')
3 +
4 +fs.writeFileSync(
5 + path.join(__dirname, '..', 'decent.css.json'),
6 + JSON.stringify(fs.readFileSync(path.join(__dirname, '..', 'decent.css'), 'utf8'))
7 +)
8 +

Built with git-ssb-web