remove most of sogsv1 and fsv1 and update version fetch url
version fetch is used to know when we have a new updatepull/1671/head
parent
3456162402
commit
6f8c8ee1b3
@ -0,0 +1,69 @@
|
||||
|
||||
A TESTER
|
||||
* MENTIONS opengroupv2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
lokiPublicChatAPI
|
||||
|
||||
|
||||
|
||||
dot_net_api
|
||||
loki_public_chat_api
|
||||
|
||||
|
||||
|
||||
OpenGroupUtils
|
||||
|
||||
OpenGroup
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
channelAPI.sendMessage
|
||||
|
||||
|
||||
getAllOpenGroupV1Conversations => migration to remove opengroupsv1 matching urls
|
||||
|
||||
|
||||
|
||||
|
||||
DELETED
|
||||
tokenlessFileServerAdnAPI
|
||||
channelId
|
||||
publicChat with only a cahnnel of 1
|
||||
loki_file_server_api
|
||||
LokiAppDotNetApi
|
||||
LokiPublicChatFactoryAPI
|
||||
lastPublicMessage
|
||||
getLastRetrievedMessage
|
||||
setLastRetrievedMessage
|
||||
attemptConnection
|
||||
attemptConnectionOneAtATime
|
||||
|
||||
getPublicSendData
|
||||
setPublicSource
|
||||
getPublicSource
|
||||
findOrCreateServer
|
||||
initAPIs
|
||||
OpenGroup.
|
||||
findOrCreateChannel
|
||||
initSpecialConversations
|
||||
updateOpenGroupV1
|
||||
partChannel
|
||||
acceptOpenGroupInvitationV1
|
||||
pollForChannelOnce
|
||||
publicConversatio
|
||||
handleOpenGroupJoinV1
|
||||
setChannelName
|
||||
setChannelAvatar
|
||||
isOpenGroupV1
|
||||
toOpenGroupV1
|
||||
setListOfMembers
|
||||
|
||||
|
||||
lokiFileServerAPI
|
||||
lokiFileServerAPIFactory
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
||||
interface LokiFileServerInstance {
|
||||
downloadAttachment(url: string): Promise<ArrayBuffer>;
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/* global log, window */
|
||||
/* global log: false */
|
||||
|
||||
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
|
||||
|
||||
// can have multiple of these instances as each user can have a
|
||||
// different home server
|
||||
class LokiFileServerInstance {
|
||||
constructor(ourKey) {
|
||||
this.ourKey = ourKey;
|
||||
}
|
||||
|
||||
// FIXME: this is not file-server specific
|
||||
// and is currently called by LokiAppDotNetAPI.
|
||||
// LokiAppDotNetAPI (base) should not know about LokiFileServer.
|
||||
async establishConnection(serverUrl, options) {
|
||||
// why don't we extend this?
|
||||
this._server = new LokiAppDotNetAPI(this.ourKey, serverUrl);
|
||||
|
||||
// make sure pubKey & pubKeyHex are set in _server
|
||||
this.pubKey = this._server.getPubKeyForUrl();
|
||||
|
||||
if (options !== undefined && options.skipToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get a token for multidevice
|
||||
const gotToken = await this._server.getOrRefreshServerToken();
|
||||
// TODO: Handle this failure gracefully
|
||||
if (!gotToken) {
|
||||
log.error('You are blacklisted form this home server');
|
||||
}
|
||||
}
|
||||
|
||||
// for files
|
||||
async downloadAttachment(url) {
|
||||
return this._server.downloadAttachment(url);
|
||||
}
|
||||
}
|
||||
|
||||
// extends LokiFileServerInstance with functions we'd only perform on our own home server
|
||||
// so we don't accidentally send info to the wrong file server
|
||||
class LokiHomeServerInstance extends LokiFileServerInstance {
|
||||
// you only upload to your own home server
|
||||
// you can download from any server...
|
||||
uploadAvatar(data) {
|
||||
if (!this._server.token) {
|
||||
log.warn('uploadAvatar no token yet');
|
||||
}
|
||||
return this._server.uploadAvatar(data);
|
||||
}
|
||||
|
||||
static uploadPrivateAttachment(data) {
|
||||
return window.tokenlessFileServerAdnAPI.uploadData(data);
|
||||
}
|
||||
}
|
||||
|
||||
// this will be our instance factory
|
||||
class LokiFileServerFactoryAPI {
|
||||
constructor(ourKey) {
|
||||
this.ourKey = ourKey;
|
||||
this.servers = [];
|
||||
}
|
||||
|
||||
establishHomeConnection(serverUrl) {
|
||||
let thisServer = this.servers.find(server => server._server.baseServerUrl === serverUrl);
|
||||
if (!thisServer) {
|
||||
thisServer = new LokiHomeServerInstance(this.ourKey);
|
||||
log.info(`Registering HomeServer ${serverUrl}`);
|
||||
// not await, so a failure or slow connection doesn't hinder loading of the app
|
||||
thisServer.establishConnection(serverUrl);
|
||||
this.servers.push(thisServer);
|
||||
}
|
||||
return thisServer;
|
||||
}
|
||||
|
||||
async establishConnection(serverUrl) {
|
||||
let thisServer = this.servers.find(server => server._server.baseServerUrl === serverUrl);
|
||||
if (!thisServer) {
|
||||
thisServer = new LokiFileServerInstance(this.ourKey);
|
||||
log.info(`Registering FileServer ${serverUrl}`);
|
||||
await thisServer.establishConnection(serverUrl, { skipToken: true });
|
||||
this.servers.push(thisServer);
|
||||
}
|
||||
return thisServer;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LokiFileServerFactoryAPI;
|
@ -1,20 +0,0 @@
|
||||
import { LokiAppDotNetServerInterface, LokiPublicChannelAPI } from './loki_app_dot_net_api';
|
||||
|
||||
export interface LokiPublicChatFactoryInterface {
|
||||
ourKey: string;
|
||||
openGroupPubKeys: { [key: string]: string };
|
||||
findOrCreateServer(url: string): Promise<LokiAppDotNetServerInterface | null>;
|
||||
findOrCreateChannel(
|
||||
url: string,
|
||||
channelId: number,
|
||||
conversationId: string
|
||||
): Promise<LokiPublicChannelAPI | null>;
|
||||
getListOfMembers(): Promise<Array<{ authorPhoneNumber: string; authorProfileName?: string }>>;
|
||||
setListOfMembers(members: Array<{ authorPhoneNumber: string; authorProfileName?: string }>);
|
||||
}
|
||||
|
||||
declare class LokiPublicChatFactoryAPI implements LokiPublicChatFactoryInterface {
|
||||
constructor(ourKey: string);
|
||||
}
|
||||
|
||||
export default LokiPublicChatFactoryAPI;
|
@ -1,149 +0,0 @@
|
||||
/* global log, process */
|
||||
const EventEmitter = require('events');
|
||||
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
|
||||
const OpenGroupUtils = require('../../ts/opengroup/utils/OpenGroupUtils');
|
||||
|
||||
class LokiPublicChatFactoryAPI extends EventEmitter {
|
||||
constructor(ourKey) {
|
||||
super();
|
||||
this.ourKey = ourKey;
|
||||
this.servers = [];
|
||||
this.allMembers = [];
|
||||
this.openGroupPubKeys = {};
|
||||
// Multidevice states
|
||||
}
|
||||
|
||||
// MessageReceiver.connect calls this
|
||||
// start polling in all existing registered channels
|
||||
async open() {
|
||||
await Promise.all(this.servers.map(server => server.open()));
|
||||
}
|
||||
|
||||
// MessageReceiver.close
|
||||
async close() {
|
||||
await Promise.all(this.servers.map(server => server.close()));
|
||||
}
|
||||
|
||||
// server getter/factory
|
||||
async findOrCreateServer(serverUrl) {
|
||||
let thisServer = this.servers.find(server => server.baseServerUrl === serverUrl);
|
||||
if (!thisServer) {
|
||||
log.info(`loki_public_chat::findOrCreateServer - creating ${serverUrl}`);
|
||||
|
||||
const serverIsValid = await OpenGroupUtils.validOpenGroupServer(serverUrl);
|
||||
if (!serverIsValid) {
|
||||
// FIXME: add toast?
|
||||
log.error(`loki_public_chat::findOrCreateServer - error: ${serverUrl} is not valid`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// after verification then we can start up all the pollers
|
||||
if (process.env.USE_STUBBED_NETWORK) {
|
||||
// eslint-disable-next-line global-require
|
||||
const StubAppDotNetAPI = require('../.././ts/test/session/integration/stubs/stub_app_dot_net_api');
|
||||
thisServer = new StubAppDotNetAPI(this.ourKey, serverUrl);
|
||||
} else {
|
||||
thisServer = new LokiAppDotNetAPI(this.ourKey, serverUrl);
|
||||
if (this.openGroupPubKeys[serverUrl]) {
|
||||
thisServer.getPubKeyForUrl();
|
||||
if (!thisServer.pubKeyHex) {
|
||||
log.warn(`loki_public_chat::findOrCreateServer - failed to set public key`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const gotToken = await thisServer.getOrRefreshServerToken();
|
||||
if (!gotToken) {
|
||||
log.warn(`loki_public_chat::findOrCreateServer - Invalid server ${serverUrl}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.servers.push(thisServer);
|
||||
}
|
||||
return thisServer;
|
||||
}
|
||||
|
||||
// channel getter/factory
|
||||
async findOrCreateChannel(serverUrl, channelId, conversationId) {
|
||||
const server = await this.findOrCreateServer(serverUrl);
|
||||
if (!server) {
|
||||
log.error(`Failed to create server for: ${serverUrl}`);
|
||||
return null;
|
||||
}
|
||||
return server.findOrCreateChannel(this, channelId, conversationId);
|
||||
}
|
||||
|
||||
// deallocate resources server uses
|
||||
unregisterChannel(serverUrl, channelId) {
|
||||
const i = this.servers.findIndex(server => server.baseServerUrl === serverUrl);
|
||||
if (i === -1) {
|
||||
log.warn(`Tried to unregister from nonexistent server ${serverUrl}`);
|
||||
return;
|
||||
}
|
||||
const thisServer = this.servers[i];
|
||||
if (!thisServer) {
|
||||
log.warn(`Tried to unregister from nonexistent server ${i}`);
|
||||
return;
|
||||
}
|
||||
thisServer.unregisterChannel(channelId);
|
||||
this.servers.splice(i, 1);
|
||||
}
|
||||
|
||||
// shouldn't this be scoped per conversation?
|
||||
async getListOfMembers() {
|
||||
// enable in the next release
|
||||
/*
|
||||
let members = [];
|
||||
await Promise.all(this.servers.map(async server => {
|
||||
await Promise.all(server.channels.map(async channel => {
|
||||
const newMembers = await channel.getSubscribers();
|
||||
members = [...members, ...newMembers];
|
||||
}));
|
||||
}));
|
||||
const results = members.map(member => {
|
||||
return { authorPhoneNumber: member.username };
|
||||
});
|
||||
*/
|
||||
return this.allMembers;
|
||||
}
|
||||
|
||||
// TODO: make this private (or remove altogether) when
|
||||
// we switch to polling the server for group members
|
||||
setListOfMembers(members) {
|
||||
this.allMembers = members;
|
||||
}
|
||||
|
||||
async setProfileName(profileName) {
|
||||
await Promise.all(
|
||||
this.servers.map(async server => {
|
||||
await server.setProfileName(profileName);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async setHomeServer(homeServer) {
|
||||
await Promise.all(
|
||||
this.servers.map(async server => {
|
||||
// this may fail
|
||||
// but we can't create a sql table to remember to retry forever
|
||||
// I think we just silently fail for now
|
||||
await server.setHomeServer(homeServer);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async setAvatar(url, profileKey) {
|
||||
await Promise.all(
|
||||
this.servers.map(async server => {
|
||||
// this may fail
|
||||
// but we can't create a sql table to remember to retry forever
|
||||
// I think we just silently fail for now
|
||||
await server.setAvatar(url, profileKey);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// These files are expected to be in commonjs so we can't use es6 syntax :(
|
||||
// If we move these to TS then we should be able to use es6
|
||||
module.exports = LokiPublicChatFactoryAPI;
|
@ -1,98 +0,0 @@
|
||||
/* global window: false */
|
||||
/* global callWorker: false */
|
||||
/* global textsecure: false */
|
||||
/* global Event: false */
|
||||
/* global dcodeIO: false */
|
||||
/* global lokiPublicChatAPI: false */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
/* eslint-disable no-unreachable */
|
||||
|
||||
let openGroupBound = false;
|
||||
|
||||
function MessageReceiver() {
|
||||
this.pending = Promise.resolve();
|
||||
|
||||
// only do this once to prevent duplicates
|
||||
if (lokiPublicChatAPI) {
|
||||
window.log.info('Binding open group events handler', openGroupBound);
|
||||
if (!openGroupBound) {
|
||||
openGroupBound = true;
|
||||
}
|
||||
} else {
|
||||
window.log.warn('Can not handle open group data, API is not available');
|
||||
}
|
||||
}
|
||||
|
||||
MessageReceiver.stringToArrayBuffer = string =>
|
||||
Promise.resolve(dcodeIO.ByteBuffer.wrap(string, 'binary').toArrayBuffer());
|
||||
MessageReceiver.arrayBufferToString = arrayBuffer =>
|
||||
Promise.resolve(dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('binary'));
|
||||
MessageReceiver.arrayBufferToStringBase64 = arrayBuffer =>
|
||||
callWorker('arrayBufferToStringBase64', arrayBuffer);
|
||||
|
||||
MessageReceiver.prototype = new textsecure.EventTarget();
|
||||
MessageReceiver.prototype.extend({
|
||||
constructor: MessageReceiver,
|
||||
connect() {
|
||||
if (this.calledClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hasConnected) {
|
||||
const ev = new Event('reconnect');
|
||||
this.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
this.hasConnected = true;
|
||||
|
||||
// start polling all open group rooms you have registered
|
||||
// if not registered yet, they'll get started when they're created
|
||||
if (lokiPublicChatAPI) {
|
||||
lokiPublicChatAPI.open();
|
||||
}
|
||||
|
||||
// Ensures that an immediate 'empty' event from the websocket will fire only after
|
||||
// all cached envelopes are processed.
|
||||
this.incoming = [this.pending];
|
||||
},
|
||||
stopProcessing() {
|
||||
window.log.info('MessageReceiver: stopProcessing requested');
|
||||
this.stoppingProcessing = true;
|
||||
return this.close();
|
||||
},
|
||||
shutdown() {},
|
||||
async close() {
|
||||
window.log.info('MessageReceiver.close()');
|
||||
this.calledClose = true;
|
||||
|
||||
// stop polling all open group rooms
|
||||
if (lokiPublicChatAPI) {
|
||||
await lokiPublicChatAPI.close();
|
||||
}
|
||||
},
|
||||
onopen() {},
|
||||
onerror() {},
|
||||
onclose() {},
|
||||
});
|
||||
|
||||
window.textsecure = window.textsecure || {};
|
||||
|
||||
textsecure.MessageReceiver = function MessageReceiverWrapper() {
|
||||
const messageReceiver = new MessageReceiver();
|
||||
this.addEventListener = messageReceiver.addEventListener.bind(messageReceiver);
|
||||
this.removeEventListener = messageReceiver.removeEventListener.bind(messageReceiver);
|
||||
this.close = messageReceiver.close.bind(messageReceiver);
|
||||
|
||||
this.stopProcessing = messageReceiver.stopProcessing.bind(messageReceiver);
|
||||
|
||||
messageReceiver.connect();
|
||||
};
|
||||
|
||||
textsecure.MessageReceiver.prototype = {
|
||||
constructor: textsecure.MessageReceiver,
|
||||
};
|
||||
|
||||
textsecure.MessageReceiver.stringToArrayBuffer = MessageReceiver.stringToArrayBuffer;
|
||||
textsecure.MessageReceiver.arrayBufferToString = MessageReceiver.arrayBufferToString;
|
||||
textsecure.MessageReceiver.arrayBufferToStringBase64 = MessageReceiver.arrayBufferToStringBase64;
|
@ -1,346 +0,0 @@
|
||||
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
|
||||
import { ConversationController } from '../../session/conversations';
|
||||
import { PromiseUtils } from '../../session/utils';
|
||||
import { allowOnlyOneAtATime } from '../../session/utils/Promise';
|
||||
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
|
||||
import { arrayBufferFromFile } from '../../types/Attachment';
|
||||
import { openGroupPrefix, prefixify } from '../utils/OpenGroupUtils';
|
||||
|
||||
interface OpenGroupParams {
|
||||
server: string;
|
||||
channel: number;
|
||||
conversationId: string;
|
||||
}
|
||||
|
||||
export async function updateOpenGroupV1(convo: any, groupName: string, avatar: any) {
|
||||
const API = await convo.getPublicSendData();
|
||||
|
||||
if (avatar) {
|
||||
// I hate duplicating this...
|
||||
const readFile = async (attachment: any) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (e: any) => {
|
||||
const data = e.target.result;
|
||||
resolve({
|
||||
...attachment,
|
||||
data,
|
||||
size: data.byteLength,
|
||||
});
|
||||
};
|
||||
fileReader.onerror = reject;
|
||||
fileReader.onabort = reject;
|
||||
fileReader.readAsArrayBuffer(attachment.file);
|
||||
});
|
||||
const avatarAttachment: any = await readFile({ file: avatar });
|
||||
|
||||
// We want a square for iOS
|
||||
const withBlob = await window.Signal.Util.AttachmentUtil.autoScale(
|
||||
{
|
||||
contentType: avatar.type,
|
||||
file: new Blob([avatarAttachment.data], {
|
||||
type: avatar.contentType,
|
||||
}),
|
||||
},
|
||||
{
|
||||
maxSide: 640,
|
||||
maxSize: 1000 * 1024,
|
||||
}
|
||||
);
|
||||
const dataResized = await arrayBufferFromFile(withBlob.file);
|
||||
// const tempUrl = window.URL.createObjectURL(avatar);
|
||||
|
||||
// Get file onto public chat server
|
||||
const fileObj = await API.serverAPI.putAttachment(dataResized);
|
||||
if (fileObj === null) {
|
||||
// problem
|
||||
window?.log?.warn('File upload failed');
|
||||
return;
|
||||
}
|
||||
|
||||
// lets not allow ANY URLs, lets force it to be local to public chat server
|
||||
const url = new URL(fileObj.url);
|
||||
|
||||
// write it to the channel
|
||||
await API.setChannelAvatar(url.pathname);
|
||||
}
|
||||
|
||||
if (await API.setChannelName(groupName)) {
|
||||
// queue update from server
|
||||
// and let that set the conversation
|
||||
API.pollForChannelOnce();
|
||||
// or we could just directly call
|
||||
// convo.setGroupName(groupName);
|
||||
// but gut is saying let the server be the definitive storage of the state
|
||||
// and trickle down from there
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenGroup {
|
||||
private static readonly serverRegex = new RegExp(
|
||||
'^((https?:\\/\\/){0,1})([\\w-]{2,}\\.){1,2}[\\w-]{2,}$'
|
||||
);
|
||||
private static readonly groupIdRegex = new RegExp(
|
||||
`^${openGroupPrefix}:[0-9]*@([\\w-]{2,}.){1,2}[\\w-]{2,}$`
|
||||
);
|
||||
|
||||
public readonly server: string;
|
||||
public readonly channel: number;
|
||||
public readonly groupId?: string;
|
||||
public readonly conversationId: string;
|
||||
|
||||
/**
|
||||
* An OpenGroup object.
|
||||
* If `params.server` is not valid, this will throw an `Error`.
|
||||
*
|
||||
* @param params.server The server URL. `https` will be prepended if `http` or `https` is not explicitly set
|
||||
* @param params.channel The server channel
|
||||
* @param params.groupId The string corresponding to the server. Eg. `${openGroupPrefix}1@chat.getsession.org`
|
||||
* @param params.conversationId The conversation ID for the backbone model
|
||||
*/
|
||||
constructor(params: OpenGroupParams) {
|
||||
this.server = prefixify(params.server.toLowerCase());
|
||||
|
||||
// Validate server format
|
||||
const isValid = OpenGroup.serverRegex.test(this.server);
|
||||
if (!isValid) {
|
||||
throw Error('an invalid server or groupId was provided');
|
||||
}
|
||||
|
||||
this.channel = params.channel;
|
||||
this.conversationId = params.conversationId;
|
||||
this.groupId = OpenGroup.getGroupId(this.server, this.channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the URL of an open group server
|
||||
*
|
||||
* @param serverUrl The server URL to validate
|
||||
*/
|
||||
public static validate(serverUrl: string): boolean {
|
||||
return this.serverRegex.test(serverUrl);
|
||||
}
|
||||
|
||||
public static getAllAlreadyJoinedOpenGroupsUrl(): Array<string> {
|
||||
const convos = ConversationController.getInstance().getConversations();
|
||||
return convos
|
||||
.filter(c => !!c.get('active_at') && c.isPublic() && !c.get('left'))
|
||||
.map(c => c.id.substring((c.id as string).lastIndexOf('@') + 1)) as Array<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to make a new instance of `OpenGroup`.
|
||||
* This does NOT respect `ConversationController` and does not guarentee the conversation's existence.
|
||||
*
|
||||
* @param groupId The string corresponding to the server. Eg. `${openGroupPrefix}1@chat.getsession.org`
|
||||
* @param conversationId The conversation ID for the backbone model
|
||||
* @returns `OpenGroup` if valid otherwise returns `undefined`.
|
||||
*/
|
||||
public static from(
|
||||
groupId: string,
|
||||
conversationId: string,
|
||||
hasSSL: boolean = true
|
||||
): OpenGroup | undefined {
|
||||
const server = this.getServer(groupId, hasSSL);
|
||||
const channel = this.getChannel(groupId);
|
||||
|
||||
// Was groupId successfully utilized?
|
||||
if (!server || !channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openGroupParams = {
|
||||
server,
|
||||
channel,
|
||||
groupId,
|
||||
conversationId,
|
||||
} as OpenGroupParams;
|
||||
|
||||
const isValid = OpenGroup.serverRegex.test(server);
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new OpenGroup(openGroupParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join an open group
|
||||
*
|
||||
* @param server The server URL
|
||||
* @param onLoading Callback function to be called once server begins connecting
|
||||
* @returns `OpenGroup` if connection success or if already connected
|
||||
*/
|
||||
public static async join(server: string, fromSyncMessage: boolean = false): Promise<void> {
|
||||
const prefixedServer = prefixify(server);
|
||||
if (!OpenGroup.validate(server)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make this not hard coded
|
||||
const channel = 1;
|
||||
let conversation;
|
||||
let conversationId;
|
||||
|
||||
// Return OpenGroup if we're already connected
|
||||
conversation = OpenGroup.getConversation(prefixedServer);
|
||||
|
||||
if (conversation) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to connect to server
|
||||
try {
|
||||
conversation = await PromiseUtils.timeout(
|
||||
OpenGroup.attemptConnectionOneAtATime(prefixedServer, channel),
|
||||
20000
|
||||
);
|
||||
|
||||
if (!conversation) {
|
||||
throw new Error(window.i18n('connectToServerFail'));
|
||||
}
|
||||
conversationId = (conversation as any)?.cid;
|
||||
|
||||
// here we managed to connect to the group.
|
||||
// if this is not a Sync Message, we should trigger one
|
||||
if (!fromSyncMessage) {
|
||||
await forceSyncConfigurationNowIfNeeded();
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the conversation model of a server from its URL
|
||||
*
|
||||
* @param server The server URL
|
||||
* @returns BackBone conversation model corresponding to the server if it exists, otherwise `undefined`
|
||||
*/
|
||||
public static getConversation(server: string): ConversationModel | undefined {
|
||||
if (!OpenGroup.validate(server)) {
|
||||
return;
|
||||
}
|
||||
const rawServerURL = server.replace(/^https?:\/\//i, '').replace(/[/\\]+$/i, '');
|
||||
const channelId = 1;
|
||||
const conversationId = `${openGroupPrefix}${channelId}@${rawServerURL}`;
|
||||
|
||||
// Quickly peak to make sure we don't already have it
|
||||
return ConversationController.getInstance().get(conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the server exists.
|
||||
* This does not compare against your conversations with the server.
|
||||
*
|
||||
* @param server The server URL
|
||||
*/
|
||||
public static async serverExists(server: string): Promise<boolean> {
|
||||
if (!OpenGroup.validate(server)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prefixedServer = prefixify(server);
|
||||
return Boolean(await window.lokiPublicChatAPI.findOrCreateServer(prefixedServer));
|
||||
}
|
||||
|
||||
private static getServer(groupId: string, hasSSL: boolean): string | undefined {
|
||||
const isValid = this.groupIdRegex.test(groupId);
|
||||
const strippedServer = isValid ? groupId.split('@')[1] : undefined;
|
||||
|
||||
// We don't know for sure if the server is https or http when taken from the groupId. Preifx accordingly.
|
||||
return strippedServer ? prefixify(strippedServer.toLowerCase(), hasSSL) : undefined;
|
||||
}
|
||||
|
||||
private static getChannel(groupId: string): number | undefined {
|
||||
const isValid = this.groupIdRegex.test(groupId);
|
||||
const channelMatch = groupId.match(/^.*\:([0-9]*)\@.*$/);
|
||||
|
||||
return channelMatch && isValid ? Number(channelMatch[1]) : undefined;
|
||||
}
|
||||
|
||||
private static getGroupId(server: string, channel: number): string {
|
||||
// Server is already validated in constructor; no need to re-check
|
||||
|
||||
// Strip server prefix
|
||||
const prefixRegex = new RegExp('https?:\\/\\/');
|
||||
const strippedServer = server.replace(prefixRegex, '');
|
||||
|
||||
return `${openGroupPrefix}${channel}@${strippedServer}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we get our configuration from the network, we might get a few times the same open group on two different messages.
|
||||
* If we don't do anything, we will join them multiple times.
|
||||
* Even if the convo exists only once, the lokiPublicChat API will have several instances polling for the same open group.
|
||||
* Which will cause a lot of duplicate messages as they will be merged on a single conversation.
|
||||
*
|
||||
* To avoid this issue, we allow only a single join of a specific opengroup at a time.
|
||||
*/
|
||||
private static async attemptConnectionOneAtATime(
|
||||
serverUrl: string,
|
||||
channelId: number = 1
|
||||
): Promise<ConversationModel> {
|
||||
if (!serverUrl) {
|
||||
throw new Error('Cannot join open group with empty URL');
|
||||
}
|
||||
const oneAtaTimeStr = `oneAtaTimeOpenGroupJoin:${serverUrl}${channelId}`;
|
||||
return allowOnlyOneAtATime(oneAtaTimeStr, async () => {
|
||||
return OpenGroup.attemptConnection(serverUrl, channelId);
|
||||
});
|
||||
}
|
||||
|
||||
// Attempts a connection to an open group server
|
||||
private static async attemptConnection(
|
||||
serverUrl: string,
|
||||
channelId: number
|
||||
): Promise<ConversationModel> {
|
||||
let completeServerURL = serverUrl.toLowerCase();
|
||||
const valid = OpenGroup.validate(completeServerURL);
|
||||
if (!valid) {
|
||||
return new Promise((_resolve, reject) => {
|
||||
reject(window.i18n('connectToServerFail'));
|
||||
});
|
||||
}
|
||||
|
||||
// Add http or https prefix to server
|
||||
completeServerURL = prefixify(completeServerURL);
|
||||
|
||||
const rawServerURL = serverUrl.replace(/^https?:\/\//i, '').replace(/[/\\]+$/i, '');
|
||||
|
||||
const conversationId = `${openGroupPrefix}${channelId}@${rawServerURL}`;
|
||||
|
||||
// Quickly peak to make sure we don't already have it
|
||||
const conversationExists = ConversationController.getInstance().get(conversationId);
|
||||
if (conversationExists) {
|
||||
// We are already a member of this public chat
|
||||
return new Promise((_resolve, reject) => {
|
||||
reject(window.i18n('publicChatExists'));
|
||||
});
|
||||
}
|
||||
|
||||
// Get server
|
||||
const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer(completeServerURL);
|
||||
// SSL certificate failure or offline
|
||||
if (!serverAPI) {
|
||||
// Url incorrect or server not compatible
|
||||
return new Promise((_resolve, reject) => {
|
||||
reject(window.i18n('connectToServerFail'));
|
||||
});
|
||||
}
|
||||
|
||||
// Create conversation
|
||||
const conversation = await ConversationController.getInstance().getOrCreateAndWait(
|
||||
conversationId,
|
||||
ConversationTypeEnum.GROUP // keep a group for this one as this is an old open group
|
||||
);
|
||||
|
||||
// Convert conversation to a public one
|
||||
await conversation.setPublicSource(completeServerURL, channelId);
|
||||
|
||||
// and finally activate it
|
||||
void conversation.getPublicSendData(); // may want "await" if you want to use the API
|
||||
|
||||
return conversation;
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
/* global clearTimeout, Buffer, TextDecoder, process */
|
||||
|
||||
import LokiAppDotNetServerAPI from '../../../../../js/modules/loki_app_dot_net_api';
|
||||
|
||||
const samplesGetMessages = {
|
||||
meta: { code: 200 },
|
||||
data: [
|
||||
{
|
||||
channel_id: 1,
|
||||
created_at: '2020-03-18T04:48:44.000Z',
|
||||
entities: {
|
||||
mentions: [],
|
||||
hashtags: [],
|
||||
links: [],
|
||||
},
|
||||
id: 3662,
|
||||
machine_only: false,
|
||||
num_replies: 0,
|
||||
source: {},
|
||||
thread_id: 3662,
|
||||
reply_to: null,
|
||||
text: 'hgt',
|
||||
html: '<span itemscope="https://app.net/schemas/Post">hgt</span>',
|
||||
annotations: [
|
||||
{
|
||||
type: 'network.loki.messenger.publicChat',
|
||||
value: {
|
||||
timestamp: 1584506921361,
|
||||
sig:
|
||||
'262ab113810564d7ff6474dea264e10e2143d91c004903d06d8d9fddb5b74b2c6245865544d5cf76ee16a3fca045bc028a48c51f8a290508a29b6013d014dc83',
|
||||
sigver: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
user: {
|
||||
id: 2448,
|
||||
username: '050cd79763303bcc251bd489a6f7da823a2b8555402b01a7959ebca550d048600f',
|
||||
created_at: '2020-03-18T02:42:05.000Z',
|
||||
canonical_url: null,
|
||||
type: null,
|
||||
timezone: null,
|
||||
locale: null,
|
||||
avatar_image: {
|
||||
url: null,
|
||||
width: null,
|
||||
height: null,
|
||||
is_default: false,
|
||||
},
|
||||
cover_image: {
|
||||
url: null,
|
||||
width: null,
|
||||
height: null,
|
||||
is_default: false,
|
||||
},
|
||||
counts: {
|
||||
following: 0,
|
||||
posts: 0,
|
||||
followers: 0,
|
||||
stars: 0,
|
||||
},
|
||||
name: 'asdf',
|
||||
annotations: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
class StubAppDotNetAPI extends LokiAppDotNetServerAPI {
|
||||
// make a request to the server
|
||||
public async serverRequest(endpoint: string, options: { method?: string } = {}) {
|
||||
const { method } = options;
|
||||
|
||||
if (endpoint === 'channels/1/messages') {
|
||||
if (!method) {
|
||||
return {
|
||||
statusCode: 200,
|
||||
response: samplesGetMessages,
|
||||
};
|
||||
}
|
||||
return {
|
||||
statusCode: 200,
|
||||
response: {
|
||||
data: [],
|
||||
meta: {
|
||||
max_id: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (endpoint === 'loki/v1/channel/1/deletes' || endpoint === 'loki/v1/channel/1/moderators') {
|
||||
return {
|
||||
statusCode: 200,
|
||||
response: {
|
||||
data: [],
|
||||
meta: {
|
||||
max_id: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (endpoint === 'files') {
|
||||
return {
|
||||
statusCode: 200,
|
||||
response: {
|
||||
data: {
|
||||
url: 'fakeurl',
|
||||
id: 12345,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (endpoint === 'channels/1') {
|
||||
let name = 'Unknown group';
|
||||
if (this.baseServerUrl.includes('/chat-dev.lokinet.org')) {
|
||||
name = 'Loki Dev Chat';
|
||||
} else if (this.baseServerUrl.includes('/chat.getsession.org')) {
|
||||
name = 'Session Public Chat';
|
||||
}
|
||||
return {
|
||||
statusCode: 200,
|
||||
response: {
|
||||
data: {
|
||||
annotations: [
|
||||
{
|
||||
type: 'net.patter-app.settings',
|
||||
value: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (endpoint === 'token') {
|
||||
return {
|
||||
statusCode: 200,
|
||||
response: {
|
||||
data: {
|
||||
user: {
|
||||
name: 'unknown name',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
response: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StubAppDotNetAPI;
|
Loading…
Reference in New Issue