diff --git a/.eslintrc.json b/.eslintrc.json index 29ec4835..c1d88b36 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -79,7 +79,9 @@ // if someone does this it's probably intentional "no-useless-concat": "off", - "no-plusplus": "off" + "no-plusplus": "off", + + "no-undefined": "off" }, "parser": "babel-eslint", "env": { diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index abce3c6f..e233199a 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -441,33 +441,11 @@ class Mediator { this.socket.on(IS_GUN_AUTH, this.isGunAuth) - this.socket.on('SET_LAST_SEEN_APP', async body => { - try { - await throwOnInvalidToken(body.token) - await new Promise((res, rej) => { - getUser() - .get('lastSeenApp') - .put(Date.now(), ack => { - if (ack.err) { - rej(new Error(ack.err)) - } else { - res() - } - }) - }) - this.socket.emit('SET_LAST_SEEN_APP', { - ok: true, - msg: null, - origBody: body - }) - } catch (e) { - this.socket.emit('SET_LAST_SEEN_APP', { - ok: false, - msg: e.message, - origBody: body - }) - } - }) + this.socket.on(Action.SET_LAST_SEEN_APP, this.setLastSeenApp) + + Object.values(Action).forEach(actionConstant => + this.socket.on(actionConstant, this.setLastSeenApp) + ) } /** @param {SimpleSocket} socket */ @@ -553,6 +531,26 @@ class Mediator { } } + /** @param {{ token: string }} body */ + setLastSeenApp = async body => { + logger.info('setLastSeen Called') + try { + await throwOnInvalidToken(body.token) + await API.Actions.setLastSeenApp() + this.socket.emit(Action.SET_LAST_SEEN_APP, { + ok: true, + msg: null, + origBody: body + }) + } catch (e) { + this.socket.emit(Action.SET_LAST_SEEN_APP, { + ok: false, + msg: e.message, + origBody: body + }) + } + } + isGunAuth = () => { try { const isGunAuth = isAuthenticated() diff --git a/services/gunDB/action-constants.js b/services/gunDB/action-constants.js index bcb52aed..4056ffec 100644 --- a/services/gunDB/action-constants.js +++ b/services/gunDB/action-constants.js @@ -9,7 +9,8 @@ const Actions = { SET_AVATAR: "SET_AVATAR", SET_DISPLAY_NAME: "SET_DISPLAY_NAME", SET_BIO: "SET_BIO", - DISCONNECT: "DISCONNECT" + DISCONNECT: "DISCONNECT", + SET_LAST_SEEN_APP: "SET_LAST_SEEN_APP" }; module.exports = Actions; diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 25155229..183781c1 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -1186,6 +1186,23 @@ const disconnect = async pub => { await generateHandshakeAddress() } +/** + * @returns {Promise} + */ +const setLastSeenApp = () => + new Promise((res, rej) => { + require('../Mediator') + .getUser() + .get(Key.LAST_SEEN_APP) + .put(Date.now(), ack => { + if (ack.err) { + rej(new Error(ack.err)) + } else { + res() + } + }) + }) + module.exports = { __createOutgoingFeed, acceptRequest, @@ -1202,5 +1219,6 @@ module.exports = { generateOrderAddress, setBio, saveSeedBackup, - disconnect + disconnect, + setLastSeenApp } diff --git a/services/gunDB/contact-api/events/index.js b/services/gunDB/contact-api/events/index.js index 0f672754..ef50dfb9 100644 --- a/services/gunDB/contact-api/events/index.js +++ b/services/gunDB/contact-api/events/index.js @@ -457,6 +457,7 @@ const processChats = debounce(() => { const Streams = require('../streams') const pubToAvatar = Streams.getPubToAvatar() const pubToDn = Streams.getPubToDn() + const pubToLastSeenApp = Streams.getPubToLastSeenApp() const existingOutgoings = /** @type {[string, Outgoing][]} */ (Object.entries( getCurrentOutgoings() ).filter(([_, o]) => o !== null)) @@ -468,11 +469,15 @@ const processChats = debounce(() => { for (const [outID, out] of existingOutgoings) { if (typeof pubToAvatar[out.with] === 'undefined') { // eslint-disable-next-line no-empty-function - Streams.onAvatar(() => {}, out.with) + Streams.onAvatar(() => {}, out.with)() } if (typeof pubToDn[out.with] === 'undefined') { // eslint-disable-next-line no-empty-function - Streams.onDisplayName(() => {}, out.with) + Streams.onDisplayName(() => {}, out.with)() + } + if (typeof pubToLastSeenApp[out.with] === 'undefined') { + // eslint-disable-next-line no-empty-function + Streams.onPubToLastSeenApp(() => {}, out.with)() } /** @type {ChatMessage[]} */ @@ -496,7 +501,8 @@ const processChats = debounce(() => { id: out.with + outID, messages: msgs, recipientAvatar: pubToAvatar[out.with] || null, - recipientDisplayName: pubToDn[out.with] || null + recipientDisplayName: pubToDn[out.with] || null, + lastSeenApp: pubToLastSeenApp[out.with] || null } newChats.push(chat) @@ -531,6 +537,7 @@ const onChats = cb => { Streams.onAvatar(processChats) Streams.onDisplayName(processChats) Streams.onPubToFeed(processChats) + Streams.onPubToLastSeenApp(processChats) onChatsSubbed = true } diff --git a/services/gunDB/contact-api/key.js b/services/gunDB/contact-api/key.js index 169b89c5..6f380432 100644 --- a/services/gunDB/contact-api/key.js +++ b/services/gunDB/contact-api/key.js @@ -36,3 +36,5 @@ exports.ORDER_TO_RESPONSE = 'orderToResponse' exports.BIO = 'bio' exports.SEED_BACKUP = 'seedBackup' + +exports.LAST_SEEN_APP = 'lastSeenApp' diff --git a/services/gunDB/contact-api/schema.js b/services/gunDB/contact-api/schema.js index ca614dd1..d4f61f57 100644 --- a/services/gunDB/contact-api/schema.js +++ b/services/gunDB/contact-api/schema.js @@ -77,6 +77,7 @@ exports.isChatMessage = item => { * @prop {ChatMessage[]} messages Sorted from most recent to least recent. * @prop {string|null} recipientDisplayName * @prop {boolean} didDisconnect True if the recipient performed a disconnect. + * @prop {number|undefined|null} lastSeenApp */ /** diff --git a/services/gunDB/contact-api/streams/index.js b/services/gunDB/contact-api/streams/index.js index b1eb872f..7d296090 100644 --- a/services/gunDB/contact-api/streams/index.js +++ b/services/gunDB/contact-api/streams/index.js @@ -187,5 +187,8 @@ module.exports = { getAddresses: require('./addresses').getAddresses, onLastSentReqIDs: require('./lastSentReqID').onLastSentReqIDs, getSentReqIDs: require('./lastSentReqID').getSentReqIDs, - PubToIncoming: require('./pubToIncoming') + PubToIncoming: require('./pubToIncoming'), + + getPubToLastSeenApp: require('./pubToLastSeenApp').getPubToLastSeenApp, + onPubToLastSeenApp: require('./pubToLastSeenApp').on } diff --git a/services/gunDB/contact-api/streams/pubToLastSeenApp.js b/services/gunDB/contact-api/streams/pubToLastSeenApp.js new file mode 100644 index 00000000..729f5e59 --- /dev/null +++ b/services/gunDB/contact-api/streams/pubToLastSeenApp.js @@ -0,0 +1,49 @@ +const Key = require('../key') +/** + * @typedef {Record} Timestamps + * @typedef {(timestamps: Timestamps) => void} Listener + */ + +/** @type {Timestamps} */ +const pubToLastSeenApp = {} + +const getPubToLastSeenApp = () => pubToLastSeenApp + +/** @type {Set} */ +const listeners = new Set() + +const notifyListeners = () => { + listeners.forEach(l => l(pubToLastSeenApp)) +} + +/** @type {Set} */ +const pubsWithListeners = new Set() + +/** + * @param {Listener} cb + * @param {string=} pub + */ +const on = (cb, pub) => { + listeners.add(cb) + cb(pubToLastSeenApp) + if (pub && pubsWithListeners.add(pub)) { + pubToLastSeenApp[pub] = null; + notifyListeners() + require('../../Mediator') + .getGun() + .user(pub) + .get(Key.LAST_SEEN_APP) + .on(timestamp => { + pubToLastSeenApp[pub] = typeof timestamp === 'number' ? timestamp : undefined + notifyListeners() + }) + } + return () => { + listeners.delete(cb) + } +} + +module.exports = { + getPubToLastSeenApp, + on, +} \ No newline at end of file