All the API updates, enable mod status retrieval and allow for message deletion

pull/455/head
Beaudan Brown 6 years ago
parent 351fa09ad6
commit 8d77d6fd79

@ -224,11 +224,12 @@
);
publicConversations.forEach(conversation => {
const settings = conversation.getPublicSource();
window.lokiPublicChatAPI.registerChannel(
const channel = window.lokiPublicChatAPI.findOrCreateChannel(
settings.server,
settings.channelId,
conversation.id
);
channel.refreshModStatus();
});
window.lokiP2pAPI = new window.LokiP2pAPI(ourKey);
window.lokiP2pAPI.on('pingContact', pubKey => {

@ -1,43 +1,46 @@
/* global log, textsecure, libloki, Signal */
/* global log, textsecure, libloki, Signal, Whisper, Headers, ConversationController */
const EventEmitter = require('events');
const nodeFetch = require('node-fetch');
const { URL, URLSearchParams } = require('url');
const GROUPCHAT_POLL_EVERY = 1000; // 1 second
// 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
// singleton to relay events to libtextsecure/message_receiver
class LokiPublicChatAPI extends EventEmitter {
constructor(ourKey) {
super();
this.ourKey = ourKey;
this.lastGot = {};
this.servers = [];
}
findOrCreateServer(hostport) {
log.info(`LokiPublicChatAPI looking for ${hostport}`);
let thisServer = this.servers.find(server => server.server === hostport);
findOrCreateServer(serverUrl) {
let thisServer = this.servers.find(
server => server.baseServerUrl === serverUrl
);
if (!thisServer) {
thisServer = new LokiPublicServerAPI(this, hostport);
log.info(`LokiPublicChatAPI creating ${serverUrl}`);
thisServer = new LokiPublicServerAPI(this, serverUrl);
this.servers.push(thisServer);
}
return thisServer;
}
registerChannel(hostport, channelId, conversationId) {
const server = this.findOrCreateServer(hostport);
server.findOrCreateChannel(channelId, conversationId);
findOrCreateChannel(serverUrl, channelId, conversationId) {
const server = this.findOrCreateServer(serverUrl);
return server.findOrCreateChannel(channelId, conversationId);
}
unregisterChannel(hostport, channelId) {
unregisterChannel(serverUrl, channelId) {
let thisServer;
let i = 0;
for (; i < this.servers.length; i += 1) {
if (this.servers[i].server === hostport) {
if (this.servers[i].server === serverUrl) {
thisServer = this.servers[i];
break;
}
}
if (!thisServer) {
log.warn(`Tried to unregister from nonexistent server ${hostport}`);
log.warn(`Tried to unregister from nonexistent server ${serverUrl}`);
return;
}
thisServer.unregisterChannel(channelId);
@ -57,6 +60,7 @@ class LokiPublicServerAPI {
channel => channel.channelId === channelId
);
if (!thisChannel) {
log.info(`LokiPublicChatAPI creating channel ${conversationId}`);
thisChannel = new LokiPublicChannelAPI(this, channelId, conversationId);
this.channels.push(thisChannel);
}
@ -79,6 +83,9 @@ class LokiPublicServerAPI {
}
async getOrRefreshServerToken() {
if (this.token) {
return this.token;
}
let token = await Signal.Data.getPublicServerTokenByServerUrl(
this.baseServerUrl
);
@ -91,6 +98,7 @@ class LokiPublicServerAPI {
});
}
}
this.token = token;
return token;
}
@ -164,9 +172,7 @@ class LokiPublicChannelAPI {
constructor(serverAPI, channelId, conversationId) {
this.serverAPI = serverAPI;
this.channelId = channelId;
this.baseChannelUrl = `${serverAPI.baseServerUrl}/channels/${
this.channelId
}`;
this.baseChannelUrl = `channels/${this.channelId}`;
this.groupName = 'unknown';
this.conversationId = conversationId;
this.lastGot = 0;
@ -174,85 +180,169 @@ class LokiPublicChannelAPI {
log.info(`registered LokiPublicChannel ${channelId}`);
// start polling
this.pollForMessages();
this.deleteLastId = 1;
this.pollForDeletions();
}
async refreshModStatus() {
const url = new URL(`${this.serverAPI.baseServerUrl}/loki/v1/user_info`);
const token = await this.serverAPI.getOrRefreshServerToken();
let modStatus = false;
try {
const result = await nodeFetch(url, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
if (result.ok) {
const response = await result.json();
if (response.data.moderator_status) {
modStatus = response.data.moderator_status;
}
}
} catch (e) {
modStatus = false;
}
const conversation = ConversationController.get(this.conversationId);
await conversation.setModStatus(modStatus);
}
async deleteMessage(messageServerId) {
// TODO: Allow deletion of your own messages without moderator status
const url = new URL(
`${
this.serverAPI.baseServerUrl
}/loki/v1/moderation/message/${messageServerId}`
);
const token = await this.serverAPI.getOrRefreshServerToken();
try {
const result = await nodeFetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
// 200 for successful delete
// 404 for trying to delete a message that doesn't exist
// 410 for successful moderator delete
const validResults = [404, 410];
if (result.ok || validResults.includes(result.status)) {
return true;
}
} catch (e) {
log.warn(
`Failed to delete message from public server with ID ${messageServerId}`
);
}
return false;
}
getEndpoint() {
const endpoint = `${this.serverAPI.baseServerUrl}/channels/${
this.channelId
const endpoint = `${this.serverAPI.baseServerUrl}/${
this.baseChannelUrl
}/messages`;
return endpoint;
}
async pollForChannel(source, endpoint) {
// groupName will be loaded from server
const url = new URL(this.baseChannelUrl);
async serverRequest(endpoint, params, method) {
const url = new URL(`${this.serverAPI.baseServerUrl}/${endpoint}`);
url.search = new URLSearchParams(params);
let res;
let success = true;
const token = await this.serverAPI.getOrRefreshServerToken();
if (!token) {
log.error('NO TOKEN');
return {
err: 'noToken',
};
}
try {
res = await nodeFetch(url);
// eslint-disable-next-line no-await-in-loop
const options = {
headers: new Headers({
Authorization: `Bearer ${token}`,
}),
};
if (method) {
options.method = method;
}
res = await nodeFetch(url, options || undefined);
} catch (e) {
success = false;
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) {
success = false;
return {
err: 'statusCode',
response,
};
}
// update this.groupId
return endpoint || success;
return {
response,
};
}
async pollForDeletions() {
// read all messages from 0 to current
// delete local copies if server state has changed to delete
// run every minute
const url = new URL(this.baseChannelUrl);
let res;
let success = true;
try {
res = await nodeFetch(url);
} catch (e) {
success = false;
}
const pollAgain = () => {
setTimeout(() => {
this.pollForDeletions();
}, DELETION_POLL_EVERY);
};
const response = await res.json();
if (response.meta.code !== 200) {
success = false;
const params = {
count: 200,
};
// full scan
let more = true;
while (more) {
params.since_id = this.deleteLastId;
const res = await this.serverRequest(
`loki/v1/channel/${this.channelId}/deletes`,
params
);
// eslint-disable-next-line no-loop-func
res.response.data.reverse().forEach(deleteEntry => {
Whisper.events.trigger('deleteLocalPublicMessage', {
messageServerId: deleteEntry.message_id,
conversationId: this.conversationId,
});
});
if (res.response.data.length < 200) {
break;
}
this.deleteLastId = res.response.meta.max_id;
({ more } = res.response);
}
return success;
pollAgain();
}
async pollForMessages() {
const url = new URL(`${this.baseChannelUrl}/messages`);
const params = {
include_annotations: 1,
count: -20,
include_deleted: false,
};
if (this.lastGot) {
params.since_id = this.lastGot;
}
url.search = new URLSearchParams(params);
let res;
let success = true;
try {
res = await nodeFetch(url);
} catch (e) {
success = false;
}
const response = await res.json();
if (this.stopPolling) {
// Stop after latest await possible
return;
}
if (response.meta.code !== 200) {
success = false;
}
const res = await this.serverRequest(
`${this.baseChannelUrl}/messages`,
params
);
if (success) {
if (!res.err && res.response) {
let receivedAt = new Date().getTime();
response.data.reverse().forEach(adnMessage => {
res.response.data.reverse().forEach(adnMessage => {
let timestamp = new Date(adnMessage.created_at).getTime();
let from = adnMessage.user.username;
let source;
@ -264,6 +354,16 @@ class LokiPublicChannelAPI {
({ from, timestamp, source } = noteValue);
}
if (
!from ||
!timestamp ||
!source ||
!adnMessage.id ||
!adnMessage.text
) {
return; // Invalid message
}
const messageData = {
serverId: adnMessage.id,
friendRequest: false,

Loading…
Cancel
Save