new onoutgoing

This commit is contained in:
Daniel Lugo 2020-01-29 11:34:14 -04:00
parent ba99c7e400
commit 527072f538

View file

@ -27,105 +27,6 @@ const Config = require('../config')
const DEBOUNCE_WAIT_TIME = 500 const DEBOUNCE_WAIT_TIME = 500
/**
* @param {string} outgoingKey
* @param {(message: Message, key: string) => void} cb
* @param {UserGUNNode} user
* @param {ISEA} SEA
* @returns {Promise<void>}
*/
const __onOutgoingMessage = async (outgoingKey, cb, user, SEA) => {
if (!user.is) {
throw new Error(ErrorCode.NOT_AUTH)
}
const mySecret = await SEA.secret(user._.sea.epub, user._.sea)
if (typeof mySecret !== 'string') {
throw new TypeError("typeof mySecret !== 'string'")
}
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
/** @type {string} */
const encryptedForMeRecipientPublicKey = await Utils.tryAndWait(
(_, user) =>
new Promise((res, rej) => {
user
.get(Key.OUTGOINGS)
.get(outgoingKey)
.get('with')
.once(erpk => {
if (typeof erpk !== 'string') {
rej(
new TypeError("Expected outgoing.get('with') to be an string.")
)
} else if (erpk.length === 0) {
rej(
new TypeError(
"Expected outgoing.get('with') to be a populated."
)
)
} else {
res(erpk)
}
})
})
)
const recipientPublicKey = await SEA.decrypt(
encryptedForMeRecipientPublicKey,
mySecret
)
if (typeof recipientPublicKey !== 'string') {
throw new TypeError(
"__onOutgoingMessage() -> typeof recipientPublicKey !== 'string'"
)
}
/** @type {string} */
const recipientEpub = await Utils.pubToEpub(recipientPublicKey)
const ourSecret = await SEA.secret(recipientEpub, user._.sea)
if (typeof ourSecret !== 'string') {
throw new TypeError(
"__onOutgoingMessage() -> typeof ourSecret !== 'string'"
)
}
user
.get(Key.OUTGOINGS)
.get(outgoingKey)
.get(Key.MESSAGES)
.map()
.on(async (msg, key) => {
if (!Schema.isMessage(msg)) {
console.warn('non message received: ' + JSON.stringify(msg))
return
}
let { body } = msg
if (body !== Actions.INITIAL_MSG) {
const decrypted = await SEA.decrypt(body, ourSecret)
if (typeof decrypted !== 'string') {
console.log("__onOutgoingMessage() -> typeof decrypted !== 'string'")
} else {
body = decrypted
}
}
callb(
{
body,
timestamp: msg.timestamp
},
key
)
})
}
/** /**
* Maps a sent request ID to the public key of the user it was sent to. * Maps a sent request ID to the public key of the user it was sent to.
* @param {(requestToUser: Record<string, string>) => void} cb * @param {(requestToUser: Record<string, string>) => void} cb
@ -407,101 +308,105 @@ const onIncomingMessages = (cb, userPK, incomingFeedID, gun, user, SEA) => {
} }
/** /**
* * @typedef {Record<string, Outgoing|null>} Outgoings
* @param {(outgoings: Record<string, Outgoing>) => void} cb * @typedef {(outgoings: Outgoings) => void} OutgoingsListener
* @param {UserGUNNode} user
* @param {ISEA} SEA
* @param {typeof __onOutgoingMessage} onOutgoingMessage
*/ */
const onOutgoing = async (
cb,
user,
SEA,
onOutgoingMessage = __onOutgoingMessage
) => {
if (!user.is) {
throw new Error(ErrorCode.NOT_AUTH)
}
const callb = debounce(cb, DEBOUNCE_WAIT_TIME) /**
* @type {Outgoings}
*/
export let currentOutgoings = {}
const mySecret = await SEA.secret(user._.sea.epub, user._.sea) /**
if (typeof mySecret !== 'string') { * @type {Outgoings}
throw new TypeError("onOutgoing() -> typeof mySecret !== 'string'") */
} export let encryptedOutgoings = {}
/** /** @type {Set<OutgoingsListener>} */
* @type {Record<string, Outgoing>} const outgoingsListeners = new Set()
*/
const outgoings = {}
callb(outgoings) export const notifyOutgoingsListeners = () => {
outgoingsListeners.forEach(l => l(currentOutgoings))
}
/** /** @type {UserGUNNode|null} */
* @type {string[]} let lastUserWithListener = null
*/
const outgoingsWithMessageListeners = []
/** @type {Set<string>} */ const processOutgoings = async () => {
const outgoingsDisconnected = new Set() const outs = encryptedOutgoings
encryptedOutgoings = {}
const mySecret = await Utils.mySecret()
const SEA = require('../Mediator').mySEA
await Utils.asyncForEach(Object.entries(outs), async ([id, out]) => {
if (out === null) {
currentOutgoings[id] = null
return
}
user if (typeof currentOutgoings[id] === 'undefined') {
.get(Key.OUTGOINGS) // We disable this rule because we are awaiting the result of the whole
.map() // for each AND each callback looks only at one single ID
.on(async (data, key) => { // eslint-disable-next-line require-atomic-updates
if (!Schema.isPartialOutgoing(data)) { currentOutgoings[id] = {
// if user disconnected messages: {},
if (data === null) { with: await SEA.decrypt(out.with, mySecret)
delete outgoings[key] }
outgoingsDisconnected.add(key) }
} else {
console.warn('not partial outgoing') const currentOut = currentOutgoings[id]
console.warn(JSON.stringify(data)) if (currentOut === null) {
return
}
// on each open() only "messages" should change, not "with"
// also messages are non-nullable and non-editable
await Utils.asyncForEach(
Object.entries(out.messages),
async ([msgID, msg]) => {
if (!currentOut.messages[msgID]) {
// each callback only looks at one particular msgID
// eslint-disable-next-line require-atomic-updates
currentOut.messages[msgID] = {
body: await SEA.decrypt(msg.body, mySecret),
timestamp: msg.timestamp
}
} }
}
)
})
notifyOutgoingsListeners()
}
callb(outgoings) /**
* @param {OutgoingsListener} cb
* @returns {() => void}
*/
const onOutgoing = cb => {
cb(currentOutgoings)
const currentUser = require('../Mediator').getUser()
if (lastUserWithListener !== currentUser) {
// in case user changed gun alias
currentOutgoings = {}
encryptedOutgoings = {}
lastUserWithListener = currentUser
currentUser.get(Key.OUTGOINGS).open(data => {
// deactivate this listener when user changes
if (lastUserWithListener !== require('../Mediator').getUser()) {
return return
} }
// @ts-ignore Let's skip schema checks for perf reasons
const decryptedRecipientPublicKey = await SEA.decrypt(data.with, mySecret) encryptedOutgoings = data
processOutgoings()
if (typeof decryptedRecipientPublicKey !== 'string') {
console.log(
"onOutgoing() -> typeof decryptedRecipientPublicKey !== 'string'"
)
return
}
outgoings[key] = {
messages: outgoings[key] ? outgoings[key].messages : {},
with: decryptedRecipientPublicKey
}
if (!outgoingsWithMessageListeners.includes(key)) {
outgoingsWithMessageListeners.push(key)
onOutgoingMessage(
key,
(msg, msgKey) => {
if (outgoingsDisconnected.has(key)) {
return
}
outgoings[key].messages = {
...outgoings[key].messages,
[msgKey]: msg
}
callb(outgoings)
},
user,
SEA
)
}
callb(outgoings)
}) })
}
return () => {
outgoingsListeners.delete(cb)
}
} }
/** /**
@ -570,44 +475,43 @@ const onChats = (cb, gun, user, SEA) => {
callCB() callCB()
onOutgoing( onOutgoing(async outgoings => {
async outgoings => { await Utils.asyncForEach(Object.values(outgoings), async outgoing => {
await Utils.asyncForEach(Object.values(outgoings), async outgoing => { if (outgoing === null) {
const recipientPK = outgoing.with return
const incomingID = await Getters.userToIncomingID(recipientPK) }
const didDisconnect = const recipientPK = outgoing.with
!!incomingID && (await Utils.didDisconnect(recipientPK, incomingID)) const incomingID = await Getters.userToIncomingID(recipientPK)
const didDisconnect =
!!incomingID && (await Utils.didDisconnect(recipientPK, incomingID))
if (!recipientPKToChat[recipientPK]) { if (!recipientPKToChat[recipientPK]) {
recipientPKToChat[recipientPK] = { recipientPKToChat[recipientPK] = {
messages: [], messages: [],
recipientAvatar: null, recipientAvatar: null,
recipientDisplayName: Utils.defaultName(recipientPK), recipientDisplayName: Utils.defaultName(recipientPK),
recipientPublicKey: recipientPK, recipientPublicKey: recipientPK,
didDisconnect, didDisconnect,
id: recipientPK + incomingID id: recipientPK + incomingID
}
} }
}
const { messages } = recipientPKToChat[recipientPK] const { messages } = recipientPKToChat[recipientPK]
for (const [msgK, msg] of Object.entries(outgoing.messages)) { for (const [msgK, msg] of Object.entries(outgoing.messages)) {
if (!messages.find(_msg => _msg.id === msgK)) { if (!messages.find(_msg => _msg.id === msgK)) {
messages.push({ messages.push({
body: msg.body, body: msg.body,
id: msgK, id: msgK,
outgoing: true, outgoing: true,
timestamp: msg.timestamp timestamp: msg.timestamp
}) })
}
} }
}) }
})
callCB() callCB()
}, })
user,
SEA
)
__onUserToIncoming( __onUserToIncoming(
async uti => { async uti => {