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 PMParent: c047dd23d6de58cf6cbac805acf5aad237a9fd58
Files changed
decent.js | ||
---|---|---|
@@ -8,48 +8,51 @@ | ||
8 | 8 … | var stringify = require('pull-stringify') |
9 | 9 … | var createHash = require('multiblob/util').createHash |
10 | 10 … | var minimist = require('minimist') |
11 | 11 … | var muxrpcli = require('muxrpcli') |
12 | -var cmdAliases = require('./lib/cli-cmd-aliases') | |
12 … | +var cmdAliases = require('scuttlebot/lib/cli-cmd-aliases') | |
13 | 13 … | |
14 | 14 … | //get config as cli options after --, options before that are |
15 | 15 … | //options to the command. |
16 | 16 … | var argv = process.argv.slice(2) |
17 | 17 … | var i = argv.indexOf('--') |
18 | 18 … | var conf = argv.slice(i+1) |
19 | 19 … | argv = ~i ? argv.slice(0, i) : argv |
20 | 20 … | |
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)) | |
22 | 22 … | |
23 | 23 … | 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')) | |
24 | 27 … | |
25 | 28 … | var manifestFile = path.join(config.path, 'manifest.json') |
26 | 29 … | |
27 | 30 … | if (argv[0] == 'server') { |
28 | 31 … | |
29 | 32 … | // special server command: |
30 | 33 … | // import sbot and start the server |
31 | 34 … | |
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')) | |
49 | 52 … | |
50 | 53 … | // add third-party plugins |
51 | - // require('./plugins/plugins').loadUserPlugins(createSbot, config) | |
54 … | + //require('scuttlebot/plugins/plugins').loadUserPlugins(createSbot, config) | |
52 | 55 … | |
53 | 56 … | // start server |
54 | 57 … | |
55 | 58 … | config.keys = keys |
@@ -77,9 +80,9 @@ | ||
77 | 80 … | ) |
78 | 81 … | } |
79 | 82 … | |
80 | 83 … | // connect |
81 | - require('./plugins/ssb-client')(keys, { | |
84 … | + require('ssb-client')(keys, { | |
82 | 85 … | manifest: manifest, |
83 | 86 … | port: config.port, |
84 | 87 … | host: config.host||'localhost', |
85 | 88 … | caps: config.caps, |
package.json | ||
---|---|---|
@@ -3,10 +3,10 @@ | ||
3 | 3 … | "version": "2.0.0", |
4 | 4 … | "description": "A decent(tralized) lite client for secure scuttlebutt", |
5 | 5 … | "homepage": "http://gitmx.com/%25Wq%2FvobdcDedC0FBO2UdowxhPcqokSwtf9Og1mjYvQGE%3D.sha256", |
6 | 6 … | "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" | |
9 | 9 … | }, |
10 | 10 … | "author": "Ev Bogue", |
11 | 11 … | "license": "MIT", |
12 | 12 … | "dependencies": { |
readme.md | ||
---|---|---|
@@ -1,15 +1,15 @@ | ||
1 | 1 … | # Decent |
2 | 2 … | |
3 | -### A decent(ralized) network for business and development. | |
3 … | +### A decent(ralized) client for secure scuttlebutt. | |
4 | 4 … | |
5 | 5 … | 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. |
6 | 6 … | |
7 | 7 … | ![Decent on Nexus](decent-nexus.jpg) |
8 | 8 … | |
9 | 9 … | ![Decent screenshot](decent-screenshot.png) |
10 | 10 … | |
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. | |
12 | 12 … | |
13 | 13 … | Decent combines all of the necessary parts of Scuttlebot for a simpler install process |
14 | 14 … | |
15 | 15 … | ``` |
@@ -18,11 +18,11 @@ | ||
18 | 18 … | % npm build |
19 | 19 … | % npm start |
20 | 20 … | ``` |
21 | 21 … | |
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. | |
23 | 23 … | |
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/). | |
25 | 25 … | |
26 | 26 … | Once you're on Decent, be sure to obey the first rule: |
27 | 27 … | |
28 | 28 … | 1. Be decent |
serve.js | ||
---|---|---|
@@ -1,20 +1,20 @@ | ||
1 | 1 … | var http = require('http'); |
2 | 2 … | var serve = require('ecstatic'); |
3 | -var client = require('./plugins/ssb-client') | |
3 … | +var client = require('ssb-client') | |
4 | 4 … | |
5 | 5 … | exports.serve = function() { |
6 | 6 … | http.createServer( |
7 | 7 … | serve({ root: __dirname + '/build/'}) |
8 | - ).listen(3001); | |
8 … | + ).listen(3013); | |
9 | 9 … | |
10 | 10 … | opts = {"modern": true} |
11 | 11 … | |
12 | 12 … | client(function (err, sbot) { |
13 | 13 … | if(err) throw err |
14 | 14 … | sbot.invite.create(opts, function (err, invite) { |
15 | 15 … | if(err) throw err |
16 | 16 … | 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) | |
18 | 18 … | }) |
19 | 19 … | }) |
20 | 20 … | } |
yarn.lock | ||
---|---|---|
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.html | ||
---|---|---|
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.css | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,0 +1,5 @@ | ||
1 … | +require('depject')( | |
2 … | + require('minbase/modules'), | |
3 … | + require('./modules') | |
4 … | +).app[0]() | |
5 … | + |
modules/app.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,0 +1,5 @@ | ||
1 … | +module.exports = { | |
2 … | + "app.js": require('./app.js'), | |
3 … | + "tabs.js": require('./tabs.js') | |
4 … | +} | |
5 … | + |
modules/tabs.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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/bin.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.css | ||
---|---|---|
@@ -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.css | ||
---|---|---|
@@ -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/LICENSE | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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/.npmignore | ||
---|---|---|
@@ -1,1 +1,0 @@ | ||
1 | -node_modules |
plugins/ssb-config/LICENSE | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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/inject.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,3 +1,0 @@ | ||
1 | - | |
2 | - | |
3 | -console.log(require('./')('testnet', {port: 9999, friends: {dunbar: 1500}})) |
plugins/ssb-links/LICENSE | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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/LICENSE | ||
---|---|---|
@@ -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/index.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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/LICENSE | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.log | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.html | ||
---|---|---|
@@ -1,28 +1,0 @@ | ||
1 | - | |
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.js | ||
---|---|---|
@@ -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, '&') | |
390 | - .replace(/</g, '<') | |
391 | - .replace(/>/g, '>') | |
392 | - .replace(/"/g, '"') | |
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.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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.css | ||
---|---|---|
@@ -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.css | ||
---|---|---|
@@ -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 | -} |
Built with git-ssb-web