mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66a8267b11 | |||
| fa703d3a1d | |||
| 3fc64f0f40 | |||
| 00ca111be3 | |||
| 2995ecd53d | |||
| a6cd3843de | |||
| fec03c74f4 | |||
| 33646f030e | |||
| df04c4a1ea | |||
| d1bd7fbf4c | |||
| e070492d02 | |||
| aef113c8cd | |||
| f16d8842b2 | |||
| 4408495cfb | |||
| 920014a06b | |||
| fd9574b5aa | |||
| 890e6abf01 | |||
| dc6ce8a2f5 | |||
| 9278234540 | |||
| cf2424f319 | |||
| 0797798136 | |||
| 013ee86899 | |||
| c73a28e7de | |||
| b3f4645011 |
@@ -39,6 +39,7 @@
|
||||
- [SimpleX Platform design](#simplex-platform-design)
|
||||
- [For developers](#for-developers)
|
||||
- [Roadmap](#roadmap)
|
||||
- [Help us pay for 3rd party security audit](#help-us-pay-for-3rd-party-security-audit)
|
||||
- [Disclaimer, License](#disclaimer)
|
||||
|
||||
## Why privacy matters
|
||||
@@ -71,13 +72,15 @@ You can use SimpleX with your own servers and still communicate with people usin
|
||||
|
||||
1. _How SimpleX can deliver messages without any user identifiers?_ See [v2 release annoucement](./blog/20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers) explaining how SimpleX works.
|
||||
|
||||
2. _Why should I not just use Signal?_ This [post](https://github.com/dessalines/essays/blob/master/why_not_signal.md) shows why Signal cannot be considered a private messenger. Signal is a centralised platform that uses phone numbers to identify its users and their contacts.
|
||||
2. _Why should I not just use Signal?_ Signal is a centralised platform that uses phone numbers to identify its users and their contacts. It means that while the content of your messages on Signal is protected with robust end-to-end encryption, there is a large amount of meta-data visible to Signal - who you talk with and when.
|
||||
|
||||
3. _How is it different from Matrix, Session, Ricochet, Cwtch, etc., that also don't require user identites?_ Although these platforms do not require a _real identity_, they do rely on anonymous user identities to deliver messages – it can be, for example, an identity key or a random number. Using a persistent user identity, even anonymous, creates a risk that user's connection graph becomes known to the observers and/or service providers, and it can lead to de-anonymizing some users. If the same user profile is used to connect to two different people via any messenger other than SimpleX, these two people can confirm if they are connected to the same person - they would use the same user identifier in the messages. With SimpleX there is no meta-data in common between your conversations with different contacts - the quality that no other messaging platform has.
|
||||
|
||||
## News and updates
|
||||
|
||||
[Jun 4, 2022. v2.2: the new Privacy and Security settings](./blog/20220604-simplex-chat-new-privacy-security-settings.md)
|
||||
Selected updates:
|
||||
|
||||
[Jul 11, 2022. v3.0: instant push notifications for iOS, e2e encrypted WebRTC audio/video calls, chat database export/import, privacy and performance improvements](./blog/20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md)
|
||||
|
||||
[May 11, 2022. v2.0 released - sending images and files in mobile apps](./blog/20220511-simplex-chat-v2-images-files.md)
|
||||
|
||||
@@ -141,11 +144,13 @@ If you are considering developing with SimpleX platform please get in touch for
|
||||
- ✅ Haskell chat bot templates.
|
||||
- ✅ v2.0 - supporting images and files in mobile apps.
|
||||
- ✅ Manual chat history deletion.
|
||||
- 🚀 End-to-end encrypted audio and video calls via the mobile apps (enable via Experimental Features).
|
||||
- 🏗 Privacy preserving instant notifications for iOS using Apple Push Notification service (in progress).
|
||||
- ✅ End-to-end encrypted WebRTC audio and video calls via the mobile apps.
|
||||
- ✅ Privacy preserving instant notifications for iOS using Apple Push Notification service.
|
||||
- ✅ Chat database export and import
|
||||
- 🏗 Chat server and TypeScript client SDK to develop chat interfaces, integrations and chat bots (in progress).
|
||||
- 🏗 Chat database portability and encryption.
|
||||
- Groups support for mobile apps.
|
||||
- 🏗 Connecting to messaging servers via Tor (in progress).
|
||||
- 🏗 Chat groups in mobile apps (in progress).
|
||||
- Chat database encryption.
|
||||
- Disappearing messages, with mutual agreement.
|
||||
- Web widgets for custom interactivity in the chats.
|
||||
- SMP protocol improvements:
|
||||
@@ -158,6 +163,26 @@ If you are considering developing with SimpleX platform please get in touch for
|
||||
- Media server to optimize sending large files to groups.
|
||||
- Channels server for large groups and broadcast channels.
|
||||
|
||||
## Help us pay for 3rd party security audit
|
||||
|
||||
I will get straight to the point: I ask you to support SimpleX Chat with donations.
|
||||
|
||||
We are prioritizing users privacy and security - it would be impossible without your support we were lucky to have so far.
|
||||
|
||||
We are planning a 3rd party security audit for the app, and it would hugely help us if some part of this $20,000+ expense could be covered with donations.
|
||||
|
||||
Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, - so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure.
|
||||
|
||||
If you are already using SimpleX Chat, or plan to use it in the future when it has more features, please consider making a donation - it will help us to raise more funds. Donating any amount, even the price of the cup of coffee, would make a huge difference for us.
|
||||
|
||||
It is possible to [donate via GitHub](https://github.com/sponsors/simplex-chat), which is commission-free for us, or [via OpenCollective](https://opencollective.com/simplex-chat), that also accepts donations in crypto-currencies, but charges a commission.
|
||||
|
||||
Thank you,
|
||||
|
||||
Evgeny
|
||||
|
||||
SimpleX Chat founder
|
||||
|
||||
## Disclaimer
|
||||
|
||||
[SimpleX protocols and security model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) was reviewed and had many improvements in v1.0.0; we are currently arranging for the independent implementation audit.
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "chat.simplex.app"
|
||||
minSdk 29
|
||||
targetSdk 32
|
||||
versionCode 41
|
||||
versionName "3.0"
|
||||
versionCode 44
|
||||
versionName "3.0.1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
ndk {
|
||||
|
||||
@@ -64,7 +64,7 @@ fun ChatArchiveLayout(
|
||||
style = MaterialTheme.typography.h1
|
||||
)
|
||||
|
||||
SettingsSectionView(stringResource(R.string.chat_database_section)) {
|
||||
SettingsSectionView(stringResource(R.string.chat_archive_section)) {
|
||||
SettingsActionItem(
|
||||
Icons.Outlined.IosShare,
|
||||
stringResource(R.string.save_archive),
|
||||
|
||||
@@ -43,7 +43,7 @@ fun DatabaseView(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val progressIndicator = remember { mutableStateOf(false) }
|
||||
val runChat = remember { mutableStateOf(false) }
|
||||
val runChat = remember { mutableStateOf(m.chatRunning.value ?: true) }
|
||||
val prefs = m.controller.appPrefs
|
||||
val chatArchiveName = remember { mutableStateOf(prefs.chatArchiveName.get()) }
|
||||
val chatArchiveTime = remember { mutableStateOf(prefs.chatArchiveTime.get()) }
|
||||
@@ -69,7 +69,7 @@ fun DatabaseView(
|
||||
chatArchiveName,
|
||||
chatArchiveTime,
|
||||
chatLastStart,
|
||||
startChat = { startChat(m, runChat) },
|
||||
startChat = { startChat(m, runChat, chatLastStart) },
|
||||
stopChatAlert = { stopChatAlert(m, runChat) },
|
||||
exportArchive = { exportArchive(context, m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) },
|
||||
deleteChatAlert = { deleteChatAlert(m, progressIndicator) },
|
||||
@@ -230,13 +230,15 @@ fun SettingsSectionFooter(text: String) {
|
||||
Text(text, color = HighOrLowlight, modifier = Modifier.padding(start = 16.dp, top = 5.dp).fillMaxWidth(0.9F), fontSize = 12.sp)
|
||||
}
|
||||
|
||||
private fun startChat(m: ChatModel, runChat: MutableState<Boolean>) {
|
||||
private fun startChat(m: ChatModel, runChat: MutableState<Boolean>, chatLastStart: MutableState<Instant?>) {
|
||||
withApi {
|
||||
try {
|
||||
m.controller.apiStartChat()
|
||||
runChat.value = true
|
||||
m.chatRunning.value = true
|
||||
m.controller.appPrefs.chatLastStart.set(Clock.System.now())
|
||||
val ts = Clock.System.now()
|
||||
m.controller.appPrefs.chatLastStart.set(ts)
|
||||
chatLastStart.value = ts
|
||||
} catch (e: Error) {
|
||||
runChat.value = false
|
||||
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_starting_chat), e.toString())
|
||||
|
||||
@@ -27,12 +27,10 @@ import chat.simplex.app.BuildConfig
|
||||
import chat.simplex.app.SimplexApp
|
||||
import chat.simplex.app.model.CIFile
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.*
|
||||
|
||||
fun withApi(action: suspend CoroutineScope.() -> Unit): Job = withScope(GlobalScope, action)
|
||||
|
||||
@@ -245,7 +243,7 @@ fun getLoadedImage(context: Context, file: CIFile?): Bitmap? {
|
||||
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath))
|
||||
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, "r")
|
||||
val fileDescriptor = parcelFileDescriptor?.fileDescriptor
|
||||
val image = BitmapFactory.decodeFileDescriptor(fileDescriptor)
|
||||
val image = decodeSampledBitmapFromFileDescriptor(fileDescriptor, 1000, 1000)
|
||||
parcelFileDescriptor?.close()
|
||||
image
|
||||
} catch (e: Exception) {
|
||||
@@ -256,6 +254,39 @@ fun getLoadedImage(context: Context, file: CIFile?): Bitmap? {
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.android.com/topic/performance/graphics/load-bitmap#load-bitmap
|
||||
private fun decodeSampledBitmapFromFileDescriptor(fileDescriptor: FileDescriptor?, reqWidth: Int, reqHeight: Int): Bitmap {
|
||||
// First decode with inJustDecodeBounds=true to check dimensions
|
||||
return BitmapFactory.Options().run {
|
||||
inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, this)
|
||||
// Calculate inSampleSize
|
||||
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
|
||||
// Decode bitmap with inSampleSize set
|
||||
inJustDecodeBounds = false
|
||||
|
||||
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
||||
// Raw height and width of image
|
||||
val (height: Int, width: Int) = options.run { outHeight to outWidth }
|
||||
var inSampleSize = 1
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
val halfHeight: Int = height / 2
|
||||
val halfWidth: Int = width / 2
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||
// height and width larger than the requested height and width.
|
||||
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
||||
inSampleSize *= 2
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize
|
||||
}
|
||||
|
||||
fun getFileName(context: Context, uri: Uri): String? {
|
||||
return context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
|
||||
@@ -479,6 +479,7 @@
|
||||
|
||||
<!-- ChatArchiveView.kt -->
|
||||
<string name="chat_archive_header">Архив чата</string>
|
||||
<string name="chat_archive_section">АРХИВ ЧАТА</string>
|
||||
<string name="save_archive">Сохранить архив</string>
|
||||
<string name="delete_archive">Удалить архив</string>
|
||||
<string name="archive_created_on_ts">Дата создания <xliff:g id="archive_ts">%1$s</xliff:g></string>
|
||||
|
||||
@@ -481,6 +481,7 @@
|
||||
|
||||
<!-- ChatArchiveView.kt -->
|
||||
<string name="chat_archive_header">Chat archive</string>
|
||||
<string name="chat_archive_section">CHAT ARCHIVE</string>
|
||||
<string name="save_archive">Save archive</string>
|
||||
<string name="delete_archive">Delete archive</string>
|
||||
<string name="archive_created_on_ts">Created on <xliff:g id="archive_ts">%1$s</xliff:g></string>
|
||||
|
||||
@@ -81,7 +81,7 @@ struct ContentView: View {
|
||||
case .success:
|
||||
userAuthorized = true
|
||||
case .failed:
|
||||
AlertManager.shared.showAlert(laFailedAlert())
|
||||
break
|
||||
case .unavailable:
|
||||
userAuthorized = true
|
||||
prefPerformLA = false
|
||||
|
||||
@@ -58,9 +58,9 @@ final class ChatModel: ObservableObject {
|
||||
chats.firstIndex(where: { $0.id == id })
|
||||
}
|
||||
|
||||
func addChat(_ chat: Chat) {
|
||||
func addChat(_ chat: Chat, at position: Int = 0) {
|
||||
withAnimation {
|
||||
chats.insert(chat, at: 0)
|
||||
chats.insert(chat, at: position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,13 +104,22 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func updateChats(with newChats: [ChatData]) {
|
||||
for c in newChats {
|
||||
if let chat = getChat(c.id) {
|
||||
for i in 0..<newChats.count {
|
||||
let c = newChats[i]
|
||||
if let j = getChatIndex(c.id) {
|
||||
let chat = chats[j]
|
||||
chat.chatInfo = c.chatInfo
|
||||
chat.chatItems = c.chatItems
|
||||
chat.chatStats = c.chatStats
|
||||
if i != j {
|
||||
if chatId != c.chatInfo.id {
|
||||
popChat_(j, to: i)
|
||||
} else if i == 0 {
|
||||
chatToTop = c.chatInfo.id
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addChat(Chat(c))
|
||||
addChat(Chat(c), at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,9 +256,9 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func popChat_(_ i: Int) {
|
||||
private func popChat_(_ i: Int, to position: Int = 0) {
|
||||
let chat = chats.remove(at: i)
|
||||
chats.insert(chat, at: 0)
|
||||
chats.insert(chat, at: position)
|
||||
}
|
||||
|
||||
func removeChat(_ id: String) {
|
||||
|
||||
@@ -428,7 +428,7 @@ func receiveFile(fileId: Int64) async {
|
||||
|
||||
func apiReceiveFile(fileId: Int64) async throws -> AChatItem {
|
||||
let r = await chatSendCmd(.receiveFile(fileId: fileId))
|
||||
if case .rcvFileAccepted(let chatItem) = r { return chatItem }
|
||||
if case let .rcvFileAccepted(chatItem) = r { return chatItem }
|
||||
throw r
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ struct SimpleXApp: App {
|
||||
init() {
|
||||
hs_init(0, nil)
|
||||
UserDefaults.standard.register(defaults: appDefaults)
|
||||
setGroupDefaults()
|
||||
setDbContainer()
|
||||
BGManager.shared.register()
|
||||
NtfManager.shared.registerCategories()
|
||||
|
||||
@@ -59,7 +59,7 @@ struct ChatView: View {
|
||||
}
|
||||
markAllRead()
|
||||
}
|
||||
.onChange(of: chatModel.chatItems.count) { _ in
|
||||
.onChange(of: chatModel.chatItems.last?.id) { _ in
|
||||
scrollToBottom(proxy)
|
||||
}
|
||||
.onChange(of: keyboardVisible) { _ in
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct PrivacySettings: View {
|
||||
@AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true
|
||||
@@ -21,6 +22,7 @@ struct PrivacySettings: View {
|
||||
Section("Chats") {
|
||||
settingsRow("photo") {
|
||||
Toggle("Auto-accept images", isOn: $autoAcceptImages)
|
||||
.onChange(of: autoAcceptImages) { privacyAcceptImagesGroupDefault.set($0) }
|
||||
}
|
||||
settingsRow("network") {
|
||||
Toggle("Send link previews", isOn: $useLinkPreviews)
|
||||
|
||||
@@ -43,6 +43,10 @@ private var indent: CGFloat = 36
|
||||
|
||||
let chatArchiveTimeDefault = DateDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CHAT_ARCHIVE_TIME)
|
||||
|
||||
func setGroupDefaults() {
|
||||
privacyAcceptImagesGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES))
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
|
||||
@@ -111,27 +111,23 @@ func receiveMessageForNotification() -> UNNotificationContent? {
|
||||
if let res = recvSimpleXMsg() {
|
||||
logger.debug("NotificationService receiveMessages: \(res.responseType)")
|
||||
switch res {
|
||||
// case let .newContactConnection(connection):
|
||||
// case let .contactConnectionDeleted(connection):
|
||||
case let .contactConnected(contact):
|
||||
return createContactConnectedNtf(contact)
|
||||
// case let .contactConnecting(contact):
|
||||
// TODO profile update
|
||||
case let .receivedContactRequest(contactRequest):
|
||||
return createContactRequestNtf(contactRequest)
|
||||
// case let .contactUpdated(toContact):
|
||||
// TODO profile updated
|
||||
case let .newChatItem(aChatItem):
|
||||
let cInfo = aChatItem.chatInfo
|
||||
let cItem = aChatItem.chatItem
|
||||
var cItem = aChatItem.chatItem
|
||||
if case .image = cItem.content.msgContent {
|
||||
if let file = cItem.file,
|
||||
file.fileSize <= maxImageSize,
|
||||
privacyAcceptImagesGroupDefault.get() {
|
||||
cItem = apiReceiveFile(fileId: file.fileId)?.chatItem ?? cItem
|
||||
}
|
||||
}
|
||||
return createMessageReceivedNtf(cInfo, cItem)
|
||||
// case let .chatItemUpdated(aChatItem):
|
||||
// TODO message updated
|
||||
// let cInfo = aChatItem.chatInfo
|
||||
// let cItem = aChatItem.chatItem
|
||||
// NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
||||
// case let .chatItemDeleted(_, toChatItem):
|
||||
// TODO message updated
|
||||
// case let .rcvFileComplete(aChatItem):
|
||||
// TODO file received?
|
||||
// let cInfo = aChatItem.chatInfo
|
||||
@@ -183,6 +179,13 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiReceiveFile(fileId: Int64) -> AChatItem? {
|
||||
let r = sendSimpleXCmd(.receiveFile(fileId: fileId))
|
||||
if case let .rcvFileAccepted(chatItem) = r { return chatItem }
|
||||
logger.error("receiveFile error: \(responseError(r))")
|
||||
return nil
|
||||
}
|
||||
|
||||
struct NtfMessages {
|
||||
var connEntity: ConnectionEntity?
|
||||
var msgTs: Date?
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; };
|
||||
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
|
||||
5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C13730A28156D2700F43030 /* ContactConnectionView.swift */; };
|
||||
5C14247E286DAC1D0004E3EE /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C142479286DAC1D0004E3EE /* libffi.a */; };
|
||||
5C14247F286DAC1D0004E3EE /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C14247A286DAC1D0004E3EE /* libgmpxx.a */; };
|
||||
5C142480286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C14247B286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj-ghc8.10.7.a */; };
|
||||
5C142481286DAC1D0004E3EE /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C14247C286DAC1D0004E3EE /* libgmp.a */; };
|
||||
5C142482286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C14247D286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj.a */; };
|
||||
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; };
|
||||
5C1B209E2879B553002FF534 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1B20992879B553002FF534 /* libgmpxx.a */; };
|
||||
5C1B209F2879B553002FF534 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1B209A2879B553002FF534 /* libgmp.a */; };
|
||||
5C1B20A02879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1B209B2879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T-ghc8.10.7.a */; };
|
||||
5C1B20A12879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1B209C2879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T.a */; };
|
||||
5C1B20A22879B553002FF534 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1B209D2879B553002FF534 /* libffi.a */; };
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; };
|
||||
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; };
|
||||
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; };
|
||||
@@ -191,12 +191,12 @@
|
||||
5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = "<group>"; };
|
||||
5C13730A28156D2700F43030 /* ContactConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionView.swift; sourceTree = "<group>"; };
|
||||
5C13730C2815740A00F43030 /* DebugJSON.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DebugJSON.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
5C142479286DAC1D0004E3EE /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C14247A286DAC1D0004E3EE /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C14247B286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5C14247C286DAC1D0004E3EE /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C14247D286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj.a"; sourceTree = "<group>"; };
|
||||
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = "<group>"; };
|
||||
5C1B20992879B553002FF534 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C1B209A2879B553002FF534 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C1B209B2879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5C1B209C2879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T.a"; sourceTree = "<group>"; };
|
||||
5C1B209D2879B553002FF534 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = "<group>"; };
|
||||
5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = "<group>"; };
|
||||
5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
|
||||
@@ -327,12 +327,12 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C14247F286DAC1D0004E3EE /* libgmpxx.a in Frameworks */,
|
||||
5C1B20A02879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T-ghc8.10.7.a in Frameworks */,
|
||||
5C1B209E2879B553002FF534 /* libgmpxx.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C142480286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj-ghc8.10.7.a in Frameworks */,
|
||||
5C14247E286DAC1D0004E3EE /* libffi.a in Frameworks */,
|
||||
5C142482286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj.a in Frameworks */,
|
||||
5C142481286DAC1D0004E3EE /* libgmp.a in Frameworks */,
|
||||
5C1B209F2879B553002FF534 /* libgmp.a in Frameworks */,
|
||||
5C1B20A22879B553002FF534 /* libffi.a in Frameworks */,
|
||||
5C1B20A12879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -387,11 +387,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C142479286DAC1D0004E3EE /* libffi.a */,
|
||||
5C14247C286DAC1D0004E3EE /* libgmp.a */,
|
||||
5C14247A286DAC1D0004E3EE /* libgmpxx.a */,
|
||||
5C14247B286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj-ghc8.10.7.a */,
|
||||
5C14247D286DAC1D0004E3EE /* libHSsimplex-chat-2.2.0-B9XlcSXdcwBJ5TIVTDfRQj.a */,
|
||||
5C1B209D2879B553002FF534 /* libffi.a */,
|
||||
5C1B209A2879B553002FF534 /* libgmp.a */,
|
||||
5C1B20992879B553002FF534 /* libgmpxx.a */,
|
||||
5C1B209B2879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T-ghc8.10.7.a */,
|
||||
5C1B209C2879B553002FF534 /* libHSsimplex-chat-3.0.0-7lBlUBoHfjhHKgM9BErF7T.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -1105,7 +1105,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 57;
|
||||
CURRENT_PROJECT_VERSION = 62;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1126,7 +1126,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0;
|
||||
MARKETING_VERSION = 3.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1145,7 +1145,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 57;
|
||||
CURRENT_PROJECT_VERSION = 62;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1166,7 +1166,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0;
|
||||
MARKETING_VERSION = 3.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1224,7 +1224,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 57;
|
||||
CURRENT_PROJECT_VERSION = 62;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1237,7 +1237,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0;
|
||||
MARKETING_VERSION = 3.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1254,7 +1254,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 57;
|
||||
CURRENT_PROJECT_VERSION = 62;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1267,7 +1267,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0;
|
||||
MARKETING_VERSION = 3.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1285,7 +1285,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 62;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1309,7 +1309,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 3.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1331,7 +1331,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 62;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1355,7 +1355,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 3.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -13,6 +13,7 @@ let GROUP_DEFAULT_APP_STATE = "appState"
|
||||
let GROUP_DEFAULT_DB_CONTAINER = "dbContainer"
|
||||
public let GROUP_DEFAULT_CHAT_LAST_START = "chatLastStart"
|
||||
let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode"
|
||||
let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
|
||||
|
||||
let APP_GROUP_NAME = "group.chat.simplex.app"
|
||||
|
||||
@@ -59,6 +60,8 @@ public let ntfPreviewModeGroupDefault = EnumDefault<NotificationPreviewMode>(
|
||||
withDefault: .message
|
||||
)
|
||||
|
||||
public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES)
|
||||
|
||||
public class DateDefault {
|
||||
var defaults: UserDefaults
|
||||
var key: String
|
||||
@@ -103,3 +106,22 @@ public class EnumDefault<T: RawRepresentable> where T.RawValue == String {
|
||||
defaults.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolDefault {
|
||||
var defaults: UserDefaults
|
||||
var key: String
|
||||
|
||||
public init(defaults: UserDefaults = UserDefaults.standard, forKey: String) {
|
||||
self.defaults = defaults
|
||||
self.key = forKey
|
||||
}
|
||||
|
||||
public func get() -> Bool {
|
||||
defaults.bool(forKey: key)
|
||||
}
|
||||
|
||||
public func set(_ value: Bool) {
|
||||
defaults.set(value, forKey: key)
|
||||
defaults.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# SimpleX announces SimpleX Chat v3
|
||||
|
||||
**Published:** Jul 11, 2022
|
||||
|
||||
## New in version 3
|
||||
|
||||
### Instant notifications for iOS
|
||||
|
||||
I wrote previously about [our design for iOS notifications](./20220404-simplex-chat-instant-notifications.md#problem---users-expect-to-be-instantly-notified-when-messages-arrive) - this is now released. The app will offer to migrate the database when updated, and then you need to choose notifications mode – instant or periodic push notifications, or previously available periodic background refresh that does not use push notifications.
|
||||
|
||||
To deliver the notifications to iOS devices we use our notification server, as there is a single private key that Apple issues for the app. This server has minimal amount of information about your chat activity:
|
||||
|
||||
- it does not have the addresses of messaging queues used to send and receive messages - there is an additional address used for notification server to receive notifications from the messaging servers.
|
||||
- notifications themselves do not include message content or contacts, even in encrypted form - they only contain end-to-end encrypted metadata about the server and the queue that has available messages - so Apple servers cannot access the information about how many contacts you have or how frequently each of them messages you - they can only see the total number of notifications your device receives.
|
||||
- message senders do not connect to the notification server, so it cannot in any way correlate sent and received traffic across multiple devices.
|
||||
|
||||
<img src="./images/20220711-notifications.png" width="480">
|
||||
|
||||
**Please note**: for periodic notifications to work the app has to be in the background - if the app is completely stopped (removed from the recent apps), it will not check the new messages and you will not receive notifications. Instant notifications work even when the app is completely stopped.
|
||||
|
||||
### End-to-end encrypted audio/video calls
|
||||
|
||||
You can now call your contacts via WebRTC, connecting via SimpleX Chat relay servers or peer-to-peer, and in the near future you will be able to configure your own STUN/TURN servers used to establish the connection. The calls are end-to-end encrypted - the key is negotiated via the connection you already have with your contact in the chat, that is also used as a signalling layer for WebRTC - in most cases only three messages in total have to be sent by your and your contact's clients for the call to start, including the initial call invitation.
|
||||
|
||||
<img src="./images/20220711-call-in-progress.png" width="480">
|
||||
|
||||
The calls are still quite limited, especially on iOS, - for example, you cannot continue the call while the app is in the background.
|
||||
|
||||
### Database export and import
|
||||
|
||||
Quite a few users asked - _how can I move my chat profile to a new device_? SimpleX Chat v3 has a solution for that - you can now export chat database from one device and import it into another - even to another platform, e.g. you can move chat database from Android phone to iOS or to the terminal (console) client.
|
||||
|
||||
<img src="./images/20220711-database-chat-stopped.png" width="480">
|
||||
|
||||
Some important limitations:
|
||||
|
||||
- you cannot run the same chat profile from two devices, neither at the same time nor in turns. You should only use the latest database version - every time you want to move it to another device you need to export a new chat archive from the device that was the latest to use it, and import it to the device where you want to use it next.
|
||||
- we plan to add internal database encryption soon, but currently it is stored on the device without encryption, and the exported archive is also not encrypted. You should store it securely, and encrypt it if you store it in the cloud or send via email.
|
||||
|
||||
This feature can be useful in some other scenarios:
|
||||
|
||||
- managing multiple chat profiles - it's not very convenient, and a better support of multiple profiles is coming soon.
|
||||
- share access to the same profile with other people, as long as it is not used at the same time. For that use case it may be better to run SimpleX Chat terminal client in the cloud, as we do with our public account that you can connect to via the app.
|
||||
- preserve chat profile when you need to temporarily delete the app from your device.
|
||||
|
||||
### Protocol privacy and performance improvement
|
||||
|
||||
Adding push notifications for iOS required SimpleX Messaging Protocol changes. We managed not just to keep the same level of meta-data privacy from passive observers, but to improve it - now all message meta-data that is passed from the server to the recipient is included into the same encrypted envelope as the message itself - as before, there is no identifiers or ciphertext in common inside TLS traffic between received and sent traffic of the server, and now there is no message timestamp inside TLS as well.
|
||||
|
||||
We also improved the protocol flow for establishing bidirectional connection between two users - it is substantially faster now, consuming much less network traffic and battery. It improves the time it takes to connect to your contacts and to start delivering images and files.
|
||||
|
||||
All these changes did not affect backward compatibility - if your contact has the previous version of the client, or you are connecting to a previous version of the server, the previous version of the protocol will be used - SimpleX has independent version negotiation in 4 protocol layers [since v1](./20220112-simplex-chat-v1-released.md#stable-protocol-implementation), allowing us to evolve the protocols without any disruption to the users.
|
||||
|
||||
## SimpleX platform
|
||||
|
||||
We are building a new platform for distributed Internet applications where privacy of the messages _and_ the network matter. [SimpleX Chat](https://github.com/simplex-chat/simplex-chat) is our first application, a messaging application built on the SimpleX platform.
|
||||
|
||||
### The first (and we believe the only) messaging platform without user identifiers of any kind - 100% private by design!
|
||||
|
||||
To protect identities of users and their connections, SimpleX Chat has no user identifiers visible to the servers and the network – unlike any other messaging platform. Not only SimpleX doesn't use phone numbers or emails, as Signal and many other platforms, it also does not have any persistent identifiers to identify users - unlike many other messengers considered private - Session, Cwtch, Ricochet, Briar, Jami, etc., - all these platforms have global user identifiers, uniquely identifying their users and creating the risks of de-anonymising the users.
|
||||
|
||||
### Why having users' identifiers is bad for the users?
|
||||
|
||||
When each user has a unique identifier on the platform, even if this is just a random number, e.g. as a Session ID, it creates risks that whoever gains access to the platform data can observe how the users are connected and how many messages are transmitted between them, and then correlate this information with the existing public social networks, determining the real identities of some users. Even with the most private messengers built on top of Tor network, having a persistent identity means that if you talk to two different users via the same profile they can prove that they communicate with the same person, as they would use the same address to send messages.
|
||||
|
||||
SimpleX platform avoids these risks by not having any user identity in its design - so even if you talk to two different people from the same chat profile, they would not be able to prove they are talking to the same person - only that user profiles look the same. And we are planning to add a feature allowing to have a different display name for each contact you connect to - quite a few users asked for it.
|
||||
|
||||
### How does it work
|
||||
|
||||
Many people asked: _if SimpleX has no user identifiers, how can it deliver messages?_
|
||||
|
||||
I wrote about it in [v2 release announcement](./20220511-simplex-chat-v2-images-files.md) and you can get more information about SimpleX platform objectives and technical design in [the whitepaper](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md).
|
||||
|
||||
## We ask you to help us pay for 3rd party security audit
|
||||
|
||||
I will get straight to the point: I ask you to support SimpleX Chat with donations.
|
||||
|
||||
We are prioritizing users privacy and security - it would be impossible without your support we were lucky to have so far.
|
||||
|
||||
We are planning a 3rd party security audit for the app, and it would hugely help us if some part of this $20000+ expense could be covered with donations.
|
||||
|
||||
Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, - so anybody can build the future implementations for the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure.
|
||||
|
||||
If you are already using SimpleX Chat, or plan to use it in the future when it has more features, please consider making a donation - it will help us to raise more funds. Donating any amount, even the price of the cup of coffee, would make a huge difference for us.
|
||||
|
||||
It is possible to [donate via GitHub](https://github.com/sponsors/simplex-chat), which is commission-free for us, or [via OpenCollective](https://opencollective.com/simplex-chat), that also accepts donations in crypto-currencies, but charges a commission.
|
||||
|
||||
Thank you,
|
||||
|
||||
Evgeny
|
||||
|
||||
SimpleX Chat founder
|
||||
@@ -1,5 +1,12 @@
|
||||
# Blog
|
||||
|
||||
Jul 11, 2022 [SimpleX Chat v3 released](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md):
|
||||
|
||||
- instant push notifications for iOS
|
||||
- e2e encrypted WebRTC audio/video calls
|
||||
- chat database export and import
|
||||
- protocol privacy and performance improvements
|
||||
|
||||
Jun 4, 2022 [v2.2: the new Privacy and Security settings](./20220604-simplex-chat-new-privacy-security-settings.md)
|
||||
|
||||
May 24, 2022 [v2.1: clearing messages for better conversation privacy](./20220524-simplex-chat-better-privacy.md)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 881 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 457 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 467 KiB |
Reference in New Issue
Block a user