diff --git a/src/routes.js b/src/routes.js index 3f61c973..565eb056 100644 --- a/src/routes.js +++ b/src/routes.js @@ -2274,6 +2274,7 @@ module.exports = async ( * @prop {string=} publicKey * @prop {string=} publicKeyForDecryption * @prop {string=} epubForDecryption + * @prop {boolean=} mustBePopulated */ /** * @param {HandleGunFetchParams} args0 @@ -2285,7 +2286,8 @@ module.exports = async ( path, publicKey, publicKeyForDecryption, - epubForDecryption + epubForDecryption, + mustBePopulated }) => { const keys = path.split('>') const { gun, user } = require('../services/gunDB/Mediator') @@ -2315,7 +2317,7 @@ module.exports = async ( } } - if (type === 'once') node.once(listener) + if (type === 'once') node.once(listener, { mustBePopulated }) if (type === 'load') node.load(listener) }) } @@ -2340,7 +2342,8 @@ module.exports = async ( startFromUserGraph: false, type: 'once', publicKeyForDecryption, - epubForDecryption + epubForDecryption, + mustBePopulated: !!req.header('must-be-populated') }) res.status(200).json({ data @@ -2388,7 +2391,8 @@ module.exports = async ( startFromUserGraph: true, type: 'once', publicKeyForDecryption, - epubForDecryption + epubForDecryption, + mustBePopulated: !!req.header('must-be-populated') }) res.status(200).json({ data diff --git a/utils/GunSmith/GunSmith.js b/utils/GunSmith/GunSmith.js index ffb7aa8e..abec84be 100644 --- a/utils/GunSmith/GunSmith.js +++ b/utils/GunSmith/GunSmith.js @@ -13,7 +13,7 @@ const { fork } = require('child_process') const logger = require('../../config/log') -const { mergePuts } = require('./misc') +const { mergePuts, isPopulated } = require('./misc') const gunUUID = () => { const RG = /** @type {any} */ (RealGun) @@ -366,14 +366,29 @@ function createReplica(path, afterMap = false) { mapListeners.delete(l) } }, - on(cb) { + on(cb, { mustBePopulated } = {}) { listenersForThisRef.push(cb) + + let canaryPeep = false + const canary = () => { + canaryPeep = true + } + listenersForThisRef.push(canary) + const checkCanary = () => + setTimeout(() => { + if (!canaryPeep && mustBePopulated) { + forge() + checkCanary() + } + }, 5000) + if (afterMap) { // eslint-disable-next-line no-multi-assign const listeners = pathToMapListeners[path] || (pathToMapListeners[path] = new Set()) listeners.add(cb) + listeners.add(canary) /** @type {Smith.SmithMsgMapOn} */ const msg = { @@ -387,6 +402,7 @@ function createReplica(path, afterMap = false) { pathToListeners[path] || (pathToListeners[path] = new Set()) listeners.add(cb) + listeners.add(canary) /** @type {Smith.SmithMsgOn} */ const msg = { @@ -398,7 +414,8 @@ function createReplica(path, afterMap = false) { return this }, - once(cb, opts = { wait: 500 }) { + once(cb, _opts) { + const opts = { ...{ mustBePopulated: false, wait: 500 }, ..._opts } // We could use this.on() but then we couldn't call .off() const tmp = createReplica(path, afterMap) if (afterMap) { @@ -412,15 +429,21 @@ function createReplica(path, afterMap = false) { }) setTimeout(() => { - if (cb) { - cb(lastVal, path.split('>')[path.split('>').length - 1]) - } tmp.off() + if (cb) { + if (opts.mustBePopulated && !isPopulated(lastVal)) { + forge() + this.once(cb, { ...opts, wait: 5000, mustBePopulated: false }) + } else { + cb(lastVal, path.split('>')[path.split('>').length - 1]) + } + } }, opts.wait) return this }, put(data, cb) { + logger.info('put()') const id = uuid() const pendingPutsForPath = pendingPuts[path] || (pendingPuts[path] = []) @@ -502,11 +525,11 @@ function createReplica(path, afterMap = false) { } } }, - then() { + then(opts) { return new Promise(res => { this.once(data => { res(data) - }) + }, opts) }) } } diff --git a/utils/GunSmith/GunT.ts b/utils/GunSmith/GunT.ts index 47147da3..625c064d 100644 --- a/utils/GunSmith/GunT.ts +++ b/utils/GunSmith/GunT.ts @@ -58,6 +58,14 @@ namespace GunT { 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 { _: Soul /** @@ -81,13 +89,26 @@ namespace GunT { load(this: GUNNode, cb?: LoadListener): GUNNode map(): GUNNode off(): void - on(this: GUNNode, cb: Listener): void - once(this: GUNNode, cb?: Listener, opts?: { wait: number }): GUNNode + on( + this: GUNNode, + cb: Listener, + opts?: { + change?: boolean + } & GunSmithFetchOpts + ): void + once( + this: GUNNode, + cb?: Listener, + opts?: { wait?: number } & GunSmithFetchOpts + ): GUNNode user(): UserGUNNode user(pub: string): GUNNode put(data: ValidDataValue, cb?: Callback): GUNNode set(data: ValidDataValue, cb?: Callback): GUNNode - then(): Promise + /** + * @param options Gunsmith only. + */ + then(opts?: GunSmithFetchOpts): Promise } export interface CreateAck {