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(); }); }; diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index d5b3269ce..deafffe87 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -1,11 +1,13 @@ -/* 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'); // 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 +205,33 @@ 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(); + } + + 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 @@ -290,18 +307,51 @@ 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 + + await this.conversation.setModStatus(this.modStatus); - const conversation = ConversationController.get(this.conversationId); - await 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(); + + // update profile name as needed + 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 + } + // update our avatar if needed + } + } } // 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}` @@ -312,7 +362,13 @@ class LokiPublicChannelAPI { log.info(`deleted ${serverId} on ${this.baseChannelUrl}`); return true; } + // 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; } @@ -324,43 +380,46 @@ class LokiPublicChannelAPI { return endpoint; } - // update room details + // get moderation actions 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); + await this.pollForChannelOnce(); } catch (e) { - log.info(`e ${e}`); - return { - err: e, - }; + log.warn(`Error while polling for public chat deletions: ${e}`); } - // eslint-disable-next-line no-await-in-loop - const response = await res.json(); - if (response.meta.code !== 200) { - return { - err: 'statusCode', - response, - }; + 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, + }, + }); + 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... + } + }); } - return { - response, - }; } // get moderation actions @@ -370,9 +429,9 @@ class LokiPublicChannelAPI { } catch (e) { log.warn(`Error while polling for public chat deletions: ${e}`); } - setTimeout(() => { + this.timers.delete = setTimeout(() => { this.pollForDeletions(); - }, DELETION_POLL_EVERY); + }, PUBLICCHAT_DELETION_POLL_EVERY); } async pollOnceForDeletions() { @@ -423,8 +482,8 @@ class LokiPublicChannelAPI { log.warn(`Error while polling for public chat messages: ${e}`); } setTimeout(() => { - this.pollForMessages(); - }, GROUPCHAT_POLL_EVERY); + this.timers.message = this.pollForMessages(); + }, PUBLICCHAT_MSG_POLL_EVERY); } async pollOnceForMessages() { @@ -433,12 +492,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`, { @@ -449,8 +507,8 @@ 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; } @@ -459,14 +517,21 @@ class LokiPublicChannelAPI { adnMessage.annotations.length !== 0 ) { 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 @@ -475,7 +540,7 @@ class LokiPublicChannelAPI { const messageData = { serverId: adnMessage.id, friendRequest: false, - source, + source: adnMessage.user.username, sourceDevice: 1, timestamp, serverTimestamp: timestamp, @@ -507,11 +572,15 @@ 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); }); - conversation.setLastRetrievedMessage(this.lastGot); + this.conversation.setLastRetrievedMessage(this.lastGot); } } 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); }