dat.dpiView |
---|
11 | 11 … | var hyperdrive = require('hyperdrive') |
12 | 12 … | var crypto = require('crypto') |
13 | 13 … | var h = require('hyperscript') |
14 | 14 … | var hyperdiscovery = require('hyperdiscovery') |
| 15 … | +var multicb = require('multicb') |
15 | 16 … | |
16 | 17 … | var dilloDir = path.join(process.env.HOME, '.dillo') |
17 | 18 … | var dpidKeysPath = path.join(dilloDir, 'dpid_comm_keys') |
18 | 19 … | var archivesPath = path.join(dilloDir, 'dat', 'archives') |
19 | 20 … | |
20 | 21 … | var dpidKeys = fs.readFileSync(dpidKeysPath, {encoding: 'ascii'}).split(/\s+/) |
21 | 22 … | |
| 23 … | +var log = console.log.bind(console, '[dat dpi]') |
| 24 … | +var error = console.error.bind(console, '[dat dpi]') |
| 25 … | + |
| 26 … | +function readJSON(archive, pathname, cb) { |
| 27 … | + archive.readFile(pathname, function (err, data) { |
| 28 … | + var obj |
| 29 … | + if (!err) try { obj = JSON.parse(data) } |
| 30 … | + catch(e) { return cb(e) } |
| 31 … | + cb(null, obj) |
| 32 … | + }) |
| 33 … | +} |
| 34 … | + |
22 | 35 … | function readManifest(archive, cb) { |
23 | | - archive.readFile('dat.json', function (err, data) { |
24 | | - var manifest |
25 | | - if (!err) try { manifest = JSON.parse(data) } catch(e) {} |
26 | | - if (manifest) return cb(null, manifest) |
| 36 … | + readJSON(archive, 'dat.json', function (err, manifest) { |
| 37 … | + if (manifest) return cb(null, manifest || {}) |
27 | 38 … | archive.readFile('CNAME', 'ascii', function (err, data) { |
28 | | - if (err) return cb('Not found') |
| 39 … | + if (err) return cb(null, {}) |
29 | 40 … | cb(null, {title: data}) |
30 | 41 … | }) |
31 | 42 … | }) |
32 | 43 … | } |
33 | 44 … | |
| 45 … | +function readPortal(archive, cb) { |
| 46 … | + readJSON(archive, 'portal.json', cb) |
| 47 … | +} |
| 48 … | + |
| 49 … | +function readRotondeInfo(archive, cb) { |
| 50 … | + var done = multicb({pluck: 1, spread: true}) |
| 51 … | + readPortal(archive, done()) |
| 52 … | + readManifest(archive, done()) |
| 53 … | + done(cb) |
| 54 … | +} |
| 55 … | + |
34 | 56 … | var archives = {} |
35 | 57 … | var getArchive = memo({cache: { |
36 | 58 … | has: function (key) { return key in archives }, |
37 | 59 … | get: function (key) { return archives[key] }, |
345 | 367 … | try { if (self.urlp.version) archive = archive.checkout(+self.urlp.version) } |
346 | 368 … | catch(e) { return self.serveError(err) } |
347 | 369 … | self.archive = archive |
348 | 370 … | self.sendStatus('serving file') |
349 | | - self.serve() |
| 371 … | + self.serveDat2() |
|
350 | 372 … | }) |
351 | 373 … | }) |
352 | 374 … | } |
353 | 375 … | |
| 376 … | +var hostsToKeys = {} |
| 377 … | +var datsToNames = {} |
| 378 … | + |
| 379 … | +function getName(url) { |
| 380 … | + var host = parseDatUrl(url, true).host |
| 381 … | + var key = hostsToKeys[host] || host |
| 382 … | + var name = datsToNames[key] |
| 383 … | + return name ? '@' + name : (String(key).substr(0, 8) + '…') |
| 384 … | +} |
| 385 … | + |
| 386 … | +DpiReq.prototype.serveRotonde = function () { |
| 387 … | + var self = this |
| 388 … | + self.urlp = parseDatUrl(self.url, true) |
| 389 … | + if (!self.urlp.host) return self.serveError('Archive not found') |
| 390 … | + self.sendStatus('resolving name') |
| 391 … | + datDns.resolveName(self.urlp.host, {ignoreCachedMiss: true}, function (err, key) { |
| 392 … | + hostsToKeys[self.urlp.host] = key |
| 393 … | + if (err) return self.serveError(err) |
| 394 … | + if (!key) return cb(new TypeError('resolve failed')) |
| 395 … | + self.sendStatus('getting archive') |
| 396 … | + withTimeout(getArchive, 10000)(key, function (err, archive) { |
| 397 … | + if (err) return self.serveError(err) |
| 398 … | + if (!archive) return self.serveError('Unable to get archive') |
| 399 … | + readPortal(archive, function (err, portal) { |
| 400 … | + if (err) return |
| 401 … | + datsToNames[key] = portal.name |
| 402 … | + }) |
| 403 … | + if (!archive) return self.serveTimedout(self.urlp) |
| 404 … | + try { if (self.urlp.version) archive = archive.checkout(+self.urlp.version) } |
| 405 … | + catch(e) { return self.serveError(err) } |
| 406 … | + self.archive = archive |
| 407 … | + self.sendStatus('serving') |
| 408 … | + self.serveRotonde2() |
| 409 … | + }) |
| 410 … | + }) |
| 411 … | +} |
| 412 … | + |
354 | 413 … | DpiReq.prototype.serveInternal = function () { |
355 | 414 … | this.urlp = parseUrl(this.url, true) |
356 | 415 … | if (this.urlp.pathname === '/dat/') return this.serveDashboard() |
357 | 416 … | this.serveNotFound() |
358 | 417 … | } |
359 | 418 … | |
360 | | -DpiReq.prototype.serve = function () { |
| 419 … | +DpiReq.prototype.serveDat2 = function () { |
361 | 420 … | var self = this |
362 | 421 … | if (self.closed) return |
363 | 422 … | self.stat(self.urlp.pathname || '/', function (err, st) { |
364 | 423 … | if (err) return self.serveNotFound(err) |
368 | 427 … | self.serveFile() |
369 | 428 … | }) |
370 | 429 … | } |
371 | 430 … | |
| 431 … | +DpiReq.prototype.serveRotonde2 = function () { |
| 432 … | + var self = this |
| 433 … | + if (self.closed) return |
| 434 … | + if (!self.urlp.pathname || self.urlp.pathname === '/') { |
| 435 … | + return self.serveRotondeFeed() |
| 436 … | + } |
| 437 … | + return this.serveNotFound() |
| 438 … | +} |
| 439 … | + |
| 440 … | +DpiReq.prototype.serveRotondeFeed = function () { |
| 441 … | + var self = this |
| 442 … | + if (self.closed) return |
| 443 … | + readRotondeInfo(self.archive, function (err, portal, manifest) { |
| 444 … | + if (err) return self.serveError(err) |
| 445 … | + // return self.serveRotondeFeed() |
| 446 … | + self.writeHeader('text/html') |
| 447 … | + self.socket.end('<!doctype html>' + h('html', [ |
| 448 … | + h('head', [ |
| 449 … | + h('title', manifest.title || ('Rotonde (' + portal.name + ')')) |
| 450 … | + ]), |
| 451 … | + h('body', [ |
| 452 … | + h('h2', portal.name), |
| 453 … | + h('blockquote', portal.desc), |
| 454 … | + h('div', h('a', {href: portal.dat})), |
| 455 … | + h('div', h('a', {href: portal.site})), |
| 456 … | + Array.isArray(portal.port) ? portal.port.map(function (port) { |
| 457 … | + var href = port.replace(/^dat:/, 'rotonde:') |
| 458 … | + return [h('a', {href: href}, getName(port)), ' '] |
| 459 … | + }) : '', |
| 460 … | + Array.isArray(portal.feed) |
| 461 … | + ? portal.feed.reverse().map(renderFeedItem) : '' |
| 462 … | + ]) |
| 463 … | + ]).outerHTML) |
| 464 … | + }) |
| 465 … | +} |
| 466 … | + |
| 467 … | +var urlRegex = /(\b(?:https?|dat):[^ ]*|<br>)/ |
| 468 … | +function linkify(text) { |
| 469 … | + var arr = text.split(urlRegex) |
| 470 … | + for (var i = 1; i < arr.length; i += 2) { |
| 471 … | + if (arr[i] === '<br>') arr[i] = h('br') |
| 472 … | + else arr[i] = h('a', {href: arr[i]}, arr[i]) |
| 473 … | + } |
| 474 … | + return arr |
| 475 … | +} |
| 476 … | + |
| 477 … | +function renderFeedItem(item) { |
| 478 … | + var date = new Date(item.timestamp) |
| 479 … | + // item.media |
| 480 … | + |
| 481 … | + return [ |
| 482 … | + h('p', date.toLocaleString()), |
| 483 … | + h('blockquote', linkify(item.message)) |
| 484 … | + ] |
| 485 … | +} |
| 486 … | + |
372 | 487 … | function DpiReq_onAuth(m) { |
373 | 488 … | this.authed = (m[1] == dpidKeys[1]) |
374 | 489 … | if (!this.authed) { |
375 | 490 … | console.error('[dat dpi] bad auth from', this.getAddress()) |
384 | 499 … | return this.socket.end() |
385 | 500 … | } |
386 | 501 … | this.url = m[1] |
387 | 502 … | if (this.url.startsWith('dat:')) return this.serveDat() |
| 503 … | + if (this.url.startsWith('rotonde:')) return this.serveRotonde() |
388 | 504 … | if (this.url.startsWith('dpi:/dat/')) return this.serveInternal() |
389 | 505 … | this.serveError('Not found') |
390 | 506 … | } |
391 | 507 … | |