mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 08:33:09 +00:00
merge master into badge-in-app-checkout
This commit is contained in:
commit
80b7f96a96
229 files changed
+8341
-1977
No files matched your search
@@ -123,6 +123,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
|
||||
private func prepareForLaunch() {
|
||||
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
|
||||
excludeAppDataFromBackup()
|
||||
}
|
||||
|
||||
static func keepScreenOn(_ on: Bool) {
|
||||
|
||||
@@ -2391,6 +2391,10 @@ func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws
|
||||
m.chatRunning = true
|
||||
chatLastStartGroupDefault.set(Date.now)
|
||||
Task { await BadgeStore.shared.presentUnfinished() }
|
||||
if shouldDeleteDatabaseBackupsDefault.get() {
|
||||
deleteDatabaseBackups()
|
||||
shouldDeleteDatabaseBackupsDefault.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
func startChatWithTemporaryDatabase(ctrl: chat_ctrl) throws -> User? {
|
||||
@@ -2952,23 +2956,19 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
m.callInvitations[invitation.contact.id] = invitation
|
||||
}
|
||||
activateCall(invitation)
|
||||
case let .callOffer(_, contact, callType, offer, sharedKey, _):
|
||||
case let .callOffer(_, contact, callType, offer, sharedKey, askConfirmation):
|
||||
await withCall(contact) { call in
|
||||
await MainActor.run {
|
||||
call.callState = .offerReceived
|
||||
call.sharedKey = sharedKey
|
||||
}
|
||||
let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY)
|
||||
let iceServers = getIceServers()
|
||||
logger.debug(".callOffer useRelay \(useRelay)")
|
||||
logger.debug(".callOffer iceServers \(String(describing: iceServers))")
|
||||
await m.callCommand.processCommand(.offer(
|
||||
offer: offer.rtcSession,
|
||||
iceCandidates: offer.rtcIceCandidates,
|
||||
media: callType.media, aesKey: sharedKey,
|
||||
iceServers: iceServers,
|
||||
relay: useRelay
|
||||
))
|
||||
if askConfirmation {
|
||||
showUnencryptedCallAlert(call) {
|
||||
Task { await processCallOffer(callType, offer, sharedKey) }
|
||||
}
|
||||
} else {
|
||||
await processCallOffer(callType, offer, sharedKey)
|
||||
}
|
||||
}
|
||||
case let .callAnswer(_, contact, answer):
|
||||
await withCall(contact) { call in
|
||||
@@ -3108,6 +3108,43 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
logger.debug("processReceivedMsg: ignoring \(res.responseType), not in call with the contact \(contact.id)")
|
||||
}
|
||||
}
|
||||
|
||||
func processCallOffer(_ callType: CallType, _ offer: WebRTCSession, _ sharedKey: String?) async {
|
||||
let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY)
|
||||
let iceServers = getIceServers()
|
||||
logger.debug(".callOffer useRelay \(useRelay)")
|
||||
logger.debug(".callOffer iceServers \(String(describing: iceServers))")
|
||||
await m.callCommand.processCommand(.offer(
|
||||
offer: offer.rtcSession,
|
||||
iceCandidates: offer.rtcIceCandidates,
|
||||
media: callType.media, aesKey: sharedKey,
|
||||
iceServers: iceServers,
|
||||
relay: useRelay
|
||||
))
|
||||
}
|
||||
|
||||
func showUnencryptedCallAlert(_ call: Call, onContinue: @escaping () -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
showAlert(
|
||||
NSLocalizedString("Call is not encrypted", comment: "alert title"),
|
||||
message: String.localizedStringWithFormat(NSLocalizedString("%@ accepted the call without end-to-end encryption.", comment: "alert message"), call.contact.displayName),
|
||||
actions: {[
|
||||
UIAlertAction(title: NSLocalizedString("End call", comment: "alert action"), style: .destructive) { _ in
|
||||
// the call may have ended, or a new one started with the same contact, while the alert was shown
|
||||
guard m.activeCall === call else { return }
|
||||
if let uuid = call.callUUID {
|
||||
CallController.shared.endCall(callUUID: uuid)
|
||||
} else {
|
||||
CallController.shared.endCall(call: call) {}
|
||||
}
|
||||
},
|
||||
UIAlertAction(title: NSLocalizedString("Continue", comment: "alert action"), style: .default) { _ in
|
||||
if m.activeCall === call { onContinue() }
|
||||
}
|
||||
]}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func switchToLocalSession() {
|
||||
|
||||
@@ -117,7 +117,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
|
||||
encryptor.delegate = self
|
||||
frameEncryptor = encryptor
|
||||
|
||||
let decryptor = RTCFrameDecryptor.init(sizeChange: -Int32(WebRTCClient.ivTagBytes))
|
||||
let decryptor = RTCFrameDecryptor.init(sizeChange: 0)
|
||||
decryptor.delegate = self
|
||||
frameDecryptor = decryptor
|
||||
}
|
||||
@@ -508,11 +508,12 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
|
||||
|
||||
func frameDecryptor(_ decryptor: RTCFrameDecryptor, mediaType: RTCRtpMediaType, withFrame encrypted: Data) -> Data? {
|
||||
guard encrypted.count > 0 else { return nil }
|
||||
let isKeyFrame = encrypted[0] & 1 == 0
|
||||
let clearTextBytesSize = mediaType.rawValue == 0 ? 1 : isKeyFrame ? 10 : 3
|
||||
guard encrypted.count >= clearTextBytesSize + WebRTCClient.ivTagBytes else { return nil }
|
||||
if var key: [CChar] = activeCall?.aesKey?.cString(using: .utf8),
|
||||
let pointer: UnsafeMutableRawPointer = malloc(encrypted.count) {
|
||||
memcpy(pointer, (encrypted as NSData).bytes, encrypted.count)
|
||||
let isKeyFrame = encrypted[0] & 1 == 0
|
||||
let clearTextBytesSize = mediaType.rawValue == 0 ? 1 : isKeyFrame ? 10 : 3
|
||||
logCrypto("decrypt", chat_decrypt_media(&key, pointer.advanced(by: clearTextBytesSize), Int32(encrypted.count - clearTextBytesSize)))
|
||||
return Data(bytes: pointer, count: encrypted.count - WebRTCClient.ivTagBytes)
|
||||
} else {
|
||||
@@ -522,11 +523,12 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
|
||||
|
||||
func frameEncryptor(_ encryptor: RTCFrameEncryptor, mediaType: RTCRtpMediaType, withFrame unencrypted: Data) -> Data? {
|
||||
guard unencrypted.count > 0 else { return nil }
|
||||
let isKeyFrame = unencrypted[0] & 1 == 0
|
||||
let clearTextBytesSize = mediaType.rawValue == 0 ? 1 : isKeyFrame ? 10 : 3
|
||||
guard unencrypted.count >= clearTextBytesSize else { return nil }
|
||||
if var key: [CChar] = activeCall?.aesKey?.cString(using: .utf8),
|
||||
let pointer: UnsafeMutableRawPointer = malloc(unencrypted.count + WebRTCClient.ivTagBytes) {
|
||||
memcpy(pointer, (unencrypted as NSData).bytes, unencrypted.count)
|
||||
let isKeyFrame = unencrypted[0] & 1 == 0
|
||||
let clearTextBytesSize = mediaType.rawValue == 0 ? 1 : isKeyFrame ? 10 : 3
|
||||
logCrypto("encrypt", chat_encrypt_media(chat_ctrl, &key, pointer.advanced(by: clearTextBytesSize), Int32(unencrypted.count + WebRTCClient.ivTagBytes - clearTextBytesSize)))
|
||||
return Data(bytes: pointer, count: unencrypted.count + WebRTCClient.ivTagBytes)
|
||||
} else {
|
||||
|
||||
@@ -22,11 +22,11 @@ struct GetStakeBanner: View {
|
||||
Button(action: onTap) {
|
||||
HStack(spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Get a stake in SimpleX Chat!")
|
||||
Text("Invest in SimpleX Chat!")
|
||||
.font(.headline)
|
||||
.foregroundColor(theme.colors.primary)
|
||||
.lineLimit(2)
|
||||
Text("Invest on Wefunder from $100")
|
||||
Text("Crowdfunding on Wefunder")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(theme.colors.onBackground)
|
||||
.lineLimit(2)
|
||||
|
||||
@@ -153,6 +153,9 @@ struct DatabaseEncryptionView: View {
|
||||
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
|
||||
}
|
||||
try await apiStorageEncryption(currentKey: currentKey, newKey: newKey)
|
||||
if currentKey != newKey {
|
||||
shouldDeleteDatabaseBackupsDefault.set(true)
|
||||
}
|
||||
encryptionStartedDefault.set(false)
|
||||
initialRandomDBPassphraseGroupDefault.set(false)
|
||||
if migration {
|
||||
|
||||
@@ -118,6 +118,9 @@ struct DatabaseErrorView: View {
|
||||
case .errorKeychain:
|
||||
titleText("Keychain error")
|
||||
errorView(Text("Cannot access keychain to save database password"))
|
||||
case .errorKeyGeneration:
|
||||
titleText("Database error")
|
||||
errorView(Text("Cannot generate random database passphrase"))
|
||||
case .invalidConfirmation:
|
||||
// this can only happen if incorrect parameter is passed
|
||||
titleText("Invalid migration confirmation")
|
||||
@@ -217,6 +220,8 @@ struct DatabaseErrorView: View {
|
||||
)
|
||||
case .errorKeychain:
|
||||
am.showAlertMsg(title: "Keychain error")
|
||||
case .errorKeyGeneration:
|
||||
am.showAlertMsg(title: "Database error", message: "Cannot generate random database passphrase")
|
||||
case let .errorSQL(_, error):
|
||||
am.showAlert(Alert(
|
||||
title: Text("Database error"),
|
||||
|
||||
@@ -453,6 +453,10 @@ struct DatabaseView: View {
|
||||
ChatReceiver.shared.start()
|
||||
chatLastStartGroupDefault.set(Date.now)
|
||||
AppChatState.shared.set(.active)
|
||||
if shouldDeleteDatabaseBackupsDefault.get() {
|
||||
deleteDatabaseBackups()
|
||||
shouldDeleteDatabaseBackupsDefault.set(false)
|
||||
}
|
||||
} catch let error {
|
||||
runChat.wrappedValue = false
|
||||
showAlert(NSLocalizedString("Error starting chat", comment: ""), message: responseError(error))
|
||||
|
||||
@@ -709,6 +709,8 @@ private func showErrorOnMigrationIfNeeded(_ status: DBMigrationResult, _ alert:
|
||||
alert.wrappedValue = .wrongPassphrase()
|
||||
case .errorKeychain:
|
||||
alert.wrappedValue = .keychainError()
|
||||
case .errorKeyGeneration:
|
||||
alert.wrappedValue = .databaseError(message: NSLocalizedString("Cannot generate random database passphrase", comment: "alert message"))
|
||||
case let .errorSQL(_, error):
|
||||
alert.wrappedValue = .databaseError(message: error)
|
||||
case let .unknown(error):
|
||||
|
||||
@@ -539,7 +539,7 @@ struct MigrateToDevice: View {
|
||||
Task {
|
||||
do {
|
||||
if !hasChatCtrl() {
|
||||
chatInitControllerRemovingDatabases()
|
||||
try chatInitControllerRemovingDatabases()
|
||||
} else if ChatModel.shared.chatRunning == true {
|
||||
// cannot delete storage if chat is running
|
||||
try await stopChatAsync()
|
||||
@@ -735,6 +735,8 @@ private func showErrorOnMigrationIfNeeded(_ status: DBMigrationResult, _ alert:
|
||||
alert.wrappedValue = .wrongPassphrase()
|
||||
case .errorKeychain:
|
||||
alert.wrappedValue = .keychainError()
|
||||
case .errorKeyGeneration:
|
||||
alert.wrappedValue = .databaseError(message: NSLocalizedString("Cannot generate random database passphrase", comment: "alert message"))
|
||||
case let .errorSQL(_, error):
|
||||
alert.wrappedValue = .databaseError(message: error)
|
||||
case let .unknown(error):
|
||||
|
||||
@@ -890,7 +890,7 @@ struct GetStakeView: View {
|
||||
var body: some View {
|
||||
ZoomablePageView {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text(verbatim: "Get a stake in\nSimpleX Chat")
|
||||
Text(verbatim: "Invest in\nSimpleX Chat")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -13,6 +13,8 @@ import SimpleXChat
|
||||
|
||||
let simplexTeamURL = URL(string: "simplex:/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw?h=smp6.simplex.im")!
|
||||
|
||||
let simplexNewsURL = URL(string: "simplex:/c#grcfG3ulVI4Sh6ow33qBsmSk7uEy3gRSl2KkJ5ER6tA?h=smp18.simplex.im")!
|
||||
|
||||
let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||
|
||||
let appBuild = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
|
||||
@@ -47,6 +49,7 @@ let DEFAULT_SHOULD_IMPORT_APP_SETTINGS = "shouldImportAppSettings"
|
||||
let DEFAULT_DEVELOPER_TOOLS = "developerTools"
|
||||
let DEFAULT_ENCRYPTION_STARTED = "encryptionStarted"
|
||||
let DEFAULT_ENCRYPTION_STARTED_AT = "encryptionStartedAt"
|
||||
let DEFAULT_SHOULD_DELETE_DATABASE_BACKUPS = "shouldDeleteDatabaseBackups"
|
||||
let DEFAULT_ACCENT_COLOR_RED = "accentColorRed" // deprecated, only used for migration
|
||||
let DEFAULT_ACCENT_COLOR_GREEN = "accentColorGreen" // deprecated, only used for migration
|
||||
let DEFAULT_ACCENT_COLOR_BLUE = "accentColorBlue" // deprecated, only used for migration
|
||||
@@ -205,6 +208,8 @@ let encryptionStartedDefault = BoolDefault(defaults: UserDefaults.standard, forK
|
||||
|
||||
let encryptionStartedAtDefault = DateDefault(defaults: UserDefaults.standard, forKey: DEFAULT_ENCRYPTION_STARTED_AT)
|
||||
|
||||
let shouldDeleteDatabaseBackupsDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOULD_DELETE_DATABASE_BACKUPS)
|
||||
|
||||
let connectViaLinkTabDefault = EnumDefault<ConnectViaLinkTab>(defaults: UserDefaults.standard, forKey: DEFAULT_CONNECT_VIA_LINK_TAB, withDefault: .scan)
|
||||
|
||||
let privacySimplexLinkModeDefault = EnumDefault<SimpleXLinkMode>(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_SIMPLEX_LINK_MODE, withDefault: .description)
|
||||
@@ -462,6 +467,15 @@ struct SettingsView: View {
|
||||
}
|
||||
|
||||
Section(header: Text("Contact").foregroundColor(theme.colors.secondary)) {
|
||||
settingsRow("antenna.radiowaves.left.and.right", color: theme.colors.secondary) {
|
||||
Button("Follow SimpleX Network News") {
|
||||
dismiss()
|
||||
DispatchQueue.main.async {
|
||||
ChatModel.shared.appOpenUrl = simplexNewsURL
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(chatModel.chatRunning != true)
|
||||
settingsRow("number", color: theme.colors.secondary) {
|
||||
Button("Send questions and ideas") {
|
||||
dismiss()
|
||||
|
||||
@@ -247,6 +247,11 @@ class ShareModel: ObservableObject {
|
||||
title: "Keychain error",
|
||||
message: "Cannot access keychain to save database password"
|
||||
)
|
||||
case .errorKeyGeneration:
|
||||
ErrorAlert(
|
||||
title: "Database error",
|
||||
message: "Cannot generate random database passphrase"
|
||||
)
|
||||
case .invalidConfirmation:
|
||||
ErrorAlert("Invalid migration confirmation")
|
||||
case let .unknown(json):
|
||||
|
||||
@@ -195,8 +195,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -590,8 +590,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -764,8 +764,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -852,8 +852,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.7-KLlSPSHmTF28mDmnoCMp3f.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.10-1nt2m8GM0NE8NSirknFyt8.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -2147,7 +2147,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2197,7 +2197,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2239,7 +2239,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
@@ -2259,7 +2259,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
@@ -2284,7 +2284,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -2321,7 +2321,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -2358,7 +2358,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2409,7 +2409,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2463,7 +2463,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2497,7 +2497,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 362;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
|
||||
@@ -32,7 +32,12 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio
|
||||
} else if useKeychain {
|
||||
if !hasDatabase() {
|
||||
logger.debug("chatMigrateInit generating a random DB key")
|
||||
dbKey = randomDatabasePassword()
|
||||
guard let key = randomDatabasePassword() else {
|
||||
let result = (false, DBMigrationResult.errorKeyGeneration)
|
||||
migrationResult = result
|
||||
return result
|
||||
}
|
||||
dbKey = key
|
||||
initialRandomDBPassphraseGroupDefault.set(true)
|
||||
} else if let key = kcDatabasePassword.get() {
|
||||
dbKey = key
|
||||
@@ -56,7 +61,7 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio
|
||||
|
||||
public func chatInitTemporaryDatabase(url: URL, key: String? = nil, confirmation: MigrationConfirmation = .error) -> (DBMigrationResult, chat_ctrl?) {
|
||||
let dbPath = url.path
|
||||
let dbKey = key ?? randomDatabasePassword()
|
||||
guard let dbKey = key ?? randomDatabasePassword() else { return (.errorKeyGeneration, nil) }
|
||||
logger.debug("chatInitTemporaryDatabase path: \(dbPath)")
|
||||
var temporaryController: chat_ctrl? = nil
|
||||
var cPath = dbPath.cString(using: .utf8)!
|
||||
@@ -66,14 +71,14 @@ public func chatInitTemporaryDatabase(url: URL, key: String? = nil, confirmation
|
||||
return (dbMigrationResult(dataFromCString(cjson)), temporaryController)
|
||||
}
|
||||
|
||||
public func chatInitControllerRemovingDatabases() {
|
||||
public func chatInitControllerRemovingDatabases() throws {
|
||||
let dbPath = getAppDatabasePath().path
|
||||
let fm = FileManager.default
|
||||
// Remove previous databases, otherwise, can be .errorNotADatabase with nil controller
|
||||
try? fm.removeItem(atPath: dbPath + CHAT_DB)
|
||||
try? fm.removeItem(atPath: dbPath + AGENT_DB)
|
||||
|
||||
let dbKey = randomDatabasePassword()
|
||||
guard let dbKey = randomDatabasePassword() else { throw RuntimeError("Cannot generate random database passphrase") }
|
||||
logger.debug("chatInitControllerRemovingDatabases path: \(dbPath)")
|
||||
var cPath = dbPath.cString(using: .utf8)!
|
||||
var cKey = dbKey.cString(using: .utf8)!
|
||||
@@ -353,6 +358,7 @@ public enum DBMigrationResult: Decodable, Equatable {
|
||||
case errorMigration(dbFile: String, migrationError: MigrationError)
|
||||
case errorSQL(dbFile: String, migrationSQLError: String)
|
||||
case errorKeychain
|
||||
case errorKeyGeneration
|
||||
case unknown(json: String)
|
||||
}
|
||||
|
||||
|
||||
@@ -3904,6 +3904,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
|
||||
case .memberCreatedContact: return false
|
||||
case .memberProfileUpdated: return false
|
||||
case .newMemberPendingReview: return true
|
||||
case .msgBadSignature: return false
|
||||
}
|
||||
case .sndGroupEvent: return false
|
||||
case .rcvConnEvent: return false
|
||||
@@ -6075,6 +6076,7 @@ public enum RcvGroupEvent: Decodable, Hashable {
|
||||
case memberCreatedContact
|
||||
case memberProfileUpdated(fromProfile: Profile, toProfile: Profile)
|
||||
case newMemberPendingReview
|
||||
case msgBadSignature
|
||||
|
||||
var text: String { text(isChannel: false) }
|
||||
|
||||
@@ -6110,6 +6112,7 @@ public enum RcvGroupEvent: Decodable, Hashable {
|
||||
case .memberCreatedContact: return NSLocalizedString("requested connection", comment: "rcv group event chat item")
|
||||
case let .memberProfileUpdated(fromProfile, toProfile): return profileUpdatedText(fromProfile, toProfile)
|
||||
case .newMemberPendingReview: return NSLocalizedString("New member wants to join the group.", comment: "rcv group event chat item")
|
||||
case .msgBadSignature: return NSLocalizedString("message rejected: bad signature", comment: "rcv group event chat item")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ private let CHAT_DB_BAK: String = "_chat.db.bak"
|
||||
|
||||
private let AGENT_DB_BAK: String = "_agent.db.bak"
|
||||
|
||||
private let CHAT_DB_EXPORTED: String = "_chat.db.exported"
|
||||
|
||||
private let AGENT_DB_EXPORTED: String = "_agent.db.exported"
|
||||
|
||||
// Spec: spec/database.md#getDocumentsDirectory
|
||||
public func getDocumentsDirectory() -> URL {
|
||||
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
@@ -66,6 +70,18 @@ func getAppDirectory() -> URL {
|
||||
: getDocumentsDirectory()
|
||||
}
|
||||
|
||||
public func excludeAppDataFromBackup() {
|
||||
var values = URLResourceValues()
|
||||
values.isExcludedFromBackup = true
|
||||
for var dir in [getGroupContainerDirectory(), getDocumentsDirectory()] {
|
||||
do {
|
||||
try dir.setResourceValues(values)
|
||||
} catch {
|
||||
logger.error("FileUtils.excludeAppDataFromBackup error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/database.md#DB_FILE_PREFIX
|
||||
let DB_FILE_PREFIX = "simplex_v1"
|
||||
|
||||
@@ -101,6 +117,8 @@ public func deleteAppDatabaseAndFiles() {
|
||||
}
|
||||
try? fm.removeItem(atPath: dbPath + CHAT_DB_BAK)
|
||||
try? fm.removeItem(atPath: dbPath + AGENT_DB_BAK)
|
||||
try? fm.removeItem(atPath: dbPath + CHAT_DB_EXPORTED)
|
||||
try? fm.removeItem(atPath: dbPath + AGENT_DB_EXPORTED)
|
||||
try? fm.removeItem(at: getTempFilesDirectory())
|
||||
try? fm.removeItem(at: getMigrationTempFilesDirectory())
|
||||
try? fm.createDirectory(at: getTempFilesDirectory(), withIntermediateDirectories: true)
|
||||
@@ -176,6 +194,13 @@ private func restoreBackupFile(fromPath: String, toPath: String) throws {
|
||||
try fm.copyItem(atPath: fromPath, toPath: toPath)
|
||||
}
|
||||
|
||||
public func deleteDatabaseBackups() {
|
||||
let fm = FileManager.default
|
||||
let dbPath = getAppDatabasePath().path
|
||||
try? fm.removeItem(atPath: dbPath + CHAT_DB_BAK)
|
||||
try? fm.removeItem(atPath: dbPath + AGENT_DB_BAK)
|
||||
}
|
||||
|
||||
public func hasLegacyDatabase() -> Bool {
|
||||
hasDatabaseAtPath(getLegacyDatabasePath())
|
||||
}
|
||||
@@ -198,6 +223,8 @@ public func removeLegacyDatabaseAndFiles() -> Bool {
|
||||
let r2 = nil != (try? fm.removeItem(atPath: dbPath.path + CHAT_DB))
|
||||
try? fm.removeItem(atPath: dbPath.path + AGENT_DB_BAK)
|
||||
try? fm.removeItem(atPath: dbPath.path + CHAT_DB_BAK)
|
||||
try? fm.removeItem(atPath: dbPath.path + AGENT_DB_EXPORTED)
|
||||
try? fm.removeItem(atPath: dbPath.path + CHAT_DB_EXPORTED)
|
||||
try? fm.removeItem(at: appFiles)
|
||||
return r1 && r2
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public struct KeyChainItem {
|
||||
}
|
||||
}
|
||||
|
||||
func randomDatabasePassword() -> String {
|
||||
func randomDatabasePassword() -> String? {
|
||||
var keyData = Data(count: 32)
|
||||
let status = keyData.withUnsafeMutableBytes {
|
||||
SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!)
|
||||
@@ -46,7 +46,7 @@ func randomDatabasePassword() -> String {
|
||||
return keyData.base64EncodedString()
|
||||
} else {
|
||||
logger.error("randomDatabasePassword: error \(status)")
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) ->
|
||||
title = NSLocalizedString("Encrypted message: no passphrase", comment: "notification")
|
||||
case .errorMigration:
|
||||
title = NSLocalizedString("Encrypted message: database migration error", comment: "notification")
|
||||
case .errorSQL:
|
||||
case .errorSQL, .errorKeyGeneration:
|
||||
title = NSLocalizedString("Encrypted message: database error", comment: "notification")
|
||||
case .errorKeychain:
|
||||
title = NSLocalizedString("Encrypted message: keychain error", comment: "notification")
|
||||
|
||||
@@ -74,6 +74,10 @@ See [`getDocumentsDirectory()`](../SimpleXChat/FileUtils.swift#L47) and [`getGro
|
||||
|
||||
The container choice is stored in `dbContainerGroupDefault` (`GroupDefaults`).
|
||||
|
||||
### Backup Exclusion
|
||||
|
||||
`isExcludedFromBackup` is set on both containers by [`excludeAppDataFromBackup()`](../SimpleXChat/FileUtils.swift#L69-L79), which is called from [`prepareForLaunch()`](../Shared/AppDelegate.swift#L124-L127) on each app launch. Files that the app, NSE and SE create or replace in these directories later are excluded as well.
|
||||
|
||||
---
|
||||
|
||||
## 3. Haskell Store Modules
|
||||
@@ -130,6 +134,7 @@ Migration results are decoded in Swift as `DBMigrationResult`:
|
||||
- `.errorMigration(dbFile:, migrationError:)` -- migration failed
|
||||
- `.errorSQL(dbFile:, migrationSQLError:)` -- SQL error during migration
|
||||
- `.errorKeychain` -- keychain access failed
|
||||
- `.errorKeyGeneration` -- random database key generation failed
|
||||
- `.unknown(json:)` -- unrecognized response
|
||||
|
||||
---
|
||||
|
||||
Reference in new issue
Block a user