From 909a8aaf9c5de8de18246c656319b96282e4d8ce Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 21 Sep 2022 10:19:13 +0100 Subject: [PATCH] webrtc: change server ports to 443, ios: allow configuring WebRTC ICE servers (#1077) * webrtc: change server ports to 443 * pass stun/turn servers from user default * ios: configure WebRTC ICE servers * translations * update servers * translations --- apps/android/app/src/main/assets/www/call.js | 13 +- apps/ios/Shared/Model/SimpleXAPI.swift | 11 +- apps/ios/Shared/Views/Call/CallManager.swift | 12 +- apps/ios/Shared/Views/Call/WebRTC.swift | 45 +++++ .../Views/UserSettings/CallSettings.swift | 7 + .../UserSettings/NetworkAndServers.swift | 15 +- .../Views/UserSettings/RTCServers.swift | 154 ++++++++++++++++++ .../Views/UserSettings/SMPServers.swift | 2 +- .../Views/UserSettings/SettingsView.swift | 3 +- .../en.xcloc/Localized Contents/en.xliff | 45 +++++ .../ru.xcloc/Localized Contents/ru.xliff | 45 +++++ apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + apps/ios/ru.lproj/Localizable.strings | 27 +++ packages/simplex-chat-webrtc/src/call.ts | 20 +-- 14 files changed, 380 insertions(+), 23 deletions(-) create mode 100644 apps/ios/Shared/Views/UserSettings/RTCServers.swift diff --git a/apps/android/app/src/main/assets/www/call.js b/apps/android/app/src/main/assets/www/call.js index ed33745c01..b247431f4b 100644 --- a/apps/android/app/src/main/assets/www/call.js +++ b/apps/android/app/src/main/assets/www/call.js @@ -24,8 +24,8 @@ var TransformOperation; let activeCall; const processCommand = (function () { const defaultIceServers = [ - { urls: ["stun:stun.simplex.im:5349"] }, - { urls: ["turn:turn.simplex.im:5349"], username: "private", credential: "yleob6AVkiNI87hpR94Z" }, + { urls: ["stun:stun.simplex.im:443"] }, + { urls: ["turn:turn.simplex.im:443"], username: "private", credential: "yleob6AVkiNI87hpR94Z" }, ]; function getCallConfig(encodedInsertableStreams, iceServers, relay) { return { @@ -495,6 +495,7 @@ function callCryptoFunction() { const initialPlainTextRequired = { key: 10, delta: 3, + empty: 1, }; const IV_LENGTH = 12; function encryptFrame(key) { @@ -505,7 +506,9 @@ function callCryptoFunction() { const initial = data.subarray(0, n); const plaintext = data.subarray(n, data.byteLength); try { - const ciphertext = new Uint8Array(plaintext.length ? await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv.buffer }, key, plaintext) : 0); + const ciphertext = plaintext.length + ? new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv.buffer }, key, plaintext)) + : new Uint8Array(0); frame.data = concatN(initial, ciphertext, iv).buffer; controller.enqueue(frame); } @@ -523,7 +526,9 @@ function callCryptoFunction() { const ciphertext = data.subarray(n, data.byteLength - IV_LENGTH); const iv = data.subarray(data.byteLength - IV_LENGTH, data.byteLength); try { - const plaintext = new Uint8Array(ciphertext.length ? await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext) : 0); + const plaintext = ciphertext.length + ? new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext)) + : new Uint8Array(0); frame.data = concatN(initial, plaintext).buffer; controller.enqueue(frame); } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index a7e85777e3..75c9b4d609 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -899,8 +899,17 @@ func processReceivedMsg(_ res: ChatResponse) async { call.peerMedia = callType.media call.sharedKey = sharedKey let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY) + let iceServers = getIceServers() logger.debug(".callOffer useRelay \(useRelay)") - m.callCommand = .offer(offer: offer.rtcSession, iceCandidates: offer.rtcIceCandidates, media: callType.media, aesKey: sharedKey, useWorker: true, relay: useRelay) + logger.debug(".callOffer iceServers \(String(describing: iceServers))") + m.callCommand = .offer( + offer: offer.rtcSession, + iceCandidates: offer.rtcIceCandidates, + media: callType.media, aesKey: sharedKey, + useWorker: true, + iceServers: iceServers, + relay: useRelay + ) } case let .callAnswer(contact, answer): withCall(contact) { call in diff --git a/apps/ios/Shared/Views/Call/CallManager.swift b/apps/ios/Shared/Views/Call/CallManager.swift index 61bd3b9b31..962d783bcb 100644 --- a/apps/ios/Shared/Views/Call/CallManager.swift +++ b/apps/ios/Shared/Views/Call/CallManager.swift @@ -47,8 +47,16 @@ class CallManager { ) m.showCallView = true let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY) - logger.debug("answerIncomingCall useRelay \(useRelay)") - m.callCommand = .start(media: invitation.callType.media, aesKey: invitation.sharedKey, useWorker: true, relay: useRelay) + let iceServers = getIceServers() + logger.debug("answerIncomingCall useRelay: \(useRelay)") + logger.debug("answerIncomingCall iceServers: \(String(describing: iceServers))") + m.callCommand = .start( + media: invitation.callType.media, + aesKey: invitation.sharedKey, + useWorker: true, + iceServers: iceServers, + relay: useRelay + ) } func endCall(callUUID: UUID, completed: @escaping (Bool) -> Void) { diff --git a/apps/ios/Shared/Views/Call/WebRTC.swift b/apps/ios/Shared/Views/Call/WebRTC.swift index 7e63f4f499..930410af97 100644 --- a/apps/ios/Shared/Views/Call/WebRTC.swift +++ b/apps/ios/Shared/Views/Call/WebRTC.swift @@ -394,3 +394,48 @@ struct RTCIceServer: Codable, Equatable { var username: String? = nil var credential: String? = nil } + +// the servers are expected in this format: +// stun:stun.simplex.im:443 +// turn:private:yleob6AVkiNI87hpR94Z@turn.simplex.im:443 +func parseRTCIceServer(_ str: String) -> RTCIceServer? { + var s = replaceScheme(str, "stun:") + s = replaceScheme(s, "turn:") + if let u: URL = URL(string: s), + let scheme = u.scheme, + let host = u.host, + let port = u.port, + u.path == "" && (scheme == "stun" || scheme == "turn") { + return RTCIceServer( + urls: ["\(scheme):\(host):\(port)"], + username: u.user, + credential: u.password + ) + } + return nil +} + +private func replaceScheme(_ s: String, _ scheme: String) -> String { + s.starts(with: scheme) + ? s.replacingOccurrences(of: scheme, with: scheme + "//", options: .anchored, range: nil) + : s +} + +func parseRTCIceServers(_ servers: [String]) -> [RTCIceServer]? { + var iceServers: [RTCIceServer] = [] + for s in servers { + if let server = parseRTCIceServer(s) { + iceServers.append(server) + } else { + return nil + } + } + return iceServers.isEmpty ? nil : iceServers +} + +func getIceServers() -> [RTCIceServer]? { + if let servers = UserDefaults.standard.stringArray(forKey: DEFAULT_WEBRTC_ICE_SERVERS) { + return parseRTCIceServers(servers) + } + return nil +} diff --git a/apps/ios/Shared/Views/UserSettings/CallSettings.swift b/apps/ios/Shared/Views/UserSettings/CallSettings.swift index 2f7be0eebf..254820be3a 100644 --- a/apps/ios/Shared/Views/UserSettings/CallSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/CallSettings.swift @@ -16,6 +16,13 @@ struct CallSettings: View { List { Section { Toggle("Connect via relay", isOn: $webrtcPolicyRelay) + + NavigationLink { + RTCServers() + .navigationTitle("Your ICE servers") + } label: { + Text("WebRTC ICE servers") + } } header: { Text("Settings") } footer: { diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift index 10ffde6a1e..8c61ae44ed 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift @@ -37,7 +37,7 @@ struct NetworkAndServers: View { SMPServers() .navigationTitle("Your SMP servers") } label: { - settingsRow("server.rack") { Text("SMP servers") } + Text("SMP servers") } Picker("Use .onion hosts", selection: $onionHosts) { @@ -49,14 +49,23 @@ struct NetworkAndServers: View { AdvancedNetworkSettings() .navigationTitle("Network settings") } label: { - settingsRow("app.connected.to.app.below.fill") { Text("Advanced network settings") } + Text("Advanced network settings") } } } header: { - Text("") + Text("Messages") } footer: { Text("Using .onion hosts requires compatible VPN provider.") } + + Section("Calls") { + NavigationLink { + RTCServers() + .navigationTitle("Your ICE servers") + } label: { + Text("WebRTC ICE servers") + } + } } } .onAppear { diff --git a/apps/ios/Shared/Views/UserSettings/RTCServers.swift b/apps/ios/Shared/Views/UserSettings/RTCServers.swift new file mode 100644 index 0000000000..99685e6b1b --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/RTCServers.swift @@ -0,0 +1,154 @@ +// +// RTCServers.swift +// SimpleX (iOS) +// +// Created by Evgeny on 20/09/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +private let howToUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/blob/stable/docs/WEBRTC.md#configure-mobile-apps")! + + +struct RTCServers: View { + @State private var userRTCServers: [String] = [] + @State private var isUserRTCServers = false + @State private var isUserRTCServersToggle = false + @State private var editRTCServers = true + @State private var userRTCServersStr = "" + @State private var showBadServersAlert = false + @State private var showResetServersAlert = false + @FocusState private var keyboardVisible: Bool + + var body: some View { + List { + Section { + Toggle("Configure ICE servers", isOn: $isUserRTCServersToggle) + .onChange(of: isUserRTCServersToggle) { _ in + if (isUserRTCServersToggle) { + isUserRTCServers = true + } else if (userRTCServers.isEmpty) { + resetRTCServers() + } else { + showResetServersAlert = true + } + } + .alert(isPresented: $showResetServersAlert) { + Alert( + title: Text("Use SimpleX Chat servers?"), + message: Text("Saved WebRTC ICE servers will be removed"), + primaryButton: .destructive(Text("Confirm")) { + resetRTCServers() + }, secondaryButton: .cancel() { + withAnimation() { + isUserRTCServersToggle = true + } + } + ) + } + } header: { + Text("") + } footer: { + if !isUserRTCServers { + Text("Using SimpleX Chat servers.") + } + } + + if isUserRTCServers { + Section { + if editRTCServers { + TextEditor(text: $userRTCServersStr) + .focused($keyboardVisible) + .font(serversFont) + .disableAutocorrection(true) + .textInputAutocapitalization(.never) + .padding(.horizontal, -5) + .padding(.top, -8) + .frame(height: 160, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .leading) + } else { + ScrollView { + Text(userRTCServersStr) + .font(serversFont) + .frame(minHeight: 0, alignment: .topLeading) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + } + .frame(height: 160) + } + } header: { + Text("ICE servers (one per line)") + } footer: { + HStack(spacing: 20) { + if editRTCServers { + Button("Cancel") { + initialize() + } + Button("Save") { + saveUserRTCServers() + } + .alert(isPresented: $showBadServersAlert) { + Alert(title: Text("Error saving ICE servers"), message: Text("Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated.")) + } + Spacer() + howToButton() + } else { + Button("Edit") { + editRTCServers = true + } + Spacer() + howToButton() + } + } + .font(.body) + } + } + } + .onAppear { initialize() } + } + + private func initialize() { + userRTCServers = UserDefaults.standard.stringArray(forKey: DEFAULT_WEBRTC_ICE_SERVERS) ?? [] + isUserRTCServers = !userRTCServers.isEmpty + isUserRTCServersToggle = isUserRTCServers + editRTCServers = !isUserRTCServers + userRTCServersStr = isUserRTCServers ? userRTCServers.joined(separator: "\n") : "" + } + + private func resetRTCServers() { + isUserRTCServers = false + userRTCServers = [] + UserDefaults.standard.removeObject(forKey: DEFAULT_WEBRTC_ICE_SERVERS) + } + + private func saveUserRTCServers() { + let srvs = userRTCServersStr.components(separatedBy: "\n") + if srvs.count > 0 && Set(srvs).count == srvs.count && parseRTCIceServers(srvs) != nil { + userRTCServers = srvs + UserDefaults.standard.set(srvs, forKey: DEFAULT_WEBRTC_ICE_SERVERS) + editRTCServers = false + } else { + showBadServersAlert = true + } + } + + func howToButton() -> some View { + Button { + DispatchQueue.main.async { + UIApplication.shared.open(howToUrl) + } + } label: { + HStack{ + Text("How to") + Image(systemName: "arrow.up.right.circle") + } + } + } +} + +struct RTCServers_Previews: PreviewProvider { + static var previews: some View { + RTCServers() + } +} diff --git a/apps/ios/Shared/Views/UserSettings/SMPServers.swift b/apps/ios/Shared/Views/UserSettings/SMPServers.swift index 07913c59ac..886cc7a1c0 100644 --- a/apps/ios/Shared/Views/UserSettings/SMPServers.swift +++ b/apps/ios/Shared/Views/UserSettings/SMPServers.swift @@ -9,7 +9,7 @@ import SwiftUI import SimpleXChat -private let serversFont = Font.custom("Menlo", size: 14) +let serversFont = Font.custom("Menlo", size: 14) private let howToUrl = URL(string: "https://github.com/simplex-chat/simplexmq#using-smp-server-and-smp-agent")! diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 846d9b800d..0abab83924 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -19,6 +19,7 @@ let DEFAULT_SHOW_LA_NOTICE = "showLocalAuthenticationNotice" let DEFAULT_LA_NOTICE_SHOWN = "localAuthenticationNoticeShown" let DEFAULT_PERFORM_LA = "performLocalAuthentication" let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay" +let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers" let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" let DEFAULT_EXPERIMENTAL_CALLS = "experimentalCalls" @@ -193,7 +194,7 @@ struct SettingsView: View { } label: { settingsRow("terminal") { Text("Chat console") } } - settingsRow("gear") { + settingsRow("chevron.left.forwardslash.chevron.right") { Toggle("Developer tools", isOn: $developerTools) } ZStack(alignment: .leading) { diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 69bb8221b4..bfca67beff 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -303,6 +303,11 @@ Call already ended! No comment provided by engineer. + + Calls + Calls + No comment provided by engineer. + Can't invite contact! Can't invite contact! @@ -403,6 +408,11 @@ Colors No comment provided by engineer. + + Configure ICE servers + Configure ICE servers + No comment provided by engineer. + Configure SMP servers Configure SMP servers @@ -961,6 +971,11 @@ Error joining group No comment provided by engineer. + + Error saving ICE servers + Error saving ICE servers + No comment provided by engineer. + Error saving SMP servers Error saving SMP servers @@ -1136,6 +1151,11 @@ How to use markdown No comment provided by engineer. + + ICE servers (one per line) + ICE servers (one per line) + No comment provided by engineer. + If the video fails to connect, flip the camera to resolve it. If the video fails to connect, flip the camera to resolve it. @@ -1363,6 +1383,11 @@ We will be adding server redundancy to prevent lost messages. Make sure SMP server addresses are in correct format, line separated and are not duplicated. No comment provided by engineer. + + Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. + Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. + No comment provided by engineer. + Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* @@ -1398,6 +1423,11 @@ We will be adding server redundancy to prevent lost messages. Message text No comment provided by engineer. + + Messages + Messages + No comment provided by engineer. + Migrating database archive... Migrating database archive... @@ -1858,6 +1888,11 @@ We will be adding server redundancy to prevent lost messages. Saved SMP servers will be removed No comment provided by engineer. + + Saved WebRTC ICE servers will be removed + Saved WebRTC ICE servers will be removed + No comment provided by engineer. + Scan QR code Scan QR code @@ -2322,6 +2357,11 @@ To connect, please ask your contact to create another connection link and check Waiting for image No comment provided by engineer. + + WebRTC ICE servers + WebRTC ICE servers + No comment provided by engineer. + Welcome %@! Welcome %@! @@ -2477,6 +2517,11 @@ To connect, please ask your contact to create another connection link and check You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed No comment provided by engineer. + + Your ICE servers + Your ICE servers + No comment provided by engineer. + Your SMP servers Your SMP servers diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 4c15f3f034..6cb28901b2 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -303,6 +303,11 @@ Звонок уже завершен! No comment provided by engineer. + + Calls + Звонки + No comment provided by engineer. + Can't invite contact! Нельзя пригласить контакт! @@ -403,6 +408,11 @@ Цвета No comment provided by engineer. + + Configure ICE servers + Настройка ICE серверов + No comment provided by engineer. + Configure SMP servers Настройка SMP серверов @@ -961,6 +971,11 @@ Ошибка приглашения No comment provided by engineer. + + Error saving ICE servers + Ошибка при сохранении ICE серверов + No comment provided by engineer. + Error saving SMP servers Ошибка при сохранении SMP серверов @@ -1136,6 +1151,11 @@ Как форматировать No comment provided by engineer. + + ICE servers (one per line) + ICE серверы (один на строке) + No comment provided by engineer. + If the video fails to connect, flip the camera to resolve it. Если видео не соединилось, переключите камеру. @@ -1363,6 +1383,11 @@ We will be adding server redundancy to prevent lost messages. Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. No comment provided by engineer. + + Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. + Пожалуйста, проверьте, что адреса WebRTC ICE серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. + No comment provided by engineer. + Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* Много пользователей спросили: *как SimpleX доставляет сообщения без идентификаторов пользователей?* @@ -1398,6 +1423,11 @@ We will be adding server redundancy to prevent lost messages. Текст сообщения No comment provided by engineer. + + Messages + Сообщения + No comment provided by engineer. + Migrating database archive... Данные чата перемещаются... @@ -1858,6 +1888,11 @@ We will be adding server redundancy to prevent lost messages. Сохраненные SMP серверы будут удалены No comment provided by engineer. + + Saved WebRTC ICE servers will be removed + Сохраненные WebRTC ICE серверы будут удалены + No comment provided by engineer. + Scan QR code Сканировать QR код @@ -2322,6 +2357,11 @@ To connect, please ask your contact to create another connection link and check Ожидается прием изображения No comment provided by engineer. + + WebRTC ICE servers + WebRTC ICE серверы + No comment provided by engineer. + Welcome %@! Здравствуйте %@! @@ -2477,6 +2517,11 @@ To connect, please ask your contact to create another connection link and check Вы используете инкогнито профиль для этой группы - чтобы предотвратить раскрытие вашего основного профиля, приглашать контакты не разрешено No comment provided by engineer. + + Your ICE servers + Ваши ICE серверы + No comment provided by engineer. + Your SMP servers Ваши SMP серверы diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 7f2cf4c517..6977e4445f 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ 5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA91282713FD00B3292C /* CreateProfile.swift */; }; 5CB0BA962827143500B3292C /* MakeConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA952827143500B3292C /* MakeConnection.swift */; }; 5CB0BA9A2827FD8800B3292C /* HowItWorks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA992827FD8800B3292C /* HowItWorks.swift */; }; + 5CB2084F28DA4B4800D024EC /* RTCServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB2084E28DA4B4800D024EC /* RTCServers.swift */; }; 5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */; }; 5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E62868D76D001FD2EF /* NotificationsView.swift */; }; 5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */; }; @@ -271,6 +272,7 @@ 5CB0BA91282713FD00B3292C /* CreateProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfile.swift; sourceTree = ""; }; 5CB0BA952827143500B3292C /* MakeConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeConnection.swift; sourceTree = ""; }; 5CB0BA992827FD8800B3292C /* HowItWorks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowItWorks.swift; sourceTree = ""; }; + 5CB2084E28DA4B4800D024EC /* RTCServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTCServers.swift; sourceTree = ""; }; 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendChat.swift; sourceTree = ""; }; 5CB346E62868D76D001FD2EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushEnvironment.swift; sourceTree = ""; }; @@ -561,6 +563,7 @@ 5CB924E027A867BA00ACCCDD /* UserProfile.swift */, 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */, 640F50E227CF991C001E05C2 /* SMPServers.swift */, + 5CB2084E28DA4B4800D024EC /* RTCServers.swift */, 5C3F1D592844B4DE00EC8A82 /* ExperimentalFeaturesView.swift */, 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */, ); @@ -876,6 +879,7 @@ 5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */, 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */, 5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */, + 5CB2084F28DA4B4800D024EC /* RTCServers.swift in Sources */, 3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */, 3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */, 5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */, diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 22c2ee0f91..181355ce03 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -218,6 +218,9 @@ /* call status */ "calling…" = "входящий звонок…"; +/* No comment provided by engineer. */ +"Calls" = "Звонки"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Нельзя пригласить контакт!"; @@ -284,6 +287,9 @@ /* No comment provided by engineer. */ "complete" = "соединение завершено"; +/* No comment provided by engineer. */ +"Configure ICE servers" = "Настройка ICE серверов"; + /* No comment provided by engineer. */ "Configure SMP servers" = "Настройка SMP серверов"; @@ -683,6 +689,9 @@ /* No comment provided by engineer. */ "Error saving group profile" = "Ошибка при сохранении профиля группы"; +/* No comment provided by engineer. */ +"Error saving ICE servers" = "Ошибка при сохранении ICE серверов"; + /* No comment provided by engineer. */ "Error saving SMP servers" = "Ошибка при сохранении SMP серверов"; @@ -791,6 +800,9 @@ /* No comment provided by engineer. */ "How to use markdown" = "Как форматировать"; +/* No comment provided by engineer. */ +"ICE servers (one per line)" = "ICE серверы (один на строке)"; + /* No comment provided by engineer. */ "If the video fails to connect, flip the camera to resolve it." = "Если видео не соединилось, переключите камеру."; @@ -956,6 +968,9 @@ /* No comment provided by engineer. */ "Make sure SMP server addresses are in correct format, line separated and are not duplicated." = "Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется."; +/* No comment provided by engineer. */ +"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Пожалуйста, проверьте, что адреса WebRTC ICE серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется."; + /* No comment provided by engineer. */ "Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Много пользователей спросили: *как SimpleX доставляет сообщения без идентификаторов пользователей?*"; @@ -986,6 +1001,9 @@ /* No comment provided by engineer. */ "Message text" = "Текст сообщения"; +/* No comment provided by engineer. */ +"Messages" = "Сообщения"; + /* No comment provided by engineer. */ "Migrating database archive..." = "Данные чата перемещаются..."; @@ -1292,6 +1310,9 @@ /* No comment provided by engineer. */ "Saved SMP servers will be removed" = "Сохраненные SMP серверы будут удалены"; +/* No comment provided by engineer. */ +"Saved WebRTC ICE servers will be removed" = "Сохраненные WebRTC ICE серверы будут удалены"; + /* No comment provided by engineer. */ "Scan contact's QR code" = "Сосканировать QR код контакта"; @@ -1619,6 +1640,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "хочет соединиться с вами!"; +/* No comment provided by engineer. */ +"WebRTC ICE servers" = "WebRTC ICE серверы"; + /* No comment provided by engineer. */ "Welcome %@!" = "Здравствуйте %@!"; @@ -1766,6 +1790,9 @@ /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Текущие данные вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными."; +/* No comment provided by engineer. */ +"Your ICE servers" = "Ваши ICE серверы"; + /* No comment provided by engineer. */ "Your privacy" = "Конфиденциальность"; diff --git a/packages/simplex-chat-webrtc/src/call.ts b/packages/simplex-chat-webrtc/src/call.ts index 37ddc02b64..e62a5a419d 100644 --- a/packages/simplex-chat-webrtc/src/call.ts +++ b/packages/simplex-chat-webrtc/src/call.ts @@ -217,8 +217,8 @@ const processCommand = (function () { } const defaultIceServers: RTCIceServer[] = [ - {urls: ["stun:stun.simplex.im:5349"]}, - {urls: ["turn:turn.simplex.im:5349"], username: "private", credential: "yleob6AVkiNI87hpR94Z"}, + {urls: ["stun:stun.simplex.im:443"]}, + {urls: ["turn:turn.simplex.im:443"], username: "private", credential: "yleob6AVkiNI87hpR94Z"}, ] function getCallConfig(encodedInsertableStreams: boolean, iceServers?: RTCIceServer[], relay?: boolean): CallConfig { @@ -703,16 +703,12 @@ interface CallCrypto { decodeBase64url: (b64: Uint8Array) => Uint8Array | undefined } -interface RTCEncodedVideoFrame { - type: "key" | "delta" - data: ArrayBuffer -} - // Cryptography function - it is loaded both in the main window and in worker context (if the worker is used) function callCryptoFunction(): CallCrypto { const initialPlainTextRequired = { key: 10, delta: 3, + empty: 1, } const IV_LENGTH = 12 @@ -725,9 +721,9 @@ function callCryptoFunction(): CallCrypto { const initial = data.subarray(0, n) const plaintext = data.subarray(n, data.byteLength) try { - const ciphertext = new Uint8Array( - plaintext.length ? await crypto.subtle.encrypt({name: "AES-GCM", iv: iv.buffer}, key, plaintext) : 0 - ) + const ciphertext = plaintext.length + ? new Uint8Array(await crypto.subtle.encrypt({name: "AES-GCM", iv: iv.buffer}, key, plaintext)) + : new Uint8Array(0) frame.data = concatN(initial, ciphertext, iv).buffer controller.enqueue(frame) } catch (e) { @@ -745,7 +741,9 @@ function callCryptoFunction(): CallCrypto { const ciphertext = data.subarray(n, data.byteLength - IV_LENGTH) const iv = data.subarray(data.byteLength - IV_LENGTH, data.byteLength) try { - const plaintext = new Uint8Array(ciphertext.length ? await crypto.subtle.decrypt({name: "AES-GCM", iv}, key, ciphertext) : 0) + const plaintext = ciphertext.length + ? new Uint8Array(await crypto.subtle.decrypt({name: "AES-GCM", iv}, key, ciphertext)) + : new Uint8Array(0) frame.data = concatN(initial, plaintext).buffer controller.enqueue(frame) } catch (e) {