From 748732f600229a6a65de219be430f00343e8cab3 Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sat, 20 Apr 2024 16:37:51 -0400 Subject: [PATCH 1/5] create a full peer with a node server and deno client --- .gitignore | 3 +++ deno.js | 37 +++++++++++++++++++++++++++++++++++++ deno.json | 25 +++++++++++++++++++++++++ mod.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 .gitignore create mode 100644 deno.js create mode 100644 deno.json create mode 100644 mod.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3aa46bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +db +deno.lock +package-lock.json diff --git a/deno.js b/deno.js new file mode 100644 index 0000000..269aaa2 --- /dev/null +++ b/deno.js @@ -0,0 +1,37 @@ +import express from 'express' +import braidmail from './index.js' + +var app = express() + +app.use(free_the_cors) + +// Host some simple HTML +sendfile = (f) => (req, res) => res.sendFile(f, {root:'.'}) +app.get('/', sendfile('demo.html')) + +app.use('/public', express.static('public')) +app.use(braidmail) + +// Spin up the server +require('http') + .createServer(app) + .listen(7465, () => console.log('listening on 7465')) + +// Free the CORS! +function free_the_cors (req, res, next) { + console.log('free the cors!', req.method, req.url) + res.setHeader('Range-Request-Allow-Methods', 'PATCH, PUT') + res.setHeader('Range-Request-Allow-Units', 'json') + res.setHeader("Patches", "OK") + var free_the_cors = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "OPTIONS, HEAD, GET, PUT, UNSUBSCRIBE", + "Access-Control-Allow-Headers": "subscribe, client, version, parents, merge-type, content-type, patches, cache-control, peer" + } + Object.entries(free_the_cors).forEach(x => res.setHeader(x[0], x[1])) + if (req.method === 'OPTIONS') { + res.writeHead(200) + res.end() + } else + next() +} diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..2cf6905 --- /dev/null +++ b/deno.json @@ -0,0 +1,25 @@ +{ + "name": "@braid-org/braidmail", + "version": "0.1.0", + "exports": "./mod.ts", + "tasks": { + "start": "deno run -A --unstable mod.js", + + "start-client": "deno run -A deno.js", + "debug-client": "deno run -A --inspect-brk deno.js", + + "start-server": "node demo.js" + + }, + "imports": { + "@sillonious/module": "./client/public/module.js", + "@sillonious/saga": "./client/public/saga.js", + "@sillonious/brand": "./client/public/brand.js", + "colorjs.io": "https://esm.sh/colorjs.io@0.4.0", + "diffhtml": "https://esm.sh/diffhtml@1.0.0-beta.30", + "statebus": "./client/public/_statebus.js", + "marked": "https://esm.sh/marked@11.1.0" + }, + "compilerOptions": { + } +} diff --git a/mod.js b/mod.js new file mode 100644 index 0000000..03de562 --- /dev/null +++ b/mod.js @@ -0,0 +1,31 @@ +import { Pup } from "https://deno.land/x/pup/mod.ts" + +const processConfiguration = { + client: true, + server: true, + features: { + client: { + "id": "braidmail-start-client", + "cmd": "deno task start-client", + "autostart": true + }, + server: { + "id": "braidmail-start-server", + "cmd": "deno task start-server", + "autostart": true + }, + } +} + +const activeFeatures = Object.keys(processConfiguration) + .filter(x => processConfiguration[x] === true) + .map(x => processConfiguration.features[x]) + +console.log(activeFeatures) + +const pup = await new Pup({ + "processes": activeFeatures +}) + +// Go! +pup.init() From c6ba607bc8ce932fedfcb7e5f3d95f640e4f7004 Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sat, 20 Apr 2024 16:46:27 -0400 Subject: [PATCH 2/5] document the deno stuff --- readme | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/readme b/readme index cdb1489..3d0161e 100644 --- a/readme +++ b/readme @@ -7,3 +7,16 @@ A server for a feed of braided posts. Like email, or a forum, over braid. - Each new /post/* will automatically append to the /feed See demo.js for an example of how to use. + +## run the full peer + +install node (https://github.com/nvm-sh/nvm) +install deno (https://docs.deno.com/runtime/manual/getting_started/installation) + +``` +deno task start +``` + +run braidmail server (node) `deno task start-server` +run braidmail client (deno) `deno task start-client` +debug braidmail client (deno) `deno task debug-client` From 73302cacf4c96ae4973c73b4d6442b9baaa82cb3 Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sat, 20 Apr 2024 16:48:59 -0400 Subject: [PATCH 3/5] get pup in the importmap --- deno.json | 3 ++- mod.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/deno.json b/deno.json index 2cf6905..55f0466 100644 --- a/deno.json +++ b/deno.json @@ -18,7 +18,8 @@ "colorjs.io": "https://esm.sh/colorjs.io@0.4.0", "diffhtml": "https://esm.sh/diffhtml@1.0.0-beta.30", "statebus": "./client/public/_statebus.js", - "marked": "https://esm.sh/marked@11.1.0" + "marked": "https://esm.sh/marked@11.1.0", + "pup": "https://deno.land/x/pup/mod.ts" }, "compilerOptions": { } diff --git a/mod.js b/mod.js index 03de562..4b95905 100644 --- a/mod.js +++ b/mod.js @@ -1,4 +1,4 @@ -import { Pup } from "https://deno.land/x/pup/mod.ts" +import { Pup } from "pup" const processConfiguration = { client: true, From caf29766bdaf0a23f790e56f49cdc9715d42a7ad Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sat, 20 Apr 2024 16:52:26 -0400 Subject: [PATCH 4/5] fix express dependency --- deno.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.js b/deno.js index 269aaa2..5e4fc14 100644 --- a/deno.js +++ b/deno.js @@ -1,4 +1,4 @@ -import express from 'express' +import express from 'npm:express' import braidmail from './index.js' var app = express() From 72cae0614bedfb925685c89c7aa326947cbb507e Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sat, 20 Apr 2024 17:17:26 -0400 Subject: [PATCH 5/5] fork index.js as braidmail.js --- braidmail.js | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ deno.js | 15 +++-- deno.json | 7 --- 3 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 braidmail.js diff --git a/braidmail.js b/braidmail.js new file mode 100644 index 0000000..cc1fa49 --- /dev/null +++ b/braidmail.js @@ -0,0 +1,168 @@ +import braid from 'npm:braid-http' + +// Default data +var resources = { + '/feed': [ + {link: '/post/1'}, + {link: '/post/2'}, + {link: '/post/3'} + ], + '/post/1': {subject: 'First!', body: 'First post OMGGG!!!!'}, + '/post/2': {subject: 'Second...', body: `Once upon a time, +I ate a big fish. +It was really tasty.`}, + '/post/3': {subject: 'Tois.', body: "It's nice when things come in threes."} +} +var curr_version = () => [ (resources['/feed'].length + '') ] + + +// Load real data from db +try { + var resources = JSON.parse(Deno.readFileSync('db')) +} catch (e) {} +function save_db () { Deno.writeFileSync('db', JSON.stringify(resources, null, 2)) } + + +// Subscriptions +var subscriptions = {} +var rhash = (req) => JSON.stringify([req.headers.client, req.url]) + + +// The main Braidmail request handler! +export default function handler (req, res, next) { + const feed_name = '/feed' + const post_name = '/post/' + + braid.http_server(req, res) + + // We'll give each request a random ID, if it's not alraedy provided to us + req.headers.peer ??= Math.random().toString(36).substr(3) + + // Feed only supports get + if (req.url === feed_name && req.method === 'GET') { + getter(req, res) + } + + else if (req.url.startsWith(post_name)) { + + // GET /post/* + if (req.method === 'GET') + getter(req, res) + + // PUT /post/* + else if (req.method === 'PUT') { + var is_new_post = !(req.url in resources) + + // Download the post body + var body = '' + req.on('data', chunk => {body += chunk.toString()}) + req.on('end', () => { + + // Now update the post + resources[req.url] = JSON.parse(body) + + // Update subscribers + post_changed(req) + + // Update the feed + if (is_new_post) // Update this when we delete posts + append_to_feed(req) + + res.end() + }) + } + + // DELETE /post/* + else if (req.method === 'DELETE') { + if (!req.url in resources) + res.status = 404 + else { + delete resources[req.url] + res.status = 200 + post_changed(req) + } + } + } +} + +// GET the /feed or a /post/ +// - handles subscriptions +// - and regular GETs +function getter (req, res) { + // Make sure URL is valid + if (!(req.url in resources)) { + res.statusCode = 404 + res.end() + return + } + + // Set headers + res.setHeader('content-type', 'application/json') + if (req.url === '/feed') { + res.setHeader('Version-Type', 'appending-array') + } + + // Honor any subscription request + if (req.subscribe) { + console.log('Incoming subscription!!!') + res.startSubscription({ onClose: _=> delete subscriptions[rhash(req)] }) + subscriptions[rhash(req)] = res + console.log('Now there are', Object.keys(subscriptions).length, 'subscriptions') + } else + res.statusCode = 200 + + // Send the current version + res.sendUpdate({ + version: req.url === '/feed' ? curr_version() : undefined, + body: JSON.stringify(resources[req.url]) + }) + + if (!req.subscribe) + res.end() +} + + +// Add a post to the feed. Update all subscribers. +function append_to_feed (post_req) { + var post_entry = {link: post_req.url} + + // Add the post to the feed + resources['/feed'].push(post_entry) + + // Save the new database + save_db() + + // Tell everyone about it + for (var k in subscriptions) { + var [peer, url] = JSON.parse(k) + if (peer !== post_req.headers.peer && url === '/feed') { + console.log('Telling peer', peer, 'about the new', post_entry, 'in /feed') + subscriptions[k].sendUpdate({ + version: curr_version(), + patches: [{ + unit: 'json', + range: '[-0:-0]', + content: JSON.stringify(post_entry) + }] + }) + } + } +} + +// Notify everyone when a post changes +function post_changed (req) { + // Save the new database. + save_db() + + // Tell everyone + for (var k in subscriptions) { + var [peer, url] = JSON.parse(k) + if (peer !== req.headers.peer && url === req.url) { + console.log('Yes! Telling peer', {peer, url}) + subscriptions[k].sendUpdate({ + version: curr_version(), + body: JSON.stringify(resources[req.url]) + }) + } + } +} diff --git a/deno.js b/deno.js index 5e4fc14..89c5bf5 100644 --- a/deno.js +++ b/deno.js @@ -1,21 +1,20 @@ import express from 'npm:express' -import braidmail from './index.js' +import braidmail from './braidmail.js' -var app = express() +const port = 8465 +const app = express() app.use(free_the_cors) // Host some simple HTML -sendfile = (f) => (req, res) => res.sendFile(f, {root:'.'}) +const sendfile = (f) => (req, res) => res.sendFile(f, {root:'.'}) app.get('/', sendfile('demo.html')) app.use('/public', express.static('public')) app.use(braidmail) -// Spin up the server -require('http') - .createServer(app) - .listen(7465, () => console.log('listening on 7465')) +app.listen(); +console.log('client running on: ', port) // Free the CORS! function free_the_cors (req, res, next) { @@ -23,7 +22,7 @@ function free_the_cors (req, res, next) { res.setHeader('Range-Request-Allow-Methods', 'PATCH, PUT') res.setHeader('Range-Request-Allow-Units', 'json') res.setHeader("Patches", "OK") - var free_the_cors = { + const free_the_cors = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "OPTIONS, HEAD, GET, PUT, UNSUBSCRIBE", "Access-Control-Allow-Headers": "subscribe, client, version, parents, merge-type, content-type, patches, cache-control, peer" diff --git a/deno.json b/deno.json index 55f0466..c7b2533 100644 --- a/deno.json +++ b/deno.json @@ -12,13 +12,6 @@ }, "imports": { - "@sillonious/module": "./client/public/module.js", - "@sillonious/saga": "./client/public/saga.js", - "@sillonious/brand": "./client/public/brand.js", - "colorjs.io": "https://esm.sh/colorjs.io@0.4.0", - "diffhtml": "https://esm.sh/diffhtml@1.0.0-beta.30", - "statebus": "./client/public/_statebus.js", - "marked": "https://esm.sh/marked@11.1.0", "pup": "https://deno.land/x/pup/mod.ts" }, "compilerOptions": {