From 56a4a31ca2a80f9600f52ee8e82cfeb6df72fd2d Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Fri, 30 Aug 2019 01:48:01 -0700 Subject: [PATCH] sendMessage(), getOrRefreshServerToken supports forceRefresh, make modStatus a property, additional comments --- js/modules/loki_public_chat_api.js | 139 ++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 31 deletions(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 6bb819430..eb4fe6c7f 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -14,6 +14,8 @@ class LokiPublicChatAPI extends EventEmitter { this.ourKey = ourKey; this.servers = []; } + + // server getter/factory findOrCreateServer(serverUrl) { let thisServer = this.servers.find( server => server.baseServerUrl === serverUrl @@ -25,10 +27,14 @@ class LokiPublicChatAPI extends EventEmitter { } return thisServer; } + + // channel getter/factory findOrCreateChannel(serverUrl, channelId, conversationId) { const server = this.findOrCreateServer(serverUrl); return server.findOrCreateChannel(channelId, conversationId); } + + // deallocate resources server uses unregisterChannel(serverUrl, channelId) { let thisServer; let i = 0; @@ -60,6 +66,8 @@ class LokiPublicServerAPI { log.info(`set token ${ref.token}`); })(); } + + // channel getter/factory findOrCreateChannel(channelId, conversationId) { let thisChannel = this.channels.find( channel => channel.channelId === channelId @@ -71,6 +79,8 @@ class LokiPublicServerAPI { } return thisChannel; } + + // deallocate resources channel uses unregisterChannel(channelId) { let thisChannel; let i = 0; @@ -87,13 +97,17 @@ class LokiPublicServerAPI { thisChannel.stopPolling = true; } - async getOrRefreshServerToken() { - if (this.token) { - return this.token; + // get active token for this server + async getOrRefreshServerToken(forceRefresh = false) { + let token; + if (!forceRefresh) { + if (this.token) { + return this.token; + } + token = await Signal.Data.getPublicServerTokenByServerUrl( + this.baseServerUrl + ); } - let token = await Signal.Data.getPublicServerTokenByServerUrl( - this.baseServerUrl - ); if (!token) { token = await this.refreshServerToken(); if (token) { @@ -107,27 +121,36 @@ class LokiPublicServerAPI { return token; } + // get active token from server (but only allow one request at a time) async refreshServerToken() { + // if currently not in progress if (this.tokenPromise === null) { + // set lock this.tokenPromise = new Promise(async res => { + // request the oken const token = await this.requestToken(); if (!token) { res(null); return; } + // activate the token const registered = await this.submitToken(token); if (!registered) { res(null); return; } + // resolve promise to release lock res(token); }); } + // wait until we have it set const token = await this.tokenPromise; + // clear lock this.tokenPromise = null; return token; } + // request an token from the server async requestToken() { const url = new URL(`${this.baseServerUrl}/loki/v1/get_challenge`); const params = { @@ -149,6 +172,7 @@ class LokiPublicServerAPI { return token; } + // activate token async submitToken(token) { const options = { method: 'POST', @@ -193,12 +217,14 @@ class LokiPublicChannelAPI { this.pollForDeletions(); } + // make a request to the server async serverRequest(endpoint, options = {}) { + const { params = {}, method, objBody, forceFreshToken = false } = options; const url = new URL(`${this.serverAPI.baseServerUrl}/${endpoint}`); - if (options.params) { + if (params) { url.search = new URLSearchParams(params); } - let res; + let result; let { token } = this.serverAPI; if (!token) { token = await this.serverAPI.getOrRefreshServerToken(); @@ -210,38 +236,61 @@ class LokiPublicChannelAPI { } } try { - const fetchOptions = { - headers: new Headers({ - Authorization: `Bearer ${this.serverAPI.token}`, - }), + const fetchOptions = {}; + const headers = { + Authorization: `Bearer ${this.serverAPI.token}`, }; - if (options.method) { - fetchOptions.method = options.method; + if (method) { + fetchOptions.method = method; } - res = await nodeFetch(url, fetchOptions || undefined); + if (objBody) { + headers['Content-Type'] = 'application/json'; + fetchOptions.body = JSON.stringify(objBody); + } + fetchOptions.headers = new Headers(headers); + result = await nodeFetch(url, fetchOptions || undefined); } catch (e) { log.info(`e ${e}`); return { err: e, }; } - const response = await res.json(); + let response = null; + try { + response = await result.json(); + } catch (e) { + log.info(`serverRequest json arpse ${e}`); + return { + err: e, + statusCode: result.status, + }; + } // if it's a response style with a meta - if (res.status !== 200) { + if (result.status !== 200) { + if (!forceFreshToken && response.meta.code === 401) { + // copy options because lint complains if we modify this directly + const updatedOptions = options; + // force it this time + updatedOptions.forceFreshToken = true; + // retry with updated options + return this.serverRequest(endpoint, updatedOptions); + } return { err: 'statusCode', + statusCode: result.status, response, }; } return { - statusCode: res.status, + statusCode: result.status, response, }; } + // get moderator status async refreshModStatus() { - const res = serverRequest('/loki/v1/user_info'); + const res = 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; @@ -251,9 +300,12 @@ class LokiPublicChannelAPI { await conversation.setModStatus(this.modStatus); } + // delete a message on the server async deleteMessage(serverId) { const res = await this.serverRequest( - this.modStatus?`loki/v1/moderation/message/${messageServerId}`:`${this.baseChannelUrl}/messages/${serverId}`, + this.modStatus + ? `loki/v1/moderation/message/${serverId}` + : `${this.baseChannelUrl}/messages/${serverId}`, { method: 'DELETE' } ); if (!res.err && res.response) { @@ -264,6 +316,7 @@ class LokiPublicChannelAPI { return false; } + // used for sending messages getEndpoint() { const endpoint = `${this.serverAPI.baseServerUrl}/${ this.baseChannelUrl @@ -272,7 +325,7 @@ class LokiPublicChannelAPI { } // update room details - async pollForChannel(source, endpoint) { + async pollForChannel() { // groupName will be loaded from server const url = new URL(this.baseChannelUrl); let res; @@ -290,9 +343,6 @@ class LokiPublicChannelAPI { Authorization: `Bearer ${token}`, }), }; - if (method) { - options.method = method; - } res = await nodeFetch(url, options || undefined); } catch (e) { log.info(`e ${e}`); @@ -322,20 +372,21 @@ class LokiPublicChannelAPI { // start loop let more = true; - // eslint-disable-next-line no-await-in-loop while (more) { // set params to from where we last checked params.since_id = this.deleteLastId; // grab the next 200 deletions from where we last checked + // eslint-disable-next-line no-await-in-loop const res = await this.serverRequest( `loki/v1/channel/${this.channelId}/deletes`, { params } ); - // Process rresult + // Process results res.response.data.reverse().forEach(deleteEntry => { - // Escalate it up to the subsystem that can check to see if this has been processsed + // Escalate it up to the subsystem that can check to see if this has + // been processed Whisper.events.trigger('deleteLocalPublicMessage', { messageServerId: deleteEntry.message_id, conversationId: this.conversationId, @@ -368,10 +419,9 @@ class LokiPublicChannelAPI { if (this.lastGot) { params.since_id = this.lastGot; } - const res = await this.serverRequest( - `${this.baseChannelUrl}/messages`, - { params } - ); + const res = await this.serverRequest(`${this.baseChannelUrl}/messages`, { + params, + }); if (!res.err && res.response) { let receivedAt = new Date().getTime(); @@ -442,6 +492,33 @@ class LokiPublicChannelAPI { this.pollForMessages(); }, GROUPCHAT_POLL_EVERY); } + + // create a message in the channel + async sendMessage(text, messageTimeStamp, displayName, pubKey) { + const payload = { + text, + annotations: [ + { + type: 'network.loki.messenger.publicChat', + value: { + timestamp: messageTimeStamp, + // will deprecated + from: displayName, + // will deprecated + source: pubKey, + }, + }, + ], + }; + const res = await this.serverRequest(`${this.baseChannelUrl}/messages`, { + method: 'POST', + objBody: payload, + }); + if (!res.err && res.response) { + return res.response.data.id; + } + return false; + } } module.exports = LokiPublicChatAPI;