mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Merge remote-tracking branch 'upstream/dev' into open_groups_V2
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java # app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java # app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt # app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt # app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt # app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt # libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt # libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt # libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt # libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt # libsession/src/main/java/org/session/libsession/utilities/mentions/Mention.kt # libsignal/src/main/java/org/session/libsignal/service/loki/Mention.kt # libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/Mention.kt
This commit is contained in:
commit
fa528c47d5
20
app/src/main/java/org/thoughtcrime/securesms/AppContext.kt
Normal file
20
app/src/main/java/org/thoughtcrime/securesms/AppContext.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package org.thoughtcrime.securesms
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import nl.komponents.kovenant.Kovenant
|
||||||
|
import nl.komponents.kovenant.jvm.asDispatcher
|
||||||
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
object AppContext {
|
||||||
|
|
||||||
|
fun configureKovenant() {
|
||||||
|
Kovenant.context {
|
||||||
|
callbackContext.dispatcher = Executors.newSingleThreadExecutor().asDispatcher()
|
||||||
|
workerContext.dispatcher = ThreadUtils.executorPool.asDispatcher()
|
||||||
|
multipleCompletion = { v1, v2 ->
|
||||||
|
Log.d("Loki", "Promise resolved more than once (first with $v1, then with $v2); ignoring $v2.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,7 @@ import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWr
|
|||||||
import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
|
import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
|
||||||
import org.session.libsession.utilities.preferences.ProfileKeyUtil;
|
import org.session.libsession.utilities.preferences.ProfileKeyUtil;
|
||||||
import org.session.libsignal.service.api.util.StreamDetails;
|
import org.session.libsignal.service.api.util.StreamDetails;
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
|
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol;
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
import org.session.libsignal.utilities.logging.Log;
|
||||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||||
@ -93,7 +93,6 @@ import org.webrtc.voiceengine.WebRtcAudioUtils;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -104,6 +103,7 @@ import dagger.ObjectGraph;
|
|||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import kotlinx.coroutines.Job;
|
import kotlinx.coroutines.Job;
|
||||||
import network.loki.messenger.BuildConfig;
|
import network.loki.messenger.BuildConfig;
|
||||||
|
import nl.komponents.kovenant.Kovenant;
|
||||||
|
|
||||||
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
|
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
|
||||||
import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
|
import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
|
||||||
@ -164,6 +164,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||||
// Loki
|
// Loki
|
||||||
// ========
|
// ========
|
||||||
|
AppContext.INSTANCE.configureKovenant();
|
||||||
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
|
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
|
||||||
broadcaster = new Broadcaster(this);
|
broadcaster = new Broadcaster(this);
|
||||||
threadNotificationHandler = new Handler(Looper.getMainLooper());
|
threadNotificationHandler = new Handler(Looper.getMainLooper());
|
||||||
|
@ -84,6 +84,7 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
keyCachingService.setMasterSecret(new Object());
|
||||||
keyCachingService = null;
|
keyCachingService = null;
|
||||||
}
|
}
|
||||||
}, Context.BIND_AUTO_CREATE);
|
}, Context.BIND_AUTO_CREATE);
|
||||||
@ -133,7 +134,9 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
|
|||||||
private void handleAuthenticated() {
|
private void handleAuthenticated() {
|
||||||
authenticated = true;
|
authenticated = true;
|
||||||
//TODO Replace with a proper call.
|
//TODO Replace with a proper call.
|
||||||
keyCachingService.setMasterSecret(new Object());
|
if (keyCachingService != null) {
|
||||||
|
keyCachingService.setMasterSecret(new Object());
|
||||||
|
}
|
||||||
|
|
||||||
// Finish and proceed with the next intent.
|
// Finish and proceed with the next intent.
|
||||||
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
|
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
|
||||||
@ -188,6 +191,8 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
|
|||||||
|
|
||||||
if (!keyguardManager.isKeyguardSecure()) {
|
if (!keyguardManager.isKeyguardSecure()) {
|
||||||
Log.w(TAG ,"Keyguard not secure...");
|
Log.w(TAG ,"Keyguard not secure...");
|
||||||
|
TextSecurePreferences.setScreenLockEnabled(getApplicationContext(), false);
|
||||||
|
TextSecurePreferences.setScreenLockTimeout(getApplicationContext(), 0);
|
||||||
handleAuthenticated();
|
handleAuthenticated();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -109,10 +109,10 @@ import org.session.libsession.utilities.TextSecurePreferences;
|
|||||||
import org.session.libsession.utilities.Util;
|
import org.session.libsession.utilities.Util;
|
||||||
import org.session.libsession.utilities.ViewUtil;
|
import org.session.libsession.utilities.ViewUtil;
|
||||||
import org.session.libsession.utilities.concurrent.AssertedSuccessListener;
|
import org.session.libsession.utilities.concurrent.AssertedSuccessListener;
|
||||||
import org.session.libsession.utilities.mentions.Mention;
|
|
||||||
import org.session.libsession.utilities.views.Stub;
|
import org.session.libsession.utilities.views.Stub;
|
||||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
|
import org.session.libsignal.service.loki.Mention;
|
||||||
import org.session.libsignal.service.loki.utilities.HexEncodingKt;
|
import org.session.libsignal.service.loki.utilities.HexEncodingKt;
|
||||||
import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
|
import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
|
||||||
import org.session.libsignal.utilities.concurrent.ListenableFuture;
|
import org.session.libsignal.utilities.concurrent.ListenableFuture;
|
||||||
|
@ -25,10 +25,9 @@ import org.session.libsession.utilities.Util;
|
|||||||
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
|
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
|
||||||
import org.session.libsignal.service.loki.database.LokiOpenGroupDatabaseProtocol;
|
import org.session.libsignal.service.loki.LokiOpenGroupDatabaseProtocol;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -44,6 +43,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
static final String GROUP_ID = "group_id";
|
static final String GROUP_ID = "group_id";
|
||||||
private static final String TITLE = "title";
|
private static final String TITLE = "title";
|
||||||
private static final String MEMBERS = "members";
|
private static final String MEMBERS = "members";
|
||||||
|
private static final String ZOMBIE_MEMBERS = "zombie_members";
|
||||||
private static final String AVATAR = "avatar";
|
private static final String AVATAR = "avatar";
|
||||||
private static final String AVATAR_ID = "avatar_id";
|
private static final String AVATAR_ID = "avatar_id";
|
||||||
private static final String AVATAR_KEY = "avatar_key";
|
private static final String AVATAR_KEY = "avatar_key";
|
||||||
@ -64,6 +64,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
GROUP_ID + " TEXT, " +
|
GROUP_ID + " TEXT, " +
|
||||||
TITLE + " TEXT, " +
|
TITLE + " TEXT, " +
|
||||||
MEMBERS + " TEXT, " +
|
MEMBERS + " TEXT, " +
|
||||||
|
ZOMBIE_MEMBERS + " TEXT, " +
|
||||||
AVATAR + " BLOB, " +
|
AVATAR + " BLOB, " +
|
||||||
AVATAR_ID + " INTEGER, " +
|
AVATAR_ID + " INTEGER, " +
|
||||||
AVATAR_KEY + " BLOB, " +
|
AVATAR_KEY + " BLOB, " +
|
||||||
@ -81,7 +82,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] GROUP_PROJECTION = {
|
private static final String[] GROUP_PROJECTION = {
|
||||||
GROUP_ID, TITLE, MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
|
GROUP_ID, TITLE, MEMBERS, ZOMBIE_MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
|
||||||
TIMESTAMP, ACTIVE, MMS, AVATAR_URL, ADMINS
|
TIMESTAMP, ACTIVE, MMS, AVATAR_URL, ADMINS
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull List<Recipient> getGroupMembers(String groupId, boolean includeSelf) {
|
public @NonNull List<Recipient> getGroupMembers(String groupId, boolean includeSelf) {
|
||||||
List<Address> members = getCurrentMembers(groupId);
|
List<Address> members = getCurrentMembers(groupId, false);
|
||||||
List<Recipient> recipients = new LinkedList<>();
|
List<Recipient> recipients = new LinkedList<>();
|
||||||
|
|
||||||
for (Address member : members) {
|
for (Address member : members) {
|
||||||
@ -177,6 +178,17 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
return recipients;
|
return recipients;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull List<Recipient> getGroupZombieMembers(String groupId) {
|
||||||
|
List<Address> members = getCurrentZombieMembers(groupId);
|
||||||
|
List<Recipient> recipients = new LinkedList<>();
|
||||||
|
|
||||||
|
for (Address member : members) {
|
||||||
|
recipients.add(Recipient.from(context, member, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipients;
|
||||||
|
}
|
||||||
|
|
||||||
public long create(@NonNull String groupId, @Nullable String title, @NonNull List<Address> members,
|
public long create(@NonNull String groupId, @Nullable String title, @NonNull List<Address> members,
|
||||||
@Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay, @Nullable List<Address> admins, @NonNull Long formationTimestamp)
|
@Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay, @Nullable List<Address> admins, @NonNull Long formationTimestamp)
|
||||||
{
|
{
|
||||||
@ -300,6 +312,16 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateZombieMembers(String groupId, List<Address> members) {
|
||||||
|
Collections.sort(members);
|
||||||
|
|
||||||
|
ContentValues contents = new ContentValues();
|
||||||
|
contents.put(ZOMBIE_MEMBERS, Address.toSerializedList(members, ','));
|
||||||
|
contents.put(ACTIVE, 1);
|
||||||
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
|
||||||
|
new String[] {groupId});
|
||||||
|
}
|
||||||
|
|
||||||
public void updateAdmins(String groupId, List<Address> admins) {
|
public void updateAdmins(String groupId, List<Address> admins) {
|
||||||
Collections.sort(admins);
|
Collections.sort(admins);
|
||||||
|
|
||||||
@ -311,7 +333,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removeMember(String groupId, Address source) {
|
public void removeMember(String groupId, Address source) {
|
||||||
List<Address> currentMembers = getCurrentMembers(groupId);
|
List<Address> currentMembers = getCurrentMembers(groupId, false);
|
||||||
currentMembers.remove(source);
|
currentMembers.remove(source);
|
||||||
|
|
||||||
ContentValues contents = new ContentValues();
|
ContentValues contents = new ContentValues();
|
||||||
@ -329,18 +351,22 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Address> getCurrentMembers(String groupId) {
|
private List<Address> getCurrentMembers(String groupId, boolean zombieMembers) {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
|
|
||||||
|
String membersColumn = MEMBERS;
|
||||||
|
if (zombieMembers) membersColumn = ZOMBIE_MEMBERS;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {MEMBERS},
|
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {membersColumn},
|
||||||
GROUP_ID + " = ?",
|
GROUP_ID + " = ?",
|
||||||
new String[] {groupId},
|
new String[] {groupId},
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS));
|
String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow(membersColumn));
|
||||||
return Address.fromSerializedList(serializedMembers, ',');
|
if (serializedMembers != null && !serializedMembers.isEmpty())
|
||||||
|
return Address.fromSerializedList(serializedMembers, ',');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LinkedList<>();
|
return new LinkedList<>();
|
||||||
@ -350,6 +376,10 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Address> getCurrentZombieMembers(String groupId) {
|
||||||
|
return getCurrentMembers(groupId, true);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isActive(String groupId) {
|
public boolean isActive(String groupId) {
|
||||||
Optional<GroupRecord> record = getGroup(groupId);
|
Optional<GroupRecord> record = getGroup(groupId);
|
||||||
return record.isPresent() && record.get().isActive();
|
return record.isPresent() && record.get().isActive();
|
||||||
|
@ -455,6 +455,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
DatabaseFactory.getGroupDatabase(context).setActive(groupID, value)
|
DatabaseFactory.getGroupDatabase(context).setActive(groupID, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getZombieMember(groupID: String): Set<String> {
|
||||||
|
return DatabaseFactory.getGroupDatabase(context).getGroupZombieMembers(groupID).map { it.address.serialize() }.toHashSet()
|
||||||
|
}
|
||||||
|
|
||||||
override fun removeMember(groupID: String, member: Address) {
|
override fun removeMember(groupID: String, member: Address) {
|
||||||
DatabaseFactory.getGroupDatabase(context).removeMember(groupID, member)
|
DatabaseFactory.getGroupDatabase(context).removeMember(groupID, member)
|
||||||
}
|
}
|
||||||
@ -463,6 +467,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members)
|
DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateZombieMembers(groupID: String, members: List<Address>) {
|
||||||
|
DatabaseFactory.getGroupDatabase(context).updateZombieMembers(groupID, members)
|
||||||
|
}
|
||||||
|
|
||||||
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
|
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
|
||||||
val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
|
val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
|
||||||
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
|
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
|
||||||
|
@ -276,6 +276,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < lokiV23) {
|
if (oldVersion < lokiV23) {
|
||||||
|
db.execSQL("ALTER TABLE groups ADD COLUMN zombie_members TEXT");
|
||||||
db.execSQL(LokiMessageDatabase.getUpdateMessageIDTableForType());
|
db.execSQL(LokiMessageDatabase.getUpdateMessageIDTableForType());
|
||||||
db.execSQL(LokiMessageDatabase.getUpdateMessageMappingTable());
|
db.execSQL(LokiMessageDatabase.getUpdateMessageMappingTable());
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,14 @@ import java.io.IOException
|
|||||||
|
|
||||||
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
||||||
private val originalMembers = HashSet<String>()
|
private val originalMembers = HashSet<String>()
|
||||||
|
private val zombies = HashSet<String>()
|
||||||
private val members = HashSet<String>()
|
private val members = HashSet<String>()
|
||||||
|
private val allMembers: Set<String>
|
||||||
|
get() {
|
||||||
|
return members + zombies
|
||||||
|
}
|
||||||
private var hasNameChanged = false
|
private var hasNameChanged = false
|
||||||
|
private var isSelfAdmin = false
|
||||||
private var isLoading = false
|
private var isLoading = false
|
||||||
set(newValue) { field = newValue; invalidateOptionsMenu() }
|
set(newValue) { field = newValue; invalidateOptionsMenu() }
|
||||||
|
|
||||||
@ -54,7 +60,10 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val memberListAdapter by lazy {
|
private val memberListAdapter by lazy {
|
||||||
EditClosedGroupMembersAdapter(this, GlideApp.with(this), this::onMemberClick)
|
if (isSelfAdmin)
|
||||||
|
EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin, this::onMemberClick)
|
||||||
|
else
|
||||||
|
EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var mainContentContainer: LinearLayout
|
private lateinit var mainContentContainer: LinearLayout
|
||||||
@ -81,7 +90,10 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
ThemeUtil.getThemedDrawableResId(this, R.attr.actionModeCloseDrawable))
|
ThemeUtil.getThemedDrawableResId(this, R.attr.actionModeCloseDrawable))
|
||||||
|
|
||||||
groupID = intent.getStringExtra(groupIDKey)!!
|
groupID = intent.getStringExtra(groupIDKey)!!
|
||||||
originalName = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().title
|
val groupInfo = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get()
|
||||||
|
originalName = groupInfo.title
|
||||||
|
isSelfAdmin = groupInfo.admins.any{ it.serialize() == TextSecurePreferences.getLocalNumber(this) }
|
||||||
|
|
||||||
name = originalName
|
name = originalName
|
||||||
|
|
||||||
mainContentContainer = findViewById(R.id.mainContentContainer)
|
mainContentContainer = findViewById(R.id.mainContentContainer)
|
||||||
@ -116,31 +128,35 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LoaderManager.getInstance(this).initLoader(loaderID, null, object : LoaderManager.LoaderCallbacks<List<String>> {
|
LoaderManager.getInstance(this).initLoader(loaderID, null, object : LoaderManager.LoaderCallbacks<GroupMembers> {
|
||||||
|
|
||||||
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> {
|
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<GroupMembers> {
|
||||||
return EditClosedGroupLoader(this@EditClosedGroupActivity, groupID)
|
return EditClosedGroupLoader(this@EditClosedGroupActivity, groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) {
|
override fun onLoadFinished(loader: Loader<GroupMembers>, groupMembers: GroupMembers) {
|
||||||
// We no longer need any subsequent loading events
|
// We no longer need any subsequent loading events
|
||||||
// (they will occur on every activity resume).
|
// (they will occur on every activity resume).
|
||||||
LoaderManager.getInstance(this@EditClosedGroupActivity).destroyLoader(loaderID)
|
LoaderManager.getInstance(this@EditClosedGroupActivity).destroyLoader(loaderID)
|
||||||
|
|
||||||
|
members.clear()
|
||||||
|
members.addAll(groupMembers.members.toHashSet())
|
||||||
|
zombies.clear()
|
||||||
|
zombies.addAll(groupMembers.zombieMembers.toHashSet())
|
||||||
originalMembers.clear()
|
originalMembers.clear()
|
||||||
originalMembers.addAll(members.toHashSet())
|
originalMembers.addAll(members + zombies)
|
||||||
updateMembers(originalMembers)
|
updateMembers()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoaderReset(loader: Loader<List<String>>) {
|
override fun onLoaderReset(loader: Loader<GroupMembers>) {
|
||||||
updateMembers(setOf())
|
updateMembers()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_edit_closed_group, menu)
|
menuInflater.inflate(R.menu.menu_edit_closed_group, menu)
|
||||||
return members.isNotEmpty() && !isLoading
|
return allMembers.isNotEmpty() && !isLoading
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@ -153,8 +169,8 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
if (data == null || data.extras == null || !data.hasExtra(SelectContactsActivity.selectedContactsKey)) return
|
if (data == null || data.extras == null || !data.hasExtra(SelectContactsActivity.selectedContactsKey)) return
|
||||||
|
|
||||||
val selectedContacts = data.extras!!.getStringArray(SelectContactsActivity.selectedContactsKey)!!.toSet()
|
val selectedContacts = data.extras!!.getStringArray(SelectContactsActivity.selectedContactsKey)!!.toSet()
|
||||||
val changedMembers = members + selectedContacts
|
members.addAll(selectedContacts)
|
||||||
updateMembers(changedMembers)
|
updateMembers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,17 +189,12 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMembers(members: Set<String>) {
|
private fun updateMembers() {
|
||||||
this.members.clear()
|
memberListAdapter.setMembers(allMembers)
|
||||||
this.members.addAll(members)
|
memberListAdapter.setZombieMembers(zombies)
|
||||||
memberListAdapter.setMembers(members)
|
|
||||||
|
|
||||||
val admins = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().admins.map { it.toString() }.toMutableSet()
|
mainContentContainer.visibility = if (allMembers.isEmpty()) View.GONE else View.VISIBLE
|
||||||
admins.remove(TextSecurePreferences.getLocalNumber(this))
|
emptyStateContainer.visibility = if (allMembers.isEmpty()) View.VISIBLE else View.GONE
|
||||||
memberListAdapter.setLockedMembers(admins)
|
|
||||||
|
|
||||||
mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
|
|
||||||
emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
|
|
||||||
|
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
@ -200,8 +211,9 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
private fun onMemberClick(member: String) {
|
private fun onMemberClick(member: String) {
|
||||||
val bottomSheet = ClosedGroupEditingOptionsBottomSheet()
|
val bottomSheet = ClosedGroupEditingOptionsBottomSheet()
|
||||||
bottomSheet.onRemoveTapped = {
|
bottomSheet.onRemoveTapped = {
|
||||||
val changedMembers = members - member
|
if (zombies.contains(member)) zombies.remove(member)
|
||||||
updateMembers(changedMembers)
|
else members.remove(member)
|
||||||
|
updateMembers()
|
||||||
bottomSheet.dismiss()
|
bottomSheet.dismiss()
|
||||||
}
|
}
|
||||||
bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet")
|
bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet")
|
||||||
@ -209,7 +221,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
private fun onAddMembersClick() {
|
private fun onAddMembersClick() {
|
||||||
val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java)
|
val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java)
|
||||||
intent.putExtra(SelectContactsActivity.usersToExcludeKey, members.toTypedArray())
|
intent.putExtra(SelectContactsActivity.usersToExcludeKey, allMembers.toTypedArray())
|
||||||
intent.putExtra(SelectContactsActivity.emptyStateTextKey, "No contacts to add")
|
intent.putExtra(SelectContactsActivity.emptyStateTextKey, "No contacts to add")
|
||||||
startActivityForResult(intent, addUsersRequestCode)
|
startActivityForResult(intent, addUsersRequestCode)
|
||||||
}
|
}
|
||||||
@ -229,7 +241,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun commitChanges() {
|
private fun commitChanges() {
|
||||||
val hasMemberListChanges = (members != originalMembers)
|
val hasMemberListChanges = (allMembers != originalMembers)
|
||||||
|
|
||||||
if (!hasNameChanged && !hasMemberListChanges) {
|
if (!hasNameChanged && !hasMemberListChanges) {
|
||||||
return finish()
|
return finish()
|
||||||
@ -237,15 +249,13 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
val name = if (hasNameChanged) this.name else originalName
|
val name = if (hasNameChanged) this.name else originalName
|
||||||
|
|
||||||
val members = this.members.map {
|
val members = this.allMembers.map {
|
||||||
Recipient.from(this, Address.fromSerialized(it), false)
|
Recipient.from(this, Address.fromSerialized(it), false)
|
||||||
}.toSet()
|
}.toSet()
|
||||||
val originalMembers = this.originalMembers.map {
|
val originalMembers = this.originalMembers.map {
|
||||||
Recipient.from(this, Address.fromSerialized(it), false)
|
Recipient.from(this, Address.fromSerialized(it), false)
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
val admins = members.toSet() //TODO For now, consider all the users to be admins.
|
|
||||||
|
|
||||||
var isClosedGroup: Boolean
|
var isClosedGroup: Boolean
|
||||||
var groupPublicKey: String?
|
var groupPublicKey: String?
|
||||||
try {
|
try {
|
||||||
@ -303,4 +313,6 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GroupMembers(val members: List<String>, val zombieMembers: List<String>) { }
|
||||||
}
|
}
|
@ -4,12 +4,19 @@ import android.content.Context
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.util.AsyncLoader
|
import org.thoughtcrime.securesms.util.AsyncLoader
|
||||||
|
|
||||||
class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader<List<String>>(context) {
|
class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader<EditClosedGroupActivity.GroupMembers>(context) {
|
||||||
|
|
||||||
override fun loadInBackground(): List<String> {
|
override fun loadInBackground(): EditClosedGroupActivity.GroupMembers {
|
||||||
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true)
|
val groupDatabase = DatabaseFactory.getGroupDatabase(context)
|
||||||
return members.map {
|
val members = groupDatabase.getGroupMembers(groupID, true)
|
||||||
it.address.toString()
|
val zombieMembers = groupDatabase.getGroupZombieMembers(groupID)
|
||||||
}
|
return EditClosedGroupActivity.GroupMembers(
|
||||||
|
members.map {
|
||||||
|
it.address.toString()
|
||||||
|
},
|
||||||
|
zombieMembers.map {
|
||||||
|
it.address.toString()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,15 +7,17 @@ import org.session.libsession.messaging.threads.Address
|
|||||||
import org.thoughtcrime.securesms.loki.views.UserView
|
import org.thoughtcrime.securesms.loki.views.UserView
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
|
||||||
class EditClosedGroupMembersAdapter(
|
class EditClosedGroupMembersAdapter(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val glide: GlideRequests,
|
private val glide: GlideRequests,
|
||||||
|
private val admin: Boolean,
|
||||||
private val memberClickListener: ((String) -> Unit)? = null
|
private val memberClickListener: ((String) -> Unit)? = null
|
||||||
) : RecyclerView.Adapter<EditClosedGroupMembersAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<EditClosedGroupMembersAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private val members = ArrayList<String>()
|
private val members = ArrayList<String>()
|
||||||
private val lockedMembers = HashSet<String>()
|
private val zombieMembers = ArrayList<String>()
|
||||||
|
|
||||||
fun setMembers(members: Collection<String>) {
|
fun setMembers(members: Collection<String>) {
|
||||||
this.members.clear()
|
this.members.clear()
|
||||||
@ -23,9 +25,9 @@ class EditClosedGroupMembersAdapter(
|
|||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setLockedMembers(members: Collection<String>) {
|
fun setZombieMembers(members: Collection<String>) {
|
||||||
this.lockedMembers.clear()
|
this.zombieMembers.clear()
|
||||||
this.lockedMembers.addAll(members)
|
this.zombieMembers.addAll(members)
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,15 +41,20 @@ class EditClosedGroupMembersAdapter(
|
|||||||
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
|
||||||
val member = members[position]
|
val member = members[position]
|
||||||
|
|
||||||
val lockedMember = lockedMembers.contains(member)
|
val unlocked = admin && member != TextSecurePreferences.getLocalNumber(context)
|
||||||
|
|
||||||
viewHolder.view.bind(Recipient.from(
|
viewHolder.view.bind(Recipient.from(
|
||||||
context,
|
context,
|
||||||
Address.fromSerialized(member), false),
|
Address.fromSerialized(member), false),
|
||||||
glide,
|
glide,
|
||||||
if (lockedMember) UserView.ActionIndicator.None else UserView.ActionIndicator.Menu)
|
if (unlocked) UserView.ActionIndicator.Menu else UserView.ActionIndicator.None)
|
||||||
|
|
||||||
if (!lockedMember) {
|
if (zombieMembers.contains(member))
|
||||||
|
viewHolder.view.alpha = 0.5F
|
||||||
|
else
|
||||||
|
viewHolder.view.alpha = 1F
|
||||||
|
|
||||||
|
if (unlocked) {
|
||||||
viewHolder.view.setOnClickListener { this.memberClickListener?.invoke(member) }
|
viewHolder.view.setOnClickListener { this.memberClickListener?.invoke(member) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import kotlinx.coroutines.launch
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.libsignal.util.KeyHelper
|
import org.session.libsignal.libsignal.util.KeyHelper
|
||||||
import org.session.libsignal.service.loki.crypto.MnemonicCodec
|
import org.session.libsignal.service.loki.MnemonicCodec
|
||||||
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
@ -45,7 +45,7 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel
|
|||||||
private var restoreJob: Job? = null
|
private var restoreJob: Job? = null
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (restoreJob?.isActive == true) return // don't allow going back with pending job
|
if (restoreJob?.isActive == true) return // Don't allow going back with a pending job
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,14 +53,12 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setUpActionBarSessionLogo()
|
setUpActionBarSessionLogo()
|
||||||
// Set the registration sync variables
|
|
||||||
TextSecurePreferences.apply {
|
TextSecurePreferences.apply {
|
||||||
setHasViewedSeed(this@LinkDeviceActivity, true)
|
setHasViewedSeed(this@LinkDeviceActivity, true)
|
||||||
setConfigurationMessageSynced(this@LinkDeviceActivity, false)
|
setConfigurationMessageSynced(this@LinkDeviceActivity, false)
|
||||||
setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis())
|
setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis())
|
||||||
setLastProfileUpdateTime(this@LinkDeviceActivity, 0)
|
setLastProfileUpdateTime(this@LinkDeviceActivity, 0)
|
||||||
}
|
}
|
||||||
// registration variables are synced
|
|
||||||
setContentView(R.layout.activity_link_device)
|
setContentView(R.layout.activity_link_device)
|
||||||
viewPager.adapter = adapter
|
viewPager.adapter = adapter
|
||||||
tabLayout.setupWithViewPager(viewPager)
|
tabLayout.setupWithViewPager(viewPager)
|
||||||
|
@ -15,7 +15,7 @@ import kotlinx.android.synthetic.main.activity_recovery_phrase_restore.*
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.libsignal.util.KeyHelper
|
import org.session.libsignal.libsignal.util.KeyHelper
|
||||||
import org.session.libsignal.service.loki.crypto.MnemonicCodec
|
import org.session.libsignal.service.loki.MnemonicCodec
|
||||||
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
@ -30,14 +30,12 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setUpActionBarSessionLogo()
|
setUpActionBarSessionLogo()
|
||||||
// Set the registration sync variables
|
|
||||||
TextSecurePreferences.apply {
|
TextSecurePreferences.apply {
|
||||||
setHasViewedSeed(this@RecoveryPhraseRestoreActivity, true)
|
setHasViewedSeed(this@RecoveryPhraseRestoreActivity, true)
|
||||||
setConfigurationMessageSynced(this@RecoveryPhraseRestoreActivity, false)
|
setConfigurationMessageSynced(this@RecoveryPhraseRestoreActivity, false)
|
||||||
setRestorationTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis())
|
setRestorationTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis())
|
||||||
setLastProfileUpdateTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis())
|
setLastProfileUpdateTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
// registration variables are synced
|
|
||||||
setContentView(R.layout.activity_recovery_phrase_restore)
|
setContentView(R.layout.activity_recovery_phrase_restore)
|
||||||
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
|
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
|
||||||
restoreButton.setOnClickListener { restore() }
|
restoreButton.setOnClickListener { restore() }
|
||||||
|
@ -39,14 +39,12 @@ class RegisterActivity : BaseActionBarActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_register)
|
setContentView(R.layout.activity_register)
|
||||||
setUpActionBarSessionLogo()
|
setUpActionBarSessionLogo()
|
||||||
// Set the registration sync variables
|
|
||||||
TextSecurePreferences.apply {
|
TextSecurePreferences.apply {
|
||||||
setHasViewedSeed(this@RegisterActivity, false)
|
setHasViewedSeed(this@RegisterActivity, false)
|
||||||
setConfigurationMessageSynced(this@RegisterActivity, true)
|
setConfigurationMessageSynced(this@RegisterActivity, true)
|
||||||
setRestorationTime(this@RegisterActivity, 0)
|
setRestorationTime(this@RegisterActivity, 0)
|
||||||
setLastProfileUpdateTime(this@RegisterActivity, System.currentTimeMillis())
|
setLastProfileUpdateTime(this@RegisterActivity, System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
// registration variables are synced
|
|
||||||
registerButton.setOnClickListener { register() }
|
registerButton.setOnClickListener { register() }
|
||||||
copyButton.setOnClickListener { copyPublicKey() }
|
copyButton.setOnClickListener { copyPublicKey() }
|
||||||
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy")
|
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy")
|
||||||
|
@ -16,7 +16,7 @@ import org.session.libsession.utilities.IdentityKeyUtil
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.service.loki.crypto.MnemonicCodec
|
import org.session.libsignal.service.loki.MnemonicCodec
|
||||||
import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey
|
import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey
|
||||||
|
|
||||||
class SeedActivity : BaseActionBarActivity() {
|
class SeedActivity : BaseActionBarActivity() {
|
||||||
|
@ -8,7 +8,7 @@ import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
|
|||||||
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||||
import org.session.libsignal.service.loki.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
|
@ -2,12 +2,11 @@ package org.thoughtcrime.securesms.loki.database
|
|||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.session.libsignal.service.loki.database.LokiMessageDatabaseProtocol
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
|
||||||
import org.thoughtcrime.securesms.database.Database
|
import org.thoughtcrime.securesms.database.Database
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.*
|
||||||
|
import org.session.libsignal.service.loki.LokiMessageDatabaseProtocol
|
||||||
|
|
||||||
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
|
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.loki.utilities.get
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
|
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
|
import org.session.libsignal.service.loki.LokiUserDatabaseProtocol
|
||||||
|
|
||||||
class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiUserDatabaseProtocol {
|
class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiUserDatabaseProtocol {
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import kotlinx.android.synthetic.main.dialog_seed.view.*
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.IdentityKeyUtil
|
import org.session.libsession.utilities.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
||||||
import org.session.libsignal.service.loki.crypto.MnemonicCodec
|
import org.session.libsignal.service.loki.MnemonicCodec
|
||||||
import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey
|
import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.messages.Destination
|
import org.session.libsession.messaging.messages.Destination
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
|
@ -3,8 +3,9 @@ package org.thoughtcrime.securesms.loki.utilities
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import org.session.libsignal.service.loki.Broadcaster
|
||||||
|
|
||||||
class Broadcaster(private val context: Context) : org.session.libsignal.service.loki.utilities.Broadcaster {
|
class Broadcaster(private val context: Context) : Broadcaster {
|
||||||
|
|
||||||
override fun broadcast(event: String) {
|
override fun broadcast(event: String) {
|
||||||
val intent = Intent(event)
|
val intent = Intent(event)
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.loki.utilities
|
package org.thoughtcrime.securesms.loki.utilities
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.session.libsignal.service.loki.crypto.MnemonicCodec
|
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
object MnemonicUtilities {
|
object MnemonicUtilities {
|
||||||
|
|
||||||
|
@ -7,10 +7,10 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.BaseAdapter
|
import android.widget.BaseAdapter
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
import org.session.libsession.utilities.mentions.Mention
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
import org.session.libsignal.service.loki.Mention
|
||||||
|
|
||||||
class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
|
class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
|
||||||
private var mentionCandidates = listOf<Mention>()
|
private var mentionCandidates = listOf<Mention>()
|
||||||
|
@ -9,7 +9,7 @@ import android.widget.LinearLayout
|
|||||||
import kotlinx.android.synthetic.main.view_mention_candidate.view.*
|
import kotlinx.android.synthetic.main.view_mention_candidate.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupAPI
|
import org.session.libsession.messaging.open_groups.OpenGroupAPI
|
||||||
import org.session.libsession.utilities.mentions.Mention
|
import org.session.libsignal.service.loki.Mention
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
|
||||||
class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
@ -146,13 +146,13 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) {
|
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) {
|
||||||
if (publicKey.isNotEmpty()) {
|
if (publicKey.isNotEmpty()) {
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||||
if (imagesCached.contains(recipient.profileAvatar.orEmpty())) return
|
if (imagesCached.contains(publicKey)) return
|
||||||
val signalProfilePicture = recipient.contactPhoto
|
val signalProfilePicture = recipient.contactPhoto
|
||||||
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0"
|
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0"
|
||||||
&& (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
|
&& (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
|
||||||
glide.clear(imageView)
|
glide.clear(imageView)
|
||||||
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView)
|
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView)
|
||||||
imagesCached.add(recipient.profileAvatar.orEmpty())
|
imagesCached.add(publicKey)
|
||||||
} else {
|
} else {
|
||||||
val sizeInPX = resources.getDimensionPixelSize(sizeResId)
|
val sizeInPX = resources.getDimensionPixelSize(sizeResId)
|
||||||
glide.clear(imageView)
|
glide.clear(imageView)
|
||||||
@ -162,7 +162,7 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
publicKey,
|
publicKey,
|
||||||
displayName
|
displayName
|
||||||
)).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
)).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
||||||
imagesCached.add(recipient.profileAvatar.orEmpty())
|
imagesCached.add(publicKey)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
imageView.setImageDrawable(null)
|
imageView.setImageDrawable(null)
|
||||||
|
@ -102,7 +102,8 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
|||||||
if (duration == 0) {
|
if (duration == 0) {
|
||||||
TextSecurePreferences.setScreenLockTimeout(getContext(), 0);
|
TextSecurePreferences.setScreenLockTimeout(getContext(), 0);
|
||||||
} else {
|
} else {
|
||||||
long timeoutSeconds = Math.max(TimeUnit.MILLISECONDS.toSeconds(duration), 60);
|
long timeoutSeconds = TimeUnit.MILLISECONDS.toSeconds(duration);
|
||||||
|
// long timeoutSeconds = Math.max(TimeUnit.MILLISECONDS.toSeconds(duration), 60);
|
||||||
TextSecurePreferences.setScreenLockTimeout(getContext(), timeoutSeconds);
|
TextSecurePreferences.setScreenLockTimeout(getContext(), timeoutSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +119,6 @@ public class KeyCachingService extends Service {
|
|||||||
KeyCachingService.masterSecret = masterSecret;
|
KeyCachingService.masterSecret = masterSecret;
|
||||||
|
|
||||||
foregroundService();
|
foregroundService();
|
||||||
startTimeoutIfAppropriate(this);
|
|
||||||
|
|
||||||
new AsyncTask<Void, Void, Void>() {
|
new AsyncTask<Void, Void, Void>() {
|
||||||
@Override
|
@Override
|
||||||
@ -210,7 +209,7 @@ public class KeyCachingService extends Service {
|
|||||||
boolean passLockActive = timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(context);
|
boolean passLockActive = timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(context);
|
||||||
|
|
||||||
long screenTimeout = TextSecurePreferences.getScreenLockTimeout(context);
|
long screenTimeout = TextSecurePreferences.getScreenLockTimeout(context);
|
||||||
boolean screenLockActive = screenTimeout >= 60 && TextSecurePreferences.isScreenLockEnabled(context);
|
boolean screenLockActive = screenTimeout >= 0 && TextSecurePreferences.isScreenLockEnabled(context);
|
||||||
|
|
||||||
if (!appVisible && secretSet && (passLockActive || screenLockActive)) {
|
if (!appVisible && secretSet && (passLockActive || screenLockActive)) {
|
||||||
long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(context);
|
long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(context);
|
||||||
|
@ -98,13 +98,6 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@drawable/home_activity_gradient" />
|
android:background="@drawable/home_activity_gradient" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.loki.views.NewConversationButtonSetView
|
|
||||||
android:id="@+id/newConversationButtonSet"
|
|
||||||
android:layout_width="276dp"
|
|
||||||
android:layout_height="236dp"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_alignParentBottom="true" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/emptyStateContainer"
|
android:id="@+id/emptyStateContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -131,6 +124,13 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.loki.views.NewConversationButtonSetView
|
||||||
|
android:id="@+id/newConversationButtonSet"
|
||||||
|
android:layout_width="276dp"
|
||||||
|
android:layout_height="236dp"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_alignParentBottom="true" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -110,8 +110,10 @@ interface StorageProtocol {
|
|||||||
fun createGroup(groupID: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long)
|
fun createGroup(groupID: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long)
|
||||||
fun isGroupActive(groupPublicKey: String): Boolean
|
fun isGroupActive(groupPublicKey: String): Boolean
|
||||||
fun setActive(groupID: String, value: Boolean)
|
fun setActive(groupID: String, value: Boolean)
|
||||||
|
fun getZombieMember(groupID: String): Set<String>
|
||||||
fun removeMember(groupID: String, member: Address)
|
fun removeMember(groupID: String, member: Address)
|
||||||
fun updateMembers(groupID: String, members: List<Address>)
|
fun updateMembers(groupID: String, members: List<Address>)
|
||||||
|
fun updateZombieMembers(groupID: String, members: List<Address>)
|
||||||
// Closed Group
|
// Closed Group
|
||||||
fun getAllClosedGroupPublicKeys(): Set<String>
|
fun getAllClosedGroupPublicKeys(): Set<String>
|
||||||
fun getAllActiveClosedGroupPublicKeys(): Set<String>
|
fun getAllActiveClosedGroupPublicKeys(): Set<String>
|
||||||
|
@ -8,7 +8,7 @@ import org.session.libsession.snode.OnionRequestAPI
|
|||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.service.loki.utilities.*
|
import org.session.libsignal.service.loki.utilities.*
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import org.session.libsignal.service.internal.crypto.PaddingInputStream
|
|||||||
import org.session.libsignal.service.internal.push.PushAttachmentData
|
import org.session.libsignal.service.internal.push.PushAttachmentData
|
||||||
import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory
|
import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory
|
||||||
import org.session.libsignal.service.internal.util.Util
|
import org.session.libsignal.service.internal.util.Util
|
||||||
import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory
|
import org.session.libsignal.service.loki.PlaintextOutputStreamFactory
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
|
|
||||||
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {
|
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package org.session.libsession.messaging.mentions
|
package org.session.libsession.messaging.mentions
|
||||||
|
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.utilities.mentions.Mention
|
import org.session.libsignal.service.loki.Mention
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
|
import org.session.libsignal.service.loki.LokiUserDatabaseProtocol
|
||||||
|
|
||||||
class MentionsManager(private val userPublicKey: String, private val userDatabase: LokiUserDatabaseProtocol) {
|
class MentionsManager(private val userPublicKey: String, private val userDatabase: LokiUserDatabaseProtocol) {
|
||||||
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
|
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
|
||||||
|
@ -145,7 +145,7 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
|||||||
}
|
}
|
||||||
is Kind.EncryptionKeyPair -> {
|
is Kind.EncryptionKeyPair -> {
|
||||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
|
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
|
||||||
closedGroupControlMessage.publicKey = kind.publicKey
|
closedGroupControlMessage.publicKey = kind.publicKey ?: ByteString.EMPTY
|
||||||
closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() })
|
closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() })
|
||||||
}
|
}
|
||||||
is Kind.NameChange -> {
|
is Kind.NameChange -> {
|
||||||
|
@ -21,7 +21,6 @@ import java.util.*
|
|||||||
object OpenGroupAPI: DotNetAPI() {
|
object OpenGroupAPI: DotNetAPI() {
|
||||||
|
|
||||||
private val moderators: HashMap<String, HashMap<Long, Set<String>>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
|
private val moderators: HashMap<String, HashMap<Long, Set<String>>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
|
||||||
val sharedContext = Kovenant.createContext()
|
|
||||||
|
|
||||||
// region Settings
|
// region Settings
|
||||||
private val fallbackBatchCount = 64
|
private val fallbackBatchCount = 64
|
||||||
@ -61,7 +60,7 @@ object OpenGroupAPI: DotNetAPI() {
|
|||||||
parameters["count"] = fallbackBatchCount
|
parameters["count"] = fallbackBatchCount
|
||||||
parameters["include_deleted"] = 0
|
parameters["include_deleted"] = 0
|
||||||
}
|
}
|
||||||
return execute(HTTPVerb.GET, server, "channels/$channel/messages", parameters = parameters).then(sharedContext) { json ->
|
return execute(HTTPVerb.GET, server, "channels/$channel/messages", parameters = parameters).then { json ->
|
||||||
try {
|
try {
|
||||||
val data = json["data"] as List<Map<*, *>>
|
val data = json["data"] as List<Map<*, *>>
|
||||||
val messages = data.mapNotNull { message ->
|
val messages = data.mapNotNull { message ->
|
||||||
@ -166,7 +165,7 @@ object OpenGroupAPI: DotNetAPI() {
|
|||||||
} else {
|
} else {
|
||||||
parameters["count"] = fallbackBatchCount
|
parameters["count"] = fallbackBatchCount
|
||||||
}
|
}
|
||||||
return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/deletes", parameters = parameters).then(sharedContext) { json ->
|
return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/deletes", parameters = parameters).then { json ->
|
||||||
try {
|
try {
|
||||||
val deletedMessageServerIDs = (json["data"] as List<Map<*, *>>).mapNotNull { deletion ->
|
val deletedMessageServerIDs = (json["data"] as List<Map<*, *>>).mapNotNull { deletion ->
|
||||||
try {
|
try {
|
||||||
@ -202,7 +201,7 @@ object OpenGroupAPI: DotNetAPI() {
|
|||||||
retryIfNeeded(maxRetryCount) {
|
retryIfNeeded(maxRetryCount) {
|
||||||
Log.d("Loki", "Sending message to open group with ID: $channel on server: $server.")
|
Log.d("Loki", "Sending message to open group with ID: $channel on server: $server.")
|
||||||
val parameters = signedMessage.toJSON()
|
val parameters = signedMessage.toJSON()
|
||||||
execute(HTTPVerb.POST, server, "channels/$channel/messages", parameters = parameters).then(sharedContext) { json ->
|
execute(HTTPVerb.POST, server, "channels/$channel/messages", parameters = parameters).then { json ->
|
||||||
try {
|
try {
|
||||||
val data = json["data"] as Map<*, *>
|
val data = json["data"] as Map<*, *>
|
||||||
val serverID = (data["id"] as? Long) ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as String).toLong()
|
val serverID = (data["id"] as? Long) ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as String).toLong()
|
||||||
@ -255,7 +254,7 @@ object OpenGroupAPI: DotNetAPI() {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getModerators(channel: Long, server: String): Promise<Set<String>, Exception> {
|
fun getModerators(channel: Long, server: String): Promise<Set<String>, Exception> {
|
||||||
return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/get_moderators").then(sharedContext) { json ->
|
return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/get_moderators").then { json ->
|
||||||
try {
|
try {
|
||||||
@Suppress("UNCHECKED_CAST") val moderators = json["moderators"] as? List<String>
|
@Suppress("UNCHECKED_CAST") val moderators = json["moderators"] as? List<String>
|
||||||
val moderatorsAsSet = moderators.orEmpty().toSet()
|
val moderatorsAsSet = moderators.orEmpty().toSet()
|
||||||
@ -276,7 +275,7 @@ object OpenGroupAPI: DotNetAPI() {
|
|||||||
fun getChannelInfo(channel: Long, server: String): Promise<OpenGroupInfo, Exception> {
|
fun getChannelInfo(channel: Long, server: String): Promise<OpenGroupInfo, Exception> {
|
||||||
return retryIfNeeded(maxRetryCount) {
|
return retryIfNeeded(maxRetryCount) {
|
||||||
val parameters = mapOf( "include_annotations" to 1 )
|
val parameters = mapOf( "include_annotations" to 1 )
|
||||||
execute(HTTPVerb.GET, server, "/channels/$channel", parameters = parameters).then(sharedContext) { json ->
|
execute(HTTPVerb.GET, server, "/channels/$channel", parameters = parameters).then { json ->
|
||||||
try {
|
try {
|
||||||
val data = json["data"] as Map<*, *>
|
val data = json["data"] as Map<*, *>
|
||||||
val annotations = data["annotations"] as List<Map<*, *>>
|
val annotations = data["annotations"] as List<Map<*, *>>
|
||||||
@ -357,7 +356,7 @@ object OpenGroupAPI: DotNetAPI() {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getDisplayNames(publicKeys: Set<String>, server: String): Promise<Map<String, String>, Exception> {
|
fun getDisplayNames(publicKeys: Set<String>, server: String): Promise<Map<String, String>, Exception> {
|
||||||
return getUserProfiles(publicKeys, server, false).map(sharedContext) { json ->
|
return getUserProfiles(publicKeys, server, false).map { json ->
|
||||||
val mapping = mutableMapOf<String, String>()
|
val mapping = mutableMapOf<String, String>()
|
||||||
for (user in json) {
|
for (user in json) {
|
||||||
if (user["username"] != null) {
|
if (user["username"] != null) {
|
||||||
|
@ -17,16 +17,13 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
|
|||||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.Error
|
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.Error
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.utilities.AESGCM
|
import org.session.libsession.utilities.AESGCM
|
||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsignal.service.loki.HTTP
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsignal.service.loki.HTTP.Verb.*
|
||||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
|
||||||
import org.session.libsignal.service.loki.api.utilities.HTTP.Verb.*
|
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
import org.session.libsignal.utilities.Base64.*
|
import org.session.libsignal.utilities.Base64.*
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.session.libsignal.utilities.createContext
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.whispersystems.curve25519.Curve25519
|
import org.whispersystems.curve25519.Curve25519
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -39,7 +36,6 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1)
|
val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1)
|
||||||
|
|
||||||
private val sharedContext = Kovenant.createContext()
|
|
||||||
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
||||||
|
|
||||||
sealed class Error : Exception() {
|
sealed class Error : Exception() {
|
||||||
@ -128,8 +124,8 @@ object OpenGroupAPIV2 {
|
|||||||
}
|
}
|
||||||
when (request.verb) {
|
when (request.verb) {
|
||||||
GET -> requestBuilder.get()
|
GET -> requestBuilder.get()
|
||||||
PUT -> requestBuilder.put(createBody(request.parameters))
|
PUT -> requestBuilder.put(createBody(request.parameters)!!)
|
||||||
POST -> requestBuilder.post(createBody(request.parameters))
|
POST -> requestBuilder.post(createBody(request.parameters)!!)
|
||||||
DELETE -> requestBuilder.delete(createBody(request.parameters))
|
DELETE -> requestBuilder.delete(createBody(request.parameters))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +152,7 @@ object OpenGroupAPIV2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return if (request.isAuthRequired) {
|
return if (request.isAuthRequired) {
|
||||||
getAuthToken(request.room!!, request.server).bind(sharedContext) { execute(it) }
|
getAuthToken(request.room!!, request.server).bind { execute(it) }
|
||||||
} else {
|
} else {
|
||||||
execute(null)
|
execute(null)
|
||||||
}
|
}
|
||||||
@ -164,7 +160,7 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
fun downloadOpenGroupProfilePicture(roomID: String, server: String): Promise<ByteArray, Exception> {
|
fun downloadOpenGroupProfilePicture(roomID: String, server: String): Promise<ByteArray, Exception> {
|
||||||
val request = Request(verb = GET, room = roomID, server = server, endpoint = "rooms/$roomID/image", isAuthRequired = false)
|
val request = Request(verb = GET, room = roomID, server = server, endpoint = "rooms/$roomID/image", isAuthRequired = false)
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
val result = json["result"] as? String ?: throw Error.PARSING_FAILED
|
val result = json["result"] as? String ?: throw Error.PARSING_FAILED
|
||||||
decode(result)
|
decode(result)
|
||||||
}
|
}
|
||||||
@ -176,7 +172,7 @@ object OpenGroupAPIV2 {
|
|||||||
Promise.of(it)
|
Promise.of(it)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
requestNewAuthToken(room, server)
|
requestNewAuthToken(room, server)
|
||||||
.bind(sharedContext) { claimAuthToken(it, room, server) }
|
.bind { claimAuthToken(it, room, server) }
|
||||||
.success { authToken ->
|
.success { authToken ->
|
||||||
storage.setAuthToken(room, server, authToken)
|
storage.setAuthToken(room, server, authToken)
|
||||||
}
|
}
|
||||||
@ -188,7 +184,7 @@ object OpenGroupAPIV2 {
|
|||||||
?: return Promise.ofFail(Error.GENERIC)
|
?: return Promise.ofFail(Error.GENERIC)
|
||||||
val queryParameters = mutableMapOf("public_key" to publicKey)
|
val queryParameters = mutableMapOf("public_key" to publicKey)
|
||||||
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
|
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
val challenge = json["challenge"] as? Map<*, *> ?: throw Error.PARSING_FAILED
|
val challenge = json["challenge"] as? Map<*, *> ?: throw Error.PARSING_FAILED
|
||||||
val base64EncodedCiphertext = challenge["ciphertext"] as? String
|
val base64EncodedCiphertext = challenge["ciphertext"] as? String
|
||||||
?: throw Error.PARSING_FAILED
|
?: throw Error.PARSING_FAILED
|
||||||
@ -211,12 +207,12 @@ object OpenGroupAPIV2 {
|
|||||||
val headers = mapOf("Authorization" to authToken)
|
val headers = mapOf("Authorization" to authToken)
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "claim_auth_token",
|
val request = Request(verb = POST, room = room, server = server, endpoint = "claim_auth_token",
|
||||||
parameters = parameters, headers = headers, isAuthRequired = false)
|
parameters = parameters, headers = headers, isAuthRequired = false)
|
||||||
return send(request).map(sharedContext) { authToken }
|
return send(request).map { authToken }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAuthToken(room: String, server: String): Promise<Unit, Exception> {
|
fun deleteAuthToken(room: String, server: String): Promise<Unit, Exception> {
|
||||||
val request = Request(verb = DELETE, room = room, server = server, endpoint = "auth_token")
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = "auth_token")
|
||||||
return send(request).map(sharedContext) {
|
return send(request).map {
|
||||||
MessagingModuleConfiguration.shared.storage.removeAuthToken(room, server)
|
MessagingModuleConfiguration.shared.storage.removeAuthToken(room, server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,14 +222,14 @@ object OpenGroupAPIV2 {
|
|||||||
val base64EncodedFile = encodeBytes(file)
|
val base64EncodedFile = encodeBytes(file)
|
||||||
val parameters = mapOf("file" to base64EncodedFile)
|
val parameters = mapOf("file" to base64EncodedFile)
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "files", parameters = parameters)
|
val request = Request(verb = POST, room = room, server = server, endpoint = "files", parameters = parameters)
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
json["result"] as? Long ?: throw Error.PARSING_FAILED
|
json["result"] as? Long ?: throw Error.PARSING_FAILED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
|
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
|
||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file")
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
val base64EncodedFile = json["result"] as? String ?: throw Error.PARSING_FAILED
|
val base64EncodedFile = json["result"] as? String ?: throw Error.PARSING_FAILED
|
||||||
decode(base64EncodedFile) ?: throw Error.PARSING_FAILED
|
decode(base64EncodedFile) ?: throw Error.PARSING_FAILED
|
||||||
}
|
}
|
||||||
@ -243,7 +239,7 @@ object OpenGroupAPIV2 {
|
|||||||
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
|
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
|
||||||
val jsonMessage = signedMessage.toJSON()
|
val jsonMessage = signedMessage.toJSON()
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = jsonMessage)
|
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = jsonMessage)
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, Any>
|
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, Any>
|
||||||
?: throw Error.PARSING_FAILED
|
?: throw Error.PARSING_FAILED
|
||||||
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
|
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
|
||||||
@ -259,7 +255,7 @@ object OpenGroupAPIV2 {
|
|||||||
queryParameters += "from_server_id" to lastId.toString()
|
queryParameters += "from_server_id" to lastId.toString()
|
||||||
}
|
}
|
||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "messages", queryParameters = queryParameters)
|
val request = Request(verb = GET, room = room, server = server, endpoint = "messages", queryParameters = queryParameters)
|
||||||
return send(request).map(sharedContext) { jsonList ->
|
return send(request).map { jsonList ->
|
||||||
@Suppress("UNCHECKED_CAST") val rawMessages = jsonList["messages"] as? List<Map<String, Any>>
|
@Suppress("UNCHECKED_CAST") val rawMessages = jsonList["messages"] as? List<Map<String, Any>>
|
||||||
?: throw Error.PARSING_FAILED
|
?: throw Error.PARSING_FAILED
|
||||||
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0
|
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0
|
||||||
@ -296,7 +292,7 @@ object OpenGroupAPIV2 {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
||||||
val request = Request(verb = DELETE, room = room, server = server, endpoint = "messages/$serverID")
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = "messages/$serverID")
|
||||||
return send(request).map(sharedContext) {
|
return send(request).map {
|
||||||
Log.d("Loki", "Deleted server message")
|
Log.d("Loki", "Deleted server message")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -308,7 +304,7 @@ object OpenGroupAPIV2 {
|
|||||||
queryParameters["from_server_id"] = last.toString()
|
queryParameters["from_server_id"] = last.toString()
|
||||||
}
|
}
|
||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "deleted_messages", queryParameters = queryParameters)
|
val request = Request(verb = GET, room = room, server = server, endpoint = "deleted_messages", queryParameters = queryParameters)
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java)
|
val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java)
|
||||||
val idsAsString = JsonUtil.toJson(json["ids"])
|
val idsAsString = JsonUtil.toJson(json["ids"])
|
||||||
val serverIDs = JsonUtil.fromJson<List<MessageDeletion>>(idsAsString, type) ?: throw Error.PARSING_FAILED
|
val serverIDs = JsonUtil.fromJson<List<MessageDeletion>>(idsAsString, type) ?: throw Error.PARSING_FAILED
|
||||||
@ -329,7 +325,7 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
fun getModerators(room: String, server: String): Promise<List<String>, Exception> {
|
fun getModerators(room: String, server: String): Promise<List<String>, Exception> {
|
||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "moderators")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "moderators")
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
@Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List<String>
|
@Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List<String>
|
||||||
?: throw Error.PARSING_FAILED
|
?: throw Error.PARSING_FAILED
|
||||||
val id = "$server.$room"
|
val id = "$server.$room"
|
||||||
@ -342,14 +338,14 @@ object OpenGroupAPIV2 {
|
|||||||
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
||||||
val parameters = mapOf("public_key" to publicKey)
|
val parameters = mapOf("public_key" to publicKey)
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters)
|
val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters)
|
||||||
return send(request).map(sharedContext) {
|
return send(request).map {
|
||||||
Log.d("Loki", "Banned user $publicKey from $server.$room")
|
Log.d("Loki", "Banned user $publicKey from $server.$room")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
fun unban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
||||||
val request = Request(verb = DELETE, room = room, server = server, endpoint = "block_list/$publicKey")
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = "block_list/$publicKey")
|
||||||
return send(request).map(sharedContext) {
|
return send(request).map {
|
||||||
Log.d("Loki", "Unbanned user $publicKey from $server.$room")
|
Log.d("Loki", "Unbanned user $publicKey from $server.$room")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -380,7 +376,7 @@ object OpenGroupAPIV2 {
|
|||||||
}
|
}
|
||||||
val request = Request(verb = POST, room = null, server = server, endpoint = "compact_poll", isAuthRequired = false, parameters = mapOf("requests" to requests))
|
val request = Request(verb = POST, room = null, server = server, endpoint = "compact_poll", isAuthRequired = false, parameters = mapOf("requests" to requests))
|
||||||
// build a request for all rooms
|
// build a request for all rooms
|
||||||
return send(request = request).map(sharedContext) { json ->
|
return send(request = request).map { json ->
|
||||||
val results = json["results"] as? List<*> ?: throw Error.PARSING_FAILED
|
val results = json["results"] as? List<*> ?: throw Error.PARSING_FAILED
|
||||||
|
|
||||||
results.mapNotNull { roomJson ->
|
results.mapNotNull { roomJson ->
|
||||||
@ -462,7 +458,7 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
fun getInfo(room: String, server: String): Promise<Info, Exception> {
|
fun getInfo(room: String, server: String): Promise<Info, Exception> {
|
||||||
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms/$room", isAuthRequired = false)
|
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms/$room", isAuthRequired = false)
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
val rawRoom = json["room"] as? Map<*, *> ?: throw Error.PARSING_FAILED
|
val rawRoom = json["room"] as? Map<*, *> ?: throw Error.PARSING_FAILED
|
||||||
val id = rawRoom["id"] as? String ?: throw Error.PARSING_FAILED
|
val id = rawRoom["id"] as? String ?: throw Error.PARSING_FAILED
|
||||||
val name = rawRoom["name"] as? String ?: throw Error.PARSING_FAILED
|
val name = rawRoom["name"] as? String ?: throw Error.PARSING_FAILED
|
||||||
@ -473,7 +469,7 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
|
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
|
||||||
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms", isAuthRequired = false)
|
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms", isAuthRequired = false)
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
val rawRooms = json["rooms"] as? List<Map<*, *>> ?: throw Error.PARSING_FAILED
|
val rawRooms = json["rooms"] as? List<Map<*, *>> ?: throw Error.PARSING_FAILED
|
||||||
rawRooms.mapNotNull {
|
rawRooms.mapNotNull {
|
||||||
val roomJson = it as? Map<*, *> ?: return@mapNotNull null
|
val roomJson = it as? Map<*, *> ?: return@mapNotNull null
|
||||||
@ -487,7 +483,7 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
fun getMemberCount(room: String, server: String): Promise<Long, Exception> {
|
fun getMemberCount(room: String, server: String): Promise<Long, Exception> {
|
||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "member_count")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "member_count")
|
||||||
return send(request).map(sharedContext) { json ->
|
return send(request).map { json ->
|
||||||
val memberCount = json["member_count"] as? Long ?: throw Error.PARSING_FAILED
|
val memberCount = json["member_count"] as? Long ?: throw Error.PARSING_FAILED
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
storage.setUserCount(room, server, memberCount)
|
storage.setUserCount(room, server, memberCount)
|
||||||
|
@ -169,15 +169,24 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
|
|||||||
Log.d("Loki", "Invalid closed group update.")
|
Log.d("Loki", "Invalid closed group update.")
|
||||||
throw Error.InvalidClosedGroupUpdate
|
throw Error.InvalidClosedGroupUpdate
|
||||||
}
|
}
|
||||||
val updatedMembers = group.members.map { it.serialize() }.toSet() - membersToRemove
|
|
||||||
// Save the new group members
|
|
||||||
storage.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
|
|
||||||
val removeMembersAsData = membersToRemove.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
|
|
||||||
val admins = group.admins.map { it.serialize() }
|
val admins = group.admins.map { it.serialize() }
|
||||||
|
if (!admins.contains(userPublicKey)) {
|
||||||
|
Log.d("Loki", "Only an admin can remove members from a group.")
|
||||||
|
throw Error.InvalidClosedGroupUpdate
|
||||||
|
}
|
||||||
|
val updatedMembers = group.members.map { it.serialize() }.toSet() - membersToRemove
|
||||||
if (membersToRemove.any { it in admins } && updatedMembers.isNotEmpty()) {
|
if (membersToRemove.any { it in admins } && updatedMembers.isNotEmpty()) {
|
||||||
Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.")
|
Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.")
|
||||||
throw Error.InvalidClosedGroupUpdate
|
throw Error.InvalidClosedGroupUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the new group members
|
||||||
|
storage.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
|
||||||
|
// Update the zombie list
|
||||||
|
val oldZombies = storage.getZombieMember(groupID)
|
||||||
|
storage.updateZombieMembers(groupID, oldZombies.minus(membersToRemove).map { Address.fromSerialized(it) })
|
||||||
|
|
||||||
|
val removeMembersAsData = membersToRemove.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
|
||||||
val name = group.title
|
val name = group.title
|
||||||
// Send the update to the group
|
// Send the update to the group
|
||||||
val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersRemoved(removeMembersAsData)
|
val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersRemoved(removeMembersAsData)
|
||||||
@ -185,14 +194,21 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
|
|||||||
val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind)
|
val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind)
|
||||||
closedGroupControlMessage.sentTimestamp = sentTime
|
closedGroupControlMessage.sentTimestamp = sentTime
|
||||||
send(closedGroupControlMessage, Address.fromSerialized(groupID))
|
send(closedGroupControlMessage, Address.fromSerialized(groupID))
|
||||||
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
|
||||||
if (isCurrentUserAdmin) {
|
// Send the new encryption key pair to the remaining group members
|
||||||
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
|
// At this stage we know the user is admin, no need to test
|
||||||
}
|
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val infoType = SignalServiceGroup.Type.MEMBER_REMOVED
|
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
// Insert an outgoing notification
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, membersToRemove, admins, threadID, sentTime)
|
// we don't display zombie members in the notification as users have already been notified when those members left
|
||||||
|
val notificationMembers = membersToRemove.minus(oldZombies)
|
||||||
|
if (notificationMembers.isNotEmpty()) {
|
||||||
|
// no notification to display when only zombies have been removed
|
||||||
|
val infoType = SignalServiceGroup.Type.MEMBER_REMOVED
|
||||||
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
|
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, notificationMembers, admins, threadID, sentTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> {
|
fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> {
|
||||||
|
@ -298,7 +298,8 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr
|
|||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
val senderPublicKey = message.sender ?: return
|
val senderPublicKey = message.sender ?: return
|
||||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.EncryptionKeyPair ?: return
|
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.EncryptionKeyPair ?: return
|
||||||
val groupPublicKey = kind.publicKey?.toByteArray()?.toHexString() ?: message.groupPublicKey ?: return
|
var groupPublicKey = kind.publicKey?.toByteArray()?.toHexString()
|
||||||
|
if (groupPublicKey.isNullOrEmpty()) groupPublicKey = message.groupPublicKey ?: return
|
||||||
val userPublicKey = storage.getUserPublicKey()!!
|
val userPublicKey = storage.getUserPublicKey()!!
|
||||||
val userKeyPair = storage.getUserX25519KeyPair()
|
val userKeyPair = storage.getUserX25519KeyPair()
|
||||||
// Unwrap the message
|
// Unwrap the message
|
||||||
@ -312,7 +313,7 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!group.admins.map { it.toString() }.contains(senderPublicKey)) {
|
if (!group.admins.map { it.toString() }.contains(senderPublicKey)) {
|
||||||
Log.d("Loki", "Ignoring closed group encryption key pair from non-member.")
|
Log.d("Loki", "Ignoring closed group encryption key pair from non-admin.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Find our wrapper and decrypt it if possible
|
// Find our wrapper and decrypt it if possible
|
||||||
@ -393,6 +394,7 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
|
|||||||
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
|
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
|
||||||
val newMembers = members + updateMembers
|
val newMembers = members + updateMembers
|
||||||
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||||
|
|
||||||
// Notify the user
|
// Notify the user
|
||||||
if (userPublicKey == senderPublicKey) {
|
if (userPublicKey == senderPublicKey) {
|
||||||
// sender is a linked device
|
// sender is a linked device
|
||||||
@ -415,6 +417,11 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the given members from the group IF
|
||||||
|
/// • it wasn't the admin that was removed (that should happen through a `MEMBER_LEFT` message).
|
||||||
|
/// • the admin sent the message (only the admin can truly remove members).
|
||||||
|
/// If we're among the users that were removed, delete all encryption key pairs and the group public key, unsubscribe
|
||||||
|
/// from push notifications for this closed group, and remove the given members from the zombie list for this group.
|
||||||
private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroupControlMessage) {
|
private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroupControlMessage) {
|
||||||
val context = MessagingModuleConfiguration.shared.context
|
val context = MessagingModuleConfiguration.shared.context
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
@ -428,7 +435,7 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!group.isActive) {
|
if (!group.isActive) {
|
||||||
Log.d("Loki", "Ignoring closed group info message for inactive group")
|
Log.d("Loki", "Ignoring closed group info message for inactive group.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val name = group.title
|
val name = group.title
|
||||||
@ -439,6 +446,18 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
|
|||||||
// Users that are part of this remove update
|
// Users that are part of this remove update
|
||||||
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
|
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
|
||||||
|
|
||||||
|
// Check that the admin wasn't removed
|
||||||
|
if (updateMembers.contains(admins.first())) {
|
||||||
|
Log.d("Loki", "Ignoring invalid closed group update.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the message was sent by the group admin
|
||||||
|
if (!admins.contains(senderPublicKey)) {
|
||||||
|
Log.d("Loki", "Ignoring invalid closed group update.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return }
|
if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return }
|
||||||
// If admin leaves the group is disbanded
|
// If admin leaves the group is disbanded
|
||||||
val didAdminLeave = admins.any { it in updateMembers }
|
val didAdminLeave = admins.any { it in updateMembers }
|
||||||
@ -455,25 +474,34 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
|
|||||||
if (didAdminLeave || wasCurrentUserRemoved) {
|
if (didAdminLeave || wasCurrentUserRemoved) {
|
||||||
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
|
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
|
||||||
} else {
|
} else {
|
||||||
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
|
||||||
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||||
if (isCurrentUserAdmin) {
|
|
||||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// update zombie members
|
||||||
|
val zombies = storage.getZombieMember(groupID)
|
||||||
|
storage.updateZombieMembers(groupID, zombies.minus(updateMembers).map { Address.fromSerialized(it) })
|
||||||
|
|
||||||
val type = if (senderLeft) SignalServiceGroup.Type.QUIT
|
val type = if (senderLeft) SignalServiceGroup.Type.QUIT
|
||||||
else SignalServiceGroup.Type.MEMBER_REMOVED
|
else SignalServiceGroup.Type.MEMBER_REMOVED
|
||||||
|
|
||||||
// Notify the user
|
// Notify the user
|
||||||
if (userPublicKey == senderPublicKey) {
|
// we don't display zombie members in the notification as users have already been notified when those members left
|
||||||
// sender is a linked device
|
val notificationMembers = updateMembers.minus(zombies)
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
if (notificationMembers.isNotEmpty()) {
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, type, name, updateMembers, admins, threadID, message.sentTimestamp!!)
|
// no notification to display when only zombies have been removed
|
||||||
} else {
|
if (userPublicKey == senderPublicKey) {
|
||||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, updateMembers, admins, message.sentTimestamp!!)
|
// sender is a linked device
|
||||||
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
|
storage.insertOutgoingInfoMessage(context, groupID, type, name, notificationMembers, admins, threadID, message.sentTimestamp!!)
|
||||||
|
} else {
|
||||||
|
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, notificationMembers, admins, message.sentTimestamp!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If a regular member left:
|
||||||
|
/// • Mark them as a zombie (to be removed by the admin later).
|
||||||
|
/// If the admin left:
|
||||||
|
/// • Unsubscribe from PNs, delete the group public key, etc. as the group will be disbanded.
|
||||||
private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupControlMessage) {
|
private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupControlMessage) {
|
||||||
val context = MessagingModuleConfiguration.shared.context
|
val context = MessagingModuleConfiguration.shared.context
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
@ -506,11 +534,10 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont
|
|||||||
// admin left the group of linked device left the group
|
// admin left the group of linked device left the group
|
||||||
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
|
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
|
||||||
} else {
|
} else {
|
||||||
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
|
||||||
storage.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
|
storage.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
|
||||||
if (isCurrentUserAdmin) {
|
// update zombie members
|
||||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMemberList)
|
val zombies = storage.getZombieMember(groupID)
|
||||||
}
|
storage.updateZombieMembers(groupID, zombies.plus(senderPublicKey).map { Address.fromSerialized(it) })
|
||||||
}
|
}
|
||||||
// Notify the user
|
// Notify the user
|
||||||
if (userLeft) {
|
if (userLeft) {
|
||||||
|
@ -7,10 +7,8 @@ import nl.komponents.kovenant.functional.map
|
|||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsignal.service.loki.utilities.getRandomElementOrNull
|
import org.session.libsignal.service.loki.utilities.getRandomElementOrNull
|
||||||
import org.session.libsignal.utilities.Base64
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.session.libsignal.utilities.successBackground
|
import org.session.libsignal.utilities.successBackground
|
||||||
|
|
||||||
@ -71,11 +69,8 @@ class ClosedGroupPoller {
|
|||||||
// ignore inactive group's messages
|
// ignore inactive group's messages
|
||||||
return@successBackground
|
return@successBackground
|
||||||
}
|
}
|
||||||
messages.forEach { message ->
|
messages.forEach { envelope ->
|
||||||
val rawMessageAsJSON = message as? Map<*, *>
|
val job = MessageReceiveJob(envelope.toByteArray(), false)
|
||||||
val base64EncodedData = rawMessageAsJSON?.get("data") as? String
|
|
||||||
val data = base64EncodedData?.let { Base64.decode(it) } ?: return@forEach
|
|
||||||
val job = MessageReceiveJob(MessageWrapper.unwrap(data).toByteArray(), false)
|
|
||||||
JobQueue.shared.add(job)
|
JobQueue.shared.add(job)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,9 @@ import nl.komponents.kovenant.functional.bind
|
|||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.snode.SnodeModule
|
import org.session.libsession.snode.SnodeModule
|
||||||
import org.session.libsignal.service.loki.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import org.session.libsignal.utilities.Base64
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -20,9 +20,10 @@ import org.session.libsignal.service.internal.push.ProfileAvatarData
|
|||||||
import org.session.libsignal.service.internal.push.PushAttachmentData
|
import org.session.libsignal.service.internal.push.PushAttachmentData
|
||||||
import org.session.libsignal.service.internal.push.http.DigestingRequestBody
|
import org.session.libsignal.service.internal.push.http.DigestingRequestBody
|
||||||
import org.session.libsignal.service.internal.push.http.ProfileCipherOutputStreamFactory
|
import org.session.libsignal.service.internal.push.http.ProfileCipherOutputStreamFactory
|
||||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.session.libsignal.service.loki.utilities.retryIfNeeded
|
import org.session.libsignal.service.loki.HTTP
|
||||||
|
import org.session.libsignal.service.loki.utilities.*
|
||||||
import org.session.libsignal.utilities.*
|
import org.session.libsignal.utilities.*
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
|
@ -12,11 +12,13 @@ import org.session.libsignal.utilities.logging.Log
|
|||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.*
|
import org.session.libsignal.utilities.*
|
||||||
import org.session.libsignal.service.loki.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import org.session.libsignal.service.loki.api.utilities.*
|
import org.session.libsignal.service.loki.*
|
||||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||||
import org.session.libsession.utilities.getBodyForOnionRequest
|
import org.session.libsession.utilities.getBodyForOnionRequest
|
||||||
import org.session.libsession.utilities.getHeadersForOnionRequest
|
import org.session.libsession.utilities.getHeadersForOnionRequest
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.Broadcaster
|
||||||
|
import org.session.libsignal.service.loki.HTTP
|
||||||
|
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.service.loki.utilities.*
|
import org.session.libsignal.service.loki.utilities.*
|
||||||
|
|
||||||
private typealias Path = List<Snode>
|
private typealias Path = List<Snode>
|
||||||
|
@ -10,9 +10,9 @@ import org.session.libsession.messaging.utilities.MessageWrapper
|
|||||||
import org.session.libsession.snode.utilities.getRandomElement
|
import org.session.libsession.snode.utilities.getRandomElement
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||||
import org.session.libsignal.service.loki.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
import org.session.libsignal.service.loki.HTTP
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.service.loki.utilities.Broadcaster
|
import org.session.libsignal.service.loki.Broadcaster
|
||||||
import org.session.libsignal.service.loki.utilities.prettifiedDescription
|
import org.session.libsignal.service.loki.utilities.prettifiedDescription
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
import org.session.libsignal.service.loki.utilities.retryIfNeeded
|
import org.session.libsignal.service.loki.utilities.retryIfNeeded
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.session.libsession.snode
|
package org.session.libsession.snode
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.service.loki.utilities.Broadcaster
|
import org.session.libsignal.service.loki.Broadcaster
|
||||||
|
|
||||||
class SnodeModule(val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster) {
|
class SnodeModule(val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster) {
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package org.session.libsession.utilities.mentions
|
|
||||||
|
|
||||||
data class Mention(val publicKey: String, val displayName: String)
|
|
@ -22,7 +22,6 @@ import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
|
|||||||
import org.session.libsignal.service.api.messages.SignalServiceGroup;
|
import org.session.libsignal.service.api.messages.SignalServiceGroup;
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
|
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
|
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
|
||||||
import org.session.libsignal.service.api.messages.shared.SharedContact;
|
|
||||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||||
import org.session.libsignal.service.internal.push.PushTransportDetails;
|
import org.session.libsignal.service.internal.push.PushTransportDetails;
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos;
|
import org.session.libsignal.service.internal.push.SignalServiceProtos;
|
||||||
@ -34,7 +33,7 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos.ReceiptMe
|
|||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage;
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage;
|
||||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
|
import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
|
||||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities;
|
import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities;
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
|
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.session.libsignal.service.api.messages.shared;
|
package org.session.libsignal.service.api.messages.shared;
|
||||||
|
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
|
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package org.session.libsignal.service.loki.utilities
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
interface Broadcaster {
|
interface Broadcaster {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.session.libsignal.service.loki.api.utilities
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
@ -1,4 +1,4 @@
|
|||||||
package org.session.libsignal.service.loki.database
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||||
import org.session.libsignal.service.loki.Snode
|
import org.session.libsignal.service.loki.Snode
|
@ -1,4 +1,4 @@
|
|||||||
package org.session.libsignal.service.loki.database
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
interface LokiMessageDatabaseProtocol {
|
interface LokiMessageDatabaseProtocol {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.session.libsignal.service.loki.database
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
interface LokiOpenGroupDatabaseProtocol {
|
interface LokiOpenGroupDatabaseProtocol {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.session.libsignal.service.loki.database
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
interface LokiUserDatabaseProtocol {
|
interface LokiUserDatabaseProtocol {
|
||||||
|
|
@ -0,0 +1,3 @@
|
|||||||
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
|
data class Mention(val publicKey: String, val displayName: String)
|
@ -1,6 +1,5 @@
|
|||||||
package org.session.libsignal.service.loki.crypto
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,8 +94,10 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) {
|
|||||||
var result = ""
|
var result = ""
|
||||||
val n = truncatedWordSet.size.toLong()
|
val n = truncatedWordSet.size.toLong()
|
||||||
// Check preconditions
|
// Check preconditions
|
||||||
if (words.size < 12) { throw DecodingError.InputTooShort }
|
if (words.size < 12) { throw DecodingError.InputTooShort
|
||||||
if (words.size % 3 == 0) { throw DecodingError.MissingLastWord }
|
}
|
||||||
|
if (words.size % 3 == 0) { throw DecodingError.MissingLastWord
|
||||||
|
}
|
||||||
// Get checksum word
|
// Get checksum word
|
||||||
val checksumWord = words.removeAt(words.lastIndex)
|
val checksumWord = words.removeAt(words.lastIndex)
|
||||||
// Decode
|
// Decode
|
||||||
@ -106,7 +107,8 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) {
|
|||||||
val w2 = truncatedWordSet.indexOf(words[chunkStartIndex + 1].substring(0 until prefixLength))
|
val w2 = truncatedWordSet.indexOf(words[chunkStartIndex + 1].substring(0 until prefixLength))
|
||||||
val w3 = truncatedWordSet.indexOf(words[chunkStartIndex + 2].substring(0 until prefixLength))
|
val w3 = truncatedWordSet.indexOf(words[chunkStartIndex + 2].substring(0 until prefixLength))
|
||||||
val x = w1 + n * ((n - w1 + w2) % n) + n * n * ((n - w2 + w3) % n)
|
val x = w1 + n * ((n - w1 + w2) % n) + n * n * ((n - w2 + w3) % n)
|
||||||
if (x % n != w1.toLong()) { throw DecodingError.Generic }
|
if (x % n != w1.toLong()) { throw DecodingError.Generic
|
||||||
|
}
|
||||||
val string = "0000000" + x.toString(16)
|
val string = "0000000" + x.toString(16)
|
||||||
result += swap(string.substring(string.length - 8 until string.length))
|
result += swap(string.substring(string.length - 8 until string.length))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -116,7 +118,8 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) {
|
|||||||
// Verify checksum
|
// Verify checksum
|
||||||
val checksumIndex = determineChecksumIndex(words, prefixLength)
|
val checksumIndex = determineChecksumIndex(words, prefixLength)
|
||||||
val expectedChecksumWord = words[checksumIndex]
|
val expectedChecksumWord = words[checksumIndex]
|
||||||
if (expectedChecksumWord.substring(0 until prefixLength) != checksumWord.substring(0 until prefixLength)) { throw DecodingError.VerificationFailed }
|
if (expectedChecksumWord.substring(0 until prefixLength) != checksumWord.substring(0 until prefixLength)) { throw DecodingError.VerificationFailed
|
||||||
|
}
|
||||||
// Return
|
// Return
|
||||||
return result
|
return result
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.session.libsignal.service.loki.utilities
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
import org.session.libsignal.service.api.crypto.DigestingOutputStream
|
import org.session.libsignal.service.api.crypto.DigestingOutputStream
|
||||||
import org.session.libsignal.service.internal.push.http.OutputStreamFactory
|
import org.session.libsignal.service.internal.push.http.OutputStreamFactory
|
@ -1,7 +1,7 @@
|
|||||||
package org.session.libsignal.service.loki.api.crypto
|
package org.session.libsignal.service.loki.api.crypto
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
|
||||||
|
|
||||||
interface SessionProtocol {
|
interface SessionProtocol {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.session.libsignal.service.loki.api
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.api.utilities
|
|
||||||
|
|
||||||
import okhttp3.MultipartBody
|
|
||||||
import okhttp3.Request
|
|
||||||
import okio.Buffer
|
|
||||||
import org.session.libsignal.utilities.Base64
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal fun Request.getHeadersForOnionRequest(): Map<String, Any> {
|
|
||||||
val result = mutableMapOf<String, Any>()
|
|
||||||
val contentType = body()?.contentType()
|
|
||||||
if (contentType != null) {
|
|
||||||
result["content-type"] = contentType.toString()
|
|
||||||
}
|
|
||||||
val headers = headers()
|
|
||||||
for (name in headers.names()) {
|
|
||||||
val value = headers.get(name)
|
|
||||||
if (value != null) {
|
|
||||||
if (value.toLowerCase(Locale.US) == "true" || value.toLowerCase(Locale.US) == "false") {
|
|
||||||
result[name] = value.toBoolean()
|
|
||||||
} else if (value.toIntOrNull() != null) {
|
|
||||||
result[name] = value.toInt()
|
|
||||||
} else {
|
|
||||||
result[name] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun Request.getBodyForOnionRequest(): Any? {
|
|
||||||
try {
|
|
||||||
val copyOfThis = newBuilder().build()
|
|
||||||
val buffer = Buffer()
|
|
||||||
val body = copyOfThis.body() ?: return null
|
|
||||||
body.writeTo(buffer)
|
|
||||||
val bodyAsData = buffer.readByteArray()
|
|
||||||
if (body is MultipartBody) {
|
|
||||||
val base64EncodedBody: String = Base64.encodeBytes(bodyAsData)
|
|
||||||
return mapOf( "fileUpload" to base64EncodedBody )
|
|
||||||
} else {
|
|
||||||
val charset = body.contentType()?.charset() ?: Charsets.UTF_8
|
|
||||||
return bodyAsData?.toString(charset)
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -64,7 +64,7 @@ public class JsonUtil {
|
|||||||
return objectMapper.writeValueAsString(object);
|
return objectMapper.writeValueAsString(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String toJson(Object object) {
|
public static String toJson(Object object) {
|
||||||
try {
|
try {
|
||||||
return objectMapper.writeValueAsString(object);
|
return objectMapper.writeValueAsString(object);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
@ -73,32 +73,11 @@ public class JsonUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class IdentityKeySerializer extends JsonSerializer<IdentityKey> {
|
|
||||||
@Override
|
|
||||||
public void serialize(IdentityKey value, JsonGenerator gen, SerializerProvider serializers)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class IdentityKeyDeserializer extends JsonDeserializer<IdentityKey> {
|
|
||||||
@Override
|
|
||||||
public IdentityKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
|
||||||
try {
|
|
||||||
return new IdentityKey(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectMapper getMapper() {
|
public static ObjectMapper getMapper() {
|
||||||
return objectMapper;
|
return objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SaneJSONObject {
|
public static class SaneJSONObject {
|
||||||
|
|
||||||
private final JSONObject delegate;
|
private final JSONObject delegate;
|
||||||
|
|
||||||
public SaneJSONObject(JSONObject delegate) {
|
public SaneJSONObject(JSONObject delegate) {
|
||||||
|
@ -1,25 +1,11 @@
|
|||||||
@file:JvmName("PromiseUtilities")
|
@file:JvmName("PromiseUtilities")
|
||||||
package org.session.libsignal.utilities
|
package org.session.libsignal.utilities
|
||||||
|
|
||||||
import nl.komponents.kovenant.Context
|
|
||||||
import nl.komponents.kovenant.Kovenant
|
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
import nl.komponents.kovenant.jvm.asDispatcher
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
fun Kovenant.createContext(): Context {
|
|
||||||
return createContext {
|
|
||||||
callbackContext.dispatcher = Executors.newSingleThreadExecutor().asDispatcher()
|
|
||||||
workerContext.dispatcher = ThreadUtils.executorPool.asDispatcher()
|
|
||||||
multipleCompletion = { v1, v2 ->
|
|
||||||
Log.d("Loki", "Promise resolved more than once (first with $v1, then with $v2); ignoring $v2.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <V, E : Throwable> Promise<V, E>.get(defaultValue: V): V {
|
fun <V, E : Throwable> Promise<V, E>.get(defaultValue: V): V {
|
||||||
return try {
|
return try {
|
||||||
get()
|
get()
|
||||||
|
@ -3,7 +3,6 @@ package org.session.libsignal.utilities
|
|||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
|
|
||||||
object ThreadUtils {
|
object ThreadUtils {
|
||||||
|
|
||||||
val executorPool = Executors.newCachedThreadPool()
|
val executorPool = Executors.newCachedThreadPool()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -17,10 +16,8 @@ object ThreadUtils {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newDynamicSingleThreadedExecutor(): ExecutorService {
|
fun newDynamicSingleThreadedExecutor(): ExecutorService {
|
||||||
val executor = ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
|
val executor = ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||||
LinkedBlockingQueue())
|
|
||||||
executor.allowCoreThreadTimeOut(true)
|
executor.allowCoreThreadTimeOut(true)
|
||||||
return executor
|
return executor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user