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
|
* @format
|
||||||
*/
|
*/
|
||||||
|
/* eslint-disable no-use-before-define */
|
||||||
|
/* eslint-disable func-style */
|
||||||
// @ts-check
|
// @ts-check
|
||||||
/// <reference path="Smith.ts" />
|
/// <reference path="Smith.ts" />
|
||||||
/// <reference path="GunT.ts" />
|
/// <reference path="GunT.ts" />
|
||||||
|
const uuid = require('uuid').v1
|
||||||
const { fork } = require('child_process')
|
const { fork } = require('child_process')
|
||||||
|
|
||||||
const Config = require('./config')
|
const Config = require('./config')
|
||||||
|
|
||||||
|
const logger = require('../../config/log')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a path to `on()` listeners
|
* Maps a path to `on()` listeners
|
||||||
* @type {Record<string, WeakSet<GunT.Listener>>}
|
* @type {Record<string, Set<GunT.Listener>|undefined>}
|
||||||
*/
|
*/
|
||||||
const pathToListeners = {
|
const pathToListeners = {}
|
||||||
'x>x>x>': new WeakSet()
|
|
||||||
}
|
/**
|
||||||
|
* Path to pending puts. Oldest to newest
|
||||||
|
* @type {Record<string, Smith.PendingPut[]?>}
|
||||||
|
*/
|
||||||
|
const pendingPuts = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Smith.GunMsg} msg
|
* @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 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 = () => {
|
const forge = () => {
|
||||||
|
currentGun.off('message', handleMsg)
|
||||||
currentGun.kill()
|
currentGun.kill()
|
||||||
currentGun = fork('./gun')
|
const newGun = fork('./gun')
|
||||||
|
currentGun = newGun
|
||||||
|
|
||||||
currentGun.on('message', handleMsg)
|
currentGun.on('message', handleMsg)
|
||||||
|
|
||||||
const msgs = Object.keys(pathToListeners).map(path => {
|
const lastGunListeners = Object.keys(pathToListeners).map(path => {
|
||||||
/** @type {Smith.SmithMsgOn} */
|
/** @type {Smith.SmithMsgOn} */
|
||||||
const msg = {
|
const msg = {
|
||||||
path,
|
path,
|
||||||
|
|
@ -38,7 +144,11 @@ const forge = () => {
|
||||||
return msg
|
return msg
|
||||||
})
|
})
|
||||||
|
|
||||||
currentGun.send(msgs)
|
currentGun.send(lastGunListeners)
|
||||||
|
|
||||||
|
autoAuth().then(() => {
|
||||||
|
processPendingPutsFromLastGun(newGun)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,7 +156,7 @@ const forge = () => {
|
||||||
* @param {boolean=} afterMap
|
* @param {boolean=} afterMap
|
||||||
* @returns {GunT.GUNNode}
|
* @returns {GunT.GUNNode}
|
||||||
*/
|
*/
|
||||||
const createReplica = (path, afterMap = false) => {
|
function createReplica(path, afterMap = false) {
|
||||||
/** @type {GunT.Listener[]} */
|
/** @type {GunT.Listener[]} */
|
||||||
const listenersForThisRef = []
|
const listenersForThisRef = []
|
||||||
|
|
||||||
|
|
@ -59,36 +169,112 @@ const createReplica = (path, afterMap = false) => {
|
||||||
put: {}
|
put: {}
|
||||||
},
|
},
|
||||||
get(key) {
|
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)
|
return createReplica(path + '>' + key)
|
||||||
},
|
},
|
||||||
map() {
|
map() {
|
||||||
|
if (afterMap) {
|
||||||
|
throw new Error('Cannot call map() after map() on a GunSmith node')
|
||||||
|
}
|
||||||
return createReplica(path, true)
|
return createReplica(path, true)
|
||||||
},
|
},
|
||||||
off() {
|
off() {
|
||||||
|
if (afterMap) {
|
||||||
|
throw new Error('Cannot call off() after map() on a GunSmith node')
|
||||||
|
}
|
||||||
for (const l of listenersForThisRef) {
|
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) {
|
on(cb) {
|
||||||
listenersForThisRef.push(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} */
|
/** @type {Smith.SmithMsgOn} */
|
||||||
const msg = {
|
const msg = {
|
||||||
path,
|
path,
|
||||||
type: 'on'
|
type: 'on'
|
||||||
}
|
}
|
||||||
currentGun.send(msg)
|
currentGun.send(msg)
|
||||||
|
|
||||||
return this
|
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
|
return this
|
||||||
},
|
},
|
||||||
put(data, cb) {
|
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
|
return this
|
||||||
},
|
},
|
||||||
set(data, cb) {
|
set(data, cb) {
|
||||||
|
if (afterMap) {
|
||||||
|
throw new Error('Cannot call set() after map() on a GunSmith node')
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
then() {
|
then() {
|
||||||
|
if (afterMap) {
|
||||||
|
throw new Error('Cannot call then() after map() on a GunSmith node')
|
||||||
|
}
|
||||||
return Promise.resolve(null)
|
return Promise.resolve(null)
|
||||||
},
|
},
|
||||||
user(pub) {
|
user(pub) {
|
||||||
|
|
@ -98,35 +284,34 @@ const createReplica = (path, afterMap = false) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!pub) {
|
if (!pub) {
|
||||||
throw new Error(
|
return createUserReplica()
|
||||||
'Do not use `user()` (without providing a pub) on a GunSmith node, use getUser()'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const replica = createReplica(pub)
|
const replica = createReplica(pub)
|
||||||
|
// I don't know why Typescript insists on returning a UserGUNNode so here we go:
|
||||||
return {
|
return {
|
||||||
...replica,
|
...replica,
|
||||||
_: {
|
/** @returns {GunT.UserSoul} */
|
||||||
...replica._,
|
get _() {
|
||||||
sea: {
|
throw new ReferenceError(
|
||||||
epriv: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
`Do not access _ on another user's graph (${pub.slice(
|
||||||
epub: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
0,
|
||||||
priv: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
8
|
||||||
pub: 'Do not use ._.sea on a GunSmith node, use getUser()'
|
)}...${pub.slice(-8)})`
|
||||||
}
|
)
|
||||||
},
|
},
|
||||||
auth() {
|
auth() {
|
||||||
throw new Error(
|
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() {
|
create() {
|
||||||
throw new Error(
|
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() {
|
leave() {
|
||||||
throw new Error(
|
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) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
let userReplicaCalled = false
|
||||||
* @returns {GunT.GUNNode}
|
|
||||||
*/
|
|
||||||
const getGun = () => createReplica('$root')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {GunT.UserGUNNode}
|
* @returns {GunT.UserGUNNode}
|
||||||
*/
|
*/
|
||||||
const getUser = createReplica('$user')
|
function createUserReplica() {
|
||||||
|
if (userReplicaCalled) {
|
||||||
module.exports = {
|
throw new Error('Please only call gun.user() (without a pub) once.')
|
||||||
getGun,
|
}
|
||||||
getUser
|
userReplicaCalled = true
|
||||||
|
|
||||||
|
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 {
|
export interface UserGUNNode extends GUNNode {
|
||||||
_: UserSoul
|
_: UserSoul
|
||||||
auth(user: string, pass: string, cb: AuthCB): void
|
auth(alias: string, pass: string, cb: AuthCB): void
|
||||||
is?: {
|
is?: {
|
||||||
alias: string
|
alias: string
|
||||||
pub: string
|
pub: string
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,19 @@
|
||||||
*/
|
*/
|
||||||
/// <reference path="GunT.ts" />
|
/// <reference path="GunT.ts" />
|
||||||
namespace Smith {
|
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 {
|
export interface SmithMsgOn {
|
||||||
path: string
|
path: string
|
||||||
type: 'on'
|
type: 'on'
|
||||||
|
|
@ -16,27 +29,24 @@ namespace Smith {
|
||||||
|
|
||||||
export interface SmithMsgPut {
|
export interface SmithMsgPut {
|
||||||
id: string
|
id: string
|
||||||
|
data: GunT.ValidDataValue
|
||||||
path: string
|
path: string
|
||||||
type: 'put'
|
type: 'put'
|
||||||
value: any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SmithMsg = SmithMsgOn | SmithMsgOnce | SmithMsgPut | BatchSmithMsg
|
export type SmithMsg = SmithMsgOn | SmithMsgOnce | SmithMsgPut | BatchSmithMsg
|
||||||
|
|
||||||
export type BatchSmithMsg = SmithMsg[]
|
export type BatchSmithMsg = SmithMsg[]
|
||||||
|
|
||||||
export interface GunMsgOn {
|
export interface GunMsgAuth {
|
||||||
data: any
|
ack: GunT.AuthAck
|
||||||
key: string
|
type: 'auth'
|
||||||
path: string
|
|
||||||
type: 'on'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GunMsgOnce {
|
export interface GunMsgOn {
|
||||||
data: any
|
data: any
|
||||||
id: string
|
path: string
|
||||||
key: string
|
type: 'on'
|
||||||
type: 'once'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GunMsgPut {
|
export interface GunMsgPut {
|
||||||
|
|
@ -46,5 +56,5 @@ namespace Smith {
|
||||||
type: 'put'
|
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