Gunsmith restart stuff in separate methods, improve re-forge

This commit is contained in:
Daniel Lugo 2021-09-15 17:40:22 -04:00
parent 051d5aa5df
commit 7af0ce5bfc
4 changed files with 172 additions and 86 deletions

View file

@ -2268,13 +2268,12 @@ module.exports = async (
/** /**
* @typedef {object} HandleGunFetchParams * @typedef {object} HandleGunFetchParams
* @prop {'once'|'load'} type * @prop {'once'|'load'|'specialOnce'} type
* @prop {boolean} startFromUserGraph * @prop {boolean} startFromUserGraph
* @prop {string} path * @prop {string} path
* @prop {string=} publicKey * @prop {string=} publicKey
* @prop {string=} publicKeyForDecryption * @prop {string=} publicKeyForDecryption
* @prop {string=} epubForDecryption * @prop {string=} epubForDecryption
* @prop {boolean=} mustBePopulated
*/ */
/** /**
* @param {HandleGunFetchParams} args0 * @param {HandleGunFetchParams} args0
@ -2286,8 +2285,7 @@ module.exports = async (
path, path,
publicKey, publicKey,
publicKeyForDecryption, publicKeyForDecryption,
epubForDecryption, epubForDecryption
mustBePopulated
}) => { }) => {
const keys = path.split('>') const keys = path.split('>')
const { gun, user } = require('../services/gunDB/Mediator') const { gun, user } = require('../services/gunDB/Mediator')
@ -2317,8 +2315,9 @@ module.exports = async (
} }
} }
if (type === 'once') node.once(listener, { mustBePopulated }) if (type === 'once') node.once(listener)
if (type === 'load') node.load(listener) if (type === 'load') node.load(listener)
if (type === 'specialOnce') node.specialOnce(listener)
}) })
} }
@ -2342,8 +2341,31 @@ module.exports = async (
startFromUserGraph: false, startFromUserGraph: false,
type: 'once', type: 'once',
publicKeyForDecryption, publicKeyForDecryption,
epubForDecryption, epubForDecryption
mustBePopulated: !!req.header('must-be-populated') })
res.status(200).json({
data
})
} catch (e) {
logger.error(e)
res.status(500).json({
errorMessage: e.message
})
}
})
ap.get('/api/gun/specialOnce/:path', async (req, res) => {
try {
const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER)
const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER)
const { path } = req.params
logger.info(`Gun special once: ${path}`)
const data = await handleGunFetch({
path,
startFromUserGraph: false,
type: 'specialOnce',
publicKeyForDecryption,
epubForDecryption
}) })
res.status(200).json({ res.status(200).json({
data data
@ -2391,8 +2413,31 @@ module.exports = async (
startFromUserGraph: true, startFromUserGraph: true,
type: 'once', type: 'once',
publicKeyForDecryption, publicKeyForDecryption,
epubForDecryption, epubForDecryption
mustBePopulated: !!req.header('must-be-populated') })
res.status(200).json({
data
})
} catch (e) {
logger.error(e)
res.status(500).json({
errorMessage: e.message
})
}
})
ap.get('/api/gun/user/specialOnce/:path', async (req, res) => {
try {
const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER)
const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER)
const { path } = req.params
logger.info(`Gun user special once: ${path}`)
const data = await handleGunFetch({
path,
startFromUserGraph: true,
type: 'specialOnce',
publicKeyForDecryption,
epubForDecryption
}) })
res.status(200).json({ res.status(200).json({
data data
@ -2431,11 +2476,11 @@ module.exports = async (
ap.get('/api/gun/otheruser/:publicKey/:type/:path', async (req, res) => { ap.get('/api/gun/otheruser/:publicKey/:type/:path', async (req, res) => {
try { try {
const allowedTypes = ['once', 'load', 'open'] const allowedTypes = ['once', 'load', 'open', 'specialOnce']
const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER)
const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER) const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER)
const { path /*:rawPath*/, publicKey, type } = req.params const { path /*:rawPath*/, publicKey, type } = req.params
logger.info(`gun otheruser ${type}: ${path}`) logger.info(`Gun other user ${type}: ${path}`)
// const path = decodeURI(rawPath) // const path = decodeURI(rawPath)
if (!publicKey || publicKey === 'undefined') { if (!publicKey || publicKey === 'undefined') {
res.status(400).json({ res.status(400).json({

View file

@ -161,7 +161,7 @@ const auth = (alias, pass) => {
/** @param {Smith.GunMsg} msg */ /** @param {Smith.GunMsg} msg */
const _cb = msg => { const _cb = msg => {
if (msg.type === 'auth') { if (msg.type === 'auth') {
logger.info('Received auth reply.', msg) logger.info(`Received ${msg.ack.sea ? 'ok' : 'bad'} auth reply.`)
currentGun.off('message', _cb) currentGun.off('message', _cb)
isAuthing = false isAuthing = false
@ -190,17 +190,13 @@ const auth = (alias, pass) => {
}) })
} }
/** const autoAuth = async () => {
* Returns null if there's no cached credentials.
* @returns {Promise<GunT.UserPair|null>}
*/
const autoAuth = () => {
if (!lastAlias || !lastPass) { if (!lastAlias || !lastPass) {
logger.info('No credentials cached, will not auto-auth') logger.info('No credentials cached, will not auto-auth')
return Promise.resolve(null) return
} }
logger.info('Credentials cached, will auth.') logger.info('Credentials cached, will auth.')
return auth(lastAlias, lastPass) await auth(lastAlias, lastPass)
} }
const flushPendingPuts = () => { const flushPendingPuts = () => {
@ -227,20 +223,36 @@ const flushPendingPuts = () => {
logger.info(`Sent ${messages.length} pending puts.`) logger.info(`Sent ${messages.length} pending puts.`)
} }
let isReforging = false let isForging = false
/** @returns {Promise<void>} */
const isReady = () =>
new Promise(res => {
if (isForging || isAuthing) {
setTimeout(() => {
isReady().then(res)
}, 1000)
} else {
logger.info('isReady')
res()
}
})
const forge = () => { const forge = () => {
if (isReforging) { if (isForging) {
throw new Error('Double forge?') throw new Error('Double forge?')
} }
logger.info('Will reforge')
isReforging = true isForging = true
if (currentGun) { if (currentGun) {
logger.info('Will reforge')
currentGun.off('message', handleMsg) currentGun.off('message', handleMsg)
currentGun.disconnect() currentGun.disconnect()
currentGun.kill() currentGun.kill()
logger.info('Killed current gun')
} else {
logger.info('Will forge')
} }
logger.info('Killed current gun')
const newGun = fork('utils/GunSmith/gun.js') const newGun = fork('utils/GunSmith/gun.js')
currentGun = newGun currentGun = newGun
logger.info('Forged new gun') logger.info('Forged new gun')
@ -284,14 +296,15 @@ const forge = () => {
logger.info('Finished reforging, will now auto-auth') logger.info('Finished reforging, will now auto-auth')
isReforging = false autoAuth().then(() => {
autoAuth() isForging = false
})
} }
/** /**
* @param {string} path * @param {string} path
* @param {boolean=} afterMap * @param {boolean=} afterMap
* @returns {GunT.GUNNode} * @returns {GunT.GUNNode & Smith.GunSmithNode}
*/ */
function createReplica(path, afterMap = false) { function createReplica(path, afterMap = false) {
/** @type {(GunT.Listener|GunT.LoadListener)[]} */ /** @type {(GunT.Listener|GunT.LoadListener)[]} */
@ -366,29 +379,15 @@ function createReplica(path, afterMap = false) {
mapListeners.delete(l) mapListeners.delete(l)
} }
}, },
on(cb, { mustBePopulated } = {}) { on(cb) {
listenersForThisRef.push(cb) listenersForThisRef.push(cb)
let canaryPeep = false
const canary = () => {
canaryPeep = true
}
listenersForThisRef.push(canary)
const checkCanary = () =>
setTimeout(() => {
if (!canaryPeep && mustBePopulated) {
forge()
checkCanary()
}
}, 5000)
if (afterMap) { if (afterMap) {
// eslint-disable-next-line no-multi-assign // eslint-disable-next-line no-multi-assign
const listeners = const listeners =
pathToMapListeners[path] || (pathToMapListeners[path] = new Set()) pathToMapListeners[path] || (pathToMapListeners[path] = new Set())
listeners.add(cb) listeners.add(cb)
listeners.add(canary)
/** @type {Smith.SmithMsgMapOn} */ /** @type {Smith.SmithMsgMapOn} */
const msg = { const msg = {
@ -402,7 +401,6 @@ function createReplica(path, afterMap = false) {
pathToListeners[path] || (pathToListeners[path] = new Set()) pathToListeners[path] || (pathToListeners[path] = new Set())
listeners.add(cb) listeners.add(cb)
listeners.add(canary)
/** @type {Smith.SmithMsgOn} */ /** @type {Smith.SmithMsgOn} */
const msg = { const msg = {
@ -414,8 +412,7 @@ function createReplica(path, afterMap = false) {
return this return this
}, },
once(cb, _opts) { once(cb, opts = { wait: 500 }) {
const opts = { ...{ mustBePopulated: false, wait: 500 }, ..._opts }
// We could use this.on() but then we couldn't call .off() // We could use this.on() but then we couldn't call .off()
const tmp = createReplica(path, afterMap) const tmp = createReplica(path, afterMap)
if (afterMap) { if (afterMap) {
@ -430,14 +427,9 @@ function createReplica(path, afterMap = false) {
setTimeout(() => { setTimeout(() => {
tmp.off() tmp.off()
if (cb) { const keys = path.split('>')
if (opts.mustBePopulated && !isPopulated(lastVal)) { // eslint-disable-next-line no-unused-expressions
forge() cb && cb(lastVal, keys[keys.length - 1])
this.once(cb, { ...opts, wait: 5000, mustBePopulated: false })
} else {
cb(lastVal, path.split('>')[path.split('>').length - 1])
}
}
}, opts.wait) }, opts.wait)
return this return this
@ -464,9 +456,9 @@ function createReplica(path, afterMap = false) {
path, path,
type: 'put' type: 'put'
} }
if (!isAuthing && !isReforging) { isReady().then(() => {
currentGun.send(msg) currentGun.send(msg)
} })
return this return this
}, },
set(data, cb) { set(data, cb) {
@ -525,11 +517,52 @@ function createReplica(path, afterMap = false) {
} }
} }
}, },
then(opts) { then() {
return new Promise(res => { return new Promise(res => {
this.once(data => { this.once(data => {
res(data) res(data)
}, opts) })
})
},
specialOn(cb) {
let canaryPeep = false
const checkCanary = () =>
setTimeout(() => {
if (!canaryPeep) {
forge()
isReady().then(checkCanary)
}
}, 5000)
checkCanary()
return this.on((data, key) => {
canaryPeep = true
cb(data, key)
})
},
specialOnce(cb, _wait = 500) {
this.once((data, key) => {
if (isPopulated(data) || _wait === 4500) {
cb(data, key)
} else {
forge()
isReady().then(() => {
this.specialOnce(cb, _wait * 3)
})
}
})
return this
},
specialThen() {
return new Promise((res, rej) => {
this.specialOnce(data => {
if (isPopulated(data)) {
res(data)
} else {
rej(new Error(`Could not fetch data at path ${path}`))
}
})
}) })
} }
} }
@ -538,7 +571,7 @@ function createReplica(path, afterMap = false) {
let userReplicaCalled = false let userReplicaCalled = false
/** /**
* @returns {GunT.UserGUNNode} * @returns {GunT.UserGUNNode & Smith.GunSmithNode}
*/ */
function createUserReplica() { function createUserReplica() {
if (userReplicaCalled) { if (userReplicaCalled) {
@ -548,7 +581,7 @@ function createUserReplica() {
const baseReplica = createReplica('$user') const baseReplica = createReplica('$user')
/** @type {GunT.UserGUNNode} */ /** @type {GunT.UserGUNNode & Smith.GunSmithNode} */
const completeReplica = { const completeReplica = {
...baseReplica, ...baseReplica,
get _() { get _() {
@ -638,7 +671,7 @@ function createUserReplica() {
} }
/** /**
* @typedef {GunT.GUNNode & { reforge(): void }} RootNode * @typedef {GunT.GUNNode & Smith.GunSmithNode & { reforge(): void }} RootNode
*/ */
/** /**

View file

@ -58,14 +58,6 @@ namespace GunT {
export type LoadListener = (data: LoadListenerData, key: string) => void export type LoadListener = (data: LoadListenerData, key: string) => void
export interface GunSmithFetchOpts {
/**
* GunSmith exclusive. If set to true, gun will be restarted to force
* replication of this data.
*/
mustBePopulated?: boolean
}
export interface GUNNode { export interface GUNNode {
_: Soul _: Soul
/** /**
@ -86,29 +78,16 @@ namespace GunT {
> >
} }
get(key: string): GUNNode get(key: string): GUNNode
load(this: GUNNode, cb?: LoadListener): GUNNode load(this: GUNNode, cb?: LoadListener): void
map(): GUNNode map(): GUNNode
off(): void off(): void
on( on(this: GUNNode, cb: Listener): void
this: GUNNode, once(this: GUNNode, cb?: Listener, opts?: { wait?: number }): void
cb: Listener,
opts?: {
change?: boolean
} & GunSmithFetchOpts
): void
once(
this: GUNNode,
cb?: Listener,
opts?: { wait?: number } & GunSmithFetchOpts
): GUNNode
user(): UserGUNNode user(): UserGUNNode
user(pub: string): GUNNode user(pub: string): GUNNode
put(data: ValidDataValue, cb?: Callback): GUNNode put(data: ValidDataValue, cb?: Callback): void
set(data: ValidDataValue, cb?: Callback): GUNNode set(data: ValidDataValue, cb?: Callback): GUNNode
/** then(): Promise<ListenerData>
* @param options Gunsmith only.
*/
then(opts?: GunSmithFetchOpts): Promise<ListenerData>
} }
export interface CreateAck { export interface CreateAck {

View file

@ -3,6 +3,35 @@
*/ */
/// <reference path="GunT.ts" /> /// <reference path="GunT.ts" />
namespace Smith { namespace Smith {
export interface GunSmithNode {
/**
* @override
*/
map(): GunSmithNode
/**
* @override
*/
set(data: GunT.ValidDataValue, cb?: GunT.Callback): GunT.GUNNode
/**
* Gun will be restarted to force replication of data
* if needed.
* @param cb
*/
specialOn(cb: GunT.Listener): void
/**
* Gun will be restarted to force replication of data
* if needed.
* @param cb
* @param _wait
*/
specialOnce(cb: GunT.Listener, _wait?: number): GunSmithNode
/**
* Gun will be restarted to force replication of data
* if needed.
*/
specialThen(): Promise<GunT.ListenerData>
}
export interface PendingPut { export interface PendingPut {
cb: GunT.Callback cb: GunT.Callback
data: GunT.ValidDataValue data: GunT.ValidDataValue