remove most of sogsv1 and fsv1 and update version fetch url

version fetch is used to know when we have a new update
pull/1671/head
Audric Ackermann 4 years ago
parent 3456162402
commit 6f8c8ee1b3
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -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

@ -6,10 +6,8 @@
storage,
textsecure,
Whisper,
libsession,
libsignal,
BlockedNumberController,
libsession,
*/
// eslint-disable-next-line func-names
@ -91,7 +89,6 @@
window.document.title = window.getTitle();
let messageReceiver;
Whisper.events = _.clone(Backbone.Events);
Whisper.events.isListenedTo = eventName =>
Whisper.events._events ? !!Whisper.events._events[eventName] : false;
@ -100,26 +97,6 @@
window.log.info('Storage fetch');
storage.fetch();
const initAPIs = () => {
if (window.initialisedAPI) {
return;
}
const ourKey = libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
// singleton to relay events to libtextsecure/message_receiver
window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey);
// singleton to interface the File server
// If already exists we registered as a secondary device
if (!window.lokiFileServerAPI) {
window.lokiFileServerAPIFactory = new window.LokiFileServerAPI(ourKey);
window.lokiFileServerAPI = window.lokiFileServerAPIFactory.establishHomeConnection(
window.getDefaultFileServer()
);
}
window.initialisedAPI = true;
};
function mapOldThemeToNew(theme) {
switch (theme) {
case 'dark':
@ -187,10 +164,7 @@
window.libsession.Utils.AttachmentDownloads.stop();
// Stop processing incoming messages
if (messageReceiver) {
await messageReceiver.stopProcessing();
messageReceiver = null;
}
// FIXME audric stop polling opengroupv2 and swarm nodes
// Shut down the data interface cleanly
await window.Signal.Data.shutdown();
@ -489,13 +463,6 @@
window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now());
await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
// inform all your registered public servers
// could put load on all the servers
// if they just keep changing their names without sending messages
// so we could disable this here
// or least it enable for the quickest response
window.lokiPublicChatAPI.setProfileName(newName);
},
});
}
@ -513,8 +480,6 @@
window.setSettingValue('link-preview-setting', false);
}
// Get memberlist. This function is not accurate >>
// window.getMemberList = window.lokiPublicChatAPI.getListOfMembers();
window.setTheme = newTheme => {
$(document.body)
.removeClass('dark-theme')
@ -685,9 +650,8 @@
// Clear timer, since we're only called when the timer is expired
disconnectTimer = null;
if (messageReceiver) {
await messageReceiver.close();
}
// FIXME audric stop polling opengroupv2 and swarm nodes
window.libsession.Utils.AttachmentDownloads.stop();
}
@ -717,10 +681,6 @@
return;
}
if (messageReceiver) {
await messageReceiver.close();
}
connectCount += 1;
Whisper.Notifications.disable(); // avoid notification flood until empty
setTimeout(() => {
@ -728,14 +688,6 @@
}, window.CONSTANTS.NOTIFICATION_ENABLE_TIMEOUT_SECONDS * 1000);
window.NewReceiver.queueAllCached();
initAPIs();
messageReceiver = new textsecure.MessageReceiver();
// those handleMessageEvent calls are only used by opengroupv1
messageReceiver.addEventListener('message', window.DataMessageReceiver.handleMessageEvent);
messageReceiver.addEventListener('sent', window.DataMessageReceiver.handleMessageEvent);
messageReceiver.addEventListener('reconnect', onReconnect);
messageReceiver.addEventListener('configuration', onConfiguration);
window.SwarmPolling.addPubkey(window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache());
window.SwarmPolling.start();
@ -753,32 +705,4 @@
Whisper.Notifications.enable();
}
function onReconnect() {
// We disable notifications on first connect, but the same applies to reconnect. In
// scenarios where we're coming back from sleep, we can get offline/online events
// very fast, and it looks like a network blip. But we need to suppress
// notifications in these scenarios too. So we listen for 'reconnect' events.
Whisper.Notifications.disable();
// Enable back notifications once most messages have been fetched
setTimeout(() => {
Whisper.Notifications.enable();
}, window.CONSTANTS.NOTIFICATION_ENABLE_TIMEOUT_SECONDS * 1000);
}
function onConfiguration(ev) {
const { configuration } = ev;
const { readReceipts, typingIndicators, linkPreviews } = configuration;
storage.put('read-receipt-setting', readReceipts);
if (typingIndicators === true || typingIndicators === false) {
storage.put('typing-indicators-setting', typingIndicators);
}
if (linkPreviews === true || linkPreviews === false) {
storage.put('link-preview-setting', linkPreviews);
}
ev.confirm();
}
})();

@ -1,4 +1,4 @@
/* global LokiAppDotNetServerAPI, semver, log */
/* global semver, log */
// eslint-disable-next-line func-names
(function() {
'use strict';
@ -6,15 +6,6 @@
// hold last result
let expiredVersion = null;
window.tokenlessFileServerAdnAPI = new LokiAppDotNetServerAPI(
'', // no pubkey needed
window.getDefaultFileServer()
);
// use the anonymous access token
window.tokenlessFileServerAdnAPI.token = 'loki';
// configure for file server comms
window.tokenlessFileServerAdnAPI.getPubKeyForUrl();
let nextWaitSeconds = 5;
const checkForUpgrades = async () => {
try {
@ -28,18 +19,13 @@
}, nextWaitSeconds * 1000); // wait a minute
return;
}
const result = await window.tokenlessFileServerAdnAPI.serverRequest(
'loki/v1/version/client/desktop'
);
if (
result &&
result.response &&
result.response.data &&
result.response.data.length &&
result.response.data[0].length
) {
const latestVer = semver.clean(result.response.data[0][0]);
let latestVersionWithV;
try {
latestVersionWithV = await window.Fsv2.getLatestDesktopReleaseFileToFsV2();
if (!latestVersionWithV) {
throw new Error('Invalid latest version. Shceduling retry...');
}
const latestVer = semver.clean(latestVersionWithV);
if (semver.valid(latestVer)) {
const ourVersion = window.getVersion();
if (latestVer === ourVersion) {
@ -54,14 +40,18 @@
}
}
}
} else {
// give it a minute
log.warn('Could not check to see if newer version is available', result);
nextWaitSeconds = 60;
setTimeout(async () => {
await checkForUpgrades();
}, nextWaitSeconds * 1000); // wait a minute
} catch (e) {
window.log.warn('Failed to fetch latest version');
log.warn('Could not check to see if newer version is available', latestVersionWithV);
}
// wait an hour before retrying
// do this even if we did not get an error before (to be sure to pick up a new release even if
// another request told us we were up to date)
nextWaitSeconds = 3600;
setTimeout(async () => {
await checkForUpgrades();
}, nextWaitSeconds * 1000);
// no message logged means serverRequest never returned...
};

@ -1,41 +1,7 @@
import { Quote, AttachmentPointer, Preview } from '../../ts/session/messages/outgoing';
interface UploadResponse {
url: string;
id?: number;
}
export interface LokiAppDotNetServerInterface {
setAvatar(url: any, profileKey: any);
findOrCreateChannel(
api: LokiPublicChatFactoryAPI,
channelId: number,
conversationId: string
): Promise<LokiPublicChannelAPI>;
uploadData(data: FormData): Promise<UploadResponse>;
uploadAvatar(data: FormData): Promise<UploadResponse>;
putAttachment(data: ArrayBuffer): Promise<UploadResponse>;
putAvatar(data: ArrayBuffer): Promise<UploadResponse>;
downloadAttachment(url: String): Promise<ArrayBuffer>;
serverRequest(endpoint: string): Promise<any>;
}
export interface LokiPublicChannelAPI {
banUser(source: string): Promise<boolean>;
getModerators: () => Promise<Array<string>>;
serverAPI: any;
deleteMessages(arg0: any[]);
sendMessage(
data: {
quote?: Quote;
attachments: Array<AttachmentPointer>;
preview: Array<Preview>;
body?: string;
},
timestamp: number
): Promise<{ serverId; serverTimestamp }>;
}
declare class LokiAppDotNetServerAPI implements LokiAppDotNetServerInterface {
public baseServerUrl: string;
constructor(ourKey: string, url: string);

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;

@ -28,7 +28,6 @@
this.chatName = convo.get('name');
this.chatServer = convo.get('server');
this.channelId = convo.get('channelId');
this.isPublic = !!convo.isPublic();
this.convo = convo;

@ -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;

@ -313,15 +313,6 @@ window.Signal = Signal.setup({
logger: window.log,
});
if (process.env.USE_STUBBED_NETWORK) {
const StubAppDotNetAPI = require('./ts/test/session/integration/stubs/stub_app_dot_net_api');
window.LokiAppDotNetServerAPI = StubAppDotNetAPI;
} else {
window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api');
}
window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
window.LokiFileServerAPI = require('./js/modules/loki_file_server_api');
window.LokiPushNotificationServerApi = require('./js/modules/loki_push_notification_server_api');
window.SwarmPolling = require('./ts/session/snode_api/swarmPolling').SwarmPolling.getInstance();

@ -40,7 +40,6 @@ import { forceRefreshRandomSnodePool } from '../../session/snode_api/snodePool';
import { SwarmPolling } from '../../session/snode_api/swarmPolling';
import { IMAGE_JPEG } from '../../types/MIME';
import { FSv2 } from '../../fileserver';
import { stringToArrayBuffer } from '../../session/utils/String';
import { debounce } from 'underscore';
import { DURATION } from '../../session/constants';
// tslint:disable-next-line: no-import-side-effect no-submodule-imports

@ -23,7 +23,6 @@ import { ToastUtils, UserUtils } from '../../session/utils';
import { DefaultTheme } from 'styled-components';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
import { ConversationController } from '../../session/conversations';
import { OpenGroup } from '../../opengroup/opengroupV1/OpenGroup';
import { ConversationTypeEnum } from '../../models/conversation';
import { openGroupV2CompleteURLRegex } from '../../opengroup/utils/OpenGroupUtils';
import { joinOpenGroupV2WithUIEvents } from '../../opengroup/opengroupV2/JoinOpenGroupV2';
@ -360,51 +359,16 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
}
}
private async handleOpenGroupJoinV1(serverUrlV1: string) {
// Server URL valid?
if (serverUrlV1.length === 0 || !OpenGroup.validate(serverUrlV1)) {
ToastUtils.pushToastError('connectToServer', window.i18n('invalidOpenGroupUrl'));
return;
}
// Already connected?
if (OpenGroup.getConversation(serverUrlV1)) {
ToastUtils.pushToastError('publicChatExists', window.i18n('publicChatExists'));
return;
}
// Connect to server
try {
ToastUtils.pushToastInfo('connectingToServer', window.i18n('connectingToServer'));
this.setState({ loading: true });
await OpenGroup.join(serverUrlV1);
if (await OpenGroup.serverExists(serverUrlV1)) {
ToastUtils.pushToastSuccess(
'connectToServerSuccess',
window.i18n('connectToServerSuccess')
);
} else {
throw new Error('Open group joined but the corresponding server does not exist');
}
this.setState({ loading: false });
const openGroupConversation = OpenGroup.getConversation(serverUrlV1);
if (!openGroupConversation) {
window?.log?.error('Joined an opengroup but did not find ther corresponding conversation');
}
this.handleToggleOverlay(undefined);
} catch (e) {
window?.log?.error('Failed to connect to server:', e);
ToastUtils.pushToastError('connectToServerFail', window.i18n('connectToServerFail'));
this.setState({ loading: false });
}
}
private async handleOpenGroupJoinV2(serverUrlV2: string) {
const loadingCallback = (loading: boolean) => {
this.setState({ loading });
};
const joinSuccess = await joinOpenGroupV2WithUIEvents(serverUrlV2, true, loadingCallback);
const joinSuccess = await joinOpenGroupV2WithUIEvents(
serverUrlV2,
true,
false,
loadingCallback
);
return joinSuccess;
}
@ -423,8 +387,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
this.handleToggleOverlay(undefined);
}
} else {
// this is an open group v1
await this.handleOpenGroupJoinV1(serverUrl);
window.log.warn('Invalid opengroupv2 url');
}
}

@ -101,7 +101,7 @@ export const SessionJoinableRooms = () => {
roomId={r.id}
base64Data={r.base64Data}
onClick={completeUrl => {
void joinOpenGroupV2WithUIEvents(completeUrl, true);
void joinOpenGroupV2WithUIEvents(completeUrl, true, false);
}}
/>
);

@ -399,7 +399,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
overridenQuery = '';
}
if (this.props.isPublic) {
this.fetchUsersForOpenGroup(overridenQuery, callback);
window.log.warn('mentions users for opengroup v2 todo');
return;
}
if (!this.props.isPrivate) {
@ -408,26 +408,6 @@ export class SessionCompositionBox extends React.Component<Props, State> {
}
}
private fetchUsersForOpenGroup(query: any, callback: any) {
void window.lokiPublicChatAPI
.getListOfMembers()
.then(members =>
members
.filter(d => !!d)
.filter(d => d.authorProfileName !== 'Anonymous')
.filter(d => d.authorProfileName?.toLowerCase()?.includes(query.toLowerCase()))
)
// Transform the users to what react-mentions expects
.then(members => {
const toRet = members.map(user => ({
display: user.authorProfileName,
id: user.authorPhoneNumber,
}));
return toRet;
})
.then(callback);
}
private fetchUsersForClosedGroup(query: any, callback: any) {
const { selectedConversation } = this.props;
if (!selectedConversation) {

@ -23,6 +23,7 @@ export type FileServerV2Request = {
};
const FILES_ENDPOINT = 'files';
const RELEASE_VERSION_ENDPOINT = 'session_version';
// Disable this if you don't want to use the file server v2 for sending
// Receiving is always enabled if the attachments url matches a fsv2 url
@ -154,3 +155,33 @@ export const buildUrl = (request: FileServerV2Request | OpenGroupV2Request): URL
return null;
}
};
/**
* Upload a file to the file server v2
* @param fileContent the data to send
* @returns null or the fileID and complete URL to share this file
*/
export const getLatestDesktopReleaseFileToFsV2 = async (): Promise<string | null> => {
const queryParams = {
platform: 'desktop',
};
const request: FileServerV2Request = {
method: 'GET',
endpoint: RELEASE_VERSION_ENDPOINT,
queryParams,
};
const result = await sendApiV2Request(request);
const statusCode = parseStatusCodeFromOnionRequest(result);
if (statusCode !== 200) {
return null;
}
// we should probably change the logic of sendOnionRequest to not have all those levels
const latestVersionWithV = (result as any)?.result?.result as string | undefined;
if (!latestVersionWithV) {
return null;
}
return latestVersionWithV;
};

@ -1,14 +1,12 @@
import _ from 'lodash';
import { getV2OpenGroupRoom } from '../data/opengroups';
import { ConversationModel, ConversationTypeEnum } from '../models/conversation';
import { OpenGroup } from '../opengroup/opengroupV1/OpenGroup';
import { ConversationModel } from '../models/conversation';
import { ApiV2 } from '../opengroup/opengroupV2';
import { joinOpenGroupV2WithUIEvents } from '../opengroup/opengroupV2/JoinOpenGroupV2';
import { isOpenGroupV2, openGroupV2CompleteURLRegex } from '../opengroup/utils/OpenGroupUtils';
import { ConversationController } from '../session/conversations';
import { PubKey } from '../session/types';
import { ToastUtils } from '../session/utils';
import { openConversationExternal } from '../state/ducks/conversations';
export function banUser(userToBan: string, conversation?: ConversationModel) {
let pubKeyToBan: PubKey;
@ -36,12 +34,8 @@ export function banUser(userToBan: string, conversation?: ConversationModel) {
success = await ApiV2.banUser(pubKeyToBan, _.pick(roomInfos, 'serverUrl', 'roomId'));
}
} else {
const channelAPI = await conversation.getPublicSendData();
if (!channelAPI) {
window?.log?.info('cannot ban user, the corresponding channelAPI was not found.');
return;
}
success = await channelAPI.banUser(userToBan);
window?.log?.info('cannot ban user, the not an opengroupv2.');
return;
}
if (success) {
ToastUtils.pushUserBanSuccess();
@ -152,69 +146,22 @@ export async function addSenderAsModerator(sender: string, convoId: string) {
}
}
async function acceptOpenGroupInvitationV1(serverAddress: string) {
try {
if (serverAddress.length === 0 || !OpenGroup.validate(serverAddress)) {
ToastUtils.pushToastError('connectToServer', window.i18n('invalidOpenGroupUrl'));
return;
}
// Already connected?
if (OpenGroup.getConversation(serverAddress)) {
ToastUtils.pushToastError('publicChatExists', window.i18n('publicChatExists'));
return;
}
// To some degree this has been copy-pasted from LeftPaneMessageSection
const rawServerUrl = serverAddress.replace(/^https?:\/\//i, '').replace(/[/\\]+$/i, '');
const sslServerUrl = `https://${rawServerUrl}`;
const conversationId = `publicChat:1@${rawServerUrl}`;
const conversationExists = ConversationController.getInstance().get(conversationId);
if (conversationExists) {
window?.log?.warn('We are already a member of this public chat');
ToastUtils.pushAlreadyMemberOpenGroup();
return;
}
const conversation = await ConversationController.getInstance().getOrCreateAndWait(
conversationId,
ConversationTypeEnum.GROUP
);
await conversation.setPublicSource(sslServerUrl, 1);
const channelAPI = await window.lokiPublicChatAPI.findOrCreateChannel(
sslServerUrl,
1,
conversationId
);
if (!channelAPI) {
window?.log?.warn(`Could not connect to ${serverAddress}`);
return;
}
openConversationExternal(conversationId);
} catch (e) {
window?.log?.warn('failed to join opengroupv1 from invitation', e);
ToastUtils.pushToastError('connectToServerFail', window.i18n('connectToServerFail'));
}
}
const acceptOpenGroupInvitationV2 = (completeUrl: string, roomName?: string) => {
window.confirmationDialog({
title: window.i18n('joinOpenGroupAfterInvitationConfirmationTitle', roomName),
message: window.i18n('joinOpenGroupAfterInvitationConfirmationDesc', roomName),
resolve: () => joinOpenGroupV2WithUIEvents(completeUrl, true),
resolve: () => joinOpenGroupV2WithUIEvents(completeUrl, true, false),
});
// this function does not throw, and will showToasts if anything happens
};
/**
* Accepts a v1 (channelid defaults to 1) url or a v2 url (with pubkey)
* Accepts a v2 url open group invitation (with pubkey) or just log an error
*/
export const acceptOpenGroupInvitation = async (completeUrl: string, roomName?: string) => {
export const acceptOpenGroupInvitation = (completeUrl: string, roomName?: string) => {
if (completeUrl.match(openGroupV2CompleteURLRegex)) {
acceptOpenGroupInvitationV2(completeUrl, roomName);
} else {
await acceptOpenGroupInvitationV1(completeUrl);
window?.log?.warn('Invalid opengroup url:', completeUrl);
}
};

@ -35,7 +35,6 @@ import {
} from '../session/messages/outgoing/visibleMessage/VisibleMessage';
import { GroupInvitationMessage } from '../session/messages/outgoing/visibleMessage/GroupInvitationMessage';
import { ReadReceiptMessage } from '../session/messages/outgoing/controlMessage/receipt/ReadReceiptMessage';
import { OpenGroup } from '../opengroup/opengroupV1/OpenGroup';
import { OpenGroupUtils } from '../opengroup/utils';
import { ConversationInteraction } from '../interactions';
import { OpenGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
@ -77,10 +76,8 @@ export interface ConversationAttributes {
/* Avatar hash is currently used for opengroupv2. it's sha256 hash of the base64 avatar data. */
avatarHash?: string;
server?: any;
channelId?: any;
nickname?: string;
profile?: any;
lastPublicMessage?: any;
profileAvatar?: any;
profileKey?: string;
accessKey?: any;
@ -114,10 +111,8 @@ export interface ConversationAttributesOptionals {
avatar?: any;
avatarHash?: string;
server?: any;
channelId?: any;
nickname?: string;
profile?: any;
lastPublicMessage?: any;
profileAvatar?: any;
profileKey?: string;
accessKey?: any;
@ -1073,57 +1068,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public getNickname() {
return this.get('nickname');
}
// maybe "Backend" instead of "Source"?
public async setPublicSource(newServer: any, newChannelId: any) {
if (!this.isPublic()) {
window?.log?.warn(`trying to setPublicSource on non public chat conversation ${this.id}`);
return;
}
if (this.get('server') !== newServer || this.get('channelId') !== newChannelId) {
// mark active so it's not in the contacts list but in the conversation list
this.set({
server: newServer,
channelId: newChannelId,
active_at: Date.now(),
});
await this.commit();
}
}
public getPublicSource() {
if (!this.isPublic()) {
window?.log?.warn(`trying to getPublicSource on non public chat conversation ${this.id}`);
return null;
}
return {
server: this.get('server'),
channelId: this.get('channelId'),
conversationId: this.get('id'),
};
}
public async getPublicSendData() {
const channelAPI = await window.lokiPublicChatAPI.findOrCreateChannel(
this.get('server'),
this.get('channelId'),
this.id
);
return channelAPI;
}
public getLastRetrievedMessage() {
if (!this.isPublic()) {
return null;
}
const lastMessageId = this.get('lastPublicMessage') || 0;
return lastMessageId;
}
public async setLastRetrievedMessage(newLastMessageId: any) {
if (!this.isPublic()) {
return;
}
if (this.get('lastPublicMessage') !== newLastMessageId) {
this.set({ lastPublicMessage: newLastMessageId });
await this.commit();
}
}
public isAdmin(pubKey?: string) {
if (!this.isPublic()) {
return false;

@ -301,7 +301,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
serverAddress,
direction,
onJoinClick: () => {
void acceptOpenGroupInvitation(invitation.serverAddress, invitation.serverName);
acceptOpenGroupInvitation(invitation.serverAddress, invitation.serverName);
},
};
}

@ -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,8 +1,4 @@
import {
getV2OpenGroupRoomByRoomId,
OpenGroupV2Room,
removeV2OpenGroupRoom,
} from '../../data/opengroups';
import { getV2OpenGroupRoomByRoomId, OpenGroupV2Room } from '../../data/opengroups';
import { ConversationController } from '../../session/conversations';
import { PromiseUtils, ToastUtils } from '../../session/utils';
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
@ -57,7 +53,7 @@ export function parseOpenGroupV2(urlWithPubkey: string): OpenGroupV2Room | undef
* @param room The room id to join
* @param publicKey The server publicKey. It comes from the joining link. (or is already here for the default open group server)
*/
async function joinOpenGroupV2(room: OpenGroupV2Room, fromSyncMessage: boolean): Promise<void> {
async function joinOpenGroupV2(room: OpenGroupV2Room, fromConfigMessage: boolean): Promise<void> {
if (!room.serverUrl || !room.roomId || room.roomId.length < 2 || !room.serverPublicKey) {
return;
}
@ -98,7 +94,7 @@ async function joinOpenGroupV2(room: OpenGroupV2Room, fromSyncMessage: boolean):
// here we managed to connect to the group.
// if this is not a Sync Message, we should trigger one
if (!fromSyncMessage) {
if (!fromConfigMessage) {
await forceSyncConfigurationNowIfNeeded();
}
} catch (e) {
@ -124,6 +120,7 @@ async function joinOpenGroupV2(room: OpenGroupV2Room, fromSyncMessage: boolean):
export async function joinOpenGroupV2WithUIEvents(
completeUrl: string,
showToasts: boolean,
fromConfigMessage: boolean,
uiCallback?: (loading: boolean) => void
): Promise<boolean> {
try {
@ -147,7 +144,7 @@ export async function joinOpenGroupV2WithUIEvents(
if (uiCallback) {
uiCallback(true);
}
await joinOpenGroupV2(parsedRoom, showToasts);
await joinOpenGroupV2(parsedRoom, fromConfigMessage);
const isConvoCreated = ConversationController.getInstance().get(conversationID);
if (isConvoCreated) {

@ -58,86 +58,6 @@ export function getCompleteUrlFromRoom(roomInfos: OpenGroupV2Room) {
return `${roomInfos.serverUrl}/${roomInfos.roomId}?${publicKeyParam}${roomInfos.serverPublicKey}`;
}
/**
* Tries to establish a connection with the specified open group url.
*
* This will try to do an onion routing call if the `useFileOnionRequests` feature flag is set,
* or call directly insecureNodeFetch if it's not.
*
* Returns
* * true if useFileOnionRequests is false and no exception where thrown by insecureNodeFetch
* * true if useFileOnionRequests is true and we established a connection to the server with onion routing
* * false otherwise
*
*/
export const validOpenGroupServer = async (serverUrl: string) => {
// test to make sure it's online (and maybe has a valid SSL cert)
try {
const url = new URL(serverUrl);
if (!window.lokiFeatureFlags.useFileOnionRequests) {
// we are not running with onion request
// this is an insecure insecureNodeFetch. It will expose the user ip to the serverUrl (not onion routed)
window?.log?.info(`insecureNodeFetch => plaintext for ${url.toString()}`);
// we probably have to check the response here
await insecureNodeFetch(serverUrl);
return true;
}
// This MUST be an onion routing call, no nodeFetch calls below here.
/**
* this is safe (as long as node's in your trust model)
*
* First, we need to fetch the open group public key of this open group.
* The fileserver have all the open groups public keys.
* We need the open group public key because for onion routing we will need to encode
* our request with it.
* We can just ask the file-server to get the one for the open group we are trying to add.
*/
const result = await window.tokenlessFileServerAdnAPI.serverRequest(
`loki/v1/getOpenGroupKey/${url.hostname}`
);
if (result.response.meta.code === 200) {
// we got the public key of the server we are trying to add.
// decode it.
const obj = JSON.parse(result.response.data);
const pubKey = window.dcodeIO.ByteBuffer.wrap(obj.data, 'base64').toArrayBuffer();
const pubKeyHex = toHex(pubKey);
// verify we can make an onion routed call to that open group with the decoded public key
// get around the FILESERVER_HOSTS filter by not using serverRequest
const res = await sendViaOnion(pubKeyHex, url, { method: 'GET' }, { noJson: true });
if (res && res.result && res.result.status === 200) {
window?.log?.info(
`loki_public_chat::validOpenGroupServer - onion routing enabled on ${url.toString()}`
);
// save pubkey for use...
window.lokiPublicChatAPI.openGroupPubKeys[serverUrl] = pubKey;
return true;
}
// return here, just so we are sure adding some code below won't do a nodeFetch fallback
return false;
} else if (result.response.meta.code !== 404) {
// unknown error code
window?.log?.warn(
'loki_public_chat::validOpenGroupServer - unknown error code',
result.response.meta
);
}
return false;
} catch (e) {
window?.log?.warn(
`loki_public_chat::validOpenGroupServer - failing to create ${serverUrl}`,
e.code,
e.message
);
// bail out if not valid enough
}
return false;
};
/**
* Prefix server with https:// if it's not already prefixed with http or https.
*/

@ -1,7 +1,11 @@
import _ from 'lodash';
import { createOrUpdateItem, getItemById, hasSyncedInitialConfigurationItem } from '../data/data';
import { ConversationTypeEnum } from '../models/conversation';
import { OpenGroup } from '../opengroup/opengroupV1/OpenGroup';
import {
joinOpenGroupV2WithUIEvents,
parseOpenGroupV2,
} from '../opengroup/opengroupV2/JoinOpenGroupV2';
import { getOpenGroupV2ConversationId } from '../opengroup/utils/OpenGroupUtils';
import { SignalService } from '../protobuf';
import { ConversationController } from '../session/conversations';
import { UserUtils } from '../session/utils';
@ -91,16 +95,22 @@ async function handleGroupsAndContactsFromConfigMessage(
})
);
const allOpenGroups = OpenGroup.getAllAlreadyJoinedOpenGroupsUrl();
const numberOpenGroup = configMessage.openGroups?.length || 0;
// Trigger a join for all open groups we are not already in.
// Currently, if you left an open group but kept the conversation, you won't rejoin it here.
for (let i = 0; i < numberOpenGroup; i++) {
const current = configMessage.openGroups[i];
if (!allOpenGroups.includes(current)) {
window?.log?.info(`triggering join of public chat '${current}' from ConfigurationMessage`);
void OpenGroup.join(current);
const currentOpenGroupUrl = configMessage.openGroups[i];
const parsedRoom = parseOpenGroupV2(currentOpenGroupUrl);
if (!parsedRoom) {
continue;
}
const roomConvoId = getOpenGroupV2ConversationId(parsedRoom.serverUrl, parsedRoom.roomId);
if (!ConversationController.getInstance().get(roomConvoId)) {
window?.log?.info(
`triggering join of public chat '${currentOpenGroupUrl}' from ConfigurationMessage`
);
void joinOpenGroupV2WithUIEvents(currentOpenGroupUrl, false, true);
}
}
if (configMessage.contacts?.length) {

@ -191,14 +191,6 @@ export class ConversationController {
// Closed/Medium group leaving
if (conversation.isClosedGroup()) {
await conversation.leaveClosedGroup();
// open group v1
} else if (conversation.isPublic() && !conversation.isOpenGroupV2()) {
const channelAPI = await conversation.getPublicSendData();
if (channelAPI === null) {
window?.log?.warn(`Could not get API for public conversation ${id}`);
} else {
channelAPI.serverAPI.partChannel((channelAPI as any).channelId);
}
// open group v2
} else if (conversation.isOpenGroupV2()) {
window?.log?.info('leaving open group v2', conversation.id);

@ -34,7 +34,6 @@ import { ClosedGroupNameChangeMessage } from '../messages/outgoing/controlMessag
import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNewMessage';
import { SwarmPolling } from '../snode_api/swarmPolling';
import { ClosedGroupRemovedMembersMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupRemovedMembersMessage';
import { updateOpenGroupV1 } from '../../opengroup/opengroupV1/OpenGroup';
import { updateOpenGroupV2 } from '../../opengroup/opengroupV2/OpenGroupUpdate';
export type GroupInfo = {

@ -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;

@ -19,7 +19,7 @@ describe('GroupInvitationMessage', () => {
});
});
it('dataMessage.groupInvitation has serverAddress, channelId, and serverName set', () => {
it('dataMessage.groupInvitation has serverAddress, and serverName set', () => {
const plainText = message.plainTextBuffer();
const decoded = SignalService.Content.decode(plainText);

@ -20,7 +20,6 @@ import { ClosedGroupEncryptionPairMessage } from '../../../../session/messages/o
import { ClosedGroupNameChangeMessage } from '../../../../session/messages/outgoing/controlMessage/group/ClosedGroupNameChangeMessage';
import { ClosedGroupNewMessage } from '../../../../session/messages/outgoing/controlMessage/group/ClosedGroupNewMessage';
import { ClosedGroupRemovedMembersMessage } from '../../../../session/messages/outgoing/controlMessage/group/ClosedGroupRemovedMembersMessage';
import { OpenGroup } from '../../../../opengroup/opengroupV1/OpenGroup';
import { openGroupPrefix } from '../../../../opengroup/utils/OpenGroupUtils';
const { expect } = chai;

6
ts/window.d.ts vendored

@ -3,7 +3,6 @@ import { LibsignalProtocol } from '../../libtextsecure/libsignal-protocol';
import { SignalInterface } from '../../js/modules/signal';
import { Libloki } from '../libloki';
import { LokiPublicChatFactoryInterface } from '../js/modules/loki_public_chat_api';
import { LokiAppDotNetServerInterface } from '../js/modules/loki_app_dot_net_api';
import { LibTextsecure } from '../libtextsecure';
import { ConfirmationDialogParams } from '../background';
@ -29,8 +28,6 @@ declare global {
Events: any;
Lodash: any;
LokiAppDotNetServerAPI: any;
LokiFileServerAPI: any;
LokiPublicChatAPI: any;
LokiSnodeAPI: any;
Session: any;
Signal: SignalInterface;
@ -58,8 +55,6 @@ declare global {
useRequestEncryptionKeyPair: boolean;
padOutgoingAttachments: boolean;
};
lokiFileServerAPI: LokiFileServerInstance;
lokiPublicChatAPI: LokiPublicChatFactoryInterface;
lokiSnodeAPI: LokiSnodeAPI;
onLogin: any;
resetDatabase: any;
@ -77,7 +72,6 @@ declare global {
toggleMenuBar: any;
toggleSpellCheck: any;
setTheme: (newTheme: string) => any;
tokenlessFileServerAdnAPI: LokiAppDotNetServerInterface;
userConfig: any;
versionInfo: any;
getStoragePubKey: (key: string) => string;

Loading…
Cancel
Save