diff --git a/utils/GunSmith/GunSmith.js b/utils/GunSmith/GunSmith.js
index 9c1e2d14..288185ba 100644
--- a/utils/GunSmith/GunSmith.js
+++ b/utils/GunSmith/GunSmith.js
@@ -1,35 +1,141 @@
/**
* @format
*/
+/* eslint-disable no-use-before-define */
+/* eslint-disable func-style */
// @ts-check
///
///
+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>}
+ * @type {Record|undefined>}
*/
-const pathToListeners = {
- 'x>x>x>': new WeakSet()
-}
+const pathToListeners = {}
+
+/**
+ * Path to pending puts. Oldest to newest
+ * @type {Record}
+ */
+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}
+ */
+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}
+ */
+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
}
diff --git a/utils/GunSmith/GunT.ts b/utils/GunSmith/GunT.ts
index b3a74409..a4c6ef32 100644
--- a/utils/GunSmith/GunT.ts
+++ b/utils/GunSmith/GunT.ts
@@ -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
diff --git a/utils/GunSmith/Smith.ts b/utils/GunSmith/Smith.ts
index e17910e7..1988e96a 100644
--- a/utils/GunSmith/Smith.ts
+++ b/utils/GunSmith/Smith.ts
@@ -3,6 +3,19 @@
*/
///
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
}