diff --git a/services/gunDB/contact-api/SimpleGUN.ts b/services/gunDB/contact-api/SimpleGUN.ts index 3a9cd77b..6d04fdf7 100644 --- a/services/gunDB/contact-api/SimpleGUN.ts +++ b/services/gunDB/contact-api/SimpleGUN.ts @@ -1,7 +1,7 @@ /** * @prettier */ -type Primitive = boolean | string | number +export type Primitive = boolean | string | number export interface Data { [K: string]: ValidDataValue diff --git a/services/gunDB/rpc.js b/services/gunDB/rpc/index.js similarity index 61% rename from services/gunDB/rpc.js rename to services/gunDB/rpc/index.js index 8d56592d..38d04aa1 100644 --- a/services/gunDB/rpc.js +++ b/services/gunDB/rpc/index.js @@ -1,21 +1,25 @@ /** * @format */ +/* eslint-disable no-use-before-define */ // @ts-check const { makePromise, Constants, Schema } = require('shock-common') const mapValues = require('lodash/mapValues') const Bluebird = require('bluebird') +const Gun = require('gun') -const { pubToEpub } = require('./contact-api/utils') +const { pubToEpub } = require('../contact-api/utils') const { getGun, getUser, mySEA: SEA, getMySecret, $$__SHOCKWALLET__ENCRYPTED__ -} = require('./Mediator') +} = require('../Mediator') /** - * @typedef {import('./contact-api/SimpleGUN').ValidDataValue} ValidDataValue + * @typedef {import('../contact-api/SimpleGUN').ValidDataValue} ValidDataValue + * @typedef {import('./types').ValidRPCDataValue} ValidRPCDataValue + * @typedef {import('./types').RPCData} RPCData */ /** @@ -55,10 +59,11 @@ const deepDecryptIfNeeded = async (value, publicKey) => { } /** - * @param {ValidDataValue} value - * @returns {Promise} + * @param {ValidRPCDataValue} value + * @returns {Promise} */ -const deepEncryptIfNeeded = async value => { +// eslint-disable-next-line func-style +async function deepEncryptIfNeeded(value) { const u = getUser() if (!u.is) { @@ -69,6 +74,10 @@ const deepEncryptIfNeeded = async value => { return value } + if (Array.isArray(value)) { + return Promise.all(value.map(v => deepEncryptIfNeeded(v))) + } + const pk = /** @type {string|undefined} */ (value.$$__ENCRYPT__FOR) if (!pk) { @@ -92,7 +101,7 @@ const deepEncryptIfNeeded = async value => { /** * @param {string} rawPath - * @param {ValidDataValue} value + * @param {ValidRPCDataValue} value * @returns {Promise} */ const put = async (rawPath, value) => { @@ -125,25 +134,34 @@ const put = async (rawPath, value) => { return _node })() - const encryptedIfNeededValue = await deepEncryptIfNeeded(value) + const theValue = await deepEncryptIfNeeded(value) - await makePromise((res, rej) => { - node.put(encryptedIfNeededValue, ack => { - if (ack.err && typeof ack.err !== 'number') { - rej(new Error(ack.err)) - } else { - res() - } + if (Array.isArray(theValue)) { + await Promise.all(theValue.map(v => set(rawPath, v))) + } else if (Schema.isObj(theValue)) { + const writes = mapValues(theValue, (v, k) => put(`${rawPath}.${k}`, v)) + + await Bluebird.props(writes) + } /* is primitive */ else { + await makePromise((res, rej) => { + node.put(/** @type {ValidDataValue} */ (theValue), ack => { + if (ack.err && typeof ack.err !== 'number') { + rej(new Error(ack.err)) + } else { + res() + } + }) }) - }) + } } /** * @param {string} rawPath - * @param {ValidDataValue} value + * @param {ValidRPCDataValue} value * @returns {Promise} */ -const set = async (rawPath, value) => { +// eslint-disable-next-line func-style +async function set(rawPath, value) { const [root, ...path] = rawPath.split('.') const node = (() => { @@ -173,10 +191,39 @@ const set = async (rawPath, value) => { return _node })() - const encryptedIfNeededValue = await deepEncryptIfNeeded(value) + const theValue = await deepEncryptIfNeeded(value) + + if (Array.isArray(theValue)) { + // we'll create a set of sets + + // @ts-expect-error + const uuid = Gun.text.random() + + // here we are simulating the top-most set() + const subPath = rawPath + '.' + uuid + + const writes = theValue.map(v => set(subPath, v)) + + await Promise.all(writes) + + return uuid + } else if (Schema.isObj(theValue)) { + // @ts-expect-error + const uuid = Gun.text.random() // we'll handle UUID ourselves + + // so we can use our own put() + + const subPath = rawPath + '.' + uuid + + await put(subPath, theValue) + + return uuid + } + + /* else is primitive */ const id = await makePromise((res, rej) => { - const subNode = node.set(encryptedIfNeededValue, ack => { + const subNode = node.set(theValue, ack => { if (ack.err && typeof ack.err !== 'number') { rej(new Error(ack.err)) } else { diff --git a/services/gunDB/rpc/types.ts b/services/gunDB/rpc/types.ts new file mode 100644 index 00000000..e30bb4b8 --- /dev/null +++ b/services/gunDB/rpc/types.ts @@ -0,0 +1,8 @@ +import {Primitive} from '../contact-api/SimpleGUN' + + +export interface RPCData { + [K: string]: ValidRPCDataValue +} + +export type ValidRPCDataValue = Primitive | null | RPCData | Array \ No newline at end of file