From b3e2a5aba8b2af039f227d216d922dd3c891f5e0 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 2 Sep 2019 20:35:02 -0700 Subject: [PATCH 1/7] move refreshModStatus into constructor of channelAPI, just instantiate channelAPI now --- js/background.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/js/background.js b/js/background.js index aee0cbbd3..88ce7275b 100644 --- a/js/background.js +++ b/js/background.js @@ -219,13 +219,8 @@ } ); publicConversations.forEach(conversation => { - const settings = conversation.getPublicSource(); - const channel = window.lokiPublicChatAPI.findOrCreateChannel( - settings.server, - settings.channelId, - conversation.id - ); - channel.refreshModStatus(); + // weird but create the object and does everything we need + conversation.getPublicSendData(); }); }; From e25ed0aba3027bd2df6ec79fde696b144cc204ca Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 2 Sep 2019 20:38:47 -0700 Subject: [PATCH 2/7] move pubkey/name out of annotation into user object, read channel meta data from server, throw alert if delete fails, constant name clean up, store timers, Calls refreshModStatus/pollForChannel in cstr --- js/modules/loki_public_chat_api.js | 148 ++++++++++++++++++----------- 1 file changed, 93 insertions(+), 55 deletions(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index c33552523..ef99b77d4 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -4,8 +4,9 @@ const nodeFetch = require('node-fetch'); const { URL, URLSearchParams } = require('url'); // Can't be less than 1200 if we have unauth'd requests -const GROUPCHAT_POLL_EVERY = 1500; // 1.5s -const DELETION_POLL_EVERY = 5000; // 1 second +const PUBLICCHAT_MSG_POLL_EVERY = 1.5 * 1000; // 1.5s +const PUBLICCHAT_CHAN_POLL_EVERY = 20 * 1000; // 20s +const PUBLICCHAT_DELETION_POLL_EVERY = 5 * 1000; // 1 second // singleton to relay events to libtextsecure/message_receiver class LokiPublicChatAPI extends EventEmitter { @@ -203,18 +204,20 @@ class LokiPublicChannelAPI { this.serverAPI = serverAPI; this.channelId = channelId; this.baseChannelUrl = `channels/${this.channelId}`; - this.groupName = 'unknown'; - this.conversationId = conversationId; + this.conversation = ConversationController.get(conversationId); this.lastGot = null; this.stopPolling = false; this.modStatus = false; this.deleteLastId = 1; + this.timers = {}; // end properties log.info(`registered LokiPublicChannel ${channelId}`); // start polling this.pollForMessages(); this.pollForDeletions(); + this.pollForChannel(); + this.refreshModStatus(); } // make a request to the server @@ -290,14 +293,45 @@ class LokiPublicChannelAPI { // get moderator status async refreshModStatus() { + // get moderator status const res = await this.serverRequest('loki/v1/user_info'); // if no problems and we have data if (!res.err && res.response && res.response.data) { this.modStatus = res.response.data.moderator_status; } + // if problems, we won't drop moderator status - const conversation = ConversationController.get(this.conversationId); - await conversation.setModStatus(this.modStatus); + await this.conversation.setModStatus(this.modStatus); + + // get token info + const tokenRes = await this.serverRequest('token'); + // if no problems and we have data + if ( + !tokenRes.err && + tokenRes.response && + tokenRes.response.data && + tokenRes.response.data.user + ) { + // get our profile name and write it to the network + const ourNumber = textsecure.storage.user.getNumber(); + const profileConvo = ConversationController.get(ourNumber); + const profileName = profileConvo.getProfileName(); + + if (tokenRes.response.data.user.name !== profileName) { + if (profileName) { + await this.serverRequest('users/me', { + method: 'PATCH', + objBody: { + name: profileName, + }, + }); + // no big deal if it fails... + // } else { + // should we update the local from the server? + // guessing no because there will be multiple servers + } + } + } } // delete a message on the server @@ -312,8 +346,11 @@ class LokiPublicChannelAPI { log.info(`deleted ${serverId} on ${this.baseChannelUrl}`); return true; } + // fire an alert log.warn(`failed to delete ${serverId} on ${this.baseChannelUrl}`); - return false; + throw new textsecure.PublicChatError( + 'Failed to delete public chat message' + ); } // used for sending messages @@ -326,41 +363,36 @@ class LokiPublicChannelAPI { // update room details async pollForChannel() { - // groupName will be loaded from server - const url = new URL(this.baseChannelUrl); - let res; - const token = await this.serverAPI.getOrRefreshServerToken(); - if (!token) { - log.error('NO TOKEN'); - return { - err: 'noToken', - }; - } - try { - // eslint-disable-next-line no-await-in-loop - const options = { - headers: new Headers({ - Authorization: `Bearer ${token}`, - }), - }; - res = await nodeFetch(url, options || undefined); - } catch (e) { - log.info(`e ${e}`); - return { - err: e, - }; - } - // eslint-disable-next-line no-await-in-loop - const response = await res.json(); - if (response.meta.code !== 200) { - return { - err: 'statusCode', - response, - }; + const res = await this.serverRequest(`${this.baseChannelUrl}`, { + params: { + include_annotations: 1, + }, + }); + if (!res.err && res.response) { + if ( + res.response.data.annotations && + res.response.data.annotations.length + ) { + res.response.data.annotations.forEach(note => { + if (note.type === 'net.patter-app.settings') { + // note.value.description only needed for directory + // this.conversation.setGroupNameAndAvatar(note.value.name, + // note.value.avatar); + if (note.value && note.value.name) { + this.conversation.setProfileName(note.value.name); + } + if (note.value && note.value.avatar) { + this.conversation.setProfileAvatar(note.value.avatar); + } + // else could set a default in case of server problems... + } + }); + } } - return { - response, - }; + // set up next poll + this.timers.channel = setTimeout(() => { + this.pollForChannel(); + }, PUBLICCHAT_CHAN_POLL_EVERY); } // get moderation actions @@ -404,9 +436,9 @@ class LokiPublicChannelAPI { } // set up next poll - setTimeout(() => { + this.timers.delete = setTimeout(() => { this.pollForDeletions(); - }, DELETION_POLL_EVERY); + }, PUBLICCHAT_DELETION_POLL_EVERY); } // get channel messages @@ -416,12 +448,11 @@ class LokiPublicChannelAPI { count: -20, include_deleted: false, }; - const conversation = ConversationController.get(this.conversationId); - if (!conversation) { + if (!this.conversation) { log.warn('Trying to poll for non-existing public conversation'); this.lastGot = 0; } else if (!this.lastGot) { - this.lastGot = conversation.getLastRetrievedMessage(); + this.lastGot = this.conversation.getLastRetrievedMessage(); } params.since_id = this.lastGot; const res = await this.serverRequest(`${this.baseChannelUrl}/messages`, { @@ -432,21 +463,28 @@ class LokiPublicChannelAPI { let receivedAt = new Date().getTime(); res.response.data.reverse().forEach(adnMessage => { let timestamp = new Date(adnMessage.created_at).getTime(); - let from = adnMessage.user.username; - let source; + // pubKey lives in the username field + let from = adnMessage.user.name; if (adnMessage.is_deleted) { return; } - if (adnMessage.annotations !== []) { + if (adnMessage.annotations && adnMessage.annotations.length) { const noteValue = adnMessage.annotations[0].value; - ({ from, timestamp, source } = noteValue); + ({ timestamp } = noteValue); + + // if user doesn't have a name set, fallback to annotation + // pubkeys are already there in v1 (first release) + if (!from) { + ({ from } = noteValue); + } } if ( !from || !timestamp || - !source || !adnMessage.id || + !adnMessage.user || + !adnMessage.user.username || !adnMessage.text ) { return; // Invalid message @@ -455,7 +493,7 @@ class LokiPublicChannelAPI { const messageData = { serverId: adnMessage.id, friendRequest: false, - source, + source: adnMessage.user.username, sourceDevice: 1, timestamp, serverTimestamp: timestamp, @@ -491,12 +529,12 @@ class LokiPublicChannelAPI { ? adnMessage.id : Math.max(this.lastGot, adnMessage.id); }); - conversation.setLastRetrievedMessage(this.lastGot); + this.conversation.setLastRetrievedMessage(this.lastGot); } - setTimeout(() => { + this.timers.message = setTimeout(() => { this.pollForMessages(); - }, GROUPCHAT_POLL_EVERY); + }, PUBLICCHAT_MSG_POLL_EVERY); } // create a message in the channel From 428a3b70771e0e96e0ffe11109a97a4b7528cad3 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Sep 2019 18:17:35 -0700 Subject: [PATCH 3/7] add back conversationId --- js/modules/loki_public_chat_api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index ef99b77d4..984868865 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -204,6 +204,7 @@ class LokiPublicChannelAPI { this.serverAPI = serverAPI; this.channelId = channelId; this.baseChannelUrl = `channels/${this.channelId}`; + this.conversationId = conversationId; this.conversation = ConversationController.get(conversationId); this.lastGot = null; this.stopPolling = false; From 35d059eae5fb8e4976f4263b350f840f6a0aee53 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Sep 2019 21:55:13 -0700 Subject: [PATCH 4/7] was getting some weird errors, going to try this --- js/modules/loki_rss_api.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/modules/loki_rss_api.js b/js/modules/loki_rss_api.js index ee5486d53..b220b1bd2 100644 --- a/js/modules/loki_rss_api.js +++ b/js/modules/loki_rss_api.js @@ -127,8 +127,9 @@ class LokiRssAPI extends EventEmitter { message: messageData, }); }); + const ref = this; function callTimer() { - this.getFeed(); + ref.getFeed(); } this.feedTimer = setTimeout(callTimer, RSS_POLL_EVERY); } From 5b193b9e4758ed12ee031dd5a3a160dafe508a5c Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Sep 2019 21:55:43 -0700 Subject: [PATCH 5/7] remove throw for now, squash ifs, some future avatar notes --- js/modules/loki_public_chat_api.js | 41 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 984868865..7894b6c8d 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -318,6 +318,7 @@ class LokiPublicChannelAPI { const profileConvo = ConversationController.get(ourNumber); const profileName = profileConvo.getProfileName(); + // update profile name as needed if (tokenRes.response.data.user.name !== profileName) { if (profileName) { await this.serverRequest('users/me', { @@ -331,6 +332,7 @@ class LokiPublicChannelAPI { // should we update the local from the server? // guessing no because there will be multiple servers } + // update our avatar if needed } } } @@ -349,9 +351,7 @@ class LokiPublicChannelAPI { } // fire an alert log.warn(`failed to delete ${serverId} on ${this.baseChannelUrl}`); - throw new textsecure.PublicChatError( - 'Failed to delete public chat message' - ); + return false; } // used for sending messages @@ -369,26 +369,23 @@ class LokiPublicChannelAPI { include_annotations: 1, }, }); - if (!res.err && res.response) { - if ( - res.response.data.annotations && + if (!res.err && res.response && res.response.data.annotations && res.response.data.annotations.length ) { - res.response.data.annotations.forEach(note => { - if (note.type === 'net.patter-app.settings') { - // note.value.description only needed for directory - // this.conversation.setGroupNameAndAvatar(note.value.name, - // note.value.avatar); - if (note.value && note.value.name) { - this.conversation.setProfileName(note.value.name); - } - if (note.value && note.value.avatar) { - this.conversation.setProfileAvatar(note.value.avatar); - } - // else could set a default in case of server problems... + res.response.data.annotations.forEach(note => { + if (note.type === 'net.patter-app.settings') { + // note.value.description only needed for directory + // this.conversation.setGroupNameAndAvatar(note.value.name, + // note.value.avatar); + if (note.value && note.value.name) { + this.conversation.setProfileName(note.value.name); } - }); - } + if (note.value && note.value.avatar) { + this.conversation.setProfileAvatar(note.value.avatar); + } + // else could set a default in case of server problems... + } + }); } // set up next poll this.timers.channel = setTimeout(() => { @@ -526,6 +523,10 @@ class LokiPublicChannelAPI { this.serverAPI.chatAPI.emit('publicMessage', { message: messageData, }); + + // now process any user meta data updates + // - update their conversation with a potentially new avatar + this.lastGot = !this.lastGot ? adnMessage.id : Math.max(this.lastGot, adnMessage.id); From f093490becb66b40af15c1c1c2869e1ff337ffdc Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Sep 2019 22:01:20 -0700 Subject: [PATCH 6/7] lint plus constant fix --- js/modules/loki_public_chat_api.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 804ea74b4..753f5cf6c 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -369,9 +369,12 @@ class LokiPublicChannelAPI { include_annotations: 1, }, }); - if (!res.err && res.response && res.response.data.annotations && - res.response.data.annotations.length - ) { + if ( + !res.err && + res.response && + res.response.data.annotations && + res.response.data.annotations.length + ) { res.response.data.annotations.forEach(note => { if (note.type === 'net.patter-app.settings') { // note.value.description only needed for directory @@ -402,7 +405,7 @@ class LokiPublicChannelAPI { } setTimeout(() => { this.pollForDeletions(); - }, DELETION_POLL_EVERY); + }, PUBLICCHAT_DELETION_POLL_EVERY); } async pollOnceForDeletions() { @@ -454,7 +457,7 @@ class LokiPublicChannelAPI { } setTimeout(() => { this.pollForMessages(); - }, GROUPCHAT_POLL_EVERY); + }, PUBLICCHAT_MSG_POLL_EVERY); } async pollOnceForMessages() { From e382afdf91f84d994a3bfb164a22272c1e2bf94e Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Sep 2019 22:36:15 -0700 Subject: [PATCH 7/7] make timers stoppable, delete throw parameter, pollForChannel() pattern update --- js/modules/loki_public_chat_api.js | 44 ++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 753f5cf6c..deafffe87 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -1,4 +1,5 @@ -/* global log, textsecure, libloki, Signal, Whisper, Headers, ConversationController */ +/* global log, textsecure, libloki, Signal, Whisper, Headers, ConversationController, +clearTimeout */ const EventEmitter = require('events'); const nodeFetch = require('node-fetch'); const { URL, URLSearchParams } = require('url'); @@ -221,6 +222,18 @@ class LokiPublicChannelAPI { this.refreshModStatus(); } + stop() { + if (this.timers.channel) { + clearTimeout(this.timers.channel); + } + if (this.timers.delete) { + clearTimeout(this.timers.delete); + } + if (this.timers.message) { + clearTimeout(this.timers.message); + } + } + // make a request to the server async serverRequest(endpoint, options = {}) { const { params = {}, method, objBody, forceFreshToken = false } = options; @@ -338,7 +351,7 @@ class LokiPublicChannelAPI { } // delete a message on the server - async deleteMessage(serverId) { + async deleteMessage(serverId, canThrow = false) { const res = await this.serverRequest( this.modStatus ? `loki/v1/moderation/message/${serverId}` @@ -351,6 +364,11 @@ class LokiPublicChannelAPI { } // fire an alert log.warn(`failed to delete ${serverId} on ${this.baseChannelUrl}`); + if (canThrow) { + throw new textsecure.PublicChatError( + 'Failed to delete public chat message' + ); + } return false; } @@ -362,8 +380,20 @@ class LokiPublicChannelAPI { return endpoint; } - // update room details + // get moderation actions async pollForChannel() { + try { + await this.pollForChannelOnce(); + } catch (e) { + log.warn(`Error while polling for public chat deletions: ${e}`); + } + this.timers.channel = setTimeout(() => { + this.pollForChannelOnce(); + }, PUBLICCHAT_CHAN_POLL_EVERY); + } + + // update room details + async pollForChannelOnce() { const res = await this.serverRequest(`${this.baseChannelUrl}`, { params: { include_annotations: 1, @@ -390,10 +420,6 @@ class LokiPublicChannelAPI { } }); } - // set up next poll - this.timers.channel = setTimeout(() => { - this.pollForChannel(); - }, PUBLICCHAT_CHAN_POLL_EVERY); } // get moderation actions @@ -403,7 +429,7 @@ class LokiPublicChannelAPI { } catch (e) { log.warn(`Error while polling for public chat deletions: ${e}`); } - setTimeout(() => { + this.timers.delete = setTimeout(() => { this.pollForDeletions(); }, PUBLICCHAT_DELETION_POLL_EVERY); } @@ -456,7 +482,7 @@ class LokiPublicChannelAPI { log.warn(`Error while polling for public chat messages: ${e}`); } setTimeout(() => { - this.pollForMessages(); + this.timers.message = this.pollForMessages(); }, PUBLICCHAT_MSG_POLL_EVERY); }