Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
52d0d63968 |
|
@ -1 +0,0 @@
|
|||
github: chrisballinger
|
|
@ -1,4 +1,3 @@
|
|||
.env
|
||||
.DS_Store
|
||||
build
|
||||
*.mode1v3
|
||||
|
@ -15,7 +14,3 @@ Carthage/
|
|||
com.mono0926.LicensePlist.Output/
|
||||
|
||||
Secrets.plist
|
||||
Preview.html
|
||||
fastlane/report.xml
|
||||
fastlane/test_output/
|
||||
.ruby-version
|
||||
|
|
|
@ -31,12 +31,3 @@
|
|||
[submodule "Submodules/LumberjackConsole"]
|
||||
path = Submodules/LumberjackConsole
|
||||
url = git@github.com:ChatSecure/LumberjackConsole.git
|
||||
[submodule "Submodules/YapDatabase"]
|
||||
path = Submodules/YapDatabase
|
||||
url = git@github.com:ChatSecure/YapDatabase.git
|
||||
[submodule "Submodules/YapTaskQueue"]
|
||||
path = Submodules/YapTaskQueue
|
||||
url = git@github.com:ChatSecure/YapTaskQueue.git
|
||||
[submodule "Submodules/libsqlfs"]
|
||||
path = Submodules/libsqlfs
|
||||
url = git@github.com:ChatSecure/libsqlfs.git
|
||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -1,4 +1,4 @@
|
|||
osx_image: xcode12
|
||||
osx_image: xcode9.3
|
||||
language: objective-c
|
||||
|
||||
# Handle git submodules yourself
|
||||
|
@ -9,8 +9,9 @@ git:
|
|||
# Use sed to replace the SSH URL with the public URL, then initialize submodules
|
||||
before_install:
|
||||
# Fix Travis xcodebuild exited with 65 https://github.com/travis-ci/travis-ci/issues/6675#issuecomment-257964767
|
||||
- export IOS_SIMULATOR_UDID=`instruments -s devices | grep -m 1 "iPhone 8 (14" | awk -F '[ ]' '{print $4}' | awk -F '[\[]' '{print $2}' | sed 's/.$//'`
|
||||
- export IOS_SIMULATOR_UDID=`instruments -s devices | grep -m 1 "iPhone 8 (11" | awk -F '[ ]' '{print $4}' | awk -F '[\[]' '{print $2}' | sed 's/.$//'`
|
||||
- echo $IOS_SIMULATOR_UDID
|
||||
- open -a "simulator" --args -CurrentDeviceUDID $IOS_SIMULATOR_UDID
|
||||
- bundle install # We need a pre-release CocoaPods version
|
||||
- sed -i -e 's/git@github.com:/git:\/\/github.com\//' .gitmodules
|
||||
- sed -i -e 's/git@github.com:/git:\/\/github.com\//' Podfile
|
||||
|
@ -21,8 +22,12 @@ install:
|
|||
- curl -L https://github.com/ChatSecure/ChatSecure-iOS-Precompiled-Dependencies/archive/master.zip -o ChatSecure-iOS-Precompiled-Dependencies.zip
|
||||
- unzip -q ChatSecure-iOS-Precompiled-Dependencies.zip
|
||||
- mv ChatSecure-iOS-Precompiled-Dependencies-master ChatSecure-iOS-Precompiled-Dependencies
|
||||
- mkdir -p ./Carthage/Build/iOS/
|
||||
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/Carthage-iOS.zip -d ./Carthage/Build
|
||||
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/CPAProxyDependencies.zip -d ./Submodules/CPAProxy/
|
||||
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/OTRKitDependencies.zip -d ./Submodules/OTRKit/
|
||||
- mv ./Submodules/CPAProxy/CPAProxyDependencies ./Submodules/CPAProxy/CPAProxyDependencies-iOS
|
||||
- cp -r ./Submodules/CPAProxy/CPAProxyDependencies-iOS ./Submodules/CPAProxy/CPAProxyDependencies-macOS
|
||||
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/OTRKitDependencies-iOS.zip -d ./Submodules/OTRKit/
|
||||
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/Pods.zip
|
||||
|
||||
before_script:
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
github "ChatSecure/Mantle" "2.1.0_headerfix"
|
||||
github "nolanw/HTMLReader" "1d0dda3"
|
||||
github "AFNetworking/AFNetworking" ~> 3.1
|
||||
github "TheLevelUp/ZXingObjC" ~> 3.2.2
|
||||
github "soffes/SAMKeychain" ~> 1.5
|
||||
github "jdg/MBProgressHUD" ~> 1.1
|
||||
github "TTTAttributedLabel/TTTAttributedLabel" ~> 2.0
|
||||
github "PureLayout/PureLayout" ~> 3.0
|
||||
github "facebook/KVOController" ~> 1.2
|
||||
github "xmartlabs/XLForm" ~> 4.0.0
|
||||
github "mattt/FormatterKit" ~> 1.8
|
||||
|
||||
### Using CocoaPods due to Swift 3->4 issues ###
|
||||
# github "Cocoanetics/Kvitto" ~> 1.0
|
||||
# github "Cocoanetics/DTFoundation" ~> 1.7
|
||||
# github "Alamofire/Alamofire" ~> 4.4
|
|
@ -0,0 +1,11 @@
|
|||
github "AFNetworking/AFNetworking" "3.2.0"
|
||||
github "ChatSecure/Mantle" "4c1a09cb0c0811956cd35262340e42b940971cbb"
|
||||
github "PureLayout/PureLayout" "v3.0.2"
|
||||
github "TTTAttributedLabel/TTTAttributedLabel" "2.0.0"
|
||||
github "TheLevelUp/ZXingObjC" "3.2.2"
|
||||
github "facebook/KVOController" "v1.2.0"
|
||||
github "jdg/MBProgressHUD" "1.1.0"
|
||||
github "mattt/FormatterKit" "1.8.2"
|
||||
github "nolanw/HTMLReader" "1d0dda3849ff719fa15a0c4cac0118c70ef2217c"
|
||||
github "soffes/SAMKeychain" "v1.5.3"
|
||||
github "xmartlabs/XLForm" "4.0.0"
|
File diff suppressed because it is too large
Load Diff
|
@ -1,129 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||
BuildableName = "ChatSecure.app"
|
||||
BlueprintName = "ChatSecure"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "NO"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
|
||||
BuildableName = "ChatSecureTests.xctest"
|
||||
BlueprintName = "ChatSecureTests"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "macOS_Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
disableMainThreadChecker = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||
BuildableName = "ChatSecure.app"
|
||||
BlueprintName = "ChatSecure"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
|
||||
BuildableName = "ChatSecureTests.xctest"
|
||||
BlueprintName = "ChatSecureTests"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6365CEFB1E2453F6009E213F"
|
||||
BuildableName = "ChatSecureUITests.xctest"
|
||||
BlueprintName = "ChatSecureUITests"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "macOS_Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||
BuildableName = "ChatSecure.app"
|
||||
BlueprintName = "ChatSecure"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "OS_ACTIVITY_MODE"
|
||||
value = "disable"
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "macOS_Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||
BuildableName = "ChatSecure.app"
|
||||
BlueprintName = "ChatSecure"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "macOS_Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "macOS_Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -37,20 +37,10 @@
|
|||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "iOS_Debug"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
disableMainThreadChecker = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||
BuildableName = "ChatSecure.app"
|
||||
BlueprintName = "ChatSecure"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
@ -63,7 +53,17 @@
|
|||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "YES">
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9227C321BA7952100B5E1D0"
|
||||
BuildableName = "ChatSecureCoreTests.xctest"
|
||||
BlueprintName = "ChatSecureCoreTests"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6365CEFB1E2453F6009E213F"
|
||||
|
@ -73,9 +73,20 @@
|
|||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||
BuildableName = "ChatSecure.app"
|
||||
BlueprintName = "ChatSecure"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "iOS_Debug"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
@ -98,12 +109,14 @@
|
|||
<EnvironmentVariable
|
||||
key = "OS_ACTIVITY_MODE"
|
||||
value = "disable"
|
||||
isEnabled = "NO">
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "iOS_Release"
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
|
@ -120,10 +133,10 @@
|
|||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "iOS_Debug">
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "iOS_Release"
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -10,11 +10,11 @@
|
|||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C527FF235CB55C002B213A"
|
||||
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
|
||||
BuildableName = "ChatSecureCore.framework"
|
||||
BlueprintName = "ChatSecureCore"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
|
@ -23,15 +23,26 @@
|
|||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "iOS_Debug"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
|
||||
BuildableName = "ChatSecureCore.framework"
|
||||
BlueprintName = "ChatSecureCore"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "iOS_Debug"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
@ -40,9 +51,20 @@
|
|||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
|
||||
BuildableName = "ChatSecureCore.framework"
|
||||
BlueprintName = "ChatSecureCore"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "iOS_Release"
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
|
@ -50,7 +72,7 @@
|
|||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C527FF235CB55C002B213A"
|
||||
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
|
||||
BuildableName = "ChatSecureCore.framework"
|
||||
BlueprintName = "ChatSecureCore"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
|
@ -58,10 +80,10 @@
|
|||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "iOS_Debug">
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "iOS_Release"
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -23,19 +23,10 @@
|
|||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "iOS_Debug"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
|
||||
BuildableName = "ChatSecureTests.xctest"
|
||||
BlueprintName = "ChatSecureTests"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
@ -48,9 +39,20 @@
|
|||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
|
||||
BuildableName = "ChatSecureTests.xctest"
|
||||
BlueprintName = "ChatSecureTests"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "iOS_Debug"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
@ -75,9 +77,11 @@
|
|||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "iOS_Release"
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
|
@ -93,10 +97,10 @@
|
|||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "iOS_Debug">
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "iOS_Release"
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -10,8 +10,7 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
disableMainThreadChecker = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
@ -24,12 +23,22 @@
|
|||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||
BuildableName = "ChatSecure.app"
|
||||
BlueprintName = "ChatSecure"
|
||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
disableMainThreadChecker = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
@ -46,6 +55,8 @@
|
|||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
|
|
@ -8,17 +8,7 @@
|
|||
<array>
|
||||
<string>applinks:chatsecure.org</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.photos-library</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.default-data-protection</key>
|
||||
<string>NSFileProtectionComplete</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -9,14 +9,14 @@
|
|||
import Foundation
|
||||
|
||||
|
||||
extension NSData {
|
||||
public extension NSData {
|
||||
@objc public func hexString() -> String {
|
||||
return (self as Data).hexString()
|
||||
}
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/26502285/805882
|
||||
extension NSString {
|
||||
public extension NSString {
|
||||
|
||||
/// Create `Data` from hexadecimal string representation
|
||||
///
|
|
@ -63,4 +63,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@end
|
||||
|
||||
@interface UIViewController (ChatSecureURL)
|
||||
- (void) promptToShowURL:(NSURL*)url sender:(id)sender;
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -10,7 +10,6 @@
|
|||
#import "OTRConstants.h"
|
||||
@import XMPPFramework;
|
||||
@import OTRAssets;
|
||||
#import "ChatSecureCoreCompat-Swift.h"
|
||||
|
||||
@implementation NSURL (ChatSecure)
|
||||
|
||||
|
@ -198,7 +197,7 @@
|
|||
view = sender;
|
||||
}
|
||||
UIAlertAction *visitURL = [UIAlertAction actionWithTitle:OPEN_IN_SAFARI() style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[UIApplication sharedApplication] open:self];
|
||||
[[UIApplication sharedApplication] openURL:self];
|
||||
}];
|
||||
UIAlertAction *cancel = [UIAlertAction actionWithTitle:CANCEL_STRING() style:UIAlertActionStyleCancel handler:nil];
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.absoluteString message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
@ -212,4 +211,12 @@
|
|||
[viewController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIViewController (ChatSecureURL)
|
||||
- (void) promptToShowURL:(NSURL*)url sender:(id)sender {
|
||||
[url promptToShowURLFromViewController:self sender:sender];
|
||||
}
|
||||
|
||||
|
||||
@end
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// UIActionSheet+ChatSecure.h
|
||||
// ChatSecure
|
||||
//
|
||||
// Created by David Chiles on 10/24/14.
|
||||
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
@import UIKit;
|
||||
|
||||
@interface UIActionSheet (ChatSecure)
|
||||
|
||||
- (void)otr_presentInView:(UIView *)view;
|
||||
|
||||
@end
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// UIActionSheet+ChatSecure.m
|
||||
// ChatSecure
|
||||
//
|
||||
// Created by David Chiles on 10/24/14.
|
||||
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIActionSheet+ChatSecure.h"
|
||||
#import "OTRAppDelegate.h"
|
||||
|
||||
@implementation UIActionSheet (ChatSecure)
|
||||
|
||||
- (void)otr_presentInView:(UIView *)view
|
||||
{
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
|
||||
[self showInView:view];
|
||||
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
[self showInView:[OTRAppDelegate appDelegate].window];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#import "UIActivity+ChatSecure.h"
|
||||
@import ARChromeActivity;
|
||||
@import TUSafariActivity;
|
||||
#import "OTROpenInFacebookActivity.h"
|
||||
#import "OTROpenInTwitterActivity.h"
|
||||
@import OTRAssets;
|
||||
|
@ -15,21 +16,22 @@
|
|||
@implementation UIActivity (ChatSecure)
|
||||
|
||||
+ (NSArray<UIActivity*>*) otr_linkActivities {
|
||||
TUSafariActivity *safariActivity = [TUSafariActivity new];
|
||||
ARChromeActivity *chromeActivity = [ARChromeActivity new];
|
||||
chromeActivity.activityTitle = OPEN_IN_CHROME();
|
||||
chromeActivity.callbackURL = [NSURL URLWithString:@"chatsecure://"];
|
||||
OTROpenInTwitterActivity *twitterActivity = [OTROpenInTwitterActivity new];
|
||||
OTROpenInFacebookActivity *facebookActivity = [OTROpenInFacebookActivity new];
|
||||
NSArray *applicationActivites = @[twitterActivity,facebookActivity,chromeActivity];
|
||||
NSArray *applicationActivites = @[twitterActivity,facebookActivity,safariActivity,chromeActivity];
|
||||
return applicationActivites;
|
||||
}
|
||||
|
||||
+ (CGSize)otr_defaultImageSize
|
||||
{
|
||||
CGSize size = CGSizeZero;
|
||||
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
|
||||
size = CGSizeMake(43, 43);
|
||||
} else if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
size = CGSizeMake(55, 55);
|
||||
}
|
||||
return size;
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#import "UIActivityViewController+ChatSecure.h"
|
||||
@import ARChromeActivity;
|
||||
@import TUSafariActivity;
|
||||
#import "OTROpenInFacebookActivity.h"
|
||||
#import "OTROpenInTwitterActivity.h"
|
||||
@import OTRAssets;
|
|
@ -49,7 +49,7 @@ extension NotificationType: RawRepresentable {
|
|||
public typealias RawValue = String
|
||||
}
|
||||
|
||||
extension UIApplication {
|
||||
public extension UIApplication {
|
||||
|
||||
/// Removes all but one foreground notifications for typing and message events sent from APNS
|
||||
@objc public func removeExtraForegroundNotifications() {
|
||||
|
@ -138,7 +138,7 @@ extension UIApplication {
|
|||
let chatString = WANTS_TO_CHAT_STRING()
|
||||
let text = "\(name) \(chatString)"
|
||||
let unreadCount = self.applicationIconBadgeNumber + 1
|
||||
self.showLocalNotificationWith(groupingIdentifier: nil, body: text, badge: unreadCount, userInfo: [kOTRNotificationType:kOTRNotificationTypeSubscriptionRequest], recurring: false)
|
||||
self.showLocalNotificationWith(identifier: nil, body: text, badge: unreadCount, userInfo: [kOTRNotificationType:kOTRNotificationTypeSubscriptionRequest], recurring: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,26 +158,28 @@ extension UIApplication {
|
|||
let userInfo:[AnyHashable:Any] = [kOTRNotificationThreadKey:identifier,
|
||||
kOTRNotificationThreadCollection:thread.threadCollection,
|
||||
kOTRNotificationType: kOTRNotificationTypeApprovedBuddy]
|
||||
self.showLocalNotificationWith(groupingIdentifier: nil, body: message, badge: unreadCount, userInfo: userInfo, recurring: false)
|
||||
self.showLocalNotificationWith(identifier: identifier, body: message, badge: unreadCount, userInfo: userInfo, recurring: false)
|
||||
}
|
||||
}
|
||||
|
||||
internal func showLocalNotificationFor(_ thread:OTRThreadOwner?, text:String, unreadCount:Int) {
|
||||
if let thread = thread, thread.isMuted { return } // No notifications for muted
|
||||
DispatchQueue.main.async {
|
||||
var identifier:String? = nil
|
||||
var userInfo:[AnyHashable:Any]? = nil
|
||||
if let t = thread {
|
||||
identifier = t.threadIdentifier
|
||||
userInfo = [kOTRNotificationThreadKey:t.threadIdentifier,
|
||||
kOTRNotificationThreadCollection:t.threadCollection,
|
||||
kOTRNotificationType: kOTRNotificationTypeChatMessage]
|
||||
}
|
||||
self.showLocalNotificationWith(groupingIdentifier: nil, body: text, badge: unreadCount, userInfo: userInfo, recurring: false)
|
||||
self.showLocalNotificationWith(identifier: identifier, body: text, badge: unreadCount, userInfo: userInfo, recurring: false)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func showLocalNotificationWith(groupingIdentifier:String?, body:String, badge:Int, userInfo:[AnyHashable:Any]?, recurring:Bool) {
|
||||
@objc public func showLocalNotificationWith(identifier:String?, body:String, badge:Int, userInfo:[AnyHashable:Any]?, recurring:Bool) {
|
||||
DispatchQueue.main.async {
|
||||
if recurring, self.hasRecurringLocalNotificationWith(identifier: groupingIdentifier) {
|
||||
if recurring, self.hasRecurringLocalNotificationWith(identifier: identifier) {
|
||||
return // Already pending
|
||||
}
|
||||
// Use the new UserNotifications.framework on iOS 10+
|
||||
|
@ -185,9 +187,9 @@ extension UIApplication {
|
|||
let localNotification = UNMutableNotificationContent()
|
||||
localNotification.body = body
|
||||
localNotification.badge = NSNumber(integerLiteral: badge)
|
||||
localNotification.sound = UNNotificationSound.default
|
||||
if let threadKey = userInfo?[kOTRNotificationThreadKey] as? String {
|
||||
localNotification.threadIdentifier = threadKey
|
||||
localNotification.sound = UNNotificationSound.default()
|
||||
if let identifier = identifier {
|
||||
localNotification.threadIdentifier = identifier
|
||||
}
|
||||
if let userInfo = userInfo {
|
||||
localNotification.userInfo = userInfo
|
||||
|
@ -199,7 +201,7 @@ extension UIApplication {
|
|||
date.minute = 0
|
||||
trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)
|
||||
}
|
||||
let request = UNNotificationRequest(identifier: groupingIdentifier ?? UUID().uuidString, content: localNotification, trigger: trigger) // Schedule the notification.
|
||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: localNotification, trigger: trigger) // Schedule the notification.
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.add(request, withCompletionHandler: { (error: Error?) in
|
||||
if let error = error as NSError? {
|
||||
|
@ -280,9 +282,9 @@ extension UIApplication {
|
|||
let username = account.username
|
||||
var body = "\(CONNECTION_ERROR_STRING()) \(username)."
|
||||
|
||||
|
||||
if error.domain == GCDAsyncSocketErrorDomain,
|
||||
let code = GCDAsyncSocketError.Code.init(rawValue: error.code) {
|
||||
let code = GCDAsyncSocketError(rawValue: error.code) {
|
||||
|
||||
switch code {
|
||||
case .noError,
|
||||
.connectTimeoutError,
|
||||
|
@ -296,8 +298,6 @@ extension UIApplication {
|
|||
case .otherError:
|
||||
// this is probably a SSL error
|
||||
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
|
||||
@unknown default:
|
||||
return
|
||||
}
|
||||
} else if error.domain == "kCFStreamErrorDomainSSL" {
|
||||
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
|
||||
|
@ -325,12 +325,19 @@ extension UIApplication {
|
|||
let userInfo = [kOTRNotificationType: kOTRNotificationTypeConnectionError,
|
||||
kOTRNotificationAccountKey: accountKey]
|
||||
|
||||
self.showLocalNotificationWith(groupingIdentifier: accountKey, body: body, badge: badge, userInfo: userInfo, recurring: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIApplication {
|
||||
@objc public func open(_ url: URL) {
|
||||
open(url, options: [:], completionHandler: nil)
|
||||
if #available(iOS 10.0, *) {
|
||||
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { (notifications) in
|
||||
// FIXME: this deduplication code doesn't seem to work
|
||||
// if we are already showing a notification, let's not spam the user too much with more of them
|
||||
for notification in notifications {
|
||||
if notification.request.identifier == accountKey {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.showLocalNotificationWith(identifier: accountKey, body: body, badge: badge, userInfo: userInfo, recurring: false)
|
||||
})
|
||||
} else {
|
||||
showLocalNotificationWith(identifier: accountKey, body: body, badge: badge, userInfo: userInfo, recurring: false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import Foundation
|
|||
import UIKit
|
||||
|
||||
|
||||
extension UINavigationController {
|
||||
public extension UINavigationController {
|
||||
|
||||
@objc public func otr_baseViewContorllers() -> [UIViewController] {
|
||||
var result:[UIViewController] = []
|
|
@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@interface UITableView (ChatSecure)
|
||||
|
||||
/** deleteActionAlsoRemovesFromRoster is YES for the ChooseBuddy view, otherwise NO. Connection must be read-write */
|
||||
+ (nullable UISwipeActionsConfiguration *)editActionsForThread:(id<OTRThreadOwner>)thread deleteActionAlsoRemovesFromRoster:(BOOL)deleteActionAlsoRemovesFromRoster connection:(YapDatabaseConnection*)connection;
|
||||
+ (nullable NSArray<UITableViewRowAction *> *)editActionsForThread:(id<OTRThreadOwner>)thread deleteActionAlsoRemovesFromRoster:(BOOL)deleteActionAlsoRemovesFromRoster connection:(YapDatabaseConnection*)connection;
|
||||
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -8,14 +8,14 @@
|
|||
|
||||
#import "UITableView+ChatSecure.h"
|
||||
#import "OTRXMPPBuddy.h"
|
||||
#import "ChatSecureCoreCompat-Swift.h"
|
||||
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||
#import "OTRXMPPManager_Private.h"
|
||||
@import OTRAssets;
|
||||
|
||||
@implementation UITableView (ChatSecure)
|
||||
|
||||
/** Connection must be read-write */
|
||||
+ (nullable UISwipeActionsConfiguration *)editActionsForThread:(id<OTRThreadOwner>)thread deleteActionAlsoRemovesFromRoster:(BOOL)deleteActionAlsoRemovesFromRoster connection:(YapDatabaseConnection*)connection {
|
||||
+ (nullable NSArray<UITableViewRowAction *> *)editActionsForThread:(id<OTRThreadOwner>)thread deleteActionAlsoRemovesFromRoster:(BOOL)deleteActionAlsoRemovesFromRoster connection:(YapDatabaseConnection*)connection {
|
||||
NSParameterAssert(thread);
|
||||
NSParameterAssert(connection);
|
||||
if (!thread || !connection) {
|
||||
|
@ -33,23 +33,22 @@
|
|||
archiveTitle = UNARCHIVE_ACTION_STRING();
|
||||
}
|
||||
|
||||
UIContextualAction *archiveAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:archiveTitle handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
|
||||
UITableViewRowAction *archiveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:archiveTitle handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
|
||||
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
|
||||
NSString *key = [thread threadIdentifier];
|
||||
NSString *collection = [thread threadCollection];
|
||||
id object = [transaction objectForKey:key inCollection:collection];
|
||||
if (![object conformsToProtocol:@protocol(OTRThreadOwner)]) {
|
||||
completionHandler(NO);
|
||||
return;
|
||||
}
|
||||
id <OTRThreadOwner> thread = object;
|
||||
thread.isArchived = !thread.isArchived;
|
||||
[thread saveWithTransaction:transaction];
|
||||
completionHandler(YES);
|
||||
}];
|
||||
}];
|
||||
|
||||
UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:DELETE_STRING() handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
|
||||
UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:DELETE_STRING() handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
|
||||
|
||||
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[OTRBaseMessage deleteAllMessagesForBuddyId:[thread threadIdentifier] transaction:transaction];
|
||||
}];
|
||||
|
@ -62,7 +61,7 @@
|
|||
[connection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
|
||||
account = [OTRAccount fetchObjectWithUniqueID:accountKey transaction:transaction];
|
||||
}];
|
||||
OTRXMPPManager *xmppManager = (OTRXMPPManager *)[[OTRProtocolManager sharedInstance] protocolForAccount:account];
|
||||
OTRXMPPManager *xmppManager = (OTRXMPPManager *)[OTRProtocolManager.shared protocolForAccount:account];
|
||||
if (room.roomJID) {
|
||||
[xmppManager.roomManager leaveRoom:room.roomJID];
|
||||
}
|
||||
|
@ -71,7 +70,6 @@
|
|||
//Delete database items
|
||||
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[((OTRXMPPRoom *)thread) removeWithTransaction:transaction];
|
||||
completionHandler(YES);
|
||||
}];
|
||||
} else if ([thread isKindOfClass:[OTRBuddy class]] && deleteActionAlsoRemovesFromRoster) {
|
||||
OTRBuddy *dbBuddy = (OTRBuddy*)thread;
|
||||
|
@ -82,14 +80,11 @@
|
|||
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[action saveWithTransaction:transaction];
|
||||
[dbBuddy removeWithTransaction:transaction];
|
||||
completionHandler(YES);
|
||||
}];
|
||||
} else {
|
||||
completionHandler(NO);
|
||||
}
|
||||
}];
|
||||
|
||||
return [UISwipeActionsConfiguration configurationWithActions:@[deleteAction, archiveAction]];
|
||||
return @[deleteAction, archiveAction];
|
||||
}
|
||||
|
||||
@end
|
|
@ -9,38 +9,26 @@
|
|||
import UIKit
|
||||
import OTRAssets
|
||||
|
||||
extension UIViewController {
|
||||
public func prompt(toShow url: URL, sender: Any) {
|
||||
(url as NSURL).promptToShow(from: self, sender: sender)
|
||||
}
|
||||
|
||||
public extension UIViewController {
|
||||
/// Will show a prompt to bring user into system settings
|
||||
public func showPromptForSystemSettings(sender: Any) {
|
||||
public func showPromptForSystemSettings() {
|
||||
let alert = UIAlertController(title: ENABLE_PUSH_IN_SETTINGS_STRING(), message: nil, preferredStyle: .alert)
|
||||
let settingsAction = UIAlertAction(title: SETTINGS_STRING(), style: .default, handler: { (action: UIAlertAction) -> Void in
|
||||
let appSettings = URL(string: UIApplication.openSettingsURLString)
|
||||
UIApplication.shared.open(appSettings!)
|
||||
let appSettings = URL(string: UIApplicationOpenSettingsURLString)
|
||||
UIApplication.shared.openURL(appSettings!)
|
||||
})
|
||||
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
|
||||
alert.addAction(settingsAction)
|
||||
alert.addAction(cancelAction)
|
||||
if let sourceView = sender as? UIView {
|
||||
alert.popoverPresentationController?.sourceView = sourceView;
|
||||
alert.popoverPresentationController?.sourceRect = sourceView.bounds;
|
||||
}
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public func showDestructivePrompt(title: String?, buttonTitle: String, sender: Any, handler: @escaping ((_ action: UIAlertAction) -> ())) {
|
||||
public func showDestructivePrompt(title: String?, buttonTitle: String, handler: @escaping ((_ action: UIAlertAction) -> ())) {
|
||||
let alert = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
|
||||
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive, handler: handler)
|
||||
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
|
||||
alert.addAction(destroyAction)
|
||||
alert.addAction(cancelAction)
|
||||
if let sourceView = sender as? UIView {
|
||||
alert.popoverPresentationController?.sourceView = sourceView;
|
||||
alert.popoverPresentationController?.sourceRect = sourceView.bounds;
|
||||
}
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
extension XMPPMessage {
|
||||
public extension XMPPMessage {
|
||||
/// Safely extracts XEP-0359 stanza-id
|
||||
@objc public func extractStanzaId(account: OTRXMPPAccount, capabilities: XMPPCapabilities) -> String? {
|
||||
let stanzaIds = self.stanzaIds
|
|
@ -56,7 +56,7 @@ extension UIImage {
|
|||
numTries = numTries + 1
|
||||
newSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
|
||||
let scaledImage = UIImage.otr_image(with: image, scaledTo: newSize)
|
||||
scaledImageData = scaledImage.jpegData(compressionQuality: jpegQuality)
|
||||
scaledImageData = UIImageJPEGRepresentation(scaledImage, jpegQuality)
|
||||
if let imageData = scaledImageData {
|
||||
sizeInBytes = UInt(imageData.count)
|
||||
scaleFactor = scaleFactor * scaleDecrement
|
||||
|
@ -123,7 +123,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
|||
let connection: YapDatabaseConnection
|
||||
let internalQueue = DispatchQueue(label: "FileTransferManager Queue")
|
||||
let callbackQueue = DispatchQueue.main
|
||||
let sessionManager: Session
|
||||
let sessionManager: SessionManager
|
||||
private var servers: [HTTPServer] = []
|
||||
|
||||
@objc public var canUploadFiles: Bool {
|
||||
|
@ -141,7 +141,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
|||
self.serverCapabilities = serverCapabilities
|
||||
self.httpFileUpload = XMPPHTTPFileUpload()
|
||||
self.connection = connection
|
||||
self.sessionManager = Alamofire.Session(configuration: sessionConfiguration)
|
||||
self.sessionManager = Alamofire.SessionManager(configuration: sessionConfiguration)
|
||||
super.init()
|
||||
if let stream = serverCapabilities.xmppStream {
|
||||
httpFileUpload.activate(stream)
|
||||
|
@ -155,19 +155,15 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
|||
|
||||
// Resume downloads, i.e. look for media items that are partially downloaded and retry getting them. TODO - use ranges
|
||||
@objc public func resumeDownloads() {
|
||||
/// https://github.com/ChatSecure/ChatSecure-iOS/issues/1034
|
||||
DDLogWarn("WARN: Download resumption is disabled. See https://github.com/ChatSecure/ChatSecure-iOS/issues/1034 for more information.")
|
||||
// connection.asyncRead { [weak self] (transaction) in
|
||||
// let unfinished = transaction.unfinishedDownloads()
|
||||
// self?.internalQueue.async {
|
||||
// for mediaItem in unfinished {
|
||||
// if let downloadMessage = mediaItem.parentObject(with: transaction) as? OTRDownloadMessage,
|
||||
// downloadMessage.messageError == nil {
|
||||
// self?.downloadMedia(downloadMessage)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
connection.asyncRead { (transaction) in
|
||||
transaction.enumerateUnfinishedDownloads({ (mediaItem, stop) in
|
||||
if let downloadMessage = mediaItem.parentObject(with: transaction) as? OTRDownloadMessage, downloadMessage.messageError == nil {
|
||||
self.internalQueue.async {
|
||||
self.downloadMedia(downloadMessage)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This will fetch capabilities and setup XMPP transfer module if needed
|
||||
|
@ -260,7 +256,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
|||
var outData = data
|
||||
var outKeyIv: Data? = nil
|
||||
if shouldEncrypt {
|
||||
guard let key = OTRPasswordGenerator.randomData(withLength: 32), let iv = OTRSignalEncryptionHelper.generateIV() else {
|
||||
guard let key = OTRPasswordGenerator.randomData(withLength: 32), let iv = OTRPasswordGenerator.randomData(withLength: 16) else {
|
||||
DDLogError("Could not generate key/iv")
|
||||
self.callbackQueue.async {
|
||||
completion(nil, FileTransferError.keyGenerationError)
|
||||
|
@ -269,7 +265,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
|||
}
|
||||
outKeyIv = iv + key
|
||||
do {
|
||||
let crypted = try OTRSignalEncryptionHelper.encryptData(data, key: key, iv: iv)
|
||||
let crypted = try OTRCryptoUtility.encryptAESGCMData(data, key: key, iv: iv)
|
||||
outData = crypted.data + crypted.authTag
|
||||
} catch let error {
|
||||
outData = Data()
|
||||
|
@ -291,23 +287,9 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pick optional headers from the slot and filter out any not allowed by
|
||||
// XEP-0363 (https://xmpp.org/extensions/xep-0363.html#request)
|
||||
let allowedHeaders = ["authorization", "cookie", "expires"]
|
||||
var forwardedHeaders:HTTPHeaders = [:]
|
||||
for (headerName, headerValue) in slot.putHeaders {
|
||||
let name = headerName.replacingOccurrences(of: "\n", with: "").lowercased()
|
||||
if allowedHeaders.contains(name) {
|
||||
forwardedHeaders[name] = headerValue.replacingOccurrences(of: "\n", with: "")
|
||||
}
|
||||
}
|
||||
forwardedHeaders["Content-Type"] = contentType
|
||||
forwardedHeaders["Content-Length"] = "\(UInt(outData.count))"
|
||||
|
||||
self.sessionManager.upload(outData, to: slot.putURL, method: .put, headers: forwardedHeaders)
|
||||
self.sessionManager.upload(outData, to: slot.putURL, method: .put)
|
||||
.validate()
|
||||
.response(queue: self.callbackQueue) { response in
|
||||
.responseData(queue: self.callbackQueue) { response in
|
||||
switch response.result {
|
||||
case .success:
|
||||
if let outKeyIv = outKeyIv {
|
||||
|
@ -378,9 +360,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
|||
}
|
||||
mediaItem.parentObjectKey = message.messageKey
|
||||
mediaItem.parentObjectCollection = message.messageCollection
|
||||
guard let newPath = OTRMediaFileManager.path(for: mediaItem, buddyUniqueId: thread.threadIdentifier) else {
|
||||
return
|
||||
}
|
||||
let newPath = OTRMediaFileManager.path(for: mediaItem, buddyUniqueId: thread.threadIdentifier)
|
||||
self.connection.readWrite { transaction in
|
||||
message.save(with: transaction)
|
||||
mediaItem.save(with: transaction)
|
||||
|
@ -494,8 +474,6 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
|||
shouldEncrypt = true
|
||||
case .invalid, .plaintext, .plaintextWithOTR:
|
||||
shouldEncrypt = false
|
||||
@unknown default:
|
||||
fatalError("Unhandled message security value!")
|
||||
}
|
||||
|
||||
self.upload(mediaItem: mediaItem, shouldEncrypt: shouldEncrypt, prefetchedData: prefetchedData, completion: { (_url: URL?, error: Error?) in
|
||||
|
@ -679,12 +657,7 @@ extension FileTransferManager {
|
|||
// Remove placeholder media item
|
||||
mediaItem = OTRMediaItem(forMessage: downloadMessage, transaction: transaction)
|
||||
mediaItem?.remove(with: transaction)
|
||||
// If the file is encrypted, the server might not know its type
|
||||
if url.aesGcmKey != nil && contentType == "application/octet-stream" {
|
||||
mediaItem = OTRMediaItem.incomingItem(withFilename: url.lastPathComponent, mimeType: nil)
|
||||
} else {
|
||||
mediaItem = OTRMediaItem.incomingItem(withFilename: url.lastPathComponent, mimeType: contentType)
|
||||
}
|
||||
mediaItem = OTRMediaItem.incomingItem(withFilename: url.lastPathComponent, mimeType: contentType)
|
||||
mediaItem?.parentObjectKey = downloadMessage.uniqueId
|
||||
mediaItem?.parentObjectCollection = downloadMessage.messageCollection
|
||||
mediaItem?.save(with: transaction)
|
||||
|
@ -813,19 +786,19 @@ extension OTRDownloadMessage {
|
|||
}
|
||||
}
|
||||
|
||||
extension OTRMessageProtocol {
|
||||
public extension OTRMessageProtocol {
|
||||
public var downloadableURLs: [URL] {
|
||||
return self.messageText?.downloadableURLs ?? []
|
||||
}
|
||||
}
|
||||
|
||||
extension OTRBaseMessage {
|
||||
public extension OTRBaseMessage {
|
||||
@objc public var downloadableNSURLs: [NSURL] {
|
||||
return self.downloadableURLs as [NSURL]
|
||||
}
|
||||
}
|
||||
|
||||
extension OTRXMPPRoomMessage {
|
||||
public extension OTRXMPPRoomMessage {
|
||||
@objc public var downloadableNSURLs: [NSURL] {
|
||||
return self.downloadableURLs as [NSURL]
|
||||
}
|
||||
|
@ -904,31 +877,20 @@ extension URL {
|
|||
}
|
||||
|
||||
var aesGcmKey: (key: Data, iv: Data)? {
|
||||
guard let data = self.anchorData else { return nil }
|
||||
let ivLength: Int
|
||||
switch data.count {
|
||||
case 48:
|
||||
// legacy clients send 16-byte IVs
|
||||
ivLength = 16
|
||||
case 44:
|
||||
// newer clients send 12-byte IVs
|
||||
ivLength = 12
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
let iv = data.subdata(in: 0..<ivLength)
|
||||
let key = data.subdata(in: ivLength..<data.count)
|
||||
guard let data = self.anchorData, data.count == 48 else { return nil }
|
||||
let iv = data.subdata(in: 0..<16)
|
||||
let key = data.subdata(in: 16..<48)
|
||||
return (key, iv)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSString {
|
||||
public extension NSString {
|
||||
public var isSingleURLOnly: Bool {
|
||||
return (self as String).isSingleURLOnly
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
public extension String {
|
||||
|
||||
private var urlRanges: ([URL], [NSRange]) {
|
||||
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
|
||||
|
@ -980,7 +942,7 @@ extension String {
|
|||
}
|
||||
}
|
||||
|
||||
extension FileTransferManager {
|
||||
public extension FileTransferManager {
|
||||
/// Returns whether or not message should be displayed or hidden from collection. Single incoming URLs should be hidden, for example.
|
||||
@objc public static func shouldDisplayMessage(_ message: OTRMessageProtocol, transaction: YapDatabaseReadTransaction) -> Bool {
|
||||
// Always show media messages
|
|
@ -22,8 +22,11 @@ private class OutstandingActionInfo: Hashable, Equatable {
|
|||
self.completion = completion
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(action.yapKey())
|
||||
/// Needed so we can store the struct in a dictionary
|
||||
var hashValue: Int {
|
||||
get {
|
||||
return action.yapKey().hashValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,9 +45,10 @@ private struct OutstandingMessageInfo {
|
|||
|
||||
/// Needed so we can store the struct in a dictionary
|
||||
extension OutstandingMessageInfo: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(self.messageKey)
|
||||
hasher.combine(self.messageCollection)
|
||||
var hashValue: Int {
|
||||
get {
|
||||
return "\(self.messageKey)\(self.messageCollection)".hashValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,7 +217,7 @@ public class MessageQueueHandler:NSObject {
|
|||
switch message.messageSecurity {
|
||||
case .plaintext:
|
||||
self.waitingForMessage(message.uniqueId, messageCollection: message.messageCollection, messageSecurity:message.messageSecurity, completion: completion)
|
||||
OTRProtocolManager.sharedInstance().send(message)
|
||||
OTRProtocolManager.shared.send(message)
|
||||
break
|
||||
case .plaintextWithOTR:
|
||||
self.sendOTRMessage(message: message, buddyKey: buddy.uniqueId, buddyUsername: buddy.username, accountUsername: account.username, accountProtocolStrintg: account.protocolTypeString(), requiresActiveSession: false, completion: completion)
|
||||
|
@ -227,8 +231,6 @@ public class MessageQueueHandler:NSObject {
|
|||
case .invalid:
|
||||
fatalError("Invalid message security. This should never happen... so let's crash!")
|
||||
break
|
||||
@unknown default:
|
||||
fatalError("Invalid message security. This should never happen... so let's crash!")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,8 +259,6 @@ public class MessageQueueHandler:NSObject {
|
|||
room.sendRoomMessage(message)
|
||||
case .OMEMO:
|
||||
sendOMEMOMessage(message: message, accountProtocol: accountProtocol, completion: completion)
|
||||
@unknown default:
|
||||
fatalError("Invalid group message security. This should never happen.")
|
||||
}
|
||||
databaseConnection.readWrite { transaction in
|
||||
if let sentMessage = message.refetch(with: transaction)?.copyAsSelf() {
|
||||
|
@ -296,7 +296,7 @@ public class MessageQueueHandler:NSObject {
|
|||
}
|
||||
|
||||
//Get the XMPP procol manager associated with this message and therefore account
|
||||
guard let accountProtocol = OTRProtocolManager.sharedInstance().protocol(for: account) as? XMPPManager else {
|
||||
guard let accountProtocol = OTRProtocolManager.shared.protocol(for: account) as? XMPPManager else {
|
||||
completion(true, 0.0)
|
||||
return
|
||||
}
|
||||
|
@ -350,7 +350,7 @@ public class MessageQueueHandler:NSObject {
|
|||
}
|
||||
|
||||
//Get the XMPP procol manager associated with this message and therefore account
|
||||
guard let accountProtocol = OTRProtocolManager.sharedInstance().protocol(for: account) as? XMPPManager else {
|
||||
guard let accountProtocol = OTRProtocolManager.shared.protocol(for: account) as? XMPPManager else {
|
||||
completion(true, 0.0)
|
||||
return
|
||||
}
|
||||
|
@ -385,7 +385,7 @@ public class MessageQueueHandler:NSObject {
|
|||
}
|
||||
|
||||
//Get the XMPP procol manager associated with this message and therefore account
|
||||
guard let accountProtocol = OTRProtocolManager.sharedInstance().protocol(for: account) as? XMPPManager else {
|
||||
guard let accountProtocol = OTRProtocolManager.shared.protocol(for: account) as? XMPPManager else {
|
||||
completion(true, 0.0)
|
||||
return
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
@import MobileCoreServices;
|
||||
@import OTRAssets;
|
||||
#import "OTRUtilities.h"
|
||||
#import "UIActionSheet+ChatSecure.h"
|
||||
|
||||
|
||||
@interface OTRAttachmentPicker () <UINavigationControllerDelegate>
|
||||
|
@ -110,7 +111,7 @@
|
|||
finalImage = originalImage;
|
||||
}
|
||||
|
||||
if (finalImage && [self.delegate respondsToSelector:@selector(attachmentPicker:gotPhoto:withInfo:)]) {
|
||||
if ([self.delegate respondsToSelector:@selector(attachmentPicker:gotPhoto:withInfo:)]) {
|
||||
[self.delegate attachmentPicker:self gotPhoto:finalImage withInfo:info];
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
@property (nonatomic, strong, readonly) OTRAudioItem *currentAudioItem;
|
||||
@property (nonatomic, weak, readonly) OTRAudioControlsView *currentAudioControlsView;
|
||||
|
||||
- (BOOL)playAudioItem:(OTRAudioItem *)audioItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError **)error;
|
||||
- (void)playAudioItem:(OTRAudioItem *)audioItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError **)error;
|
||||
|
||||
- (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView;
|
||||
|
|
@ -44,19 +44,18 @@
|
|||
[self.currentAudioControlsView setTime:currentTime];
|
||||
}
|
||||
|
||||
- (BOOL)playURL:(NSURL *)url error:(NSError **)error;
|
||||
- (void)playURL:(NSURL *)url error:(NSError **)error;
|
||||
{
|
||||
AVURLAsset *asset = [AVURLAsset assetWithURL:url];
|
||||
self.duration = CMTimeGetSeconds(asset.duration);
|
||||
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
|
||||
error = nil;
|
||||
BOOL result = [self.audioSessionManager playAudioWithURL:url error:error];
|
||||
[self.audioSessionManager playAudioWithURL:url error:error];
|
||||
|
||||
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
|
||||
[self.currentAudioControlsView setTime:0];
|
||||
|
||||
[self startLabelTimer];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)startLabelTimer
|
||||
|
@ -94,13 +93,13 @@
|
|||
|
||||
#pragma - mark Public Methods
|
||||
|
||||
- (BOOL)playAudioItem:(OTRAudioItem *)audioItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError *__autoreleasing *)error
|
||||
- (void)playAudioItem:(OTRAudioItem *)audioItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSURL *audioURL = [[OTRMediaServer sharedInstance] urlForMediaItem:audioItem buddyUniqueId:buddyUniqueId];
|
||||
|
||||
_currentAudioItem = audioItem;
|
||||
|
||||
return [self playURL:audioURL error:error];
|
||||
[self playURL:audioURL error:error];
|
||||
}
|
||||
|
||||
- (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
@property (nonatomic, weak) id<OTRAudioSessionManagerDelegate> delegate;
|
||||
|
||||
- (BOOL)playAudioWithURL:(NSURL *)url error:(NSError **)error;
|
||||
- (void)playAudioWithURL:(NSURL *)url error:(NSError **)error;
|
||||
- (void)pausePlaying;
|
||||
- (void)resumePlaying;
|
||||
- (void)stopPlaying;
|
||||
|
@ -34,7 +34,7 @@
|
|||
- (NSURL *)currentPlayerURL;
|
||||
- (BOOL)isPlaying;
|
||||
|
||||
- (BOOL)recordAudioToURL:(NSURL *)url error:(NSError **)error;
|
||||
- (void)recordAudioToURL:(NSURL *)url error:(NSError **)error;
|
||||
- (void)stopRecording;
|
||||
- (NSTimeInterval)currentTimeRecordTime;
|
||||
- (NSURL *)currentRecorderURL;
|
|
@ -49,7 +49,7 @@
|
|||
#pragma - mark Public Methods
|
||||
|
||||
////// Playing //////
|
||||
- (BOOL)playAudioWithURL:(NSURL *)url error:(NSError **)error
|
||||
- (void)playAudioWithURL:(NSURL *)url error:(NSError **)error
|
||||
{
|
||||
[self stopPlaying];
|
||||
[self stopRecording];
|
||||
|
@ -57,22 +57,20 @@
|
|||
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:error];
|
||||
if (error) {
|
||||
return NO;
|
||||
return;
|
||||
}
|
||||
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error];
|
||||
if (error) {
|
||||
return NO;
|
||||
return;
|
||||
}
|
||||
|
||||
error = nil;
|
||||
self.currentPlayer = [self audioPlayerWithURL:url error:error];
|
||||
if (error) {
|
||||
return NO;
|
||||
return;
|
||||
}
|
||||
|
||||
[self.currentPlayer play];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)pausePlaying
|
||||
|
@ -121,32 +119,30 @@
|
|||
}
|
||||
|
||||
////// Recording //////
|
||||
- (BOOL)recordAudioToURL:(NSURL *)url error:(NSError **)error
|
||||
- (void)recordAudioToURL:(NSURL *)url error:(NSError **)error
|
||||
{
|
||||
[self stopRecording];
|
||||
[self stopPlaying];
|
||||
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:error];
|
||||
if (error) {
|
||||
return NO;
|
||||
return;
|
||||
}
|
||||
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error];
|
||||
if (error) {
|
||||
return NO;
|
||||
return;
|
||||
}
|
||||
|
||||
self.currentRecorder = [self audioRecorderWithURL:url error:error];
|
||||
|
||||
if (error) {
|
||||
return NO;
|
||||
return;
|
||||
}
|
||||
|
||||
self.currentRecorder.meteringEnabled = YES;
|
||||
self.recordDecibelTimer = [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:@selector(updateDecibelRecording:) userInfo:nil repeats:YES];
|
||||
|
||||
[self.currentRecorder record];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)stopRecording
|
||||
|
@ -183,9 +179,9 @@
|
|||
|
||||
#pragma - mark Private Methods
|
||||
|
||||
- (BOOL)deactivateSession:(NSError **)error
|
||||
- (void)deactivateSession:(NSError **)error
|
||||
{
|
||||
return [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:error];
|
||||
[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:error];
|
||||
}
|
||||
|
||||
- (AVPlayer *)audioPlayerWithURL:(NSURL *)url error:(NSError **)error
|
|
@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
directory:(nullable NSString*)directory
|
||||
withMediaStorage:(BOOL)withMediaStorage;
|
||||
|
||||
- (BOOL)setDatabasePassphrase:(NSString *)passphrase remember:(BOOL)rememeber error:(NSError *_Nullable*)error;
|
||||
- (void)setDatabasePassphrase:(NSString *)passphrase remember:(BOOL)rememeber error:(NSError *_Nullable*)error;
|
||||
|
||||
|
||||
- (BOOL)hasPassphrase;
|
|
@ -15,6 +15,7 @@
|
|||
#import "OTRConstants.h"
|
||||
#import "OTRXMPPAccount.h"
|
||||
#import "OTRXMPPTorAccount.h"
|
||||
#import "OTRGoogleOAuthXMPPAccount.h"
|
||||
#import "OTRAccount.h"
|
||||
#import "OTRIncomingMessage.h"
|
||||
#import "OTROutgoingMessage.h"
|
||||
|
@ -28,7 +29,7 @@
|
|||
#import "OTRSignalSession.h"
|
||||
#import "OTRSettingsManager.h"
|
||||
#import "OTRXMPPPresenceSubscriptionRequest.h"
|
||||
#import "ChatSecureCoreCompat-Swift.h"
|
||||
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||
|
||||
|
||||
@interface OTRDatabaseManager ()
|
||||
|
@ -123,7 +124,6 @@
|
|||
}
|
||||
return keyData;
|
||||
};
|
||||
options.cipherCompatability = YapDatabaseCipherCompatability_Version3;
|
||||
_databaseDirectory = [directory copy];
|
||||
if (!_databaseDirectory) {
|
||||
_databaseDirectory = [[self class] defaultYapDatabaseDirectory];
|
||||
|
@ -134,14 +134,17 @@
|
|||
}
|
||||
NSString *databasePath = [self.databaseDirectory stringByAppendingPathComponent:name];
|
||||
|
||||
self.database = [[YapDatabase alloc] initWithURL:[NSURL fileURLWithPath:databasePath] options:options];
|
||||
|
||||
self.database = [[YapDatabase alloc] initWithPath:databasePath
|
||||
serializer:nil
|
||||
deserializer:nil
|
||||
options:options];
|
||||
// Stop trying to setup up the database. Something went wrong. Most likely the password is incorrect.
|
||||
if (self.database == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
self.database.connectionDefaults.objectCacheLimit = 10000;
|
||||
self.database.defaultObjectPolicy = YapDatabasePolicyShare;
|
||||
self.database.defaultObjectCacheLimit = 10000;
|
||||
|
||||
[self setupConnections];
|
||||
|
||||
|
@ -193,7 +196,7 @@
|
|||
|
||||
|
||||
NSString *name = [YapDatabaseConstants extensionName:DatabaseExtensionNameMessageQueueBrokerViewName];
|
||||
self->_messageQueueBroker = [YapTaskQueueBroker setupWithDatabase:self.database name:name handler:self.messageQueueHandler error:nil];
|
||||
_messageQueueBroker = [YapTaskQueueBroker setupWithDatabase:self.database name:name handler:self.messageQueueHandler error:nil];
|
||||
|
||||
|
||||
//Register Buddy username & displayName FTS and corresponding view
|
||||
|
@ -296,17 +299,15 @@
|
|||
return [[NSFileManager defaultManager] fileExistsAtPath:[self defaultYapDatabasePathWithName:OTRYapDatabaseName]];
|
||||
}
|
||||
|
||||
- (BOOL) setDatabasePassphrase:(NSString *)passphrase remember:(BOOL)rememeber error:(NSError**)error
|
||||
- (void) setDatabasePassphrase:(NSString *)passphrase remember:(BOOL)rememeber error:(NSError**)error
|
||||
{
|
||||
BOOL result = YES;
|
||||
if (rememeber) {
|
||||
self.inMemoryPassphrase = nil;
|
||||
result = [SAMKeychain setPassword:passphrase forService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName error:error];
|
||||
[SAMKeychain setPassword:passphrase forService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName error:error];
|
||||
} else {
|
||||
[SAMKeychain deletePasswordForService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName];
|
||||
self.inMemoryPassphrase = passphrase;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)hasPassphrase
|
|
@ -14,7 +14,7 @@
|
|||
#import "OTRIncomingMessage.h"
|
||||
#import "OTRLog.h"
|
||||
#import "OTROutgoingMessage.h"
|
||||
#import "ChatSecureCoreCompat-Swift.h"
|
||||
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||
|
||||
NSString *OTRArchiveFilteredConversationsName = @"OTRFilteredConversationsName";
|
||||
NSString *OTRBuddyFilteredConversationsName = @"OTRBuddyFilteredConversationsName";
|
|
@ -21,7 +21,9 @@
|
|||
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
@import Foundation;
|
||||
@class OTRKit, OTRDataHandler, OTRFingerprint;
|
||||
@import OTRKit;
|
||||
|
||||
@class OTRPushTLVHandler;
|
||||
|
||||
extern NSString * _Nonnull const OTRMessageStateDidChangeNotification;
|
||||
extern NSString * _Nonnull const OTRWillStartGeneratingPrivateKeyNotification;
|
||||
|
@ -41,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@property (nonatomic, strong, readonly) OTRKit *otrKit;
|
||||
@property (nonatomic, strong, readonly) OTRDataHandler *dataHandler;
|
||||
@property (nonatomic, strong, readonly) OTRPushTLVHandler *pushTLVHandler;
|
||||
|
||||
/**
|
||||
* This method takes a buddy key and collection. If it finds an object in the database and `hasGoneEncryptedBefore` is true
|
|
@ -39,9 +39,10 @@
|
|||
#import "OTRMediaServer.h"
|
||||
#import "OTRDatabaseManager.h"
|
||||
#import "OTRLog.h"
|
||||
#import "OTRPushTLVHandler.h"
|
||||
#import "OTRXMPPManager.h"
|
||||
#import "OTRYapMessageSendAction.h"
|
||||
#import "ChatSecureCoreCompat-Swift.h"
|
||||
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||
|
||||
@import AVFoundation;
|
||||
@import XMPPFramework;
|
||||
|
@ -66,6 +67,7 @@ NSString *const OTRMessageStateKey = @"OTREncryptionManagerMessageStateKey";
|
|||
_otrFingerprintCache = [[NSCache alloc] init];
|
||||
_otrKit = [[OTRKit alloc] initWithDelegate:self dataPath:nil];
|
||||
_dataHandler = [[OTRDataHandler alloc] initWithOTRKit:self.otrKit delegate:self];
|
||||
_pushTLVHandler = [[OTRPushTLVHandler alloc] initWithOTRKit:self.otrKit delegate:nil];
|
||||
_readConnection = OTRDatabaseManager.shared.readConnection;
|
||||
NSArray *protectPaths = @[self.otrKit.privateKeyPath, self.otrKit.fingerprintsPath, self.otrKit.instanceTagsPath];
|
||||
for (NSString *path in protectPaths) {
|
||||
|
@ -246,7 +248,7 @@ NSString *const OTRMessageStateKey = @"OTREncryptionManagerMessageStateKey";
|
|||
} completionBlock:^{
|
||||
if (!buddy) { return; }
|
||||
message.buddyUniqueId = buddy.uniqueId;
|
||||
[[OTRProtocolManager sharedInstance] sendMessage:message];
|
||||
[OTRProtocolManager.shared sendMessage:message];
|
||||
}];
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
|
|||
//#865
|
||||
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
|
||||
buddyUniqueId:(NSString *)buddyUniqueId
|
||||
completion:(nullable void (^)(BOOL success, NSError * _Nullable error))completion
|
||||
completion:(void (^)(BOOL success, NSError * _Nullable error))completion
|
||||
completionQueue:(nullable dispatch_queue_t)completionQueue;
|
||||
|
||||
- (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem
|
||||
|
@ -43,10 +43,8 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
|
|||
buddyUniqueId:(NSString *)buddyUniqueId
|
||||
error:(NSError* __autoreleasing *)error;
|
||||
|
||||
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId;
|
||||
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash;
|
||||
|
||||
- (void)vacuum:(dispatch_block_t)completion;
|
||||
+ (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId;
|
||||
+ (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash;
|
||||
|
||||
@property (class, nonatomic, readonly) OTRMediaFileManager *shared;
|
||||
|
|
@ -52,10 +52,7 @@ NSString *const kOTRRootMediaDirectory = @"media";
|
|||
- (BOOL)setupWithPath:(NSString *)path password:(NSString *)password
|
||||
{
|
||||
_ioCipher = [[IOCipher alloc] initWithPath:path password:password];
|
||||
if (!_ioCipher) {
|
||||
return NO;
|
||||
}
|
||||
return [_ioCipher setCipherCompatibility:3];
|
||||
return _ioCipher != nil;
|
||||
}
|
||||
|
||||
- (void)copyDataFromFilePath:(NSString *)filePath
|
||||
|
@ -134,7 +131,7 @@ completionQueue:(nullable dispatch_queue_t)completionQueue {
|
|||
//#865
|
||||
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
|
||||
buddyUniqueId:(NSString *)buddyUniqueId
|
||||
completion:(nullable void (^)(BOOL success, NSError * _Nullable error))completion
|
||||
completion:(void (^)(BOOL success, NSError * _Nullable error))completion
|
||||
completionQueue:(nullable dispatch_queue_t)completionQueue {
|
||||
if (!completionQueue) {
|
||||
completionQueue = dispatch_get_main_queue();
|
||||
|
@ -284,12 +281,4 @@ completionQueue:(nullable dispatch_queue_t)completionQueue {
|
|||
}
|
||||
}
|
||||
|
||||
- (void)vacuum:(dispatch_block_t)completion {
|
||||
[self performAsyncWrite:^{
|
||||
[self.ioCipher vacuum];
|
||||
if (completion != nil) {
|
||||
dispatch_async(dispatch_get_main_queue(), completion);
|
||||
}
|
||||
}];
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// OTROAuthRefresher.h
|
||||
// Off the Record
|
||||
//
|
||||
// Created by David Chiles on 3/28/14.
|
||||
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
@import Foundation;
|
||||
|
||||
@class GTMOAuth2Authentication;
|
||||
@class FBAccessTokenData;
|
||||
@class OTROAuthXMPPAccount;
|
||||
|
||||
typedef void(^OTROAuthCompletionBlock)(id token,NSError *);
|
||||
|
||||
@interface OTROAuthRefresher : NSObject
|
||||
|
||||
+ (void)refreshAccount:(OTROAuthXMPPAccount *)account completion:(OTROAuthCompletionBlock)completionBlock;
|
||||
|
||||
@end
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// OTROAuthRefresher.m
|
||||
// Off the Record
|
||||
//
|
||||
// Created by David Chiles on 3/28/14.
|
||||
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OTROAuthRefresher.h"
|
||||
|
||||
#import "GTMOAuth2Authentication.h"
|
||||
#import "OTRSecrets.h"
|
||||
#import "OTRConstants.h"
|
||||
|
||||
#import "OTROAuthXMPPAccount.h"
|
||||
|
||||
|
||||
@implementation OTROAuthRefresher
|
||||
|
||||
+ (void)refreshGoogleToken:(GTMOAuth2Authentication *)authToken completion:(OTROAuthCompletionBlock)completionBlock
|
||||
{
|
||||
[authToken authorizeRequest:nil completionHandler:^(NSError *error) {
|
||||
if (completionBlock) {
|
||||
if (!error) {
|
||||
completionBlock(authToken,nil);
|
||||
}
|
||||
else {
|
||||
completionBlock(nil,error);
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)refreshAccount:(OTROAuthXMPPAccount *)account completion:(OTROAuthCompletionBlock)completionBlock
|
||||
{
|
||||
if (account.accountType == OTRAccountTypeGoogleTalk) {
|
||||
[self refreshGoogleToken:[account accountSpecificToken] completion:completionBlock];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -19,16 +19,16 @@ import SignalProtocolObjC
|
|||
|
||||
@objc public static let DeviceListUpdateNotificationName = Notification.Name(rawValue: "DeviceListUpdateNotification")
|
||||
|
||||
public let signalEncryptionManager:OTRAccountSignalEncryptionManager
|
||||
public let omemoStorageManager:OTROMEMOStorageManager
|
||||
@objc public let accountYapKey:String
|
||||
@objc public let databaseConnection:YapDatabaseConnection
|
||||
open let signalEncryptionManager:OTRAccountSignalEncryptionManager
|
||||
open let omemoStorageManager:OTROMEMOStorageManager
|
||||
@objc open let accountYapKey:String
|
||||
@objc open let databaseConnection:YapDatabaseConnection
|
||||
@objc open weak var omemoModule:OMEMOModule?
|
||||
@objc open weak var omemoModuleQueue:DispatchQueue?
|
||||
@objc open var callbackQueue:DispatchQueue
|
||||
@objc public let workQueue:DispatchQueue
|
||||
@objc public let messageStorage: MessageStorage
|
||||
@objc public let roomManager: OTRXMPPRoomManager
|
||||
@objc open let workQueue:DispatchQueue
|
||||
@objc open let messageStorage: MessageStorage
|
||||
@objc open let roomManager: OTRXMPPRoomManager
|
||||
|
||||
private var roomStorage: RoomStorage {
|
||||
return roomManager.roomStorage
|
||||
|
@ -295,7 +295,7 @@ import SignalProtocolObjC
|
|||
//Strong self work here
|
||||
var buddies: [OTRXMPPBuddy] = []
|
||||
self.databaseConnection.read { (transaction) in
|
||||
buddies = buddyKeys.compactMap({ key in
|
||||
buddies = buddyKeys.flatMap({ key in
|
||||
OTRXMPPBuddy.fetchObject(withUniqueID: key, transaction: transaction)
|
||||
})
|
||||
}
|
||||
|
@ -310,7 +310,10 @@ import SignalProtocolObjC
|
|||
}
|
||||
do {
|
||||
//Create the encrypted payload
|
||||
let gcmData = try OTRSignalEncryptionHelper.encryptData(messageBodyData, key: keyData, iv: ivData)
|
||||
guard let gcmData = try OTRSignalEncryptionHelper.encryptData(messageBodyData, key: keyData, iv: ivData) else {
|
||||
DDLogError("OMEMO Encryption error: Could not perform AES-GCM operation")
|
||||
return
|
||||
}
|
||||
|
||||
// this does the signal encryption. If we fail it doesn't matter here. We end up trying the next device and fail later if no devices worked.
|
||||
let encryptClosure:(OMEMODevice) -> (OMEMOKeyData?) = { device in
|
||||
|
@ -331,7 +334,7 @@ import SignalProtocolObjC
|
|||
4. Remove optional values
|
||||
*/
|
||||
let buddyKeyDataArray = buddyKeys.flatMap({ key in
|
||||
self.omemoStorageManager.getDevicesForParentYapKey(key, yapCollection: OTRXMPPBuddy.collection, trustedOnly: true).map(encryptClosure).compactMap{ $0 }
|
||||
self.omemoStorageManager.getDevicesForParentYapKey(key, yapCollection: OTRXMPPBuddy.collection, trustedOnly: true).map(encryptClosure).flatMap{ $0 }
|
||||
})
|
||||
|
||||
// Stop here if we were not able to encrypt to any of the buddies
|
||||
|
@ -351,7 +354,7 @@ import SignalProtocolObjC
|
|||
*/
|
||||
let ourDevicesKeyData = self.omemoStorageManager.getDevicesForOurAccount(trustedOnly: true).filter({ (device) -> Bool in
|
||||
return device.deviceId.uint32Value != self.signalEncryptionManager.registrationId
|
||||
}).map(encryptClosure).compactMap{ $0 }
|
||||
}).map(encryptClosure).flatMap{ $0 }
|
||||
|
||||
// Combine teh two arrays for all key data
|
||||
let keyDataArray = ourDevicesKeyData + buddyKeyDataArray
|
||||
|
@ -365,7 +368,6 @@ import SignalProtocolObjC
|
|||
let groupMessage = self.omemoModule?.message(forKeyData: keyDataArray, iv: ivData, to: destinationJID, payload: finalPayload, elementId: message.remoteMessageId)
|
||||
{
|
||||
groupMessage.addAttribute(withName: "type", stringValue: "groupchat")
|
||||
groupMessage.addReceiptRequest()
|
||||
self.omemoModule?.xmppStream?.send(groupMessage)
|
||||
} else if message is OTROutgoingMessage {
|
||||
self.omemoModule?.sendKeyData(keyDataArray, iv: ivData, to: destinationJID, payload: finalPayload, elementId: message.remoteMessageId)
|
|
@ -21,6 +21,7 @@
|
|||
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
@class OTROutgoingMessage, OTRBuddy, OTRAccount;
|
||||
@protocol PushControllerProtocol;
|
||||
|
||||
typedef NS_ENUM(int, OTRProtocolType) {
|
||||
OTRProtocolTypeNone = 0,
|
|
@ -30,16 +30,16 @@
|
|||
@class OTRAccount, OTRXMPPAccount, OTRBuddy, OTROutgoingMessage, PushController, OTRXMPPManager;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@interface OTRProtocolManager : NSObject
|
||||
@interface _OTRProtocolManager : NSObject
|
||||
|
||||
@property (atomic, readonly) NSUInteger numberOfConnectedProtocols;
|
||||
@property (atomic, readonly) NSUInteger numberOfConnectingProtocols;
|
||||
@property (atomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
|
||||
|
||||
@property (atomic, readonly) NSArray<id<OTRProtocol>> *allProtocols;
|
||||
|
||||
- (BOOL)existsProtocolForAccount:(OTRAccount *)account;
|
||||
- (nullable id <OTRProtocol>)protocolForAccount:(OTRAccount *)account;
|
||||
- (nullable OTRXMPPManager*)xmppManagerForAccount:(OTRAccount *)account;
|
||||
- (void)removeProtocolForAccount:(OTRAccount *)account;
|
||||
- (void)setProtocol:(id <OTRProtocol>)protocol forAccount:(OTRAccount *)account;
|
||||
|
||||
- (BOOL)isAccountConnected:(OTRAccount *)account;
|
||||
|
||||
|
@ -47,8 +47,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)loginAccount:(OTRAccount *)account userInitiated:(BOOL)userInitiated;
|
||||
- (void)loginAccounts:(NSArray<OTRAccount*> *)accounts;
|
||||
- (void)goAwayForAllAccounts;
|
||||
- (void)disconnectAllAccounts;
|
||||
- (void)disconnectAllAccountsSocketOnly:(BOOL)socketOnly timeout:(NSTimeInterval)timeout completionBlock:(nullable void (^)())completionBlock;
|
||||
|
||||
- (void)sendMessage:(OTROutgoingMessage *)message;
|
||||
|
||||
|
@ -58,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
+ (instancetype)sharedInstance; // Singleton method
|
||||
|
||||
/** Convenience for sharedInstance */
|
||||
@property (class, nonatomic, readonly) OTRProtocolManager *shared;
|
||||
//@property (class, nonatomic, readonly) OTRProtocolManager *shared;
|
||||
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -26,23 +26,24 @@
|
|||
#import "OTRIncomingMessage.h"
|
||||
#import "OTROutgoingMessage.h"
|
||||
#import "OTRConstants.h"
|
||||
#import "OTROAuthRefresher.h"
|
||||
#import "OTROAuthXMPPAccount.h"
|
||||
#import "OTRDatabaseManager.h"
|
||||
#import "OTRPushTLVHandler.h"
|
||||
#import <BBlock/NSObject+BBlock.h>
|
||||
@import YapDatabase;
|
||||
|
||||
@import KVOController;
|
||||
@import OTRAssets;
|
||||
#import "OTRLog.h"
|
||||
#import "ChatSecureCoreCompat-Swift.h"
|
||||
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||
#import "OTRXMPPPresenceSubscriptionRequest.h"
|
||||
|
||||
@interface OTRProtocolManager ()
|
||||
@property (atomic, readwrite) NSUInteger numberOfConnectedProtocols;
|
||||
@property (atomic, readwrite) NSUInteger numberOfConnectingProtocols;
|
||||
@property (nonatomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
|
||||
@interface _OTRProtocolManager ()
|
||||
//@property (atomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
|
||||
@end
|
||||
|
||||
@implementation OTRProtocolManager
|
||||
@implementation _OTRProtocolManager
|
||||
|
||||
-(instancetype)init
|
||||
{
|
||||
|
@ -50,53 +51,36 @@
|
|||
if(self)
|
||||
{
|
||||
_protocolManagers = [[NSMutableDictionary alloc] init];
|
||||
|
||||
_numberOfConnectedProtocols = 0;
|
||||
_numberOfConnectingProtocols = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<id<OTRProtocol>> *)allProtocols {
|
||||
return self.protocolManagers.allValues;
|
||||
}
|
||||
|
||||
- (void)removeProtocolForAccount:(OTRAccount *)account
|
||||
{
|
||||
NSParameterAssert(account);
|
||||
if (!account) { return; }
|
||||
id<OTRProtocol> protocol = nil;
|
||||
@synchronized (self) {
|
||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||
}
|
||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||
if (protocol && [protocol respondsToSelector:@selector(disconnect)]) {
|
||||
[protocol disconnect];
|
||||
}
|
||||
[self.KVOController unobserve:protocol];
|
||||
@synchronized (self) {
|
||||
[self.protocolManagers removeObjectForKey:account.uniqueId];
|
||||
}
|
||||
[self.protocolManagers removeObjectForKey:account.uniqueId];
|
||||
}
|
||||
|
||||
- (void)addProtocol:(id<OTRProtocol>)protocol forAccount:(OTRAccount *)account
|
||||
{
|
||||
@synchronized (self) {
|
||||
[self.protocolManagers setObject:protocol forKey:account.uniqueId];
|
||||
}
|
||||
[self.KVOController observe:protocol keyPath:NSStringFromSelector(@selector(loginStatus)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld action:@selector(protocolDidChange:)];
|
||||
[self.protocolManagers setObject:protocol forKey:account.uniqueId];
|
||||
}
|
||||
|
||||
- (BOOL)existsProtocolForAccount:(OTRAccount *)account
|
||||
{
|
||||
NSParameterAssert(account.uniqueId);
|
||||
if (!account.uniqueId) { return NO; }
|
||||
@synchronized (self) {
|
||||
return [self.protocolManagers objectForKey:account.uniqueId] != nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setProtocol:(id <OTRProtocol>)protocol forAccount:(OTRAccount *)account
|
||||
{
|
||||
NSParameterAssert(protocol);
|
||||
NSParameterAssert(account.uniqueId);
|
||||
if (!protocol || !account.uniqueId) { return; }
|
||||
[self addProtocol:protocol forAccount:account];
|
||||
return [self.protocolManagers objectForKey:account.uniqueId] != nil;
|
||||
}
|
||||
|
||||
- (id <OTRProtocol>)protocolForAccount:(OTRAccount *)account
|
||||
|
@ -104,14 +88,12 @@
|
|||
NSParameterAssert(account);
|
||||
if (!account.uniqueId) { return nil; }
|
||||
id <OTRProtocol> protocol = nil;
|
||||
@synchronized (self) {
|
||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||
if(!protocol)
|
||||
{
|
||||
protocol = [[[account protocolClass] alloc] initWithAccount:account];
|
||||
if (protocol && account.uniqueId) {
|
||||
[self addProtocol:protocol forAccount:account];
|
||||
}
|
||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||
if(!protocol)
|
||||
{
|
||||
protocol = [[[account protocolClass] alloc] initWithAccount:account];
|
||||
if (protocol && account.uniqueId) {
|
||||
[self addProtocol:protocol forAccount:account];
|
||||
}
|
||||
}
|
||||
return protocol;
|
||||
|
@ -133,7 +115,22 @@
|
|||
if (!account) { return; }
|
||||
id <OTRProtocol> protocol = [self protocolForAccount:account];
|
||||
|
||||
[protocol connectUserInitiated:userInitiated];
|
||||
if([account isKindOfClass:[OTROAuthXMPPAccount class]])
|
||||
{
|
||||
[OTROAuthRefresher refreshAccount:(OTROAuthXMPPAccount *)account completion:^(id token, NSError *error) {
|
||||
if (!error) {
|
||||
((OTROAuthXMPPAccount *)account).accountSpecificToken = token;
|
||||
[protocol connectUserInitiated:userInitiated];
|
||||
}
|
||||
else {
|
||||
DDLogError(@"Error Refreshing Token");
|
||||
}
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[protocol connectUserInitiated:userInitiated];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loginAccount:(OTRAccount *)account
|
||||
|
@ -149,89 +146,19 @@
|
|||
}
|
||||
|
||||
- (void)goAwayForAllAccounts {
|
||||
@synchronized (self) {
|
||||
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) {
|
||||
if ([protocol isKindOfClass:[OTRXMPPManager class]]) {
|
||||
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
||||
[xmpp goAway];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)disconnectAllAccountsSocketOnly:(BOOL)socketOnly timeout:(NSTimeInterval)timeout completionBlock:(nullable void (^)())completionBlock
|
||||
{
|
||||
@synchronized (self) {
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
NSMutableDictionary<NSString*, NSObject<OTRProtocol>*> *observingManagersForTokens = [NSMutableDictionary new];
|
||||
for (NSObject<OTRProtocol> *manager in self.protocolManagers.allValues) {
|
||||
OTRXMPPManager *xmpp = (OTRXMPPManager*)manager;
|
||||
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
||||
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
||||
DDLogError(@"Wrong protocol class for manager %@", manager);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (xmpp.loginStatus != OTRLoginStatusDisconnected) {
|
||||
dispatch_group_enter(group);
|
||||
NSString *token = [xmpp addObserverForKeyPath:NSStringFromSelector(@selector(loginStatus))
|
||||
options:0
|
||||
block:^(NSString *keyPath, OTRXMPPManager *mgr, NSDictionary *change) {
|
||||
if (mgr.loginStatus == OTRLoginStatusDisconnected) {
|
||||
dispatch_group_leave(group);
|
||||
}
|
||||
}];
|
||||
observingManagersForTokens[token] = manager;
|
||||
[manager disconnectSocketOnly:socketOnly];
|
||||
}
|
||||
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) {
|
||||
if ([protocol isKindOfClass:[OTRXMPPManager class]]) {
|
||||
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
||||
[xmpp goAway];
|
||||
}
|
||||
if (timeout > 0) {
|
||||
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t) (timeout * NSEC_PER_SEC)));
|
||||
}
|
||||
for (NSString *token in observingManagersForTokens.allKeys) {
|
||||
[observingManagersForTokens[token] removeObserverForToken:token];
|
||||
}
|
||||
if (completionBlock != nil) {
|
||||
completionBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)disconnectAllAccounts
|
||||
{
|
||||
[self disconnectAllAccountsSocketOnly:NO timeout:0 completionBlock:nil];
|
||||
}
|
||||
|
||||
- (void)protocolDidChange:(NSDictionary *)change
|
||||
{
|
||||
__block NSUInteger connected = 0;
|
||||
__block NSUInteger connecting = 0;
|
||||
@synchronized (self) {
|
||||
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id<OTRProtocol> _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
OTRXMPPManager *xmpp = (OTRXMPPManager*)obj;
|
||||
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
||||
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
||||
DDLogError(@"Wrong protocol class for account %@", obj);
|
||||
return;
|
||||
}
|
||||
if (xmpp.loginStatus == OTRLoginStatusAuthenticated) {
|
||||
connected++;
|
||||
} else if (xmpp.loginStatus == OTRLoginStatusConnecting) {
|
||||
connecting++;
|
||||
}
|
||||
}];
|
||||
}
|
||||
self.numberOfConnectedProtocols = connected;
|
||||
self.numberOfConnectingProtocols = connecting;
|
||||
}];
|
||||
}
|
||||
|
||||
-(BOOL)isAccountConnected:(OTRAccount *)account;
|
||||
{
|
||||
BOOL connected = NO;
|
||||
id <OTRProtocol> protocol = nil;
|
||||
@synchronized (self) {
|
||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||
}
|
||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
||||
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
||||
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
||||
|
@ -250,7 +177,7 @@
|
|||
OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction];
|
||||
account = [OTRAccount fetchObjectWithUniqueID:buddy.accountUniqueId transaction:transaction];
|
||||
} completionBlock:^{
|
||||
OTRProtocolManager * protocolManager = [OTRProtocolManager sharedInstance];
|
||||
OTRProtocolManager * protocolManager = [OTRProtocolManager shared];
|
||||
id<OTRProtocol> protocol = [protocolManager protocolForAccount:account];
|
||||
[protocol sendMessage:message];
|
||||
}];
|
||||
|
@ -264,7 +191,7 @@
|
|||
if (otrFingerprint.length == 40) {
|
||||
message = [message stringByAppendingFormat:@"\n%@", otrFingerprint];
|
||||
}
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:ADD_BUDDY_STRING() message:message preferredStyle:(UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert];
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:ADD_BUDDY_STRING() message:message preferredStyle:(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert];
|
||||
NSMutableArray<OTRAccount*> *accounts = [NSMutableArray array];
|
||||
[OTRDatabaseManager.shared.readConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
NSArray<OTRAccount*> *allAccounts = [OTRAccount allAccountsWithTransaction:transaction];
|
||||
|
@ -289,7 +216,7 @@
|
|||
title = account.username;
|
||||
}
|
||||
UIAlertAction *action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
OTRXMPPManager *manager = (OTRXMPPManager *)[[OTRProtocolManager sharedInstance] protocolForAccount:account];
|
||||
OTRXMPPManager *manager = (OTRXMPPManager *)[[OTRProtocolManager shared] protocolForAccount:account];
|
||||
|
||||
OTRXMPPBuddy *buddy = [manager addToRosterWithJID:jid displayName:nil];
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// OTRProtocolManager.swift
|
||||
// ChatSecureCore
|
||||
//
|
||||
// Created by Chris Ballinger on 1/22/18.
|
||||
// Copyright © 2018 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OTRAssets
|
||||
|
||||
@objc
|
||||
public class OTRProtocolManager: NSObject {
|
||||
private var protocols: [String:OTRProtocol] = [:]
|
||||
private var xmppManagers: [XMPPManager] {
|
||||
return protocols.values.compactMap { $0 as? XMPPManager }
|
||||
}
|
||||
|
||||
@objc
|
||||
public func existsProtocolForAccount(_ account: OTRAccount) -> Bool {
|
||||
return existsProtocol(for: account)
|
||||
}
|
||||
|
||||
public func existsProtocol(for account: OTRAccount) -> Bool {
|
||||
return protocols[account.uniqueId] != nil
|
||||
}
|
||||
|
||||
@objc
|
||||
public func protocolForAccount(_ account: OTRAccount) -> OTRProtocol? {
|
||||
return protocols[account.uniqueId]
|
||||
}
|
||||
|
||||
public func `protocol`(for account: OTRAccount) -> OTRProtocol? {
|
||||
return protocolForAccount(account)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func xmppManagerForAccount(_ account: OTRAccount) -> XMPPManager? {
|
||||
return xmppManager(for: account)
|
||||
}
|
||||
|
||||
public func xmppManager(for account: OTRAccount) -> XMPPManager? {
|
||||
return protocolForAccount(account) as? XMPPManager
|
||||
}
|
||||
|
||||
@objc
|
||||
public func removeProtocolForAccount(_ account: OTRAccount) {
|
||||
removeProtocolForAccount(account)
|
||||
}
|
||||
|
||||
public func removeProtocol(for account: OTRAccount) {
|
||||
protocols[account.uniqueId] = nil
|
||||
}
|
||||
|
||||
@objc
|
||||
public func isAccountConnected(_ account: OTRAccount) -> Bool {
|
||||
return xmppManager(for: account)?.loginStatus == .authenticated
|
||||
}
|
||||
|
||||
@objc
|
||||
public func loginAccount(_ account: OTRAccount) {
|
||||
loginAccount(account, userInitiated: false)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func loginAccount(_ account: OTRAccount, userInitiated: Bool) {
|
||||
xmppManager(for: account)?.connectUserInitiated(userInitiated)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func loginAccounts(_ accounts: [OTRAccount]) {
|
||||
accounts.forEach {
|
||||
self.loginAccount($0)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func goAwayForAllAccounts() {
|
||||
xmppManagers.forEach {
|
||||
$0.goAway()
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func sendMessage(_ message: OTROutgoingMessage) {
|
||||
send(message)
|
||||
}
|
||||
|
||||
/// This should probably be moved elsewhere
|
||||
public func send(_ message: OTROutgoingMessage) {
|
||||
let _account = OTRDatabaseManager.shared.connections?.read.fetch {
|
||||
message.buddy(with: $0)?.account(with: $0)
|
||||
}
|
||||
guard let account = _account else { return }
|
||||
xmppManager(for: account)?.send(message)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func disconnectAllAccounts() {
|
||||
disconnectAllAccountsSocketOnly(false, timeout: 0, completionBlock: nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func disconnectAllAccountsSocketOnly(_ socketOnly: Bool,
|
||||
timeout: TimeInterval,
|
||||
completionBlock: (()->Void)?) {
|
||||
let group = DispatchGroup()
|
||||
var observers: [NSKeyValueObservation] = []
|
||||
xmppManagers.forEach { (xmpp) in
|
||||
guard xmpp.loginStatus != .disconnected else {
|
||||
return
|
||||
}
|
||||
group.enter()
|
||||
let observer = xmpp.observe(\.loginStatus, changeHandler: { (xmpp, change) in
|
||||
if xmpp.loginStatus == .disconnected {
|
||||
group.leave()
|
||||
}
|
||||
})
|
||||
observers.append(observer)
|
||||
xmpp.disconnectSocketOnly(socketOnly)
|
||||
}
|
||||
group.notify(queue: .main) {
|
||||
observers.removeAll()
|
||||
completionBlock?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension OTRProtocolManager {
|
||||
#if DEBUG
|
||||
/// when OTRBranding.pushStagingAPIURL is nil (during tests) a valid value must be supplied for the integration tests to pass
|
||||
private static let pushApiEndpoint: URL = OTRBranding.pushStagingAPIURL ?? URL(string: "http://localhost")!
|
||||
#else
|
||||
private static let pushApiEndpoint: URL = OTRBranding.pushAPIURL
|
||||
#endif
|
||||
|
||||
@objc public static let encryptionManager = OTREncryptionManager()
|
||||
@objc public static let shared = OTRProtocolManager()
|
||||
@objc public static func sharedInstance() -> OTRProtocolManager {
|
||||
return OTRProtocolManager.shared
|
||||
}
|
||||
|
||||
@objc public static let pushController = PushController(baseURL: OTRProtocolManager.pushApiEndpoint, sessionConfiguration: URLSessionConfiguration.ephemeral)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// OTRPurchaseController.h
|
||||
// Off the Record
|
||||
//
|
||||
// Created by Christopher Ballinger on 9/28/12.
|
||||
// Copyright (c) 2012 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
// This file is part of ChatSecure.
|
||||
//
|
||||
// ChatSecure is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ChatSecure is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
#import "OTRStoreTableViewCell.h"
|
||||
|
||||
#define kOTRPurchaseControllerProductUpdateNotification @"kOTRPurchaseControllerProductUpdateNotification"
|
||||
|
||||
@protocol OTRPurchaseControllerDelegate <NSObject>
|
||||
@required
|
||||
- (void) productsUpdated:(NSArray*)products;
|
||||
@end
|
||||
|
||||
@interface OTRPurchaseController : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
|
||||
|
||||
@property (nonatomic, weak) id<OTRPurchaseControllerDelegate> delegate;
|
||||
@property (nonatomic, strong) NSArray *products;
|
||||
|
||||
- (void) requestProducts;
|
||||
- (void) buyProduct:(SKProduct *)product;
|
||||
- (void) restorePurchases;
|
||||
|
||||
- (BOOL) isProductIdentifierPurchased:(NSString*)productIdentifier;
|
||||
- (void) setProductIdentifier:(NSString*)productIdentifier purchased:(BOOL)purchased;
|
||||
|
||||
+ (OTRPurchaseController*) sharedInstance;
|
||||
|
||||
@end
|
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// OTRPurchaseController.m
|
||||
// Off the Record
|
||||
//
|
||||
// Created by Christopher Ballinger on 9/28/12.
|
||||
// Copyright (c) 2012 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
// This file is part of ChatSecure.
|
||||
//
|
||||
// ChatSecure is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ChatSecure is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#import "OTRPurchaseController.h"
|
||||
#import "AFNetworking.h"
|
||||
#import "Strings.h"
|
||||
#import "OTRPushAPIClient.h"
|
||||
|
||||
#define REQUEST_PRODUCT_IDENTIFIERS @"request_product_identifiers"
|
||||
#define PRODUCT_IDENTIFIERS_KEY @"identifiers"
|
||||
|
||||
#define PRODUCTS_KEY @"products"
|
||||
|
||||
@implementation OTRPurchaseController
|
||||
@synthesize products;
|
||||
|
||||
- (void) dealloc {
|
||||
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
|
||||
}
|
||||
|
||||
- (id) init {
|
||||
if (self = [super init]) {
|
||||
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (OTRPurchaseController*)sharedInstance
|
||||
{
|
||||
static dispatch_once_t once;
|
||||
static OTRPurchaseController *sharedInstance;
|
||||
dispatch_once(&once, ^{
|
||||
sharedInstance = [[OTRPurchaseController alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (void) requestProducts {
|
||||
if (products) {
|
||||
[self.delegate productsUpdated:products];
|
||||
} else {
|
||||
[self requestProductIdentifiers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) requestProductIdentifiers {
|
||||
// Code to request product identifiers here
|
||||
NSURL *requestURL = [NSURL URLWithString:REQUEST_PRODUCT_IDENTIFIERS relativeToURL:[OTRPushAPIClient sharedClient].baseURL];
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
[manager GET:requestURL.absoluteString parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
|
||||
[self fetchProductsWithIdentifiers:[NSSet setWithArray:[responseObject objectForKey:PRODUCT_IDENTIFIERS_KEY]]];
|
||||
} failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
NSLog(@"Error loading product identifiers: %@%@", [error localizedDescription], [error userInfo]);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) fetchProductsWithIdentifiers:(NSSet*)identifiers {
|
||||
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers];
|
||||
request.delegate = self;
|
||||
[request start];
|
||||
}
|
||||
|
||||
|
||||
- (void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
|
||||
if ([response.invalidProductIdentifiers count] > 0) {
|
||||
NSLog(@"Invalid products identifiers: %@", [response.invalidProductIdentifiers description]);
|
||||
}
|
||||
self.products = response.products;
|
||||
[self.delegate productsUpdated:products];
|
||||
}
|
||||
|
||||
- (void) buyProduct:(SKProduct *)product {
|
||||
if ([SKPaymentQueue canMakePayments]) {
|
||||
SKPayment *payment = [SKPayment paymentWithProduct:product];
|
||||
[[SKPaymentQueue defaultQueue] addPayment:payment];
|
||||
} else {
|
||||
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:ERROR_STRING message:PAYMENTS_SETUP_ERROR_STRING delegate:nil cancelButtonTitle:nil otherButtonTitles:OK_STRING, nil];
|
||||
[alert show];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
|
||||
for (SKPaymentTransaction *transaction in transactions) {
|
||||
NSLog(@"Transaction: %@", transaction.transactionIdentifier);
|
||||
switch (transaction.transactionState) {
|
||||
case SKPaymentTransactionStatePurchased:
|
||||
NSLog(@"Transaction purchased");
|
||||
//[[OTRPushController sharedInstance] registerWithReceipt:transaction.transactionReceipt resetAccount:NO];
|
||||
[self setProductIdentifier:transaction.payment.productIdentifier purchased:YES];
|
||||
[self sendProductUpdateNotification];
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
|
||||
break;
|
||||
case SKPaymentTransactionStateFailed:
|
||||
NSLog(@"Transaction failed");
|
||||
[self sendProductUpdateNotification];
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
|
||||
break;
|
||||
case SKPaymentTransactionStateRestored:
|
||||
NSLog(@"Original transaction restored: %@", transaction.originalTransaction.transactionIdentifier);
|
||||
//[[OTRPushController sharedInstance] registerWithReceipt:transaction.transactionReceipt resetAccount:YES];
|
||||
[self setProductIdentifier:transaction.payment.productIdentifier purchased:YES];
|
||||
[self sendProductUpdateNotification];
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
|
||||
NSLog(@"Transaction restored");
|
||||
break;
|
||||
case SKPaymentTransactionStatePurchasing:
|
||||
NSLog(@"Purchasing transaction... ");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) sendProductUpdateNotification {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kOTRPurchaseControllerProductUpdateNotification object:self];
|
||||
}
|
||||
|
||||
- (BOOL) isProductIdentifierPurchased:(NSString*)productIdentifier {
|
||||
NSMutableDictionary *productsDictionary = [self productsDictionary];
|
||||
NSNumber *productValue = [productsDictionary objectForKey:productIdentifier];
|
||||
if (!productValue) {
|
||||
return NO;
|
||||
}
|
||||
return [productValue boolValue];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary*) productsDictionary {
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableDictionary *productDictionary = [NSMutableDictionary dictionaryWithDictionary:[defaults objectForKey:PRODUCTS_KEY]];
|
||||
if (!productDictionary) {
|
||||
productDictionary = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return productDictionary;
|
||||
}
|
||||
|
||||
- (void) saveProductsDictionary:(NSMutableDictionary*)productsDictionary {
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
[defaults setObject:productsDictionary forKey:PRODUCTS_KEY];
|
||||
BOOL success = [defaults synchronize];
|
||||
if (!success) {
|
||||
NSLog(@"Product preferences not saved to disk!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void) setProductIdentifier:(NSString*)productIdentifier purchased:(BOOL)purchased {
|
||||
NSMutableDictionary *productsDictionary = [self productsDictionary];
|
||||
[productsDictionary setObject:[NSNumber numberWithBool:purchased] forKey:productIdentifier];
|
||||
[self saveProductsDictionary:productsDictionary];
|
||||
}
|
||||
|
||||
- (void) restorePurchases {
|
||||
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// OTRPushTLVHandler.h
|
||||
// ChatSecure
|
||||
//
|
||||
// Created by David Chiles on 9/28/15.
|
||||
// Copyright © 2015 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
@import Foundation;
|
||||
@import OTRKit;
|
||||
#import "OTRPushTLVHandlerProtocols.h"
|
||||
|
||||
@interface OTRPushTLVHandler : NSObject <OTRTLVHandler, OTRPushTLVHandlerProtocol>
|
||||
|
||||
@property (nonatomic, weak, readwrite) id<OTRPushTLVHandlerDelegate> delegate;
|
||||
@property (nonatomic, weak, readwrite) OTRKit *otrKit;
|
||||
|
||||
- (instancetype)initWithOTRKit:(OTRKit *)otrKit delegate:(id<OTRPushTLVHandlerDelegate>)delegate;
|
||||
|
||||
@end
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// OTRPushTLVHandler.m
|
||||
// ChatSecure
|
||||
//
|
||||
// Created by David Chiles on 9/28/15.
|
||||
// Copyright © 2015 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OTRPushTLVHandler.h"
|
||||
@import OTRKit;
|
||||
|
||||
static const uint16_t OTRPushTLVType = 0x01A4;
|
||||
|
||||
@implementation OTRPushTLVHandler
|
||||
|
||||
- (instancetype)initWithOTRKit:(OTRKit *)otrKit delegate:(id<OTRPushTLVHandlerDelegate>)delegate;
|
||||
{
|
||||
if (self = [self init]) {
|
||||
self.otrKit = otrKit;
|
||||
self.delegate = delegate;
|
||||
[self.otrKit registerTLVHandler:self];
|
||||
}
|
||||
return self;
|
||||
|
||||
}
|
||||
|
||||
- (NSArray *)handledTLVTypes
|
||||
{
|
||||
return @[@(OTRPushTLVType)];
|
||||
}
|
||||
|
||||
- (void)receiveTLV:(OTRTLV *)tlv username:(NSString *)username accountName:(NSString *)accountName protocol:(NSString *)protocol fingerprint:(OTRFingerprint *)fingerprint tag:(id)tag
|
||||
{
|
||||
[self.delegate receivePushData:tlv.data username:username accountName:accountName protocolString:protocol fingerprint:fingerprint];
|
||||
}
|
||||
|
||||
- (void)sendPushData:(NSData *)data username:(NSString *)username accountName:(NSString *)accountName protocol:(NSString *)protocol
|
||||
{
|
||||
OTRTLV *tlv = [[OTRTLV alloc] initWithType:OTRPushTLVType data:data];
|
||||
[self.otrKit encodeMessage:nil tlvs:@[tlv] username:username accountName:accountName protocol:protocol tag:nil];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// OTRPushTLVHandlerDelegateProtocol.h
|
||||
// ChatSecure
|
||||
//
|
||||
// Created by David Chiles on 9/28/15.
|
||||
// Copyright © 2015 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
@class OTRFingerprint;
|
||||
@protocol OTRPushTLVHandlerDelegate
|
||||
|
||||
@required
|
||||
- (void)receivePushData:(NSData *)tlvData
|
||||
username:(NSString *)username
|
||||
accountName:(NSString *)accountName
|
||||
protocolString:(NSString *)protocolString
|
||||
fingerprint:(OTRFingerprint *)fingerprint;
|
||||
|
||||
@end
|
||||
|
||||
@protocol OTRPushTLVHandlerProtocol
|
||||
|
||||
@required
|
||||
- (void)sendPushData:(NSData *)data
|
||||
username:(NSString *)username
|
||||
accountName:(NSString *)accountName
|
||||
protocol:(NSString *)protocol;
|
||||
|
||||
@end
|
|
@ -36,7 +36,7 @@
|
|||
#import "OTRIntSetting.h"
|
||||
#import "OTRCertificateSetting.h"
|
||||
#import "OTRUtilities.h"
|
||||
#import "ChatSecureCoreCompat-Swift.h"
|
||||
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||
|
||||
#import "OTRUtilities.h"
|
||||
|
||||
|
@ -64,11 +64,11 @@
|
|||
[settingsGroups addObject:accountsGroup];
|
||||
|
||||
if (OTRBranding.allowsDonation) {
|
||||
NSString *donateTitle = nil;
|
||||
NSString *donateTitle = DONATE_STRING();
|
||||
if (TransactionObserver.hasValidReceipt) {
|
||||
donateTitle = [NSString stringWithFormat:@"%@ ✅", DONATE_STRING()];
|
||||
} else {
|
||||
donateTitle = [NSString stringWithFormat:@"%@ 🎁", DONATE_STRING()];
|
||||
donateTitle = [NSString stringWithFormat:@"%@ 🆕", DONATE_STRING()];
|
||||
}
|
||||
OTRDonateSetting *donateSetting = [[OTRDonateSetting alloc] initWithTitle:donateTitle description:nil];
|
||||
//donateSetting.imageName = @"29-heart.png";
|
||||
|
@ -101,18 +101,16 @@
|
|||
description:ALLOW_DB_PASSPHRASE_BACKUP_DESCRIPTION_STRING()
|
||||
settingsKey:kOTRSettingKeyAllowDBPassphraseBackup];
|
||||
|
||||
if ([PushController getPushPreference] != PushPreferenceEnabled) {
|
||||
if (![PushController canReceivePushNotifications] ||
|
||||
[PushController getPushPreference] != PushPreferenceEnabled) {
|
||||
OTRViewSetting *pushViewSetting = [[OTRViewSetting alloc] initWithTitle:CHATSECURE_PUSH_STRING() description:nil viewControllerClass:[EnablePushViewController class]];
|
||||
pushViewSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
OTRSettingsGroup *pushGroup = [[OTRSettingsGroup alloc] initWithTitle:PUSH_TITLE_STRING() settings:@[pushViewSetting]];
|
||||
[settingsGroups addObject:pushGroup];
|
||||
}
|
||||
|
||||
OTRStorageUsageSetting *storageUsageSetting = [[OTRStorageUsageSetting alloc] initWithTitle:STORAGE_USAGE_TITLE()
|
||||
description:STORAGE_USAGE_DESCRIPTION()];
|
||||
storageUsageSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
|
||||
NSArray *chatSettings = @[deletedDisconnectedConversations, storageUsageSetting];
|
||||
NSArray *chatSettings = @[deletedDisconnectedConversations];
|
||||
OTRSettingsGroup *chatSettingsGroup = [[OTRSettingsGroup alloc] initWithTitle:CHAT_STRING() settings:chatSettings];
|
||||
[settingsGroups addObject:chatSettingsGroup];
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// OTROTRSignalEncryptionHelper.swift
|
||||
// ChatSecure
|
||||
//
|
||||
// Created by David Chiles on 10/3/16.
|
||||
// Copyright © 2016 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import OTRKit
|
||||
|
||||
class OTRSignalEncryptionHelper {
|
||||
|
||||
/**
|
||||
Encrypt data with IV and key using aes-128-gcm
|
||||
|
||||
- parameter data: The data to be encrypted.
|
||||
- parameter key The symmetric key
|
||||
- parameter iv The initialization vector
|
||||
|
||||
returns: The encrypted data
|
||||
*/
|
||||
class func encryptData(_ data:Data, key:Data, iv:Data) throws -> OTRCryptoData? {
|
||||
return try OTRCryptoUtility.encryptAESGCMData(data, key: key, iv: iv)
|
||||
}
|
||||
|
||||
/**
|
||||
Decrypt data with IV and key using aes-128-gcm
|
||||
|
||||
- parameter data: The data to be decrypted.
|
||||
- parameter key The symmetric key
|
||||
- parameter iv The initialization vector
|
||||
|
||||
returns: The Decrypted data
|
||||
*/
|
||||
class func decryptData(_ data:Data, key:Data, iv:Data, authTag:Data) throws -> Data? {
|
||||
let cryptoData = OTRCryptoData(data: data, authTag: authTag)
|
||||
return try OTRCryptoUtility.decryptAESGCMData(cryptoData, key: key, iv: iv)
|
||||
}
|
||||
|
||||
/** Generates random data of length 16 bytes */
|
||||
fileprivate class func randomDataOfBlockLength() -> Data? {
|
||||
return OTRPasswordGenerator.randomData(withLength: 16)
|
||||
}
|
||||
|
||||
/** Generates random key of length 16 bytes*/
|
||||
class func generateSymmetricKey() -> Data? {
|
||||
return self.randomDataOfBlockLength()
|
||||
}
|
||||
/** Generates random iv of length 16 bytes */
|
||||
class func generateIV() -> Data? {
|
||||
return self.randomDataOfBlockLength()
|
||||
}
|
||||
|
||||
}
|
|
@ -101,7 +101,7 @@ extension OMEMOBundle {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol OTRSignalStorageManagerDelegate: AnyObject {
|
||||
public protocol OTRSignalStorageManagerDelegate: class {
|
||||
/** Generate a new account key*/
|
||||
func generateNewIdenityKeyPairForAccountKey(_ accountKey:String) -> OTRAccountSignalIdentity
|
||||
}
|
||||
|
@ -110,8 +110,8 @@ public protocol OTRSignalStorageManagerDelegate: AnyObject {
|
|||
* This class implements the SignalStore protocol. One OTRSignalStorageManager should be created per account key/collection.
|
||||
*/
|
||||
open class OTRSignalStorageManager: NSObject {
|
||||
public let accountKey:String
|
||||
public let databaseConnection:YapDatabaseConnection
|
||||
open let accountKey:String
|
||||
open let databaseConnection:YapDatabaseConnection
|
||||
open weak var delegate:OTRSignalStorageManagerDelegate?
|
||||
|
||||
/**
|
||||
|
@ -232,7 +232,7 @@ open class OTRSignalStorageManager: NSObject {
|
|||
}
|
||||
|
||||
let query = YapDatabaseQuery(string: "WHERE (OTRYapDatabaseSignalPreKeyAccountKeySecondaryIndexColumnName) = ?", parameters: ["\(self.accountKey)"])
|
||||
let _ = secondaryIndexTransaction.iterateKeysAndObjects(matching: query, using: { (collection, key, object, stop) in
|
||||
secondaryIndexTransaction.enumerateKeysAndObjects(matching: query, using: { (collection, key, object, stop) in
|
||||
guard let preKey = object as? OTRSignalPreKey else {
|
||||
return
|
||||
}
|
|
@ -7,14 +7,12 @@
|
|||
//
|
||||
|
||||
@import Foundation;
|
||||
@class CPAProxyManager;
|
||||
@import CPAProxy;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@interface OTRTorManager : NSObject
|
||||
|
||||
@property (nonatomic, strong, nullable) CPAProxyManager *torManager;
|
||||
@property (nonatomic, strong) CPAProxyManager *torManager;
|
||||
|
||||
+ (instancetype) sharedInstance;
|
||||
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// OTRTorManager.m
|
||||
// ChatSecure
|
||||
//
|
||||
// Created by Christopher Ballinger on 10/3/14.
|
||||
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OTRTorManager.h"
|
||||
|
||||
@import CPAProxy;
|
||||
|
||||
@implementation OTRTorManager
|
||||
|
||||
- (instancetype) init {
|
||||
if (self = [super init]) {
|
||||
// Get resource paths for the torrc and geoip files from the main bundle
|
||||
NSBundle *cpaProxyFrameworkBundle = [NSBundle bundleForClass:[CPAProxyManager class]];
|
||||
NSURL *cpaProxyBundleURL = [cpaProxyFrameworkBundle URLForResource:@"CPAProxy" withExtension:@"bundle"];
|
||||
NSBundle *cpaProxyBundle = [[NSBundle alloc] initWithURL:cpaProxyBundleURL];
|
||||
NSParameterAssert(cpaProxyBundle != nil);
|
||||
|
||||
NSString *torrcPath = [[NSBundle mainBundle] pathForResource:@"torrc" ofType:nil]; // use custom torrc
|
||||
NSString *geoipPath = [cpaProxyBundle pathForResource:@"geoip" ofType:nil];
|
||||
NSString *dataDirectory = [[[[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"com.ChatSecure.Tor"] path];
|
||||
|
||||
// Initialize a CPAProxyManager
|
||||
CPAConfiguration *configuration = [CPAConfiguration configurationWithTorrcPath:torrcPath geoipPath:geoipPath torDataDirectoryPath:dataDirectory];
|
||||
configuration.isolateDestinationAddress = YES;
|
||||
configuration.isolateDestinationPort = YES;
|
||||
self.torManager = [CPAProxyManager proxyWithConfiguration:configuration];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma - mark Singleton Methodd
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static id _sharedInstance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_sharedInstance = [[self alloc] init];
|
||||
});
|
||||
|
||||
return _sharedInstance;
|
||||
}
|
||||
|
||||
@end
|
|
@ -12,7 +12,7 @@ import XMPPFramework
|
|||
@objc open class OTRXMPPChangeAvatar: NSObject {
|
||||
|
||||
open weak var xmppvCardTempModule:XMPPvCardTempModule?
|
||||
public let photoData:Data
|
||||
open let photoData:Data
|
||||
fileprivate let workQueue = DispatchQueue(label: "OTRXMPPChangeAvatar-workQueue", attributes: [])
|
||||
|
||||
fileprivate var waitingForVCardFetch:Bool = false
|
|
@ -7,7 +7,8 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import YapDatabase
|
||||
import YapDatabase.YapDatabaseFullTextSearch
|
||||
import YapDatabase.YapDatabaseSearchResultsView
|
||||
|
||||
open class OTRYapExtensions:NSObject {
|
||||
|
||||
|
@ -17,7 +18,7 @@ open class OTRYapExtensions:NSObject {
|
|||
let usernameColumnName = BuddyFTSColumnName.username.name()
|
||||
let displayNameColumnName = BuddyFTSColumnName.displayName.name()
|
||||
|
||||
let searchHandler = YapDatabaseFullTextSearchHandler.withObjectBlock { (transaction, dict, collection, key, object) in
|
||||
let searchHandler = YapDatabaseFullTextSearchHandler.withObjectBlock { (dict, collection, key, object) in
|
||||
guard let buddy = object as? OTRBuddy else {
|
||||
return
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import YapDatabase
|
||||
import YapDatabase.YapDatabaseView
|
||||
|
||||
@objc public protocol OTRYapViewHandlerDelegateProtocol:NSObjectProtocol {
|
||||
|
|
@ -10,13 +10,12 @@ import Foundation
|
|||
import ChatSecure_Push_iOS
|
||||
import YapDatabase
|
||||
import UserNotifications
|
||||
import OTRKit
|
||||
|
||||
@objc public protocol PushControllerProtocol {
|
||||
|
||||
func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void)
|
||||
func receiveRemoteNotification(_ notification:[AnyHashable: Any], completion: @escaping (_ buddy:OTRBuddy?, _ error:NSError?) -> Void)
|
||||
func pushStorage() -> PushStorageProtocol?
|
||||
func pushStorage() -> PushStorageProtocol
|
||||
}
|
||||
|
||||
@objc public enum PushPreference: Int {
|
||||
|
@ -69,38 +68,33 @@ public class PushInfo: NSObject {
|
|||
The purpose of this class is to tie together the api client and the data store, YapDatabase.
|
||||
It also provides some helper methods that makes dealing with the api easier
|
||||
*/
|
||||
open class PushController: NSObject, PushControllerProtocol {
|
||||
open class PushController: NSObject, OTRPushTLVHandlerDelegate, PushControllerProtocol {
|
||||
|
||||
private var _storage: PushStorageProtocol?
|
||||
private var storage: PushStorageProtocol? {
|
||||
if _storage == nil,
|
||||
let write = connections?.write {
|
||||
let storage = PushStorage(databaseConnection: write)
|
||||
finishStorageSetup(storage: storage)
|
||||
_storage = storage
|
||||
}
|
||||
return _storage
|
||||
}
|
||||
|
||||
|
||||
let storage: PushStorageProtocol
|
||||
var apiClient : Client
|
||||
var callbackQueue = OperationQueue()
|
||||
var otrListener: PushOTRListener?
|
||||
let timeBufffer:TimeInterval = 60*60*24
|
||||
var pubsubEndpoint: NSString?
|
||||
private var connections: DatabaseConnections? {
|
||||
return OTRDatabaseManager.shared.connections
|
||||
}
|
||||
|
||||
@objc public init(baseURL: URL, sessionConfiguration: URLSessionConfiguration, databaseConnection: YapDatabaseConnection? = nil) {
|
||||
@objc public init(baseURL: URL, sessionConfiguration: URLSessionConfiguration, databaseConnection: YapDatabaseConnection? = nil, tlvHandler:OTRPushTLVHandlerProtocol? = nil) {
|
||||
let databaseConnection = databaseConnection ?? OTRDatabaseManager.shared.writeConnection!
|
||||
|
||||
self.apiClient = Client(baseUrl: baseURL, urlSessionConfiguration: sessionConfiguration, account: nil)
|
||||
self.storage = PushStorage(databaseConnection: databaseConnection)
|
||||
super.init()
|
||||
}
|
||||
|
||||
private func finishStorageSetup(storage: PushStorageProtocol) {
|
||||
// We need to make sure we aren't doing a blocking read on a read/write connection
|
||||
// because this causes long pauses on app launch
|
||||
let readConnection = OTRDatabaseManager.shared.database?.newConnection()
|
||||
var account: Account? = nil;
|
||||
connections?.read.asyncRead({ (transaction) in
|
||||
account = storage.thisDevicePushAccount()
|
||||
readConnection?.asyncRead({ (transaction) in
|
||||
account = self.storage.thisDevicePushAccount()
|
||||
}, completionBlock: {
|
||||
self.apiClient.account = account
|
||||
storage.removeAllOurExpiredUnusedTokens(self.timeBufffer, completion: nil)
|
||||
self.otrListener = PushOTRListener(storage: self.storage, pushController: self, tlvHandler: tlvHandler)
|
||||
self.storage.removeAllOurExpiredUnusedTokens(self.timeBufffer, completion: nil)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -108,7 +102,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
public func deactivate(completion: (()->())?, callbackQueue: DispatchQueue?) {
|
||||
apiClient.unregister { (success, error) in
|
||||
PushController.setPushPreference(.disabled)
|
||||
self.storage?.deleteEverything(completion: completion, callbackQueue: callbackQueue)
|
||||
self.storage.deleteEverything(completion: completion, callbackQueue: callbackQueue)
|
||||
self.apiClient.account = nil
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +141,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
self.apiClient.registerNewUser(username, password: password, email: nil) {[weak self] (account, error) -> Void in
|
||||
if let newAccount = account {
|
||||
self?.apiClient.account = newAccount
|
||||
self?.storage?.saveThisAccount(newAccount)
|
||||
self?.storage.saveThisAccount(newAccount)
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
completion(true, nil)
|
||||
})
|
||||
|
@ -164,14 +158,14 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
|
||||
- returns: The push storage object that controls storing and retrieving push tokens
|
||||
*/
|
||||
@objc open func pushStorage() -> PushStorageProtocol? {
|
||||
@objc open func pushStorage() -> PushStorageProtocol {
|
||||
return self.storage
|
||||
}
|
||||
|
||||
@objc open func registerThisDevice(_ apns:String, completion:@escaping (_ success: Bool, _ error: Error?) -> Void) {
|
||||
self.apiClient.registerDevice(apns, name: nil, deviceID: nil) {[weak self] (device, error) -> Void in
|
||||
if let newDevice = device {
|
||||
self?.storage?.saveThisDevice(newDevice)
|
||||
self?.storage.saveThisDevice(newDevice)
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
completion(true, nil)
|
||||
})
|
||||
|
@ -187,7 +181,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
|
||||
@objc open func updateThisDevice(_ apns:String, completion:@escaping (_ success: Bool, _ error: Error?) -> Void) {
|
||||
DispatchQueue.global().async {[weak self] () -> Void in
|
||||
guard let device = self?.storage?.thisDevice() else {
|
||||
guard let device = self?.storage.thisDevice() else {
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
|
||||
completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil))
|
||||
|
@ -204,7 +198,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
|
||||
self?.apiClient.updateDevice(id, APNSToken: apns, name: device.name, deviceID: device.id, completion: {[weak self] (device, error) -> Void in
|
||||
if let newDevice = device {
|
||||
self?.storage?.saveThisDevice(newDevice)
|
||||
self?.storage.saveThisDevice(newDevice)
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
completion(true, nil)
|
||||
})
|
||||
|
@ -240,7 +234,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
|
||||
@objc open func getNewPushToken(_ buddyKey:String?, completion:@escaping (_ token:TokenContainer?,_ error:NSError?) -> Void) {
|
||||
DispatchQueue.global().async {[weak self] () -> Void in
|
||||
guard let tokenContainer = self?.storage?.unusedToken() else {
|
||||
guard let tokenContainer = self?.storage.unusedToken() else {
|
||||
self?.updateUnusedTokenStore({[weak self] (success, error) -> Void in
|
||||
if success {
|
||||
self?.getNewPushToken(buddyKey, completion: completion)
|
||||
|
@ -253,11 +247,11 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
return
|
||||
}
|
||||
|
||||
self?.storage?.removeUnusedToken(tokenContainer)
|
||||
self?.storage.removeUnusedToken(tokenContainer)
|
||||
if let buddyKey = buddyKey {
|
||||
self?.storage?.associateBuddy(tokenContainer, buddyKey: buddyKey)
|
||||
self?.storage.associateBuddy(tokenContainer, buddyKey: buddyKey)
|
||||
} else {
|
||||
self?.storage?.saveUsedToken(tokenContainer)
|
||||
self?.storage.saveUsedToken(tokenContainer)
|
||||
}
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
completion(tokenContainer, nil)
|
||||
|
@ -276,7 +270,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
return
|
||||
}
|
||||
tokenContainer.pushToken = newToken
|
||||
self?.storage?.saveUnusedToken(tokenContainer)
|
||||
self?.storage.saveUnusedToken(tokenContainer)
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
completion(true,nil)
|
||||
})
|
||||
|
@ -299,7 +293,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
@objc open func updateUnusedTokenStore(_ completion:@escaping (_ success:Bool,_ error:Error?) -> Void) {
|
||||
|
||||
DispatchQueue.global().async {[weak self] () -> Void in
|
||||
guard let id = self?.storage?.thisDevice()?.id else {
|
||||
guard let id = self?.storage.thisDevice()?.id else {
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil))
|
||||
})
|
||||
|
@ -308,11 +302,11 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
|
||||
var tokensToCreate:UInt = 0
|
||||
|
||||
guard let unusedTokens = self?.storage?.numberUnusedTokens() else {
|
||||
guard let unusedTokens = self?.storage.numberUnusedTokens() else {
|
||||
return;
|
||||
}
|
||||
|
||||
guard let minimumCount = self?.storage?.unusedTokenStoreMinimum() else {
|
||||
guard let minimumCount = self?.storage.unusedTokenStoreMinimum() else {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -369,7 +363,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
tokenContainer.pushToken = token
|
||||
tokenContainer.endpoint = endpointURL
|
||||
tokenContainer.buddyKey = buddyKey
|
||||
self?.storage?.saveUsedToken(tokenContainer)
|
||||
self?.storage.saveUsedToken(tokenContainer)
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
completion(true, nil)
|
||||
})
|
||||
|
@ -412,7 +406,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
public func receiveRemoteNotification(_ notification: [AnyHashable : Any], completion: @escaping (OTRBuddy?, NSError?) -> Void) {
|
||||
do {
|
||||
let message = try Deserializer.messageFromPushDictionary(notification)
|
||||
guard let buddy = self.storage?.buddy(message.token) else {
|
||||
guard let buddy = self.storage.buddy(message.token) else {
|
||||
self.callbackQueue.addOperation({ () -> Void in
|
||||
completion(nil, NSError.chatSecureError(PushError.noBuddyFound, userInfo: nil))
|
||||
})
|
||||
|
@ -437,7 +431,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
@objc open func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void) {
|
||||
DispatchQueue.global().async {[weak self] () -> Void in
|
||||
do {
|
||||
guard let token = try self?.storage?.tokensForBuddy(buddyKey, createdByThisAccount: false).first else {
|
||||
guard let token = try self?.storage.tokensForBuddy(buddyKey, createdByThisAccount: false).first else {
|
||||
self?.callbackQueue.addOperation({ () -> Void in
|
||||
completion(false, NSError.chatSecureError(PushError.noTokensFound, userInfo: nil))
|
||||
})
|
||||
|
@ -464,7 +458,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
|
||||
if ((error as NSError?)?.code == 404) {
|
||||
// Token was revoked or was never valid.
|
||||
self?.storage?.removeToken(token)
|
||||
self?.storage.removeToken(token)
|
||||
// Retry and see if we have another token to use or will error out with noTokensFound
|
||||
self?.sendKnock(buddyKey, completion: completion)
|
||||
}
|
||||
|
@ -547,7 +541,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
//MARK: OTRPushTLVHandlerDelegate
|
||||
@objc open func receivePush(_ tlvData: Data!, username: String!, accountName: String!, protocolString: String!, fingerprint:OTRFingerprint!) {
|
||||
|
||||
let buddy = self.storage?.buddy(username, accountName: accountName)
|
||||
let buddy = self.storage.buddy(username, accountName: accountName)
|
||||
|
||||
guard let buddyKey = buddy?.uniqueId else {
|
||||
//Error fetching buddy
|
||||
|
@ -566,7 +560,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
}
|
||||
|
||||
// Don't store tokens for Tor accounts
|
||||
let account = self.storage?.account(buddy!.accountUniqueId)
|
||||
let account = self.storage.account(buddy!.accountUniqueId)
|
||||
if account?.accountType == OTRAccountType.xmppTor {
|
||||
return
|
||||
}
|
||||
|
@ -584,7 +578,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
|
||||
//MARK: Push Preferences
|
||||
|
||||
@objc public static func getPushPreference() -> PushPreference {
|
||||
@objc open static func getPushPreference() -> PushPreference {
|
||||
guard let value = UserDefaults.standard.object(forKey: kOTRPushEnabledKey) as? NSNumber else {
|
||||
return .undefined
|
||||
}
|
||||
|
@ -595,7 +589,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
public static func setPushPreference(_ preference: PushPreference) {
|
||||
open static func setPushPreference(_ preference: PushPreference) {
|
||||
var bool = false
|
||||
if preference == .enabled {
|
||||
bool = true
|
||||
|
@ -607,79 +601,79 @@ open class PushController: NSObject, PushControllerProtocol {
|
|||
//MARK: Utility
|
||||
|
||||
/// If callbackQueue is nil, it will complete on main queue
|
||||
public func gatherPushInfo(completion: @escaping (PushInfo?) -> (), callbackQueue: DispatchQueue = DispatchQueue.main) {
|
||||
guard let storage = self.storage else {
|
||||
callbackQueue.async {
|
||||
completion(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
public func gatherPushInfo(completion: @escaping (PushInfo) -> (), callbackQueue: DispatchQueue?) {
|
||||
var pubsubEndpoint: String?
|
||||
var pushPermitted = false
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
DispatchQueue.main.async {
|
||||
PushController.canReceivePushNotifications(completion: { (enabled) in
|
||||
pushPermitted = enabled
|
||||
group.leave()
|
||||
})
|
||||
}
|
||||
pushPermitted = PushController.canReceivePushNotifications() // This will be async in a later version when we do iOS 10 refactor
|
||||
group.enter()
|
||||
group.leave()
|
||||
getPubsubEndpoint { (endpoint, error) in
|
||||
pubsubEndpoint = endpoint
|
||||
group.leave()
|
||||
}
|
||||
group.notify(queue: .main) {
|
||||
let device = storage.thisDevice()
|
||||
let queue = callbackQueue ?? DispatchQueue.main
|
||||
group.notify(queue: DispatchQueue.global(qos: .default)) {
|
||||
let device = self.storage.thisDevice()
|
||||
let newPushInfo = PushInfo(
|
||||
pushAPIURL: self.apiClient.baseUrl,
|
||||
hasPushAccount: storage.hasPushAccount(),
|
||||
numUsedTokens: storage.numberUsedTokens(),
|
||||
numUnusedTokens: storage.numberUnusedTokens(),
|
||||
hasPushAccount: self.storage.hasPushAccount(),
|
||||
numUsedTokens: self.storage.numberUsedTokens(),
|
||||
numUnusedTokens: self.storage.numberUnusedTokens(),
|
||||
pushPermitted: pushPermitted,
|
||||
pubsubEndpoint: pubsubEndpoint,
|
||||
device: device)
|
||||
callbackQueue.async {
|
||||
queue.async {
|
||||
completion(newPushInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public static func registerForPushNotifications() {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { (granted, error) in
|
||||
DispatchQueue.main.async(execute: {
|
||||
// TODO: Handle push registration error
|
||||
let app = UIApplication.shared
|
||||
NotificationCenter.default.post(name: Notification.Name(rawValue: OTRUserNotificationsChanged), object: app.delegate, userInfo:nil)
|
||||
@objc open static func registerForPushNotifications() {
|
||||
if #available(iOS 10.0, *) {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { (granted, error) in
|
||||
DispatchQueue.main.async(execute: {
|
||||
// TODO: Handle push registration error
|
||||
let app = UIApplication.shared
|
||||
NotificationCenter.default.post(name: Notification.Name(rawValue: OTRUserNotificationsChanged), object: app.delegate, userInfo:nil)
|
||||
if (granted) {
|
||||
app.registerForRemoteNotifications()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
}
|
||||
|
||||
@objc public static func canReceivePushNotifications(completion: @escaping (Bool)->Void) {
|
||||
UNUserNotificationCenter.current?.getNotificationSettings { (settings) in
|
||||
DispatchQueue.main.async {
|
||||
completion(settings.authorizationStatus == .authorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public static func openAppSettings() {
|
||||
guard let appSettings = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
UIApplication.shared.open(appSettings)
|
||||
}
|
||||
}
|
||||
|
||||
extension UNUserNotificationCenter {
|
||||
/// XCTest & UNUserNotificationCenter: requires bundle identifier, crashes when accessed
|
||||
/// http://www.openradar.me/27768556
|
||||
static var current: UNUserNotificationCenter? {
|
||||
// Return if this is a unit test
|
||||
if let _ = NSClassFromString("XCTest") {
|
||||
return nil
|
||||
} else {
|
||||
return .current()
|
||||
let notificationSettings = UIUserNotificationSettings(types: [.badge, .alert, .sound], categories: nil)
|
||||
UIApplication.shared.registerUserNotificationSettings(notificationSettings)
|
||||
}
|
||||
}
|
||||
|
||||
@objc open static func canReceivePushNotifications() -> Bool {
|
||||
var isEnabled = false
|
||||
if let settings = UIApplication.shared.currentUserNotificationSettings {
|
||||
isEnabled = settings.types != UIUserNotificationType()
|
||||
}
|
||||
return isEnabled
|
||||
// Making this function async to satisfy the iOS 10 way is extremely difficult due to how OTRSettingsManager.populateSettings works
|
||||
/*
|
||||
if #available(iOS 10.0, *) {
|
||||
let center = UNUserNotificationCenter.currentNotificationCenter()
|
||||
center.getNotificationSettingsWithCompletionHandler({ (settings: UNNotificationSettings) in
|
||||
let isEnabled = settings.authorizationStatus != .Authorized
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
completion(canReceive: isEnabled)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
var isEnabled = false
|
||||
if let settings = UIApplication.sharedApplication().currentUserNotificationSettings() {
|
||||
isEnabled = settings.types != .None
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
completion(canReceive: isEnabled)
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// PushOTRListener.swift
|
||||
// ChatSecure
|
||||
//
|
||||
// Created by David Chiles on 9/29/15.
|
||||
// Copyright © 2015 Chris Ballinger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ChatSecure_Push_iOS
|
||||
|
||||
/**
|
||||
* Listen for changes from EncryptionManager for changes in state and when detetced going encrypted
|
||||
* ensures push token is transfered
|
||||
*/
|
||||
class PushOTRListener: NSObject {
|
||||
|
||||
let queue = OperationQueue()
|
||||
var notification:NSObjectProtocol?
|
||||
weak var storage:PushStorageProtocol?
|
||||
weak var pushController:PushController?
|
||||
weak var tlvHandler:OTRPushTLVHandlerProtocol?
|
||||
|
||||
init (storage:PushStorageProtocol?, pushController:PushController?, tlvHandler:OTRPushTLVHandlerProtocol?) {
|
||||
self.storage = storage
|
||||
self.pushController = pushController
|
||||
self.tlvHandler = tlvHandler
|
||||
super.init()
|
||||
self.startObserving()
|
||||
}
|
||||
|
||||
func startObserving() {
|
||||
self.notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.OTRMessageStateDidChange, object: nil, queue: self.queue) {[weak self] (notification) -> Void in
|
||||
self?.handleNotification(notification)
|
||||
}
|
||||
}
|
||||
|
||||
func handleNotification(_ notification:Notification) {
|
||||
guard let buddy = notification.object as? OTRBuddy else {
|
||||
return
|
||||
}
|
||||
|
||||
if let dictionary = notification.userInfo as? [String:AnyObject] {
|
||||
let number = dictionary[OTRMessageStateKey] as? NSNumber
|
||||
if let enumValue = number?.uintValue, enumValue == OTREncryptionMessageState.encrypted.rawValue {
|
||||
|
||||
|
||||
if let account = self.storage?.account(buddy.accountUniqueId) {
|
||||
//Everytime we're starting a new OTR Session we resend a new fresh push token either from the server or the cache
|
||||
self.pushController?.getNewPushToken(buddy.uniqueId, completion: {[weak self] (t, error) -> Void in
|
||||
if let token = t, let pushToken = token.pushToken {
|
||||
do {
|
||||
try self?.sendOffToken(pushToken, buddyUsername: buddy.username, accountUsername: account.username, protocol: account.protocolTypeString())
|
||||
} catch let error as NSError {
|
||||
|
||||
if (error.code == PushError.misingExpiresDate.rawValue) {
|
||||
self?.pushController?.storage.removeToken(token)
|
||||
//Somehow we got a token without a expires date. We need to clear the database of these tokens and try again
|
||||
guard let timeBuffer = self?.pushController?.timeBufffer else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.pushController?.storage.removeAllOurExpiredUnusedTokens(timeBuffer, completion: { (count) in
|
||||
//try again
|
||||
self?.handleNotification(notification)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendOffToken(_ token:Token, buddyUsername:String, accountUsername:String, protocol:String) throws -> Void {
|
||||
if let url = self.pushController?.apiClient.messageEndpont().absoluteString {
|
||||
if let data = try PushSerializer.serialize([token], APIEndpoint: url) {
|
||||
self.tlvHandler?.sendPush(data, username: buddyUsername, accountName:accountUsername , protocol: `protocol`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let token = self.notification {
|
||||
NotificationCenter.default.removeObserver(token)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,7 +10,7 @@ import Foundation
|
|||
import ChatSecure_Push_iOS
|
||||
import YapDatabase
|
||||
|
||||
@objc public protocol PushStorageProtocol: AnyObject {
|
||||
@objc public protocol PushStorageProtocol: class {
|
||||
func thisDevicePushAccount() -> Account?
|
||||
func hasPushAccount() -> Bool
|
||||
func saveThisAccount(_ account:Account)
|
||||
|
@ -132,9 +132,11 @@ class PushStorage: NSObject, PushStorageProtocol {
|
|||
func unusedToken() -> TokenContainer? {
|
||||
var tokenContainer:TokenContainer? = nil
|
||||
self.databaseConnection.read { (transaction) -> Void in
|
||||
transaction.iterateKeysAndObjects(inCollection: PushYapCollections.unusedTokenCollection.rawValue, using: { (key, tc: TokenContainer, stop) -> Void in
|
||||
tokenContainer = tc
|
||||
stop = true
|
||||
transaction.enumerateKeysAndObjects(inCollection: PushYapCollections.unusedTokenCollection.rawValue, using: { (key, object, stop) -> Void in
|
||||
if let tc = object as? TokenContainer {
|
||||
tokenContainer = tc
|
||||
}
|
||||
stop.initialize(to: true)
|
||||
})
|
||||
}
|
||||
return tokenContainer
|
||||
|
@ -234,16 +236,18 @@ class PushStorage: NSObject, PushStorageProtocol {
|
|||
self.databaseConnection.asyncReadWrite({ (transaction) in
|
||||
let collection = PushYapCollections.unusedTokenCollection.rawValue
|
||||
var removeKeyArray:[String] = []
|
||||
transaction.iterateKeysAndObjects(inCollection: collection, using: { (key, token: TokenContainer, stop) in
|
||||
//Check that there is an expires date otherwise remove
|
||||
guard let expiresDate = token.pushToken?.expires else {
|
||||
removeKeyArray.append(token.uniqueId)
|
||||
return
|
||||
}
|
||||
transaction.enumerateKeysAndObjects(inCollection: collection, using: { (key, object, stop) in
|
||||
if let token = object as? TokenContainer {
|
||||
//Check that there is an expires date otherwise remove
|
||||
guard let expiresDate = token.pushToken?.expires else {
|
||||
removeKeyArray.append(token.uniqueId)
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the date is farther in the future than currentDate + timeBuffer
|
||||
if (Date(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .orderedDescending ) {
|
||||
removeKeyArray.append(token.uniqueId)
|
||||
// Check that the date is farther in the future than currentDate + timeBuffer
|
||||
if (Date(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .orderedDescending ) {
|
||||
removeKeyArray.append(token.uniqueId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -177,7 +177,9 @@ extension ServerCheck: XMPPPushDelegate {
|
|||
public func pushModule(_ module: XMPPPushModule, readyWithCapabilities caps: XMLElement, jid: XMPPJID) {
|
||||
// This _should_ be handled elsewhere in OTRServerCapabilities
|
||||
// Not sure why it's not working properly
|
||||
result.capabilities?[.XEP0357]?.status = .Available
|
||||
if var caps = result.capabilities {
|
||||
caps[.XEP0357]?.status = .Available
|
||||
}
|
||||
checkReady()
|
||||
}
|
||||
}
|
|
@ -22,11 +22,11 @@ open class ShareControllerURLSource: NSObject, UIActivityItemSource {
|
|||
return self.url!
|
||||
}
|
||||
|
||||
public func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||
public func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType?) -> Any? {
|
||||
return self.url
|
||||
}
|
||||
|
||||
open func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
|
||||
open func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivityType?) -> String {
|
||||
var name = SOMEONE_STRING()
|
||||
if let displayName = account?.username {
|
||||
name = displayName
|
||||
|
@ -38,7 +38,7 @@ open class ShareControllerURLSource: NSObject, UIActivityItemSource {
|
|||
}
|
||||
|
||||
open class ShareController: NSObject {
|
||||
@objc public static func shareAccount(_ account: OTRAccount, sender: Any, viewController: UIViewController) {
|
||||
@objc open static func shareAccount(_ account: OTRAccount, sender: Any, viewController: UIViewController) {
|
||||
let fingerprintTypes = Set([NSNumber(value: OTRFingerprintType.OTR.rawValue as Int32)])
|
||||
|
||||
account.generateShareURL(withFingerprintTypes: fingerprintTypes, completion: { (url: URL?, error: Error?) -> Void in
|
||||
|
@ -48,7 +48,7 @@ open class ShareController: NSObject {
|
|||
|
||||
let qrCodeActivity = OTRQRCodeActivity()
|
||||
let activityViewController = UIActivityViewController(activityItems: [self.getShareSource(account, url: url)], applicationActivities: [qrCodeActivity])
|
||||
activityViewController.excludedActivityTypes = [UIActivity.ActivityType.print, UIActivity.ActivityType.saveToCameraRoll, UIActivity.ActivityType.addToReadingList]
|
||||
activityViewController.excludedActivityTypes = [UIActivityType.print, UIActivityType.saveToCameraRoll, UIActivityType.addToReadingList]
|
||||
if let ppc = activityViewController.popoverPresentationController {
|
||||
if let view = sender as? UIView {
|
||||
ppc.sourceView = view
|
||||
|
@ -61,7 +61,7 @@ open class ShareController: NSObject {
|
|||
|
||||
}
|
||||
|
||||
@objc public static func getShareSource(_ account: OTRAccount, url: URL) -> AnyObject {
|
||||
@objc open static func getShareSource(_ account: OTRAccount, url: URL) -> AnyObject {
|
||||
return ShareControllerURLSource(account: account, url: url)
|
||||
}
|
||||
}
|
|
@ -49,8 +49,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
if ([super activate:aXmppStream])
|
||||
{
|
||||
[self performBlock:^{
|
||||
[self.capabilities addDelegate:self delegateQueue:self->moduleQueue];
|
||||
self->_tracker = [[XMPPIDTracker alloc] initWithStream:aXmppStream dispatchQueue:self->moduleQueue];
|
||||
[self.capabilities addDelegate:self delegateQueue:moduleQueue];
|
||||
_tracker = [[XMPPIDTracker alloc] initWithStream:aXmppStream dispatchQueue:moduleQueue];
|
||||
}];
|
||||
return YES;
|
||||
}
|
||||
|
@ -60,8 +60,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
|
||||
- (void) deactivate {
|
||||
[self performBlock:^{
|
||||
[self->_tracker removeAllIDs];
|
||||
self->_tracker = nil;
|
||||
[_tracker removeAllIDs];
|
||||
_tracker = nil;
|
||||
[self.capabilities removeDelegate:self];
|
||||
self.discoveredServices = nil;
|
||||
}];
|
||||
|
@ -74,7 +74,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
- (BOOL) autoDiscoverServices {
|
||||
__block BOOL discover = NO;
|
||||
[self performBlock:^{
|
||||
discover = self->_autoDiscoverServices;
|
||||
discover = _autoDiscoverServices;
|
||||
}];
|
||||
return discover;
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
- (void) setAutoDiscoverServices:(BOOL)autoDiscoverServices {
|
||||
[self performBlockAsync:^{
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))];
|
||||
self->_autoDiscoverServices = autoDiscoverServices;
|
||||
_autoDiscoverServices = autoDiscoverServices;
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))];
|
||||
}];
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
- (void) setDiscoveredServices:(NSArray<NSXMLElement *> * _Nullable)discoveredServices {
|
||||
[self performBlockAsync:^{
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))];
|
||||
self->_discoveredServices = [discoveredServices copy];
|
||||
_discoveredServices = [discoveredServices copy];
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))];
|
||||
}];
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
- (nullable NSArray<NSXMLElement *>*) discoveredServices {
|
||||
__block NSArray<NSXMLElement *> *services = nil;
|
||||
[self performBlock:^{
|
||||
services = self->_discoveredServices;
|
||||
services = _discoveredServices;
|
||||
}];
|
||||
return services;
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
- (void) setAllCapabilities:(NSDictionary<XMPPJID *,NSXMLElement *> * _Nullable)allCapabilities {
|
||||
[self performBlockAsync:^{
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))];
|
||||
self->_allCapabilities = [allCapabilities copy];
|
||||
_allCapabilities = [allCapabilities copy];
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))];
|
||||
}];
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
- (nullable NSDictionary<XMPPJID*, NSXMLElement *> *) allCapabilities {
|
||||
__block NSDictionary<XMPPJID*, NSXMLElement *> *caps = nil;
|
||||
[self performBlock:^{
|
||||
caps = self->_allCapabilities;
|
||||
caps = _allCapabilities;
|
||||
}];
|
||||
return caps;
|
||||
}
|
||||
|
@ -122,8 +122,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
- (NSXMLElement*) streamFeatures {
|
||||
__block NSXMLElement *features = nil;
|
||||
[self performBlock:^{
|
||||
if (self->xmppStream.state >= STATE_XMPP_POST_NEGOTIATION) {
|
||||
features = [[self->xmppStream.rootElement elementForName:@"stream:features"] copy];
|
||||
if (xmppStream.state >= STATE_XMPP_POST_NEGOTIATION) {
|
||||
features = [[xmppStream.rootElement elementForName:@"stream:features"] copy];
|
||||
}
|
||||
}];
|
||||
return features;
|
||||
|
@ -149,21 +149,21 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
{
|
||||
// This is a public method, so it may be invoked on any thread/queue.
|
||||
[self performBlockAsync:^{
|
||||
if (self->_hasRequestedServices) return; // We've already requested services
|
||||
if (self->_discoveredServices) { // We've already discovered the services
|
||||
[self->multicastDelegate serverCapabilities:self didDiscoverServices:self->_discoveredServices];
|
||||
if (_hasRequestedServices) return; // We've already requested services
|
||||
if (_discoveredServices) { // We've already discovered the services
|
||||
[multicastDelegate serverCapabilities:self didDiscoverServices:_discoveredServices];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *toStr = self->xmppStream.myJID.domain;
|
||||
NSString *toStr = xmppStream.myJID.domain;
|
||||
NSXMLElement *query = [NSXMLElement elementWithName:@"query"
|
||||
xmlns:XMPPDiscoverItemsNamespace];
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"get"
|
||||
to:[XMPPJID jidWithString:toStr]
|
||||
elementID:[self->xmppStream generateUUID]
|
||||
elementID:[xmppStream generateUUID]
|
||||
child:query];
|
||||
if (!iq) {
|
||||
XMPPLogInfo(@"OTRServerCapabilities: Could not discover services for stream: %@", self->xmppStream);
|
||||
XMPPLogInfo(@"OTRServerCapabilities: Could not discover services for stream: %@", xmppStream);
|
||||
return;
|
||||
}
|
||||
XMPPLogInfo(@"OTRServerCapabilities: Discovering services for domain %@...", toStr);
|
||||
|
@ -173,8 +173,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
selector:@selector(handleDiscoverServicesQueryIQ:withInfo:)
|
||||
timeout:15];
|
||||
|
||||
[self->xmppStream sendElement:iq];
|
||||
self->_hasRequestedServices = YES;
|
||||
[xmppStream sendElement:iq];
|
||||
_hasRequestedServices = YES;
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -184,11 +184,11 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
*/
|
||||
- (void)fetchAllCapabilities {
|
||||
[self performBlockAsync:^{
|
||||
if (self->xmppStream.state != STATE_XMPP_CONNECTED) {
|
||||
if (xmppStream.state != STATE_XMPP_CONNECTED) {
|
||||
XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not connected. %@", self);
|
||||
return;
|
||||
}
|
||||
if (![self->xmppStream isAuthenticated]) {
|
||||
if (![xmppStream isAuthenticated]) {
|
||||
XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not authenticated. %@", self);
|
||||
return;
|
||||
}
|
||||
|
@ -209,11 +209,11 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
// [self.allJIDs addObject:myJID];
|
||||
[self.allJIDs addObject:myJID.domainJID];
|
||||
}
|
||||
if (!self->_autoDiscoverServices) {
|
||||
if (!_autoDiscoverServices) {
|
||||
return;
|
||||
}
|
||||
[self discoverServices];
|
||||
if (!self->_autoFetchAllCapabilities) {
|
||||
if (!_autoFetchAllCapabilities) {
|
||||
return;
|
||||
}
|
||||
[self fetchCapabilitiesForJIDs:self.allJIDs];
|
||||
|
@ -241,7 +241,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
- (void)handleDiscoverServicesQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info
|
||||
{
|
||||
[self performBlockAsync:^{
|
||||
self->_hasRequestedServices = NO; // Set this back to NO to allow for future requests
|
||||
_hasRequestedServices = NO; // Set this back to NO to allow for future requests
|
||||
NSError *error = nil;
|
||||
if (!iq) {
|
||||
NSDictionary *dict = @{NSLocalizedDescriptionKey : @"The request timed out.",
|
||||
|
@ -262,15 +262,15 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
}
|
||||
}
|
||||
if (error) {
|
||||
XMPPLogError(@"OTRServerCapabilities: Error discovering services for domain %@: %@", self->xmppStream.myJID.domain, error);
|
||||
[self->multicastDelegate serverCapabilitiesFailedToDiscoverServices:self
|
||||
XMPPLogError(@"OTRServerCapabilities: Error discovering services for domain %@: %@", xmppStream.myJID.domain, error);
|
||||
[multicastDelegate serverCapabilitiesFailedToDiscoverServices:self
|
||||
withError:error];
|
||||
|
||||
// Deal with the race condition where we've already fetched your JID and server caps
|
||||
// but for whatever reason fetching services fails.
|
||||
NSSet *allCaps = [NSSet setWithArray:self.allCapabilities.allKeys];
|
||||
if ([self.allJIDs isEqualToSet:allCaps]) {
|
||||
[self->multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities];
|
||||
[multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -279,19 +279,16 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
xmlns:XMPPDiscoverItemsNamespace];
|
||||
|
||||
NSArray<NSXMLElement*> *items = [query elementsForName:@"item"];
|
||||
if (!items) {
|
||||
items = @[];
|
||||
}
|
||||
self.discoveredServices = [items copy];
|
||||
[self->multicastDelegate serverCapabilities:self didDiscoverServices:items];
|
||||
[multicastDelegate serverCapabilities:self didDiscoverServices:items];
|
||||
|
||||
XMPPLogInfo(@"OTRServerCapabilities: Discovered services for domain %@:\n%@", self->xmppStream.myJID.domain, items);
|
||||
XMPPLogInfo(@"OTRServerCapabilities: Discovered services for domain %@:\n%@", xmppStream.myJID.domain, items);
|
||||
|
||||
// Recursively fetch service capabilities if needed
|
||||
if (!self->_autoFetchAllCapabilities) {
|
||||
if (!_autoFetchAllCapabilities) {
|
||||
return;
|
||||
}
|
||||
NSSet<XMPPJID*> *jids = [self jidsFromItems:self->_discoveredServices];
|
||||
NSSet<XMPPJID*> *jids = [self jidsFromItems:_discoveredServices];
|
||||
[self.allJIDs unionSet:jids];
|
||||
[self fetchCapabilitiesForJIDs:jids];
|
||||
}];
|
||||
|
@ -343,7 +340,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
}
|
||||
[newCaps setObject:caps forKey:jid];
|
||||
self.allCapabilities = newCaps;
|
||||
[self->multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities];
|
||||
[multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities];
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -388,9 +385,9 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
|||
return;
|
||||
}
|
||||
id <XMPPCapabilitiesStorage> storage = self.capabilities.xmppCapabilitiesStorage;
|
||||
BOOL fetched = [storage areCapabilitiesKnownForJID:jid xmppStream:self->xmppStream];
|
||||
BOOL fetched = [storage areCapabilitiesKnownForJID:jid xmppStream:xmppStream];
|
||||
if (fetched) {
|
||||
NSXMLElement *capabilities = [storage capabilitiesForJID:jid xmppStream:self->xmppStream];
|
||||
NSXMLElement *capabilities = [storage capabilitiesForJID:jid xmppStream:xmppStream];
|
||||
if (capabilities) {
|
||||
[newCaps setObject:capabilities forKey:jid];
|
||||
*stop = YES;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue