Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deno peer #2

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
db
deno.lock
package-lock.json
168 changes: 168 additions & 0 deletions braidmail.js
Original file line number Diff line number Diff line change
@@ -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])
})
}
}
}
36 changes: 36 additions & 0 deletions deno.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import express from 'npm:express'
import braidmail from './braidmail.js'

const port = 8465
const app = express()

app.use(free_the_cors)

// Host some simple HTML
const sendfile = (f) => (req, res) => res.sendFile(f, {root:'.'})
app.get('/', sendfile('demo.html'))

app.use('/public', express.static('public'))
app.use(braidmail)

app.listen();
console.log('client running on: ', port)

// 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")
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"
}
Object.entries(free_the_cors).forEach(x => res.setHeader(x[0], x[1]))
if (req.method === 'OPTIONS') {
res.writeHead(200)
res.end()
} else
next()
}
19 changes: 19 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"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": {
"pup": "https://deno.land/x/pup/mod.ts"
},
"compilerOptions": {
}
}
31 changes: 31 additions & 0 deletions mod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Pup } from "pup"

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()
13 changes: 13 additions & 0 deletions readme
Original file line number Diff line number Diff line change
Expand Up @@ -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`