Further foundations for GunSmith
This commit is contained in:
parent
eabbb18917
commit
e1e0af488c
3 changed files with 273 additions and 45 deletions
|
|
@ -1,35 +1,141 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
/* eslint-disable no-use-before-define */
|
||||
/* eslint-disable func-style */
|
||||
// @ts-check
|
||||
/// <reference path="Smith.ts" />
|
||||
/// <reference path="GunT.ts" />
|
||||
const uuid = require('uuid').v1
|
||||
const { fork } = require('child_process')
|
||||
|
||||
const Config = require('./config')
|
||||
|
||||
const logger = require('../../config/log')
|
||||
|
||||
/**
|
||||
* Maps a path to `on()` listeners
|
||||
* @type {Record<string, WeakSet<GunT.Listener>>}
|
||||
* @type {Record<string, Set<GunT.Listener>|undefined>}
|
||||
*/
|
||||
const pathToListeners = {
|
||||
'x>x>x>': new WeakSet()
|
||||
}
|
||||
const pathToListeners = {}
|
||||
|
||||
/**
|
||||
* Path to pending puts. Oldest to newest
|
||||
* @type {Record<string, Smith.PendingPut[]?>}
|
||||
*/
|
||||
const pendingPuts = {}
|
||||
|
||||
/**
|
||||
* @param {Smith.GunMsg} msg
|
||||
*/
|
||||
const handleMsg = msg => {}
|
||||
const handleMsg = msg => {
|
||||
if (msg.type === 'on') {
|
||||
const { data, path } = msg
|
||||
|
||||
// eslint-disable-next-line no-multi-assign
|
||||
const listeners = (pathToListeners[path] =
|
||||
pathToListeners[path] || new Set())
|
||||
|
||||
for (const l of listeners) {
|
||||
l(data, path.split('>')[path.split('>').length - 1])
|
||||
}
|
||||
}
|
||||
if (msg.type === 'put') {
|
||||
const { ack, id, path } = msg
|
||||
|
||||
const pendingPutsForPath = pendingPuts[path] || (pendingPuts[path] = [])
|
||||
|
||||
const pendingPut = pendingPutsForPath.find(pp => pp.id === id)
|
||||
const idx = pendingPutsForPath.findIndex(pp => pp.id === id)
|
||||
|
||||
if (pendingPut) {
|
||||
if (pendingPut.cb) {
|
||||
pendingPut.cb(ack)
|
||||
}
|
||||
pendingPutsForPath.splice(idx, 1)
|
||||
} else {
|
||||
logger.error(
|
||||
`Could not find request for put message from gun subprocess. Data will be logged below.`
|
||||
)
|
||||
logger.info({ msg, pendingPut })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let currentGun = fork('./gun')
|
||||
|
||||
let lastAlias = ''
|
||||
let lastPass = ''
|
||||
|
||||
/**
|
||||
* @param {string} alias
|
||||
* @param {string} pass
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const auth = (alias, pass) =>
|
||||
new Promise((res, rej) => {
|
||||
/** @type {Smith.SmithMsgAuth} */
|
||||
const msg = {
|
||||
alias,
|
||||
pass,
|
||||
type: 'auth'
|
||||
}
|
||||
/** @param {Smith.GunMsg} msg */
|
||||
const _cb = msg => {
|
||||
if (msg.type === 'auth') {
|
||||
currentGun.off('message', _cb)
|
||||
|
||||
const { ack } = msg
|
||||
|
||||
if (ack.err) {
|
||||
rej(
|
||||
new Error(
|
||||
|
||||
ack.err
|
||||
)
|
||||
)
|
||||
} else if (ack.sea) {
|
||||
lastAlias = alias;
|
||||
lastPass = pass
|
||||
res(ack.sea.pub)
|
||||
} else {
|
||||
rej(
|
||||
new Error(
|
||||
'Auth: ack.sea undefined'
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
currentGun.on('message', _cb)
|
||||
currentGun.send(msg)
|
||||
})
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const autoAuth = () => {
|
||||
if (!lastAlias || !lastPass) {
|
||||
return Promise.resolve('')
|
||||
}
|
||||
return auth(lastAlias, lastPass)
|
||||
}
|
||||
|
||||
const processPendingPutsFromLastGun = async (forGun, pps = pendingPuts) => {
|
||||
// TODO
|
||||
}
|
||||
|
||||
const forge = () => {
|
||||
currentGun.off('message', handleMsg)
|
||||
currentGun.kill()
|
||||
currentGun = fork('./gun')
|
||||
const newGun = fork('./gun')
|
||||
currentGun = newGun
|
||||
|
||||
currentGun.on('message', handleMsg)
|
||||
|
||||
const msgs = Object.keys(pathToListeners).map(path => {
|
||||
const lastGunListeners = Object.keys(pathToListeners).map(path => {
|
||||
/** @type {Smith.SmithMsgOn} */
|
||||
const msg = {
|
||||
path,
|
||||
|
|
@ -38,7 +144,11 @@ const forge = () => {
|
|||
return msg
|
||||
})
|
||||
|
||||
currentGun.send(msgs)
|
||||
currentGun.send(lastGunListeners)
|
||||
|
||||
autoAuth().then(() => {
|
||||
processPendingPutsFromLastGun(newGun)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,7 +156,7 @@ const forge = () => {
|
|||
* @param {boolean=} afterMap
|
||||
* @returns {GunT.GUNNode}
|
||||
*/
|
||||
const createReplica = (path, afterMap = false) => {
|
||||
function createReplica(path, afterMap = false) {
|
||||
/** @type {GunT.Listener[]} */
|
||||
const listenersForThisRef = []
|
||||
|
||||
|
|
@ -59,36 +169,112 @@ const createReplica = (path, afterMap = false) => {
|
|||
put: {}
|
||||
},
|
||||
get(key) {
|
||||
if (afterMap) {
|
||||
throw new Error(
|
||||
'Cannot call get() after map() on a GunSmith node, you should only call on() after map()'
|
||||
)
|
||||
}
|
||||
return createReplica(path + '>' + key)
|
||||
},
|
||||
map() {
|
||||
if (afterMap) {
|
||||
throw new Error('Cannot call map() after map() on a GunSmith node')
|
||||
}
|
||||
return createReplica(path, true)
|
||||
},
|
||||
off() {
|
||||
if (afterMap) {
|
||||
throw new Error('Cannot call off() after map() on a GunSmith node')
|
||||
}
|
||||
for (const l of listenersForThisRef) {
|
||||
pathToListeners[path].delete(l)
|
||||
// eslint-disable-next-line no-multi-assign
|
||||
const listeners = (pathToListeners[path] =
|
||||
pathToListeners[path] || new Set())
|
||||
|
||||
listeners.delete(l)
|
||||
}
|
||||
},
|
||||
on(cb) {
|
||||
listenersForThisRef.push(cb)
|
||||
|
||||
// eslint-disable-next-line no-multi-assign
|
||||
const listeners = (pathToListeners[path] =
|
||||
pathToListeners[path] || new Set())
|
||||
|
||||
listeners.add(cb)
|
||||
|
||||
/** @type {Smith.SmithMsgOn} */
|
||||
const msg = {
|
||||
path,
|
||||
type: 'on'
|
||||
}
|
||||
currentGun.send(msg)
|
||||
|
||||
return this
|
||||
},
|
||||
once(cb, opts) {
|
||||
once(cb, opts = { wait: 200 }) {
|
||||
const tmp = createReplica(path, afterMap)
|
||||
if (afterMap) {
|
||||
//
|
||||
} else {
|
||||
let lastVal = null
|
||||
|
||||
tmp.on((data, key) => {
|
||||
lastVal = data
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (cb) {
|
||||
cb(lastVal, path.split('>').lastItem)
|
||||
}
|
||||
}, opts.wait)
|
||||
}
|
||||
return this
|
||||
},
|
||||
put(data, cb) {
|
||||
const id = uuid()
|
||||
|
||||
const pendingPutsForPath = pendingPuts[path] || (pendingPuts[path] = [])
|
||||
|
||||
/** @type {Smith.PendingPut} */
|
||||
const pendingPut = {
|
||||
cb(ack) {
|
||||
const idx = pendingPutsForPath.indexOf(this)
|
||||
if (idx > -1) {
|
||||
pendingPutsForPath.splice(idx, 1)
|
||||
} else {
|
||||
logger.warn(`???`)
|
||||
}
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
cb && cb(ack)
|
||||
},
|
||||
data,
|
||||
id
|
||||
}
|
||||
|
||||
pendingPutsForPath.push(pendingPut)
|
||||
|
||||
/** @type {Smith.SmithMsgPut} */
|
||||
const msg = {
|
||||
data,
|
||||
id,
|
||||
path,
|
||||
type: 'put'
|
||||
}
|
||||
|
||||
currentGun.send(msg)
|
||||
return this
|
||||
},
|
||||
set(data, cb) {
|
||||
if (afterMap) {
|
||||
throw new Error('Cannot call set() after map() on a GunSmith node')
|
||||
}
|
||||
return this
|
||||
},
|
||||
then() {
|
||||
if (afterMap) {
|
||||
throw new Error('Cannot call then() after map() on a GunSmith node')
|
||||
}
|
||||
return Promise.resolve(null)
|
||||
},
|
||||
user(pub) {
|
||||
|
|
@ -98,35 +284,34 @@ const createReplica = (path, afterMap = false) => {
|
|||
)
|
||||
}
|
||||
if (!pub) {
|
||||
throw new Error(
|
||||
'Do not use `user()` (without providing a pub) on a GunSmith node, use getUser()'
|
||||
)
|
||||
return createUserReplica()
|
||||
}
|
||||
const replica = createReplica(pub)
|
||||
// I don't know why Typescript insists on returning a UserGUNNode so here we go:
|
||||
return {
|
||||
...replica,
|
||||
_: {
|
||||
...replica._,
|
||||
sea: {
|
||||
epriv: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
||||
epub: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
||||
priv: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
||||
pub: 'Do not use ._.sea on a GunSmith node, use getUser()'
|
||||
}
|
||||
/** @returns {GunT.UserSoul} */
|
||||
get _() {
|
||||
throw new ReferenceError(
|
||||
`Do not access _ on another user's graph (${pub.slice(
|
||||
0,
|
||||
8
|
||||
)}...${pub.slice(-8)})`
|
||||
)
|
||||
},
|
||||
auth() {
|
||||
throw new Error(
|
||||
'Do not call auth() on a GunSmith node, use getUser()'
|
||||
"Do not call auth() on another user's graph (gun.user(otherUserPub))"
|
||||
)
|
||||
},
|
||||
create() {
|
||||
throw new Error(
|
||||
'Do not call create() on a GunSmith node, use getUser()'
|
||||
"Do not call create() on another user's graph (gun.user(otherUserPub))"
|
||||
)
|
||||
},
|
||||
leave() {
|
||||
throw new Error(
|
||||
'Do not call leave() on a GunSmith node, use getUser()'
|
||||
"Do not call leave() on another user's graph (gun.user(otherUserPub))"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -134,17 +319,50 @@ const createReplica = (path, afterMap = false) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {GunT.GUNNode}
|
||||
*/
|
||||
const getGun = () => createReplica('$root')
|
||||
let userReplicaCalled = false
|
||||
|
||||
/**
|
||||
* @returns {GunT.UserGUNNode}
|
||||
*/
|
||||
const getUser = createReplica('$user')
|
||||
function createUserReplica() {
|
||||
if (userReplicaCalled) {
|
||||
throw new Error('Please only call gun.user() (without a pub) once.')
|
||||
}
|
||||
userReplicaCalled = true
|
||||
|
||||
module.exports = {
|
||||
getGun,
|
||||
getUser
|
||||
const baseReplica = createReplica('$user')
|
||||
|
||||
/** @type {GunT.UserGUNNode} */
|
||||
const completeReplica = {
|
||||
...baseReplica,
|
||||
_: {
|
||||
...baseReplica._,
|
||||
// TODO
|
||||
sea: {
|
||||
epriv: '',
|
||||
epub: '',
|
||||
priv: '',
|
||||
pub: ''
|
||||
}
|
||||
},
|
||||
auth(alias, pass, cb) {
|
||||
auth(alias, pass).then((pub) => {
|
||||
cb({
|
||||
err: undefined,
|
||||
sea: {
|
||||
pub,
|
||||
}
|
||||
})
|
||||
}).catch(e => {
|
||||
cb({
|
||||
err: e.message,
|
||||
sea: undefined
|
||||
})
|
||||
})
|
||||
},
|
||||
create() {},
|
||||
leave() {}
|
||||
}
|
||||
|
||||
return completeReplica
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ namespace GunT {
|
|||
|
||||
export interface UserGUNNode extends GUNNode {
|
||||
_: UserSoul
|
||||
auth(user: string, pass: string, cb: AuthCB): void
|
||||
auth(alias: string, pass: string, cb: AuthCB): void
|
||||
is?: {
|
||||
alias: string
|
||||
pub: string
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@
|
|||
*/
|
||||
/// <reference path="GunT.ts" />
|
||||
namespace Smith {
|
||||
|
||||
export interface PendingPut {
|
||||
cb: GunT.Callback
|
||||
data: GunT.ValidDataValue
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface SmithMsgAuth {
|
||||
alias: string
|
||||
pass: string
|
||||
type: 'auth'
|
||||
}
|
||||
|
||||
export interface SmithMsgOn {
|
||||
path: string
|
||||
type: 'on'
|
||||
|
|
@ -16,27 +29,24 @@ namespace Smith {
|
|||
|
||||
export interface SmithMsgPut {
|
||||
id: string
|
||||
data: GunT.ValidDataValue
|
||||
path: string
|
||||
type: 'put'
|
||||
value: any
|
||||
}
|
||||
|
||||
export type SmithMsg = SmithMsgOn | SmithMsgOnce | SmithMsgPut | BatchSmithMsg
|
||||
|
||||
export type BatchSmithMsg = SmithMsg[]
|
||||
|
||||
export interface GunMsgOn {
|
||||
data: any
|
||||
key: string
|
||||
path: string
|
||||
type: 'on'
|
||||
export interface GunMsgAuth {
|
||||
ack: GunT.AuthAck
|
||||
type: 'auth'
|
||||
}
|
||||
|
||||
export interface GunMsgOnce {
|
||||
export interface GunMsgOn {
|
||||
data: any
|
||||
id: string
|
||||
key: string
|
||||
type: 'once'
|
||||
path: string
|
||||
type: 'on'
|
||||
}
|
||||
|
||||
export interface GunMsgPut {
|
||||
|
|
@ -46,5 +56,5 @@ namespace Smith {
|
|||
type: 'put'
|
||||
}
|
||||
|
||||
export type GunMsg = GunMsgOn | GunMsgOnce | GunMsgPut
|
||||
export type GunMsg = GunMsgAuth| GunMsgOn | GunMsgPut
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue