diff --git a/README.md b/README.md index e62fb8c8b3..c2df084477 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ You must: Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment. -You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D) +You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D) There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FvYCRjIflKNMGYlfTkuHe4B40qSlQ0439%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAHNdcqNbzXZhyMoSBjT2R0-Eb1EPaLyUg3KZjn-kmM1w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22PD20tcXjw7IpkkMCfR6HLA%3D%3D%22%7D) for developers who build on SimpleX platform: @@ -83,7 +83,7 @@ There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=s There are groups in other languages, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users: -[\#SimpleX-DE](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FkIEl7OQzcp-J6aDmjdlQbRJwqkcZE7XR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAR16PCu02MobRmKAsjzhDWMZcWP9hS8l5AUZi-Gs8z18%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22puYPMCQt11yPUvgmI5jCiw%3D%3D%22%7D) (German-speaking), [\#SimpleX-ES](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FaJ8O1O8A8GbeoaHTo_V8dcefaCl7ouPb%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA034qWTA3sWcTsi6aWhNf9BA34vKVCFaEBdP2R66z6Ao%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22wiZ1v_wNjLPlT-nCSB-bRA%3D%3D%22%7D) (Spanish-speaking), [\#SimpleX-FR](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FvIHQDxTor53nwnWWTy5cHNwQQAdWN5Hw%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAPdgK1eBnETmgiqEQufbUkydKBJafoRx4iRrtrC2NAGc%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%221FyUryBPza-1ZFFE80Ekbg%3D%3D%22%7D) (French-speaking), [\#SimpleX-RU](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FXZyt3hJmWsycpN7Dqve_wbrAqb6myk1R%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAMFVIoytozTEa_QXOgoZFq_oe0IwZBYKvW50trSFXzXo%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xz05ngjA3pNIxLZ32a8Vxg%3D%3D%22%7D) (Russian-speaking), [\#SimpleX-IT](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F0weR-ZgDUl7ruOtI_8TZwEsnJP6UiImA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAq4PSThO9Fvb5ydF48wB0yNbpzCbuQJCW3vZ9BGUfcxk%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22e-iceLA0SctC62eARgYDWg%3D%3D%22%7D) (Italian-speaking). +[\#SimpleX-DE](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FmfiivxDKWFuowXrQOp11jsY8TuP__rBL%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAiz3pKNwvKudckFYMUfgoT0s96B0jfZ7ALHAu7rtE9HQ%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jZeJpXGrRXQJU_-MSJ_v2A%3D%3D%22%7D) (German-speaking), [\#SimpleX-ES](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FJ5ES83pJimY2BRklS8fvy_iQwIU37xra%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA0F0STP6UqN_12_k2cjjTrIjFgBGeWhOAmbY1qlk3pnM%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22VmUU0fqmYdCRmVCyvStvHA%3D%3D%22%7D) (Spanish-speaking), [\#SimpleX-FR](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FxCHBE_6PBRMqNEpm4UQDHXb9cz-mN7dd%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAetqlcM7zTCRw-iatnwCrvpJSto7lq5Yv6AsBMWv7GSM%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22foO5Xw4hhjOa_x7zET7otw%3D%3D%22%7D) (French-speaking), [\#SimpleX-RU](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FVXQTB0J2lLjYkgjWByhl6-1qmb5fgZHh%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAI6JaEWezfSwvcoTEkk6au-gkjrXR2ew2OqZYMYBvayk%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22ORH9OEe8Duissh-hslfeVg%3D%3D%22%7D) (Russian-speaking), [\#SimpleX-IT](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FqpHu0psOUdYfc11yQCzSyq5JhijrBzZT%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEACZ_7fbwlM45wl6cGif8cY47oPQ_AMdP0ATqOYLA6zHY%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%229uRQRTir3ealdcSfB0zsrw%3D%3D%22%7D) (Italian-speaking). You can join either by opening these links in the app or by opening them in a desktop browser and scanning the QR code. diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 570e274197..154876f09d 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -218,12 +218,12 @@ D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; }; D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; + E51B923B2CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51B92362CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU.a */; }; + E51B923C2CAB2B8800C212F2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51B92372CAB2B8800C212F2 /* libgmp.a */; }; + E51B923D2CAB2B8800C212F2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51B92382CAB2B8800C212F2 /* libffi.a */; }; + E51B923E2CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51B92392CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU-ghc9.6.3.a */; }; + E51B923F2CAB2B8800C212F2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51B923A2CAB2B8800C212F2 /* libgmpxx.a */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E5D826852CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D826802CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi-ghc9.6.3.a */; }; - E5D826862CA5F56100A9B74D /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D826812CA5F56100A9B74D /* libffi.a */; }; - E5D826872CA5F56100A9B74D /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D826822CA5F56100A9B74D /* libgmp.a */; }; - E5D826882CA5F56100A9B74D /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D826832CA5F56100A9B74D /* libgmpxx.a */; }; - E5D826892CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D826842CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -559,12 +559,12 @@ D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + E51B92362CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU.a"; sourceTree = ""; }; + E51B92372CAB2B8800C212F2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E51B92382CAB2B8800C212F2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51B92392CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU-ghc9.6.3.a"; sourceTree = ""; }; + E51B923A2CAB2B8800C212F2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E5D826802CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi-ghc9.6.3.a"; sourceTree = ""; }; - E5D826812CA5F56100A9B74D /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E5D826822CA5F56100A9B74D /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E5D826832CA5F56100A9B74D /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E5D826842CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi.a"; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -655,14 +655,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E5D826852CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi-ghc9.6.3.a in Frameworks */, + E51B923B2CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU.a in Frameworks */, + E51B923D2CAB2B8800C212F2 /* libffi.a in Frameworks */, + E51B923C2CAB2B8800C212F2 /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E5D826892CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi.a in Frameworks */, + E51B923F2CAB2B8800C212F2 /* libgmpxx.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E5D826862CA5F56100A9B74D /* libffi.a in Frameworks */, - E5D826872CA5F56100A9B74D /* libgmp.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E5D826882CA5F56100A9B74D /* libgmpxx.a in Frameworks */, + E51B923E2CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -739,11 +739,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E5D826812CA5F56100A9B74D /* libffi.a */, - E5D826822CA5F56100A9B74D /* libgmp.a */, - E5D826832CA5F56100A9B74D /* libgmpxx.a */, - E5D826802CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi-ghc9.6.3.a */, - E5D826842CA5F56100A9B74D /* libHSsimplex-chat-6.1.0.4-5C0H3SCWHuhICcJbTCMAKi.a */, + E51B92382CAB2B8800C212F2 /* libffi.a */, + E51B92372CAB2B8800C212F2 /* libgmp.a */, + E51B923A2CAB2B8800C212F2 /* libgmpxx.a */, + E51B92392CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU-ghc9.6.3.a */, + E51B92362CAB2B8800C212F2 /* libHSsimplex-chat-6.1.0.5-HWShuJBYoppEOt1hLB8GPU.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index b0cd62a867..96371ff0e0 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1353,6 +1353,7 @@ public struct NetCfg: Codable, Equatable { public var sessionMode = TransportSessionMode.user public var smpProxyMode: SMPProxyMode = .unknown public var smpProxyFallback: SMPProxyFallback = .allowProtected + var smpWebPort = false public var tcpConnectTimeout: Int // microseconds public var tcpTimeout: Int // microseconds public var tcpTimeoutPerKb: Int // microseconds diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index e7fd11f5ac..dbcd05b3c7 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -737,7 +737,11 @@ fun WebRTCView(callCommand: SnapshotStateList, onResponse: (WVAPIM } } catch (e: Exception) { Log.e(TAG, "Error initializing WebView: ${e.stackTraceToString()}") - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), generalGetString(MR.strings.error_initializing_web_view).format(e.stackTraceToString())) + if (e.stackTraceToString().contains("/lib64")) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), generalGetString(MR.strings.error_initializing_web_view_wrong_arch).format(e.stackTraceToString())) + } else { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), generalGetString(MR.strings.error_initializing_web_view).format(e.stackTraceToString())) + } return@AndroidView View(androidAppContext) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 09b0ececb0..0297536577 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3653,6 +3653,7 @@ data class NetCfg( val sessionMode: TransportSessionMode = TransportSessionMode.User, val smpProxyMode: SMPProxyMode = SMPProxyMode.Unknown, val smpProxyFallback: SMPProxyFallback = SMPProxyFallback.AllowProtected, + val smpWebPort: Boolean = false, val tcpConnectTimeout: Long, // microseconds val tcpTimeout: Long, // microseconds val tcpTimeoutPerKb: Long, // microseconds diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index f723306456..89883e1bf8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -17,7 +17,7 @@ data class Call( val callUUID: String?, val callState: CallState, val initialCallType: CallMediaType, - val localMediaSources: CallMediaSources = CallMediaSources(mic = true, camera = initialCallType == CallMediaType.Video && appPlatform.isAndroid), + val localMediaSources: CallMediaSources = CallMediaSources(mic = true, camera = initialCallType == CallMediaType.Video), val localCapabilities: CallCapabilities? = null, val peerMediaSources: CallMediaSources = CallMediaSources(), val sharedKey: String? = null, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 1cc81a351f..2ba4316b0f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -12,6 +12,7 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.layoutId @@ -779,30 +780,16 @@ fun ChatInfoToolbar( if (chatInfo is ChatInfo.Direct && chatInfo.contact.mergedPreferences.calls.enabled.forUser) { if (activeCall == null) { barButtons.add { - if (appPlatform.isAndroid) { - IconButton({ - showMenu.value = false - startCall(CallMediaType.Audio) - }, enabled = chatInfo.contact.ready && chatInfo.contact.active - ) { - Icon( - painterResource(MR.images.ic_call_500), - stringResource(MR.strings.icon_descr_audio_call).capitalize(Locale.current), - tint = if (chatInfo.contact.ready && chatInfo.contact.active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary - ) - } - } else { - IconButton({ - showMenu.value = false - startCall(CallMediaType.Video) - }, enabled = chatInfo.contact.ready && chatInfo.contact.active - ) { - Icon( - painterResource(MR.images.ic_videocam), - stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), - tint = if (chatInfo.contact.ready && chatInfo.contact.active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary - ) - } + IconButton({ + showMenu.value = false + startCall(CallMediaType.Audio) + }, enabled = chatInfo.contact.ready && chatInfo.contact.active + ) { + Icon( + painterResource(MR.images.ic_call_500), + stringResource(MR.strings.icon_descr_audio_call).capitalize(Locale.current), + tint = if (chatInfo.contact.ready && chatInfo.contact.active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary + ) } } } else if (activeCall?.contact?.id == chatInfo.id && appPlatform.isDesktop) { @@ -836,17 +823,10 @@ fun ChatInfoToolbar( } if (chatInfo.contact.ready && chatInfo.contact.active && activeCall == null) { menuItems.add { - if (appPlatform.isAndroid) { - ItemAction(stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), painterResource(MR.images.ic_videocam), onClick = { - showMenu.value = false - startCall(CallMediaType.Video) - }) - } else { - ItemAction(stringResource(MR.strings.icon_descr_audio_call).capitalize(Locale.current), painterResource(MR.images.ic_call_500), onClick = { - showMenu.value = false - startCall(CallMediaType.Audio) - }) - } + ItemAction(stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), painterResource(MR.images.ic_videocam), onClick = { + showMenu.value = false + startCall(CallMediaType.Video) + }) } } } else if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canAddMembers) { @@ -1271,6 +1251,12 @@ fun BoxWithConstraintsScope.ChatItemsList( } } FloatingButtons(chatModel.chatItems, unreadCount, remoteHostId, chatInfo, searchValue, markRead, setFloatingButton, listState) + + FloatingDate( + Modifier.padding(top = 10.dp).align(Alignment.TopCenter), + listState, + ) + LaunchedEffect(Unit) { snapshotFlow { listState.isScrollInProgress } .collect { @@ -1497,6 +1483,108 @@ private fun TopEndFloatingButton( } } +@Composable +private fun FloatingDate( + modifier: Modifier, + listState: LazyListState, +) { + var nearBottomIndex by remember { mutableStateOf(-1) } + var isNearBottom by remember { mutableStateOf(true) } + val lastVisibleItemDate = remember { + derivedStateOf { + if (listState.layoutInfo.visibleItemsInfo.lastIndex >= 0 && listState.firstVisibleItemIndex >= 0) { + val lastVisibleChatItemIndex = chatModel.chatItems.value.lastIndex - listState.firstVisibleItemIndex - listState.layoutInfo.visibleItemsInfo.lastIndex + val item = chatModel.chatItems.value.getOrNull(lastVisibleChatItemIndex) + val timeZone = TimeZone.currentSystemDefault() + item?.meta?.itemTs?.toLocalDateTime(timeZone)?.date?.atStartOfDayIn(timeZone) + } else { + null + } + } + } + val showDate = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + launch { + snapshotFlow { chatModel.chatId.value } + .distinctUntilChanged() + .collect { + showDate.value = false + isNearBottom = true + nearBottomIndex = -1 + } + } + } + + LaunchedEffect(Unit) { + snapshotFlow { listState.layoutInfo.visibleItemsInfo } + .collect { visibleItemsInfo -> + if (visibleItemsInfo.find { it.index == 0 } != null) { + var elapsedOffset = 0 + + for (it in visibleItemsInfo) { + if (elapsedOffset >= listState.layoutInfo.viewportSize.height / 2.5) { + nearBottomIndex = it.index + break; + } + elapsedOffset += it.size + } + } + + isNearBottom = if (nearBottomIndex == -1) true else (visibleItemsInfo.firstOrNull()?.index ?: 0) <= nearBottomIndex + } + } + + fun setDateVisibility(isVisible: Boolean) { + if (isVisible) { + val now = Clock.System.now() + val date = lastVisibleItemDate.value + if (!isNearBottom && !showDate.value && date != null && getTimestampDateText(date) != getTimestampDateText(now)) { + showDate.value = true + } + } else if (showDate.value) { + showDate.value = false + } + } + + LaunchedEffect(Unit) { + var hideDateWhenNotScrolling: Job = Job() + snapshotFlow { listState.firstVisibleItemScrollOffset } + .collect { + setDateVisibility(true) + hideDateWhenNotScrolling.cancel() + hideDateWhenNotScrolling = launch { + delay(1000) + setDateVisibility(false) + } + } + } + + AnimatedVisibility( + modifier = modifier, + visible = showDate.value, + enter = fadeIn(tween(durationMillis = 350)), + exit = fadeOut(tween(durationMillis = 350)) + ) { + val date = lastVisibleItemDate.value + Column { + Text( + text = if (date != null) getTimestampDateText(date) else "", + Modifier + .background( + color = MaterialTheme.colors.secondaryVariant, + RoundedCornerShape(25.dp) + ) + .padding(vertical = 4.dp, horizontal = 8.dp) + .clip(RoundedCornerShape(25.dp)), + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center, + color = MaterialTheme.colors.secondary + ) + } + } +} + @Composable private fun DownloadFilesButton( forwardConfirmation: ForwardConfirmation.FilesNotAccepted, @@ -1560,7 +1648,7 @@ private fun ButtonRow(horizontalArrangement: Arrangement.Horizontal, content: @C private fun DateSeparator(date: Instant) { Text( text = getTimestampDateText(date), - Modifier.padding(DEFAULT_PADDING).fillMaxWidth(), + Modifier.padding(vertical = DEFAULT_PADDING_HALF + 4.dp, horizontal = DEFAULT_PADDING_HALF).fillMaxWidth(), fontSize = 14.sp, fontWeight = FontWeight.Medium, textAlign = TextAlign.Center, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt index 68077d31f8..4ec2a885e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt @@ -125,9 +125,9 @@ fun reserveSpaceForMeta( showViaProxy: Boolean = false, showTimestamp: Boolean ): String { - val iconSpace = " " - val whiteSpace = " " - var res = iconSpace + val iconSpace = " \u00A0\u00A0\u00A0" + val whiteSpace = "\u00A0" + var res = if (showTimestamp) "" else iconSpace var space: String? = null fun appendSpace() { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 901a0565e1..429ca4af09 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -989,6 +989,7 @@ Headphones Bluetooth Error initializing WebView. Update your system to the new version. Please contact developers.\nError: %s + Error initializing WebView. Make sure you have WebView installed and it\'s supported architecture is arm64.\nError: %s The next generation\nof private messaging diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js index b5dcc9a2c2..f042859270 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js @@ -76,6 +76,8 @@ const processCommand = (function () { iceCandidatePoolSize: 10, encodedInsertableStreams, iceTransportPolicy: relay ? "relay" : "all", + // needed for Android WebView >= 69 && <= 72 where default was "plan-b" which is incompatible with transceivers + sdpSemantics: "unified-plan", }, iceCandidates: { delay: 750, @@ -186,6 +188,7 @@ const processCommand = (function () { localStream, localScreenStream, remoteStream, + remoteTracks: new Map(), remoteScreenStream, peerMediaSources: { mic: false, @@ -201,7 +204,12 @@ const processCommand = (function () { localOrPeerMediaSourcesChanged(call); await setupMediaStreams(call); let connectionTimeout = setTimeout(connectionHandler, answerTimeout); - pc.addEventListener("connectionstatechange", connectionStateChange); + if (pc.connectionState) { + pc.addEventListener("connectionstatechange", connectionStateChange); + } + else { + pc.addEventListener("iceconnectionstatechange", connectionStateChange); + } return call; async function connectionStateChange() { // "failed" means the second party did not answer in time (15 sec timeout in Chrome WebView) @@ -210,26 +218,38 @@ const processCommand = (function () { connectionHandler(); } async function connectionHandler() { + var _a; sendMessageToNative({ resp: { type: "connection", state: { - connectionState: pc.connectionState, + connectionState: (_a = pc.connectionState) !== null && _a !== void 0 ? _a : (pc.iceConnectionState != "completed" && pc.iceConnectionState != "checking" + ? pc.iceConnectionState + : pc.iceConnectionState == "completed" + ? "connected" + : "connecting") /* webView 69-70 doesn't have connectionState yet */, iceConnectionState: pc.iceConnectionState, iceGatheringState: pc.iceGatheringState, signalingState: pc.signalingState, }, }, }); - if (pc.connectionState == "disconnected" || pc.connectionState == "failed") { + if (pc.connectionState == "disconnected" || + pc.connectionState == "failed" || + (!pc.connectionState && (pc.iceConnectionState == "disconnected" || pc.iceConnectionState == "failed"))) { clearConnectionTimeout(); - pc.removeEventListener("connectionstatechange", connectionStateChange); + if (pc.connectionState) { + pc.removeEventListener("connectionstatechange", connectionStateChange); + } + else { + pc.removeEventListener("iceconnectionstatechange", connectionStateChange); + } if (activeCall) { setTimeout(() => sendMessageToNative({ resp: { type: "ended" } }), 0); } endCall(); } - else if (pc.connectionState == "connected") { + else if (pc.connectionState == "connected" || (!pc.connectionState && pc.iceConnectionState == "connected")) { clearConnectionTimeout(); const stats = (await pc.getStats()); for (const stat of stats.values()) { @@ -276,7 +296,7 @@ const processCommand = (function () { endCall(); let localStream = null; try { - localStream = await getLocalMediaStream(true, command.media == CallMediaType.Video && !isDesktop, VideoCamera.User); + localStream = await getLocalMediaStream(true, command.media == CallMediaType.Video, VideoCamera.User); const videos = getVideoElements(); if (videos) { videos.local.srcObject = localStream; @@ -305,7 +325,7 @@ const processCommand = (function () { if (activeCall) endCall(); inactiveCallMediaSources.mic = true; - inactiveCallMediaSources.camera = command.media == CallMediaType.Video && !isDesktop; + inactiveCallMediaSources.camera = command.media == CallMediaType.Video; inactiveCallMediaSourcesChanged(inactiveCallMediaSources); const { media, iceServers, relay } = command; const encryption = supportsInsertableStreams(useWorker); @@ -354,7 +374,7 @@ const processCommand = (function () { activeCall = await initializeCall(getCallConfig(!!aesKey, iceServers, relay), media, aesKey); const pc = activeCall.connection; // console.log("offer remoteIceCandidates", JSON.stringify(remoteIceCandidates)) - await pc.setRemoteDescription(new RTCSessionDescription(offer)); + await pc.setRemoteDescription(new RTCSessionDescription(!webView69Or70() ? offer : adaptSdpToOldWebView(offer))); // setting up local stream only after setRemoteDescription in order to have transceivers set await setupLocalStream(false, activeCall); setupEncryptionForLocalStream(activeCall); @@ -396,7 +416,7 @@ const processCommand = (function () { const answer = parse(command.answer); const remoteIceCandidates = parse(command.iceCandidates); // console.log("answer remoteIceCandidates", JSON.stringify(remoteIceCandidates)) - await pc.setRemoteDescription(new RTCSessionDescription(answer)); + await pc.setRemoteDescription(new RTCSessionDescription(!webView69Or70() ? answer : adaptSdpToOldWebView(answer))); adaptToOldVersion(pc.getTransceivers()[2].currentDirection == "sendonly", activeCall); addIceCandidates(pc, remoteIceCandidates); addIceCandidates(pc, afterCallInitializedCandidates); @@ -548,14 +568,6 @@ const processCommand = (function () { call.worker = new Worker(URL.createObjectURL(new Blob([workerCode], { type: "text/javascript" }))); call.worker.onerror = ({ error, filename, lineno, message }) => console.log({ error, filename, lineno, message }); // call.worker.onmessage = ({data}) => console.log(JSON.stringify({message: data})) - call.worker.onmessage = ({ data }) => { - console.log(JSON.stringify({ message: data })); - const transceiverMid = data.transceiverMid; - const mute = data.mute; - if (transceiverMid && mute != undefined) { - onMediaMuteUnmute(transceiverMid, mute); - } - }; } } } @@ -661,12 +673,7 @@ const processCommand = (function () { } setupMuteUnmuteListener(event.transceiver, track); const mediaSource = mediaSourceFromTransceiverMid(event.transceiver.mid); - if (mediaSource == CallMediaSource.ScreenAudio || mediaSource == CallMediaSource.ScreenVideo) { - call.remoteScreenStream.addTrack(track); - } - else { - call.remoteStream.addTrack(track); - } + call.remoteTracks.set(mediaSource, track); console.log(`ontrack success`); } catch (e) { @@ -946,6 +953,7 @@ const processCommand = (function () { }); } if (inboundStatsId) { + // even though MSDN site says `packetsReceived` is available in WebView 80+, in reality it's available even in 69 const packets = (_a = stats.get(inboundStatsId)) === null || _a === void 0 ? void 0 : _a.packetsReceived; if (packets <= lastPacketsReceived) { mutedSeconds++; @@ -1023,10 +1031,26 @@ const processCommand = (function () { if (!mute) videos.remoteScreen.play().catch((e) => console.log(e)); } + if (!mute) + addRemoteTracksWhenUnmuted(source, activeCall); localOrPeerMediaSourcesChanged(activeCall); // Make sure that remote camera and remote screen video in their places and shown/hidden based on layout type currently in use changeLayout(activeCall.layout); } + /* + When new remote tracks are coming, they don't get added to remote streams. They are stored in a map and once any of them "unmuted", + that track is added to the stream. Such workaround needed because Safari doesn't play one stream + if another one is not playing too, eg. no audio if only audio is playing while video track is present too but muted. + But we have possibility to have only one currently active track, even no active track at all. + */ + function addRemoteTracksWhenUnmuted(source, call) { + const track = call.remoteTracks.get(source); + if (track) { + const stream = source == CallMediaSource.Mic || source == CallMediaSource.Camera ? call.remoteStream : call.remoteScreenStream; + stream.addTrack(track); + call.remoteTracks.delete(source); + } + } async function getLocalMediaStream(mic, camera, facingMode) { if (!mic && !camera) return new MediaStream(); @@ -1136,7 +1160,7 @@ const processCommand = (function () { if (peerHasOldVersion) { console.log("The peer has an old version.", "Tracks size:", activeCall.remoteStream.getAudioTracks().length, activeCall.remoteStream.getVideoTracks().length); onMediaMuteUnmute("0", false); - if (activeCall.remoteStream.getVideoTracks().length > 0) { + if (activeCall.remoteStream.getVideoTracks().length > 0 || activeCall.remoteTracks.get(CallMediaSource.Camera)) { onMediaMuteUnmute("1", false); } if (activeCall.localMediaSources.camera && !activeCall.peerMediaSources.camera) { @@ -1152,6 +1176,22 @@ const processCommand = (function () { } } } + function webView69Or70() { + return !isDesktop && (navigator.userAgent.includes("Chrome/69.") || navigator.userAgent.includes("Chrome/70.")); + } + // Adding `a=extmap-allow-mixed` causes exception on old WebViews + // https://groups.google.com/a/chromium.org/g/blink-dev/c/7z3uvp0-ZAc/m/8Z7qpp71BgAJ + function adaptSdpToOldWebView(desc) { + var _a; + const res = []; + (_a = desc.sdp) === null || _a === void 0 ? void 0 : _a.split("\n").forEach((line) => { + // Chrome has a bug related to SDP parser in old web view versions + if (!line.includes("a=extmap-allow-mixed")) { + res.push(line); + } + }); + return { sdp: res.join("\n"), type: desc.type }; + } return processCommand; })(); function toggleRemoteVideoFitFill() { diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/lz-string.min.js b/apps/multiplatform/common/src/commonMain/resources/assets/www/lz-string.min.js index 2d1900a0d3..f7c26ae2b4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/lz-string.min.js +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/lz-string.min.js @@ -1 +1 @@ -var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;ne;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;ie;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString); +var LZString=function(){var r=String.fromCharCode,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",e={};function t(r,o){if(!e[r]){e[r]={};for(var n=0;n>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null==o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;e>=1}else{for(t=1,e=0;e>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e>=1;0==--l&&(l=Math.pow(2,h),h++),s[p]=f++,c=String(a)}if(""!==c){if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e>=1}else{for(t=1,e=0;e>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e>=1;0==--l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;e>=1;for(;;){if(m<<=1,v==o-1){d.push(n(m));break}v++}return d.join("")},decompress:function(r){return null==r?"":""==r?null:i._decompress(r.length,32768,function(o){return r.charCodeAt(o)})},_decompress:function(o,n,e){var t,i,s,u,a,p,c,l=[],f=4,h=4,d=3,m="",v=[],g={val:e(0),position:n,index:1};for(t=0;t<3;t+=1)l[t]=t;for(s=0,a=Math.pow(2,2),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 2:return""}for(l[3]=c,i=c,v.push(c);;){if(g.index>o)return"";for(s=0,a=Math.pow(2,d),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(c=s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 2:return v.join("")}if(0==f&&(f=Math.pow(2,d),d++),l[c])m=l[c];else{if(c!==h)return null;m=i+i.charAt(0)}v.push(m),l[h++]=i+m.charAt(0),i=m,0==--f&&(f=Math.pow(2,d),d++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString}); \ No newline at end of file diff --git a/cabal.project b/cabal.project index 3823a31790..5a732f84d5 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 03168b9fbfd8a507a5374ff3375609e705225e29 + tag: da79d544cf990fb0638ae5434407d6a30724fb87 source-repository-package type: git @@ -49,3 +49,17 @@ source-repository-package type: git location: https://github.com/simplex-chat/zip.git tag: bd421c6b19cc4c465cd7af1f6f26169fb8ee1ebc + +-- waiting for published warp-tls-3.4.7 +source-repository-package + type: git + location: https://github.com/yesodweb/wai.git + tag: ec5e017d896a78e787a5acea62b37a4e677dec2e + subdir: warp-tls + +-- backported fork due http-5.0 +source-repository-package + type: git + location: https://github.com/simplex-chat/wai.git + tag: 2f6e5aa5f05ba9140ac99e195ee647b4f7d926b0 + subdir: warp diff --git a/package.yaml b/package.yaml index edeb47ce27..edfbd98134 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.4 +version: 6.1.0.5 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/packages/simplex-chat-webrtc/package.json b/packages/simplex-chat-webrtc/package.json index f11ea36343..a6598b6ce7 100644 --- a/packages/simplex-chat-webrtc/package.json +++ b/packages/simplex-chat-webrtc/package.json @@ -27,7 +27,7 @@ "author": "SimpleX Chat", "license": "AGPL-3.0-or-later", "devDependencies": { - "@types/lz-string": "^1.3.34", + "@types/lz-string": "1.3.34", "husky": "^7.0.4", "isomorphic-webcrypto": "^2.3.8", "lint-staged": "^12.4.1", @@ -38,6 +38,6 @@ "**/*": "prettier --write --ignore-unknown" }, "dependencies": { - "lz-string": "^1.4.4" + "lz-string": "1.5.0" } } diff --git a/packages/simplex-chat-webrtc/src/call.ts b/packages/simplex-chat-webrtc/src/call.ts index 793bc8e5c2..f27c87b870 100644 --- a/packages/simplex-chat-webrtc/src/call.ts +++ b/packages/simplex-chat-webrtc/src/call.ts @@ -248,7 +248,10 @@ interface Call { localCamera: VideoCamera localStream: MediaStream localScreenStream: MediaStream + // has no tracks in the beggining, see addRemoteTracksWhenUnmuted remoteStream: MediaStream + remoteTracks: Map + // has no tracks in the beggining too remoteScreenStream: MediaStream peerMediaSources: CallMediaSources aesKey?: string @@ -308,8 +311,12 @@ const processCommand = (function () { encodedInsertableStreams: boolean } + type RTCConfigurationWithSdpSemantics = RTCConfiguration & { + sdpSemantics: string + } + interface CallConfig { - peerConnectionConfig: RTCConfigurationWithEncryption + peerConnectionConfig: RTCConfigurationWithEncryption & RTCConfigurationWithSdpSemantics iceCandidates: { delay: number extrasInterval: number @@ -331,6 +338,8 @@ const processCommand = (function () { iceCandidatePoolSize: 10, encodedInsertableStreams, iceTransportPolicy: relay ? "relay" : "all", + // needed for Android WebView >= 69 && <= 72 where default was "plan-b" which is incompatible with transceivers + sdpSemantics: "unified-plan", }, iceCandidates: { delay: 750, @@ -439,6 +448,7 @@ const processCommand = (function () { localStream, localScreenStream, remoteStream, + remoteTracks: new Map(), remoteScreenStream, peerMediaSources: { mic: false, @@ -454,7 +464,11 @@ const processCommand = (function () { localOrPeerMediaSourcesChanged(call) await setupMediaStreams(call) let connectionTimeout: number | undefined = setTimeout(connectionHandler, answerTimeout) - pc.addEventListener("connectionstatechange", connectionStateChange) + if (pc.connectionState) { + pc.addEventListener("connectionstatechange", connectionStateChange) + } else { + pc.addEventListener("iceconnectionstatechange", connectionStateChange) + } return call async function connectionStateChange() { @@ -468,21 +482,35 @@ const processCommand = (function () { resp: { type: "connection", state: { - connectionState: pc.connectionState, + connectionState: + pc.connectionState ?? + (pc.iceConnectionState != "completed" && pc.iceConnectionState != "checking" + ? pc.iceConnectionState + : pc.iceConnectionState == "completed" + ? "connected" + : "connecting") /* webView 69-70 doesn't have connectionState yet */, iceConnectionState: pc.iceConnectionState, iceGatheringState: pc.iceGatheringState, signalingState: pc.signalingState, }, }, }) - if (pc.connectionState == "disconnected" || pc.connectionState == "failed") { + if ( + pc.connectionState == "disconnected" || + pc.connectionState == "failed" || + (!pc.connectionState && (pc.iceConnectionState == "disconnected" || pc.iceConnectionState == "failed")) + ) { clearConnectionTimeout() - pc.removeEventListener("connectionstatechange", connectionStateChange) + if (pc.connectionState) { + pc.removeEventListener("connectionstatechange", connectionStateChange) + } else { + pc.removeEventListener("iceconnectionstatechange", connectionStateChange) + } if (activeCall) { setTimeout(() => sendMessageToNative({resp: {type: "ended"}}), 0) } endCall() - } else if (pc.connectionState == "connected") { + } else if (pc.connectionState == "connected" || (!pc.connectionState && pc.iceConnectionState == "connected")) { clearConnectionTimeout() const stats = (await pc.getStats()) as Map for (const stat of stats.values()) { @@ -532,7 +560,7 @@ const processCommand = (function () { let localStream: MediaStream | null = null try { - localStream = await getLocalMediaStream(true, command.media == CallMediaType.Video && !isDesktop, VideoCamera.User) + localStream = await getLocalMediaStream(true, command.media == CallMediaType.Video, VideoCamera.User) const videos = getVideoElements() if (videos) { videos.local.srcObject = localStream @@ -560,7 +588,7 @@ const processCommand = (function () { if (activeCall) endCall() inactiveCallMediaSources.mic = true - inactiveCallMediaSources.camera = command.media == CallMediaType.Video && !isDesktop + inactiveCallMediaSources.camera = command.media == CallMediaType.Video inactiveCallMediaSourcesChanged(inactiveCallMediaSources) const {media, iceServers, relay} = command @@ -609,7 +637,7 @@ const processCommand = (function () { activeCall = await initializeCall(getCallConfig(!!aesKey, iceServers, relay), media, aesKey) const pc = activeCall.connection // console.log("offer remoteIceCandidates", JSON.stringify(remoteIceCandidates)) - await pc.setRemoteDescription(new RTCSessionDescription(offer)) + await pc.setRemoteDescription(new RTCSessionDescription(!webView69Or70() ? offer : adaptSdpToOldWebView(offer))) // setting up local stream only after setRemoteDescription in order to have transceivers set await setupLocalStream(false, activeCall) setupEncryptionForLocalStream(activeCall) @@ -650,7 +678,7 @@ const processCommand = (function () { const remoteIceCandidates: RTCIceCandidateInit[] = parse(command.iceCandidates) // console.log("answer remoteIceCandidates", JSON.stringify(remoteIceCandidates)) - await pc.setRemoteDescription(new RTCSessionDescription(answer)) + await pc.setRemoteDescription(new RTCSessionDescription(!webView69Or70() ? answer : adaptSdpToOldWebView(answer))) adaptToOldVersion(pc.getTransceivers()[2].currentDirection == "sendonly", activeCall!) addIceCandidates(pc, remoteIceCandidates) addIceCandidates(pc, afterCallInitializedCandidates) @@ -794,14 +822,6 @@ const processCommand = (function () { call.worker = new Worker(URL.createObjectURL(new Blob([workerCode], {type: "text/javascript"}))) call.worker.onerror = ({error, filename, lineno, message}: ErrorEvent) => console.log({error, filename, lineno, message}) // call.worker.onmessage = ({data}) => console.log(JSON.stringify({message: data})) - call.worker.onmessage = ({data}) => { - console.log(JSON.stringify({message: data})) - const transceiverMid: string = data.transceiverMid - const mute: boolean = data.mute - if (transceiverMid && mute != undefined) { - onMediaMuteUnmute(transceiverMid, mute) - } - } } } } @@ -927,11 +947,7 @@ const processCommand = (function () { setupMuteUnmuteListener(event.transceiver, track) const mediaSource = mediaSourceFromTransceiverMid(event.transceiver.mid) - if (mediaSource == CallMediaSource.ScreenAudio || mediaSource == CallMediaSource.ScreenVideo) { - call.remoteScreenStream.addTrack(track) - } else { - call.remoteStream.addTrack(track) - } + call.remoteTracks.set(mediaSource, track) console.log(`ontrack success`) } catch (e) { console.log(`ontrack error: ${(e as Error).message}`) @@ -1227,6 +1243,7 @@ const processCommand = (function () { }) } if (inboundStatsId) { + // even though MSDN site says `packetsReceived` is available in WebView 80+, in reality it's available even in 69 const packets = (stats as any).get(inboundStatsId)?.packetsReceived if (packets <= lastPacketsReceived) { mutedSeconds++ @@ -1296,11 +1313,27 @@ const processCommand = (function () { sendMessageToNative({resp: resp}) if (!mute) videos.remoteScreen.play().catch((e) => console.log(e)) } + if (!mute) addRemoteTracksWhenUnmuted(source, activeCall) localOrPeerMediaSourcesChanged(activeCall) // Make sure that remote camera and remote screen video in their places and shown/hidden based on layout type currently in use changeLayout(activeCall.layout) } + /* + When new remote tracks are coming, they don't get added to remote streams. They are stored in a map and once any of them "unmuted", + that track is added to the stream. Such workaround needed because Safari doesn't play one stream + if another one is not playing too, eg. no audio if only audio is playing while video track is present too but muted. + But we have possibility to have only one currently active track, even no active track at all. + */ + function addRemoteTracksWhenUnmuted(source: CallMediaSource, call: Call) { + const track = call.remoteTracks.get(source) + if (track) { + const stream = source == CallMediaSource.Mic || source == CallMediaSource.Camera ? call.remoteStream : call.remoteScreenStream + stream.addTrack(track) + call.remoteTracks.delete(source) + } + } + async function getLocalMediaStream(mic: boolean, camera: boolean, facingMode: VideoCamera): Promise { if (!mic && !camera) return new MediaStream() const constraints = callMediaConstraints(mic, camera, facingMode) @@ -1422,7 +1455,7 @@ const processCommand = (function () { activeCall.remoteStream.getVideoTracks().length ) onMediaMuteUnmute("0", false) - if (activeCall.remoteStream.getVideoTracks().length > 0) { + if (activeCall.remoteStream.getVideoTracks().length > 0 || activeCall.remoteTracks.get(CallMediaSource.Camera)) { onMediaMuteUnmute("1", false) } if (activeCall.localMediaSources.camera && !activeCall.peerMediaSources.camera) { @@ -1439,6 +1472,23 @@ const processCommand = (function () { } } + function webView69Or70(): boolean { + return !isDesktop && (navigator.userAgent.includes("Chrome/69.") || navigator.userAgent.includes("Chrome/70.")) + } + + // Adding `a=extmap-allow-mixed` causes exception on old WebViews + // https://groups.google.com/a/chromium.org/g/blink-dev/c/7z3uvp0-ZAc/m/8Z7qpp71BgAJ + function adaptSdpToOldWebView(desc: RTCSessionDescriptionInit): RTCSessionDescriptionInit { + const res: string[] = [] + desc.sdp?.split("\n").forEach((line) => { + // Chrome has a bug related to SDP parser in old web view versions + if (!line.includes("a=extmap-allow-mixed")) { + res.push(line) + } + }) + return {sdp: res.join("\n"), type: desc.type} + } + return processCommand })() diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index eaa7f0d129..928a9e3c8e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."03168b9fbfd8a507a5374ff3375609e705225e29" = "0g6xry0far2wppfxv99hqh3ckldglmpazib5l8vsvcmivcz5zww1"; + "https://github.com/simplex-chat/simplexmq.git"."da79d544cf990fb0638ae5434407d6a30724fb87" = "11rxpcg2g781rbr5d20fw6zpv81q06w3c9bh6qh5cpnza230yvl3"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; @@ -7,4 +7,6 @@ "https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj"; "https://github.com/simplex-chat/android-support.git"."9aa09f148089d6752ce563b14c2df1895718d806" = "0pbf2pf13v2kjzi397nr13f1h3jv0imvsq8rpiyy2qyx5vd50pqn"; "https://github.com/simplex-chat/zip.git"."bd421c6b19cc4c465cd7af1f6f26169fb8ee1ebc" = "1csqfjhvc8wb5h4kxxndmb6iw7b4ib9ff2n81hrizsmnf45a6gg0"; + "https://github.com/yesodweb/wai.git"."ec5e017d896a78e787a5acea62b37a4e677dec2e" = "1ckcpmpjfy9jiqrb52q20lj7ln4hmq9v2jk6kpkf3m68c1m9c2bx"; + "https://github.com/simplex-chat/wai.git"."2f6e5aa5f05ba9140ac99e195ee647b4f7d926b0" = "199g4rjdf1zp1fcw8nqdsyr1h36hmg424qqx03071jk7j00z7ay4"; } diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 2386128bb6..5c943df73e 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.0.4 +version: 6.1.0.5 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 42c12f1c6e..9a5c9f2a92 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -52,7 +52,7 @@ import Simplex.Messaging.Protocol (srvHostnamesSMPClientVersion) import Simplex.Messaging.Server (runSMPServerBlocking) import Simplex.Messaging.Server.Env.STM import Simplex.Messaging.Transport -import Simplex.Messaging.Transport.Server (TransportServerConfig (..), defaultTransportServerConfig) +import Simplex.Messaging.Transport.Server (ServerCredentials (..), TransportServerConfig (..), defaultTransportServerConfig) import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal import System.Directory (createDirectoryIfMissing, removeDirectoryRecursive) @@ -420,7 +420,7 @@ concurrentlyN_ = mapConcurrently_ id smpServerCfg :: ServerConfig smpServerCfg = ServerConfig - { transports = [(serverPort, transport @TLS)], + { transports = [(serverPort, transport @TLS, False)], tbqSize = 1, -- serverTbqSize = 1, msgQueueQuota = 16, @@ -428,23 +428,30 @@ smpServerCfg = msgIdBytes = 6, storeLogFile = Nothing, storeMsgsFile = Nothing, + storeNtfsFile = Nothing, allowNewQueues = True, -- server password is disabled as otherwise v1 tests fail newQueueBasicAuth = Nothing, -- Just "server_password", controlPortUserAuth = Nothing, controlPortAdminAuth = Nothing, messageExpiration = Just defaultMessageExpiration, + notificationExpiration = defaultNtfExpiration, inactiveClientExpiration = Just defaultInactiveClientExpiration, - caCertificateFile = "tests/fixtures/tls/ca.crt", - privateKeyFile = "tests/fixtures/tls/server.key", - certificateFile = "tests/fixtures/tls/server.crt", + smpCredentials = + ServerCredentials + { caCertificateFile = Just "tests/fixtures/tls/ca.crt", + privateKeyFile = "tests/fixtures/tls/server.key", + certificateFile = "tests/fixtures/tls/server.crt" + }, + httpCredentials = Nothing, logStatsInterval = Nothing, logStatsStartTime = 0, serverStatsLogFile = "tests/smp-server-stats.daily.log", serverStatsBackupFile = Nothing, pendingENDInterval = 500000, + ntfDeliveryInterval = 200000, smpServerVRange = supportedServerSMPRelayVRange, - transportConfig = defaultTransportServerConfig {alpn = Just supportedSMPHandshakes}, + transportConfig = defaultTransportServerConfig, smpHandshakeTimeout = 1000000, controlPort = Nothing, smpAgentCfg = defaultSMPClientAgentConfig, @@ -457,7 +464,7 @@ withSmpServer :: IO () -> IO () withSmpServer = withSmpServer' smpServerCfg withSmpServer' :: ServerConfig -> IO () -> IO () -withSmpServer' cfg = serverBracket (`runSMPServerBlocking` cfg) +withSmpServer' cfg = serverBracket (\started -> runSMPServerBlocking started cfg Nothing) xftpTestPort :: ServiceName xftpTestPort = "7002" @@ -481,16 +488,19 @@ xftpServerConfig = fileExpiration = Just defaultFileExpiration, fileTimeout = 10000000, inactiveClientExpiration = Just defaultInactiveClientExpiration, - caCertificateFile = "tests/fixtures/tls/ca.crt", - privateKeyFile = "tests/fixtures/tls/server.key", - certificateFile = "tests/fixtures/tls/server.crt", + xftpCredentials = + ServerCredentials + { caCertificateFile = Just "tests/fixtures/tls/ca.crt", + privateKeyFile = "tests/fixtures/tls/server.key", + certificateFile = "tests/fixtures/tls/server.crt" + }, xftpServerVRange = supportedFileServerVRange, logStatsInterval = Nothing, logStatsStartTime = 0, serverStatsLogFile = "tests/tmp/xftp-server-stats.daily.log", serverStatsBackupFile = Nothing, controlPort = Nothing, - transportConfig = defaultTransportServerConfig {alpn = Just supportedXFTPhandshakes}, + transportConfig = defaultTransportServerConfig, responseDelay = 0 } @@ -502,7 +512,7 @@ withXFTPServer' cfg = serverBracket ( \started -> do createDirectoryIfMissing False xftpServerFiles - runXFTPServerBlocking started cfg + runXFTPServerBlocking started cfg Nothing ) serverBracket :: (TMVar Bool -> IO ()) -> IO () -> IO () diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index c65c7b8085..1d12625cdc 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -6419,7 +6419,7 @@ testGroupMemberInactive tmp = do where serverCfg' = smpServerCfg - { transports = [("7003", transport @TLS)], + { transports = [("7003", transport @TLS, False)], msgQueueQuota = 2 } fastRetryInterval = defaultReconnectInterval {initialInterval = 50_000} -- same as in agent tests diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index a36eef8ca9..003fba7cfe 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1689,7 +1689,7 @@ testChangePCCUserDiffSrv tmp = do where serverCfg' = smpServerCfg - { transports = [("7003", transport @TLS), ("7002", transport @TLS)], + { transports = [("7003", transport @TLS, False), ("7002", transport @TLS, False)], msgQueueQuota = 2 }