From 19b0dabe11cfbe87934e0f790607e282a09a695b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 13 Sep 2019 15:48:57 +1000 Subject: [PATCH 01/13] Keep a cache of the last 5 fetched messages for public chat so we can use it to detect duplicate messages. --- js/modules/loki_public_chat_api.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 4823b0c1d..30a9758dc 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -213,6 +213,10 @@ class LokiPublicChannelAPI { this.deleteLastId = 1; this.timers = {}; this.running = true; + + // Cache for duplicate checking + this.lastMessagesCache = []; + // end properties log.info(`registered LokiPublicChannel ${channelId}`); @@ -574,6 +578,32 @@ class LokiPublicChannelAPI { return; // Invalid message } + // Duplicate check + const isDuplicate = message => { + const sameUsername = message.username === adnMessage.user.username; + const sameText = message.text === adnMessage.text; + // Don't filter out messages that are too far apart from each other + const timestampsSimilar = + Math.abs(message.timestamp - timestamp) <= 10000; + + return sameUsername && sameText && timestampsSimilar; + }; + + // Filter out any messages that we got previously + if (this.lastMessagesCache.some(isDuplicate)) { + return; // Duplicate message + } + + // Add the message to the lastMessage cache and keep the last 5 recent messages + this.lastMessagesCache = [ + ...this.lastMessagesCache, + { + username: adnMessage.user.username, + text: adnMessage.text, + timestamp, + }, + ].splice(-5); + const messageData = { serverId: adnMessage.id, friendRequest: false, From 001d8822738ae5e3586b8a84dd6e9256baf6f47d Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 16 Sep 2019 10:10:05 +1000 Subject: [PATCH 02/13] Add comment to fix up confusion. --- 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 30a9758dc..5bd563537 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -580,6 +580,7 @@ class LokiPublicChannelAPI { // Duplicate check const isDuplicate = message => { + // The username in this case is the users pubKey const sameUsername = message.username === adnMessage.user.username; const sameText = message.text === adnMessage.text; // Don't filter out messages that are too far apart from each other From 75a527e82801513e6c09f44c61b9c5654801de10 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 16 Sep 2019 10:18:46 +1000 Subject: [PATCH 03/13] Replaced value with a descriptive constant. --- js/modules/loki_public_chat_api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 5bd563537..250baf1a3 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -9,6 +9,7 @@ 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; // 5s const PUBLICCHAT_MOD_POLL_EVERY = 30 * 1000; // 30s +const PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES = 10 * 1000; // 10s // singleton to relay events to libtextsecure/message_receiver class LokiPublicChatAPI extends EventEmitter { @@ -585,7 +586,8 @@ class LokiPublicChannelAPI { const sameText = message.text === adnMessage.text; // Don't filter out messages that are too far apart from each other const timestampsSimilar = - Math.abs(message.timestamp - timestamp) <= 10000; + Math.abs(message.timestamp - timestamp) <= + PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES; return sameUsername && sameText && timestampsSimilar; }; From 88b44390bf471a8aa7dda70f73296cf4a8997bac Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 17 Sep 2019 01:14:29 -0700 Subject: [PATCH 04/13] Desktop Analytics --- js/background.js | 2 ++ js/modules/loki_message_api.js | 6 ++++++ js/modules/loki_mixpanel.js | 17 +++++++++++++++++ js/modules/loki_public_chat_api.js | 7 +++++++ js/modules/loki_snode_api.js | 5 +++++ js/views/inbox_view.js | 9 +++++++++ libtextsecure/account_manager.js | 4 ++++ package.json | 1 + preload.js | 2 ++ ts/components/SearchResults.tsx | 4 +++- yarn.lock | 9 ++++++++- 11 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 js/modules/loki_mixpanel.js diff --git a/js/background.js b/js/background.js index 5e656247c..1c58b43c1 100644 --- a/js/background.js +++ b/js/background.js @@ -230,6 +230,8 @@ window.feeds = []; window.lokiMessageAPI = new window.LokiMessageAPI(ourKey); window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey); + window.mixpanel = new window.LokiMixpanelAPI(); + // window.mixpanel.track("Desktop boot"); window.lokiP2pAPI = new window.LokiP2pAPI(ourKey); window.lokiP2pAPI.on('pingContact', pubKey => { const isPing = true; diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index f9c3610d6..80d403dbf 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -4,6 +4,9 @@ const _ = require('lodash'); const { rpc } = require('./loki_rpc'); +const LokiMixpanelAPI = require('./loki_mixpanel.js'); + +const Mixpanel = new LokiMixpanelAPI(); const DEFAULT_CONNECTIONS = 3; const MAX_ACCEPTABLE_FAILURES = 1; @@ -178,6 +181,7 @@ class LokiMessageAPI { try { // eslint-disable-next-line more/no-then success = await firstTrue(promises); + Mixpanel.track('Sent Message Using Swarm API'); } catch (e) { if (e instanceof textsecure.WrongDifficultyError) { // Force nonce recalculation @@ -191,6 +195,7 @@ class LokiMessageAPI { throw e; } if (!success) { + Mixpanel.track('Failed to Send Message Using Swarm API'); throw new window.textsecure.EmptySwarmError( pubKey, 'Ran out of swarm nodes to query' @@ -255,6 +260,7 @@ class LokiMessageAPI { } catch (e) { log.warn('Loki send message:', e); if (e instanceof textsecure.WrongSwarmError) { + Mixpanel.track('Migrated Snode'); const { newSwarm } = e; await lokiSnodeAPI.updateSwarmNodes(params.pubKey, newSwarm); this.sendingData[params.timestamp].swarm = newSwarm; diff --git a/js/modules/loki_mixpanel.js b/js/modules/loki_mixpanel.js new file mode 100644 index 000000000..681452982 --- /dev/null +++ b/js/modules/loki_mixpanel.js @@ -0,0 +1,17 @@ +/* eslint-disable class-methods-use-this */ + +const EventEmitter = require('events'); +const Mixpanel = require('mixpanel'); +// require('setimmediate'); + +class LokiMixpanelAPI extends EventEmitter { + constructor() { + super(); + this.mixpanel = Mixpanel.init('736cd9a854a157591153efacd1164e9a'); + } + track(label) { + this.mixpanel.track(label); + } +} + +module.exports = LokiMixpanelAPI; diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 4823b0c1d..b6738dfe7 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -3,6 +3,9 @@ clearTimeout, MessageController */ const EventEmitter = require('events'); const nodeFetch = require('node-fetch'); const { URL, URLSearchParams } = require('url'); +const LokiMixpanelAPI = require('./loki_mixpanel.js'); + +const Mixpanel = new LokiMixpanelAPI(); // Can't be less than 1200 if we have unauth'd requests const PUBLICCHAT_MSG_POLL_EVERY = 1.5 * 1000; // 1.5s @@ -663,8 +666,12 @@ class LokiPublicChannelAPI { objBody: payload, }); if (!res.err && res.response) { + Mixpanel.track('Public Message Sent'); return res.response.data.id; } + // there's no retry on desktop + // this is supposed to be after retries + Mixpanel.track('Failed to Send Public Message'); return false; } } diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 6483727d7..c94598316 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -6,6 +6,9 @@ const dns = require('dns'); const process = require('process'); const { rpc } = require('./loki_rpc'); const natUpnp = require('nat-upnp'); +const LokiMixpanelAPI = require('./loki_mixpanel.js'); + +const Mixpanel = new LokiMixpanelAPI(); const resolve4 = url => new Promise((resolve, reject) => { @@ -118,6 +121,7 @@ class LokiSnodeAPI { port: snode.storage_port, })); } catch (e) { + Mixpanel.track('Seed Node Failed'); if (seedNodes.length === 0) { throw new window.textsecure.SeedNodeError( 'Failed to contact seed node' @@ -133,6 +137,7 @@ class LokiSnodeAPI { const filteredNodes = swarmNodes.filter( node => node.address !== nodeUrl && node.ip !== nodeUrl ); + Mixpanel.track('Unreachable Snode'); await conversation.updateSwarmNodes(filteredNodes); } diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index 16c613c8a..04c24c9d1 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -302,6 +302,15 @@ } if (conversation) { + if (conversation.isRss()) { + window.mixpanel.track('RSS Feed Opened'); + } + if (conversation.isPublic()) { + window.mixpanel.track('Loki Public Chat Opened'); + } + if (conversation.isPrivate()) { + window.mixpanel.track('Conversation Opened'); + } conversation.updateProfileName(); } diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 0d64a2a6a..bd7843922 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -27,6 +27,8 @@ function AccountManager(username, password) { // this.server = window.WebAPI.connect({ username, password }); this.pending = Promise.resolve(); + // set up mixpanel + window.mixpanel = new window.LokiMixpanelAPI(); } function getNumber(numberId) { @@ -136,8 +138,10 @@ ).toArrayBuffer(); return libsignal.Curve.async.createKeyPair(privKey); }; + window.mixpanel.track('Seed Restored'); } else { generateKeypair = libsignal.KeyHelper.generateIdentityKeyPair; + window.mixpanel.track('Seed Created'); } return this.queueTask(() => generateKeypair().then(async identityKeyPair => diff --git a/package.json b/package.json index 11fdafdf4..ae2b283c9 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "libsodium-wrappers": "^0.7.4", "linkify-it": "2.0.3", "lodash": "4.17.11", + "mixpanel": "^0.10.2", "mkdirp": "0.5.1", "moment": "2.21.0", "mustache": "2.3.0", diff --git a/preload.js b/preload.js index fc5b12ca0..fca170532 100644 --- a/preload.js +++ b/preload.js @@ -328,6 +328,8 @@ window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api'); window.LokiRssAPI = require('./js/modules/loki_rss_api'); +window.LokiMixpanelAPI = require('./js/modules/loki_mixpanel.js'); + window.LocalLokiServer = require('./libloki/modules/local_loki_server'); window.localServerPort = config.localServerPort; diff --git a/ts/components/SearchResults.tsx b/ts/components/SearchResults.tsx index ae5d4b6b5..8ebb25274 100644 --- a/ts/components/SearchResults.tsx +++ b/ts/components/SearchResults.tsx @@ -11,6 +11,8 @@ import { StartNewConversation } from './StartNewConversation'; import { LocalizerType } from '../types/Util'; +declare var mixpanel: any; + export type PropsData = { contacts: Array; friends: Array; @@ -36,7 +38,7 @@ type Props = PropsData & PropsHousekeeping; export class SearchResults extends React.Component { public handleStartNewConversation = () => { const { regionCode, searchTerm, startNewConversation } = this.props; - + mixpanel.track('New Conversation Started'); startNewConversation(searchTerm, { regionCode }); }; diff --git a/yarn.lock b/yarn.lock index 72d1e6646..f81384a2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4466,7 +4466,7 @@ https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" -https-proxy-agent@^2.2.1: +https-proxy-agent@2.2.1, https-proxy-agent@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== @@ -6022,6 +6022,13 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mixpanel@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.10.2.tgz#10ff6cd76034b262d469094ad3d8c99039345376" + integrity sha512-+zbBQGd/Q5LLRooqJ2iyEDzKz2/ly4TipH5tE9te0BDMJpROxUMGffPulyHbh4FtMcbJuPmIUSIfy//JhhnlnA== + dependencies: + https-proxy-agent "2.2.1" + mkdirp@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" From 1496a368e9e0e75c104d7ff03b952d4a53e7364d Mon Sep 17 00:00:00 2001 From: Maxim Shishmarev Date: Tue, 17 Sep 2019 16:51:54 +1000 Subject: [PATCH 05/13] Add UI elements for searching and selecting members in a group chat --- background.html | 2 + js/modules/signal.js | 2 + js/views/conversation_view.js | 141 +++++++++++++++++++++- js/views/member_list_view.js | 65 ++++++++++ stylesheets/_mentions.scss | 49 ++++++++ stylesheets/_variables.scss | 1 + stylesheets/manifest.scss | 1 + ts/components/conversation/MemberList.tsx | 93 ++++++++++++++ 8 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 js/views/member_list_view.js create mode 100644 stylesheets/_mentions.scss create mode 100644 ts/components/conversation/MemberList.tsx diff --git a/background.html b/background.html index 28aaadb9f..e3bbc00ba 100644 --- a/background.html +++ b/background.html @@ -126,6 +126,7 @@