Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
52d0d63968 |
|
@ -1 +0,0 @@
|
||||||
github: chrisballinger
|
|
|
@ -1,4 +1,3 @@
|
||||||
.env
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
build
|
build
|
||||||
*.mode1v3
|
*.mode1v3
|
||||||
|
@ -15,7 +14,3 @@ Carthage/
|
||||||
com.mono0926.LicensePlist.Output/
|
com.mono0926.LicensePlist.Output/
|
||||||
|
|
||||||
Secrets.plist
|
Secrets.plist
|
||||||
Preview.html
|
|
||||||
fastlane/report.xml
|
|
||||||
fastlane/test_output/
|
|
||||||
.ruby-version
|
|
||||||
|
|
|
@ -31,12 +31,3 @@
|
||||||
[submodule "Submodules/LumberjackConsole"]
|
[submodule "Submodules/LumberjackConsole"]
|
||||||
path = Submodules/LumberjackConsole
|
path = Submodules/LumberjackConsole
|
||||||
url = git@github.com:ChatSecure/LumberjackConsole.git
|
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
|
language: objective-c
|
||||||
|
|
||||||
# Handle git submodules yourself
|
# Handle git submodules yourself
|
||||||
|
@ -9,8 +9,9 @@ git:
|
||||||
# Use sed to replace the SSH URL with the public URL, then initialize submodules
|
# Use sed to replace the SSH URL with the public URL, then initialize submodules
|
||||||
before_install:
|
before_install:
|
||||||
# Fix Travis xcodebuild exited with 65 https://github.com/travis-ci/travis-ci/issues/6675#issuecomment-257964767
|
# 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
|
- echo $IOS_SIMULATOR_UDID
|
||||||
|
- open -a "simulator" --args -CurrentDeviceUDID $IOS_SIMULATOR_UDID
|
||||||
- bundle install # We need a pre-release CocoaPods version
|
- 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\//' .gitmodules
|
||||||
- sed -i -e 's/git@github.com:/git:\/\/github.com\//' Podfile
|
- 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
|
- 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
|
- unzip -q ChatSecure-iOS-Precompiled-Dependencies.zip
|
||||||
- mv ChatSecure-iOS-Precompiled-Dependencies-master ChatSecure-iOS-Precompiled-Dependencies
|
- 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/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
|
- unzip -q ./ChatSecure-iOS-Precompiled-Dependencies/Pods.zip
|
||||||
|
|
||||||
before_script:
|
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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1200"
|
LastUpgradeVersion = "0930"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
@ -37,20 +37,10 @@
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
buildConfiguration = "iOS_Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
disableMainThreadChecker = "YES">
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
|
||||||
BuildableName = "ChatSecure.app"
|
|
||||||
BlueprintName = "ChatSecure"
|
|
||||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
|
@ -63,7 +53,17 @@
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
<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
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "6365CEFB1E2453F6009E213F"
|
BlueprintIdentifier = "6365CEFB1E2453F6009E213F"
|
||||||
|
@ -73,9 +73,20 @@
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||||
|
BuildableName = "ChatSecure.app"
|
||||||
|
BlueprintName = "ChatSecure"
|
||||||
|
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "iOS_Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
|
@ -98,12 +109,14 @@
|
||||||
<EnvironmentVariable
|
<EnvironmentVariable
|
||||||
key = "OS_ACTIVITY_MODE"
|
key = "OS_ACTIVITY_MODE"
|
||||||
value = "disable"
|
value = "disable"
|
||||||
isEnabled = "NO">
|
isEnabled = "YES">
|
||||||
</EnvironmentVariable>
|
</EnvironmentVariable>
|
||||||
</EnvironmentVariables>
|
</EnvironmentVariables>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "iOS_Release"
|
buildConfiguration = "Release"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
|
@ -120,10 +133,10 @@
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "iOS_Debug">
|
buildConfiguration = "Debug">
|
||||||
</AnalyzeAction>
|
</AnalyzeAction>
|
||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "iOS_Release"
|
buildConfiguration = "Release"
|
||||||
revealArchiveInOrganizer = "YES">
|
revealArchiveInOrganizer = "YES">
|
||||||
</ArchiveAction>
|
</ArchiveAction>
|
||||||
</Scheme>
|
</Scheme>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1200"
|
LastUpgradeVersion = "0930"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
@ -10,11 +10,11 @@
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
buildForRunning = "YES"
|
buildForRunning = "YES"
|
||||||
buildForProfiling = "YES"
|
buildForProfiling = "YES"
|
||||||
buildForArchiving = "YES"
|
buildForArchiving = "NO"
|
||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "D9C527FF235CB55C002B213A"
|
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
|
||||||
BuildableName = "ChatSecureCore.framework"
|
BuildableName = "ChatSecureCore.framework"
|
||||||
BlueprintName = "ChatSecureCore"
|
BlueprintName = "ChatSecureCore"
|
||||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||||
|
@ -23,15 +23,26 @@
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
buildConfiguration = "iOS_Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
</Testables>
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
|
||||||
|
BuildableName = "ChatSecureCore.framework"
|
||||||
|
BlueprintName = "ChatSecureCore"
|
||||||
|
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "iOS_Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
|
@ -40,9 +51,20 @@
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
|
||||||
|
BuildableName = "ChatSecureCore.framework"
|
||||||
|
BlueprintName = "ChatSecureCore"
|
||||||
|
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "iOS_Release"
|
buildConfiguration = "Release"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
|
@ -50,7 +72,7 @@
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "D9C527FF235CB55C002B213A"
|
BlueprintIdentifier = "D9227C291BA7952100B5E1D0"
|
||||||
BuildableName = "ChatSecureCore.framework"
|
BuildableName = "ChatSecureCore.framework"
|
||||||
BlueprintName = "ChatSecureCore"
|
BlueprintName = "ChatSecureCore"
|
||||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||||
|
@ -58,10 +80,10 @@
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "iOS_Debug">
|
buildConfiguration = "Debug">
|
||||||
</AnalyzeAction>
|
</AnalyzeAction>
|
||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "iOS_Release"
|
buildConfiguration = "Release"
|
||||||
revealArchiveInOrganizer = "YES">
|
revealArchiveInOrganizer = "YES">
|
||||||
</ArchiveAction>
|
</ArchiveAction>
|
||||||
</Scheme>
|
</Scheme>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1200"
|
LastUpgradeVersion = "0930"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
@ -23,19 +23,10 @@
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
buildConfiguration = "iOS_Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
|
|
||||||
BuildableName = "ChatSecureTests.xctest"
|
|
||||||
BlueprintName = "ChatSecureTests"
|
|
||||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
|
@ -48,9 +39,20 @@
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "6396AFB21A169D54009F3E6C"
|
||||||
|
BuildableName = "ChatSecureTests.xctest"
|
||||||
|
BlueprintName = "ChatSecureTests"
|
||||||
|
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "iOS_Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
|
@ -75,9 +77,11 @@
|
||||||
isEnabled = "YES">
|
isEnabled = "YES">
|
||||||
</EnvironmentVariable>
|
</EnvironmentVariable>
|
||||||
</EnvironmentVariables>
|
</EnvironmentVariables>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "iOS_Release"
|
buildConfiguration = "Release"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
|
@ -93,10 +97,10 @@
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "iOS_Debug">
|
buildConfiguration = "Debug">
|
||||||
</AnalyzeAction>
|
</AnalyzeAction>
|
||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "iOS_Release"
|
buildConfiguration = "Release"
|
||||||
revealArchiveInOrganizer = "YES">
|
revealArchiveInOrganizer = "YES">
|
||||||
</ArchiveAction>
|
</ArchiveAction>
|
||||||
</Scheme>
|
</Scheme>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1200"
|
LastUpgradeVersion = "0930"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
@ -10,8 +10,7 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
disableMainThreadChecker = "YES">
|
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
|
@ -24,12 +23,22 @@
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "6396AF991A169D54009F3E6C"
|
||||||
|
BuildableName = "ChatSecure.app"
|
||||||
|
BlueprintName = "ChatSecure"
|
||||||
|
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
disableMainThreadChecker = "YES"
|
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
@ -46,6 +55,8 @@
|
||||||
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
ReferencedContainer = "container:ChatSecure.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
|
|
@ -8,17 +8,7 @@
|
||||||
<array>
|
<array>
|
||||||
<string>applinks:chatsecure.org</string>
|
<string>applinks:chatsecure.org</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.developer.default-data-protection</key>
|
||||||
<true/>
|
<string>NSFileProtectionComplete</string>
|
||||||
<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/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -9,14 +9,14 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
extension NSData {
|
public extension NSData {
|
||||||
@objc public func hexString() -> String {
|
@objc public func hexString() -> String {
|
||||||
return (self as Data).hexString()
|
return (self as Data).hexString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://stackoverflow.com/a/26502285/805882
|
// http://stackoverflow.com/a/26502285/805882
|
||||||
extension NSString {
|
public extension NSString {
|
||||||
|
|
||||||
/// Create `Data` from hexadecimal string representation
|
/// Create `Data` from hexadecimal string representation
|
||||||
///
|
///
|
|
@ -63,4 +63,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface UIViewController (ChatSecureURL)
|
||||||
|
- (void) promptToShowURL:(NSURL*)url sender:(id)sender;
|
||||||
|
@end
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
|
@ -10,7 +10,6 @@
|
||||||
#import "OTRConstants.h"
|
#import "OTRConstants.h"
|
||||||
@import XMPPFramework;
|
@import XMPPFramework;
|
||||||
@import OTRAssets;
|
@import OTRAssets;
|
||||||
#import "ChatSecureCoreCompat-Swift.h"
|
|
||||||
|
|
||||||
@implementation NSURL (ChatSecure)
|
@implementation NSURL (ChatSecure)
|
||||||
|
|
||||||
|
@ -198,7 +197,7 @@
|
||||||
view = sender;
|
view = sender;
|
||||||
}
|
}
|
||||||
UIAlertAction *visitURL = [UIAlertAction actionWithTitle:OPEN_IN_SAFARI() style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
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];
|
UIAlertAction *cancel = [UIAlertAction actionWithTitle:CANCEL_STRING() style:UIAlertActionStyleCancel handler:nil];
|
||||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.absoluteString message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.absoluteString message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
||||||
|
@ -212,4 +211,12 @@
|
||||||
[viewController presentViewController:alert animated:YES completion:nil];
|
[viewController presentViewController:alert animated:YES completion:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation UIViewController (ChatSecureURL)
|
||||||
|
- (void) promptToShowURL:(NSURL*)url sender:(id)sender {
|
||||||
|
[url promptToShowURLFromViewController:self sender:sender];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@end
|
@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 "UIActivity+ChatSecure.h"
|
||||||
@import ARChromeActivity;
|
@import ARChromeActivity;
|
||||||
|
@import TUSafariActivity;
|
||||||
#import "OTROpenInFacebookActivity.h"
|
#import "OTROpenInFacebookActivity.h"
|
||||||
#import "OTROpenInTwitterActivity.h"
|
#import "OTROpenInTwitterActivity.h"
|
||||||
@import OTRAssets;
|
@import OTRAssets;
|
||||||
|
@ -15,21 +16,22 @@
|
||||||
@implementation UIActivity (ChatSecure)
|
@implementation UIActivity (ChatSecure)
|
||||||
|
|
||||||
+ (NSArray<UIActivity*>*) otr_linkActivities {
|
+ (NSArray<UIActivity*>*) otr_linkActivities {
|
||||||
|
TUSafariActivity *safariActivity = [TUSafariActivity new];
|
||||||
ARChromeActivity *chromeActivity = [ARChromeActivity new];
|
ARChromeActivity *chromeActivity = [ARChromeActivity new];
|
||||||
chromeActivity.activityTitle = OPEN_IN_CHROME();
|
chromeActivity.activityTitle = OPEN_IN_CHROME();
|
||||||
chromeActivity.callbackURL = [NSURL URLWithString:@"chatsecure://"];
|
chromeActivity.callbackURL = [NSURL URLWithString:@"chatsecure://"];
|
||||||
OTROpenInTwitterActivity *twitterActivity = [OTROpenInTwitterActivity new];
|
OTROpenInTwitterActivity *twitterActivity = [OTROpenInTwitterActivity new];
|
||||||
OTROpenInFacebookActivity *facebookActivity = [OTROpenInFacebookActivity new];
|
OTROpenInFacebookActivity *facebookActivity = [OTROpenInFacebookActivity new];
|
||||||
NSArray *applicationActivites = @[twitterActivity,facebookActivity,chromeActivity];
|
NSArray *applicationActivites = @[twitterActivity,facebookActivity,safariActivity,chromeActivity];
|
||||||
return applicationActivites;
|
return applicationActivites;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (CGSize)otr_defaultImageSize
|
+ (CGSize)otr_defaultImageSize
|
||||||
{
|
{
|
||||||
CGSize size = CGSizeZero;
|
CGSize size = CGSizeZero;
|
||||||
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
|
||||||
size = CGSizeMake(43, 43);
|
size = CGSizeMake(43, 43);
|
||||||
} else if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||||
size = CGSizeMake(55, 55);
|
size = CGSizeMake(55, 55);
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#import "UIActivityViewController+ChatSecure.h"
|
#import "UIActivityViewController+ChatSecure.h"
|
||||||
@import ARChromeActivity;
|
@import ARChromeActivity;
|
||||||
|
@import TUSafariActivity;
|
||||||
#import "OTROpenInFacebookActivity.h"
|
#import "OTROpenInFacebookActivity.h"
|
||||||
#import "OTROpenInTwitterActivity.h"
|
#import "OTROpenInTwitterActivity.h"
|
||||||
@import OTRAssets;
|
@import OTRAssets;
|
|
@ -49,7 +49,7 @@ extension NotificationType: RawRepresentable {
|
||||||
public typealias RawValue = String
|
public typealias RawValue = String
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIApplication {
|
public extension UIApplication {
|
||||||
|
|
||||||
/// Removes all but one foreground notifications for typing and message events sent from APNS
|
/// Removes all but one foreground notifications for typing and message events sent from APNS
|
||||||
@objc public func removeExtraForegroundNotifications() {
|
@objc public func removeExtraForegroundNotifications() {
|
||||||
|
@ -138,7 +138,7 @@ extension UIApplication {
|
||||||
let chatString = WANTS_TO_CHAT_STRING()
|
let chatString = WANTS_TO_CHAT_STRING()
|
||||||
let text = "\(name) \(chatString)"
|
let text = "\(name) \(chatString)"
|
||||||
let unreadCount = self.applicationIconBadgeNumber + 1
|
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,
|
let userInfo:[AnyHashable:Any] = [kOTRNotificationThreadKey:identifier,
|
||||||
kOTRNotificationThreadCollection:thread.threadCollection,
|
kOTRNotificationThreadCollection:thread.threadCollection,
|
||||||
kOTRNotificationType: kOTRNotificationTypeApprovedBuddy]
|
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) {
|
internal func showLocalNotificationFor(_ thread:OTRThreadOwner?, text:String, unreadCount:Int) {
|
||||||
if let thread = thread, thread.isMuted { return } // No notifications for muted
|
if let thread = thread, thread.isMuted { return } // No notifications for muted
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
var identifier:String? = nil
|
||||||
var userInfo:[AnyHashable:Any]? = nil
|
var userInfo:[AnyHashable:Any]? = nil
|
||||||
if let t = thread {
|
if let t = thread {
|
||||||
|
identifier = t.threadIdentifier
|
||||||
userInfo = [kOTRNotificationThreadKey:t.threadIdentifier,
|
userInfo = [kOTRNotificationThreadKey:t.threadIdentifier,
|
||||||
kOTRNotificationThreadCollection:t.threadCollection,
|
kOTRNotificationThreadCollection:t.threadCollection,
|
||||||
kOTRNotificationType: kOTRNotificationTypeChatMessage]
|
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 {
|
DispatchQueue.main.async {
|
||||||
if recurring, self.hasRecurringLocalNotificationWith(identifier: groupingIdentifier) {
|
if recurring, self.hasRecurringLocalNotificationWith(identifier: identifier) {
|
||||||
return // Already pending
|
return // Already pending
|
||||||
}
|
}
|
||||||
// Use the new UserNotifications.framework on iOS 10+
|
// Use the new UserNotifications.framework on iOS 10+
|
||||||
|
@ -185,9 +187,9 @@ extension UIApplication {
|
||||||
let localNotification = UNMutableNotificationContent()
|
let localNotification = UNMutableNotificationContent()
|
||||||
localNotification.body = body
|
localNotification.body = body
|
||||||
localNotification.badge = NSNumber(integerLiteral: badge)
|
localNotification.badge = NSNumber(integerLiteral: badge)
|
||||||
localNotification.sound = UNNotificationSound.default
|
localNotification.sound = UNNotificationSound.default()
|
||||||
if let threadKey = userInfo?[kOTRNotificationThreadKey] as? String {
|
if let identifier = identifier {
|
||||||
localNotification.threadIdentifier = threadKey
|
localNotification.threadIdentifier = identifier
|
||||||
}
|
}
|
||||||
if let userInfo = userInfo {
|
if let userInfo = userInfo {
|
||||||
localNotification.userInfo = userInfo
|
localNotification.userInfo = userInfo
|
||||||
|
@ -199,7 +201,7 @@ extension UIApplication {
|
||||||
date.minute = 0
|
date.minute = 0
|
||||||
trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)
|
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()
|
let center = UNUserNotificationCenter.current()
|
||||||
center.add(request, withCompletionHandler: { (error: Error?) in
|
center.add(request, withCompletionHandler: { (error: Error?) in
|
||||||
if let error = error as NSError? {
|
if let error = error as NSError? {
|
||||||
|
@ -280,9 +282,9 @@ extension UIApplication {
|
||||||
let username = account.username
|
let username = account.username
|
||||||
var body = "\(CONNECTION_ERROR_STRING()) \(username)."
|
var body = "\(CONNECTION_ERROR_STRING()) \(username)."
|
||||||
|
|
||||||
|
|
||||||
if error.domain == GCDAsyncSocketErrorDomain,
|
if error.domain == GCDAsyncSocketErrorDomain,
|
||||||
let code = GCDAsyncSocketError.Code.init(rawValue: error.code) {
|
let code = GCDAsyncSocketError(rawValue: error.code) {
|
||||||
|
|
||||||
switch code {
|
switch code {
|
||||||
case .noError,
|
case .noError,
|
||||||
.connectTimeoutError,
|
.connectTimeoutError,
|
||||||
|
@ -296,8 +298,6 @@ extension UIApplication {
|
||||||
case .otherError:
|
case .otherError:
|
||||||
// this is probably a SSL error
|
// this is probably a SSL error
|
||||||
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
|
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
|
||||||
@unknown default:
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else if error.domain == "kCFStreamErrorDomainSSL" {
|
} else if error.domain == "kCFStreamErrorDomainSSL" {
|
||||||
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
|
body = body + " \(CONNECTION_ERROR_CERTIFICATE_VERIFY_STRING())"
|
||||||
|
@ -325,12 +325,19 @@ extension UIApplication {
|
||||||
let userInfo = [kOTRNotificationType: kOTRNotificationTypeConnectionError,
|
let userInfo = [kOTRNotificationType: kOTRNotificationTypeConnectionError,
|
||||||
kOTRNotificationAccountKey: accountKey]
|
kOTRNotificationAccountKey: accountKey]
|
||||||
|
|
||||||
self.showLocalNotificationWith(groupingIdentifier: accountKey, body: body, badge: badge, userInfo: userInfo, recurring: false)
|
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
|
||||||
extension UIApplication {
|
for notification in notifications {
|
||||||
@objc public func open(_ url: URL) {
|
if notification.request.identifier == accountKey {
|
||||||
open(url, options: [:], completionHandler: nil)
|
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
|
import UIKit
|
||||||
|
|
||||||
|
|
||||||
extension UINavigationController {
|
public extension UINavigationController {
|
||||||
|
|
||||||
@objc public func otr_baseViewContorllers() -> [UIViewController] {
|
@objc public func otr_baseViewContorllers() -> [UIViewController] {
|
||||||
var result:[UIViewController] = []
|
var result:[UIViewController] = []
|
|
@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
@interface UITableView (ChatSecure)
|
@interface UITableView (ChatSecure)
|
||||||
|
|
||||||
/** deleteActionAlsoRemovesFromRoster is YES for the ChooseBuddy view, otherwise NO. Connection must be read-write */
|
/** 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
|
@end
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
|
@ -8,14 +8,14 @@
|
||||||
|
|
||||||
#import "UITableView+ChatSecure.h"
|
#import "UITableView+ChatSecure.h"
|
||||||
#import "OTRXMPPBuddy.h"
|
#import "OTRXMPPBuddy.h"
|
||||||
#import "ChatSecureCoreCompat-Swift.h"
|
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||||
#import "OTRXMPPManager_Private.h"
|
#import "OTRXMPPManager_Private.h"
|
||||||
@import OTRAssets;
|
@import OTRAssets;
|
||||||
|
|
||||||
@implementation UITableView (ChatSecure)
|
@implementation UITableView (ChatSecure)
|
||||||
|
|
||||||
/** Connection must be read-write */
|
/** 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(thread);
|
||||||
NSParameterAssert(connection);
|
NSParameterAssert(connection);
|
||||||
if (!thread || !connection) {
|
if (!thread || !connection) {
|
||||||
|
@ -33,23 +33,22 @@
|
||||||
archiveTitle = UNARCHIVE_ACTION_STRING();
|
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) {
|
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
|
||||||
NSString *key = [thread threadIdentifier];
|
NSString *key = [thread threadIdentifier];
|
||||||
NSString *collection = [thread threadCollection];
|
NSString *collection = [thread threadCollection];
|
||||||
id object = [transaction objectForKey:key inCollection:collection];
|
id object = [transaction objectForKey:key inCollection:collection];
|
||||||
if (![object conformsToProtocol:@protocol(OTRThreadOwner)]) {
|
if (![object conformsToProtocol:@protocol(OTRThreadOwner)]) {
|
||||||
completionHandler(NO);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
id <OTRThreadOwner> thread = object;
|
id <OTRThreadOwner> thread = object;
|
||||||
thread.isArchived = !thread.isArchived;
|
thread.isArchived = !thread.isArchived;
|
||||||
[thread saveWithTransaction:transaction];
|
[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) {
|
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||||
[OTRBaseMessage deleteAllMessagesForBuddyId:[thread threadIdentifier] transaction:transaction];
|
[OTRBaseMessage deleteAllMessagesForBuddyId:[thread threadIdentifier] transaction:transaction];
|
||||||
}];
|
}];
|
||||||
|
@ -62,7 +61,7 @@
|
||||||
[connection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
|
[connection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
|
||||||
account = [OTRAccount fetchObjectWithUniqueID:accountKey transaction:transaction];
|
account = [OTRAccount fetchObjectWithUniqueID:accountKey transaction:transaction];
|
||||||
}];
|
}];
|
||||||
OTRXMPPManager *xmppManager = (OTRXMPPManager *)[[OTRProtocolManager sharedInstance] protocolForAccount:account];
|
OTRXMPPManager *xmppManager = (OTRXMPPManager *)[OTRProtocolManager.shared protocolForAccount:account];
|
||||||
if (room.roomJID) {
|
if (room.roomJID) {
|
||||||
[xmppManager.roomManager leaveRoom:room.roomJID];
|
[xmppManager.roomManager leaveRoom:room.roomJID];
|
||||||
}
|
}
|
||||||
|
@ -71,7 +70,6 @@
|
||||||
//Delete database items
|
//Delete database items
|
||||||
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||||
[((OTRXMPPRoom *)thread) removeWithTransaction:transaction];
|
[((OTRXMPPRoom *)thread) removeWithTransaction:transaction];
|
||||||
completionHandler(YES);
|
|
||||||
}];
|
}];
|
||||||
} else if ([thread isKindOfClass:[OTRBuddy class]] && deleteActionAlsoRemovesFromRoster) {
|
} else if ([thread isKindOfClass:[OTRBuddy class]] && deleteActionAlsoRemovesFromRoster) {
|
||||||
OTRBuddy *dbBuddy = (OTRBuddy*)thread;
|
OTRBuddy *dbBuddy = (OTRBuddy*)thread;
|
||||||
|
@ -82,14 +80,11 @@
|
||||||
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
[connection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||||
[action saveWithTransaction:transaction];
|
[action saveWithTransaction:transaction];
|
||||||
[dbBuddy removeWithTransaction:transaction];
|
[dbBuddy removeWithTransaction:transaction];
|
||||||
completionHandler(YES);
|
|
||||||
}];
|
}];
|
||||||
} else {
|
|
||||||
completionHandler(NO);
|
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return [UISwipeActionsConfiguration configurationWithActions:@[deleteAction, archiveAction]];
|
return @[deleteAction, archiveAction];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
|
@ -9,38 +9,26 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import OTRAssets
|
import OTRAssets
|
||||||
|
|
||||||
extension UIViewController {
|
public extension UIViewController {
|
||||||
public func prompt(toShow url: URL, sender: Any) {
|
|
||||||
(url as NSURL).promptToShow(from: self, sender: sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Will show a prompt to bring user into system settings
|
/// 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 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 settingsAction = UIAlertAction(title: SETTINGS_STRING(), style: .default, handler: { (action: UIAlertAction) -> Void in
|
||||||
let appSettings = URL(string: UIApplication.openSettingsURLString)
|
let appSettings = URL(string: UIApplicationOpenSettingsURLString)
|
||||||
UIApplication.shared.open(appSettings!)
|
UIApplication.shared.openURL(appSettings!)
|
||||||
})
|
})
|
||||||
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
|
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
|
||||||
alert.addAction(settingsAction)
|
alert.addAction(settingsAction)
|
||||||
alert.addAction(cancelAction)
|
alert.addAction(cancelAction)
|
||||||
if let sourceView = sender as? UIView {
|
|
||||||
alert.popoverPresentationController?.sourceView = sourceView;
|
|
||||||
alert.popoverPresentationController?.sourceRect = sourceView.bounds;
|
|
||||||
}
|
|
||||||
present(alert, animated: true, completion: nil)
|
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 alert = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
|
||||||
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive, handler: handler)
|
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive, handler: handler)
|
||||||
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
|
let cancelAction = UIAlertAction(title: CANCEL_STRING(), style: .cancel, handler: nil)
|
||||||
alert.addAction(destroyAction)
|
alert.addAction(destroyAction)
|
||||||
alert.addAction(cancelAction)
|
alert.addAction(cancelAction)
|
||||||
if let sourceView = sender as? UIView {
|
|
||||||
alert.popoverPresentationController?.sourceView = sourceView;
|
|
||||||
alert.popoverPresentationController?.sourceRect = sourceView.bounds;
|
|
||||||
}
|
|
||||||
present(alert, animated: true, completion: nil)
|
present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension XMPPMessage {
|
public extension XMPPMessage {
|
||||||
/// Safely extracts XEP-0359 stanza-id
|
/// Safely extracts XEP-0359 stanza-id
|
||||||
@objc public func extractStanzaId(account: OTRXMPPAccount, capabilities: XMPPCapabilities) -> String? {
|
@objc public func extractStanzaId(account: OTRXMPPAccount, capabilities: XMPPCapabilities) -> String? {
|
||||||
let stanzaIds = self.stanzaIds
|
let stanzaIds = self.stanzaIds
|
|
@ -56,7 +56,7 @@ extension UIImage {
|
||||||
numTries = numTries + 1
|
numTries = numTries + 1
|
||||||
newSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
|
newSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
|
||||||
let scaledImage = UIImage.otr_image(with: image, scaledTo: newSize)
|
let scaledImage = UIImage.otr_image(with: image, scaledTo: newSize)
|
||||||
scaledImageData = scaledImage.jpegData(compressionQuality: jpegQuality)
|
scaledImageData = UIImageJPEGRepresentation(scaledImage, jpegQuality)
|
||||||
if let imageData = scaledImageData {
|
if let imageData = scaledImageData {
|
||||||
sizeInBytes = UInt(imageData.count)
|
sizeInBytes = UInt(imageData.count)
|
||||||
scaleFactor = scaleFactor * scaleDecrement
|
scaleFactor = scaleFactor * scaleDecrement
|
||||||
|
@ -123,7 +123,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
||||||
let connection: YapDatabaseConnection
|
let connection: YapDatabaseConnection
|
||||||
let internalQueue = DispatchQueue(label: "FileTransferManager Queue")
|
let internalQueue = DispatchQueue(label: "FileTransferManager Queue")
|
||||||
let callbackQueue = DispatchQueue.main
|
let callbackQueue = DispatchQueue.main
|
||||||
let sessionManager: Session
|
let sessionManager: SessionManager
|
||||||
private var servers: [HTTPServer] = []
|
private var servers: [HTTPServer] = []
|
||||||
|
|
||||||
@objc public var canUploadFiles: Bool {
|
@objc public var canUploadFiles: Bool {
|
||||||
|
@ -141,7 +141,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
||||||
self.serverCapabilities = serverCapabilities
|
self.serverCapabilities = serverCapabilities
|
||||||
self.httpFileUpload = XMPPHTTPFileUpload()
|
self.httpFileUpload = XMPPHTTPFileUpload()
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.sessionManager = Alamofire.Session(configuration: sessionConfiguration)
|
self.sessionManager = Alamofire.SessionManager(configuration: sessionConfiguration)
|
||||||
super.init()
|
super.init()
|
||||||
if let stream = serverCapabilities.xmppStream {
|
if let stream = serverCapabilities.xmppStream {
|
||||||
httpFileUpload.activate(stream)
|
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
|
// Resume downloads, i.e. look for media items that are partially downloaded and retry getting them. TODO - use ranges
|
||||||
@objc public func resumeDownloads() {
|
@objc public func resumeDownloads() {
|
||||||
/// https://github.com/ChatSecure/ChatSecure-iOS/issues/1034
|
connection.asyncRead { (transaction) in
|
||||||
DDLogWarn("WARN: Download resumption is disabled. See https://github.com/ChatSecure/ChatSecure-iOS/issues/1034 for more information.")
|
transaction.enumerateUnfinishedDownloads({ (mediaItem, stop) in
|
||||||
// connection.asyncRead { [weak self] (transaction) in
|
if let downloadMessage = mediaItem.parentObject(with: transaction) as? OTRDownloadMessage, downloadMessage.messageError == nil {
|
||||||
// let unfinished = transaction.unfinishedDownloads()
|
self.internalQueue.async {
|
||||||
// self?.internalQueue.async {
|
self.downloadMedia(downloadMessage)
|
||||||
// for mediaItem in unfinished {
|
}
|
||||||
// if let downloadMessage = mediaItem.parentObject(with: transaction) as? OTRDownloadMessage,
|
}
|
||||||
// downloadMessage.messageError == nil {
|
})
|
||||||
// self?.downloadMedia(downloadMessage)
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This will fetch capabilities and setup XMPP transfer module if needed
|
/// This will fetch capabilities and setup XMPP transfer module if needed
|
||||||
|
@ -260,7 +256,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
||||||
var outData = data
|
var outData = data
|
||||||
var outKeyIv: Data? = nil
|
var outKeyIv: Data? = nil
|
||||||
if shouldEncrypt {
|
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")
|
DDLogError("Could not generate key/iv")
|
||||||
self.callbackQueue.async {
|
self.callbackQueue.async {
|
||||||
completion(nil, FileTransferError.keyGenerationError)
|
completion(nil, FileTransferError.keyGenerationError)
|
||||||
|
@ -269,7 +265,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
||||||
}
|
}
|
||||||
outKeyIv = iv + key
|
outKeyIv = iv + key
|
||||||
do {
|
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
|
outData = crypted.data + crypted.authTag
|
||||||
} catch let error {
|
} catch let error {
|
||||||
outData = Data()
|
outData = Data()
|
||||||
|
@ -291,23 +287,9 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.sessionManager.upload(outData, to: slot.putURL, method: .put)
|
||||||
// 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)
|
|
||||||
.validate()
|
.validate()
|
||||||
.response(queue: self.callbackQueue) { response in
|
.responseData(queue: self.callbackQueue) { response in
|
||||||
switch response.result {
|
switch response.result {
|
||||||
case .success:
|
case .success:
|
||||||
if let outKeyIv = outKeyIv {
|
if let outKeyIv = outKeyIv {
|
||||||
|
@ -378,9 +360,7 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
||||||
}
|
}
|
||||||
mediaItem.parentObjectKey = message.messageKey
|
mediaItem.parentObjectKey = message.messageKey
|
||||||
mediaItem.parentObjectCollection = message.messageCollection
|
mediaItem.parentObjectCollection = message.messageCollection
|
||||||
guard let newPath = OTRMediaFileManager.path(for: mediaItem, buddyUniqueId: thread.threadIdentifier) else {
|
let newPath = OTRMediaFileManager.path(for: mediaItem, buddyUniqueId: thread.threadIdentifier)
|
||||||
return
|
|
||||||
}
|
|
||||||
self.connection.readWrite { transaction in
|
self.connection.readWrite { transaction in
|
||||||
message.save(with: transaction)
|
message.save(with: transaction)
|
||||||
mediaItem.save(with: transaction)
|
mediaItem.save(with: transaction)
|
||||||
|
@ -494,8 +474,6 @@ public class FileTransferManager: NSObject, OTRServerCapabilitiesDelegate {
|
||||||
shouldEncrypt = true
|
shouldEncrypt = true
|
||||||
case .invalid, .plaintext, .plaintextWithOTR:
|
case .invalid, .plaintext, .plaintextWithOTR:
|
||||||
shouldEncrypt = false
|
shouldEncrypt = false
|
||||||
@unknown default:
|
|
||||||
fatalError("Unhandled message security value!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.upload(mediaItem: mediaItem, shouldEncrypt: shouldEncrypt, prefetchedData: prefetchedData, completion: { (_url: URL?, error: Error?) in
|
self.upload(mediaItem: mediaItem, shouldEncrypt: shouldEncrypt, prefetchedData: prefetchedData, completion: { (_url: URL?, error: Error?) in
|
||||||
|
@ -679,12 +657,7 @@ extension FileTransferManager {
|
||||||
// Remove placeholder media item
|
// Remove placeholder media item
|
||||||
mediaItem = OTRMediaItem(forMessage: downloadMessage, transaction: transaction)
|
mediaItem = OTRMediaItem(forMessage: downloadMessage, transaction: transaction)
|
||||||
mediaItem?.remove(with: transaction)
|
mediaItem?.remove(with: transaction)
|
||||||
// If the file is encrypted, the server might not know its type
|
mediaItem = OTRMediaItem.incomingItem(withFilename: url.lastPathComponent, mimeType: contentType)
|
||||||
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?.parentObjectKey = downloadMessage.uniqueId
|
mediaItem?.parentObjectKey = downloadMessage.uniqueId
|
||||||
mediaItem?.parentObjectCollection = downloadMessage.messageCollection
|
mediaItem?.parentObjectCollection = downloadMessage.messageCollection
|
||||||
mediaItem?.save(with: transaction)
|
mediaItem?.save(with: transaction)
|
||||||
|
@ -813,19 +786,19 @@ extension OTRDownloadMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OTRMessageProtocol {
|
public extension OTRMessageProtocol {
|
||||||
public var downloadableURLs: [URL] {
|
public var downloadableURLs: [URL] {
|
||||||
return self.messageText?.downloadableURLs ?? []
|
return self.messageText?.downloadableURLs ?? []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OTRBaseMessage {
|
public extension OTRBaseMessage {
|
||||||
@objc public var downloadableNSURLs: [NSURL] {
|
@objc public var downloadableNSURLs: [NSURL] {
|
||||||
return self.downloadableURLs as [NSURL]
|
return self.downloadableURLs as [NSURL]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OTRXMPPRoomMessage {
|
public extension OTRXMPPRoomMessage {
|
||||||
@objc public var downloadableNSURLs: [NSURL] {
|
@objc public var downloadableNSURLs: [NSURL] {
|
||||||
return self.downloadableURLs as [NSURL]
|
return self.downloadableURLs as [NSURL]
|
||||||
}
|
}
|
||||||
|
@ -904,31 +877,20 @@ extension URL {
|
||||||
}
|
}
|
||||||
|
|
||||||
var aesGcmKey: (key: Data, iv: Data)? {
|
var aesGcmKey: (key: Data, iv: Data)? {
|
||||||
guard let data = self.anchorData else { return nil }
|
guard let data = self.anchorData, data.count == 48 else { return nil }
|
||||||
let ivLength: Int
|
let iv = data.subdata(in: 0..<16)
|
||||||
switch data.count {
|
let key = data.subdata(in: 16..<48)
|
||||||
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)
|
|
||||||
return (key, iv)
|
return (key, iv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSString {
|
public extension NSString {
|
||||||
public var isSingleURLOnly: Bool {
|
public var isSingleURLOnly: Bool {
|
||||||
return (self as String).isSingleURLOnly
|
return (self as String).isSingleURLOnly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension String {
|
public extension String {
|
||||||
|
|
||||||
private var urlRanges: ([URL], [NSRange]) {
|
private var urlRanges: ([URL], [NSRange]) {
|
||||||
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
|
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.
|
/// 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 {
|
@objc public static func shouldDisplayMessage(_ message: OTRMessageProtocol, transaction: YapDatabaseReadTransaction) -> Bool {
|
||||||
// Always show media messages
|
// Always show media messages
|
|
@ -22,8 +22,11 @@ private class OutstandingActionInfo: Hashable, Equatable {
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
}
|
}
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
/// Needed so we can store the struct in a dictionary
|
||||||
hasher.combine(action.yapKey())
|
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
|
/// Needed so we can store the struct in a dictionary
|
||||||
extension OutstandingMessageInfo: Hashable {
|
extension OutstandingMessageInfo: Hashable {
|
||||||
func hash(into hasher: inout Hasher) {
|
var hashValue: Int {
|
||||||
hasher.combine(self.messageKey)
|
get {
|
||||||
hasher.combine(self.messageCollection)
|
return "\(self.messageKey)\(self.messageCollection)".hashValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +217,7 @@ public class MessageQueueHandler:NSObject {
|
||||||
switch message.messageSecurity {
|
switch message.messageSecurity {
|
||||||
case .plaintext:
|
case .plaintext:
|
||||||
self.waitingForMessage(message.uniqueId, messageCollection: message.messageCollection, messageSecurity:message.messageSecurity, completion: completion)
|
self.waitingForMessage(message.uniqueId, messageCollection: message.messageCollection, messageSecurity:message.messageSecurity, completion: completion)
|
||||||
OTRProtocolManager.sharedInstance().send(message)
|
OTRProtocolManager.shared.send(message)
|
||||||
break
|
break
|
||||||
case .plaintextWithOTR:
|
case .plaintextWithOTR:
|
||||||
self.sendOTRMessage(message: message, buddyKey: buddy.uniqueId, buddyUsername: buddy.username, accountUsername: account.username, accountProtocolStrintg: account.protocolTypeString(), requiresActiveSession: false, completion: completion)
|
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:
|
case .invalid:
|
||||||
fatalError("Invalid message security. This should never happen... so let's crash!")
|
fatalError("Invalid message security. This should never happen... so let's crash!")
|
||||||
break
|
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)
|
room.sendRoomMessage(message)
|
||||||
case .OMEMO:
|
case .OMEMO:
|
||||||
sendOMEMOMessage(message: message, accountProtocol: accountProtocol, completion: completion)
|
sendOMEMOMessage(message: message, accountProtocol: accountProtocol, completion: completion)
|
||||||
@unknown default:
|
|
||||||
fatalError("Invalid group message security. This should never happen.")
|
|
||||||
}
|
}
|
||||||
databaseConnection.readWrite { transaction in
|
databaseConnection.readWrite { transaction in
|
||||||
if let sentMessage = message.refetch(with: transaction)?.copyAsSelf() {
|
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
|
//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)
|
completion(true, 0.0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -350,7 +350,7 @@ public class MessageQueueHandler:NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get the XMPP procol manager associated with this message and therefore account
|
//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)
|
completion(true, 0.0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -385,7 +385,7 @@ public class MessageQueueHandler:NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get the XMPP procol manager associated with this message and therefore account
|
//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)
|
completion(true, 0.0)
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
@import MobileCoreServices;
|
@import MobileCoreServices;
|
||||||
@import OTRAssets;
|
@import OTRAssets;
|
||||||
#import "OTRUtilities.h"
|
#import "OTRUtilities.h"
|
||||||
|
#import "UIActionSheet+ChatSecure.h"
|
||||||
|
|
||||||
|
|
||||||
@interface OTRAttachmentPicker () <UINavigationControllerDelegate>
|
@interface OTRAttachmentPicker () <UINavigationControllerDelegate>
|
||||||
|
@ -110,7 +111,7 @@
|
||||||
finalImage = originalImage;
|
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];
|
[self.delegate attachmentPicker:self gotPhoto:finalImage withInfo:info];
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
@property (nonatomic, strong, readonly) OTRAudioItem *currentAudioItem;
|
@property (nonatomic, strong, readonly) OTRAudioItem *currentAudioItem;
|
||||||
@property (nonatomic, weak, readonly) OTRAudioControlsView *currentAudioControlsView;
|
@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;
|
- (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView;
|
||||||
|
|
|
@ -44,19 +44,18 @@
|
||||||
[self.currentAudioControlsView setTime:currentTime];
|
[self.currentAudioControlsView setTime:currentTime];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)playURL:(NSURL *)url error:(NSError **)error;
|
- (void)playURL:(NSURL *)url error:(NSError **)error;
|
||||||
{
|
{
|
||||||
AVURLAsset *asset = [AVURLAsset assetWithURL:url];
|
AVURLAsset *asset = [AVURLAsset assetWithURL:url];
|
||||||
self.duration = CMTimeGetSeconds(asset.duration);
|
self.duration = CMTimeGetSeconds(asset.duration);
|
||||||
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
|
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
|
||||||
error = nil;
|
error = nil;
|
||||||
BOOL result = [self.audioSessionManager playAudioWithURL:url error:error];
|
[self.audioSessionManager playAudioWithURL:url error:error];
|
||||||
|
|
||||||
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
|
self.currentAudioControlsView.playPuaseProgressView.status = OTRPlayPauseProgressViewStatusPause;
|
||||||
[self.currentAudioControlsView setTime:0];
|
[self.currentAudioControlsView setTime:0];
|
||||||
|
|
||||||
[self startLabelTimer];
|
[self startLabelTimer];
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)startLabelTimer
|
- (void)startLabelTimer
|
||||||
|
@ -94,13 +93,13 @@
|
||||||
|
|
||||||
#pragma - mark Public Methods
|
#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];
|
NSURL *audioURL = [[OTRMediaServer sharedInstance] urlForMediaItem:audioItem buddyUniqueId:buddyUniqueId];
|
||||||
|
|
||||||
_currentAudioItem = audioItem;
|
_currentAudioItem = audioItem;
|
||||||
|
|
||||||
return [self playURL:audioURL error:error];
|
[self playURL:audioURL error:error];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView
|
- (void)attachAudioControlsView:(OTRAudioControlsView *)audioControlsView
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
@property (nonatomic, weak) id<OTRAudioSessionManagerDelegate> delegate;
|
@property (nonatomic, weak) id<OTRAudioSessionManagerDelegate> delegate;
|
||||||
|
|
||||||
- (BOOL)playAudioWithURL:(NSURL *)url error:(NSError **)error;
|
- (void)playAudioWithURL:(NSURL *)url error:(NSError **)error;
|
||||||
- (void)pausePlaying;
|
- (void)pausePlaying;
|
||||||
- (void)resumePlaying;
|
- (void)resumePlaying;
|
||||||
- (void)stopPlaying;
|
- (void)stopPlaying;
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
- (NSURL *)currentPlayerURL;
|
- (NSURL *)currentPlayerURL;
|
||||||
- (BOOL)isPlaying;
|
- (BOOL)isPlaying;
|
||||||
|
|
||||||
- (BOOL)recordAudioToURL:(NSURL *)url error:(NSError **)error;
|
- (void)recordAudioToURL:(NSURL *)url error:(NSError **)error;
|
||||||
- (void)stopRecording;
|
- (void)stopRecording;
|
||||||
- (NSTimeInterval)currentTimeRecordTime;
|
- (NSTimeInterval)currentTimeRecordTime;
|
||||||
- (NSURL *)currentRecorderURL;
|
- (NSURL *)currentRecorderURL;
|
|
@ -49,7 +49,7 @@
|
||||||
#pragma - mark Public Methods
|
#pragma - mark Public Methods
|
||||||
|
|
||||||
////// Playing //////
|
////// Playing //////
|
||||||
- (BOOL)playAudioWithURL:(NSURL *)url error:(NSError **)error
|
- (void)playAudioWithURL:(NSURL *)url error:(NSError **)error
|
||||||
{
|
{
|
||||||
[self stopPlaying];
|
[self stopPlaying];
|
||||||
[self stopRecording];
|
[self stopRecording];
|
||||||
|
@ -57,22 +57,20 @@
|
||||||
|
|
||||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:error];
|
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:error];
|
||||||
if (error) {
|
if (error) {
|
||||||
return NO;
|
return;
|
||||||
}
|
}
|
||||||
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error];
|
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error];
|
||||||
if (error) {
|
if (error) {
|
||||||
return NO;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = nil;
|
error = nil;
|
||||||
self.currentPlayer = [self audioPlayerWithURL:url error:error];
|
self.currentPlayer = [self audioPlayerWithURL:url error:error];
|
||||||
if (error) {
|
if (error) {
|
||||||
return NO;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[self.currentPlayer play];
|
[self.currentPlayer play];
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)pausePlaying
|
- (void)pausePlaying
|
||||||
|
@ -121,32 +119,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
////// Recording //////
|
////// Recording //////
|
||||||
- (BOOL)recordAudioToURL:(NSURL *)url error:(NSError **)error
|
- (void)recordAudioToURL:(NSURL *)url error:(NSError **)error
|
||||||
{
|
{
|
||||||
[self stopRecording];
|
[self stopRecording];
|
||||||
[self stopPlaying];
|
[self stopPlaying];
|
||||||
|
|
||||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:error];
|
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:error];
|
||||||
if (error) {
|
if (error) {
|
||||||
return NO;
|
return;
|
||||||
}
|
}
|
||||||
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error];
|
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeSpokenAudio error:error];
|
||||||
if (error) {
|
if (error) {
|
||||||
return NO;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentRecorder = [self audioRecorderWithURL:url error:error];
|
self.currentRecorder = [self audioRecorderWithURL:url error:error];
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return NO;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentRecorder.meteringEnabled = YES;
|
self.currentRecorder.meteringEnabled = YES;
|
||||||
self.recordDecibelTimer = [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:@selector(updateDecibelRecording:) userInfo:nil repeats:YES];
|
self.recordDecibelTimer = [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:@selector(updateDecibelRecording:) userInfo:nil repeats:YES];
|
||||||
|
|
||||||
[self.currentRecorder record];
|
[self.currentRecorder record];
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)stopRecording
|
- (void)stopRecording
|
||||||
|
@ -183,9 +179,9 @@
|
||||||
|
|
||||||
#pragma - mark Private Methods
|
#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
|
- (AVPlayer *)audioPlayerWithURL:(NSURL *)url error:(NSError **)error
|
|
@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
directory:(nullable NSString*)directory
|
directory:(nullable NSString*)directory
|
||||||
withMediaStorage:(BOOL)withMediaStorage;
|
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;
|
- (BOOL)hasPassphrase;
|
|
@ -15,6 +15,7 @@
|
||||||
#import "OTRConstants.h"
|
#import "OTRConstants.h"
|
||||||
#import "OTRXMPPAccount.h"
|
#import "OTRXMPPAccount.h"
|
||||||
#import "OTRXMPPTorAccount.h"
|
#import "OTRXMPPTorAccount.h"
|
||||||
|
#import "OTRGoogleOAuthXMPPAccount.h"
|
||||||
#import "OTRAccount.h"
|
#import "OTRAccount.h"
|
||||||
#import "OTRIncomingMessage.h"
|
#import "OTRIncomingMessage.h"
|
||||||
#import "OTROutgoingMessage.h"
|
#import "OTROutgoingMessage.h"
|
||||||
|
@ -28,7 +29,7 @@
|
||||||
#import "OTRSignalSession.h"
|
#import "OTRSignalSession.h"
|
||||||
#import "OTRSettingsManager.h"
|
#import "OTRSettingsManager.h"
|
||||||
#import "OTRXMPPPresenceSubscriptionRequest.h"
|
#import "OTRXMPPPresenceSubscriptionRequest.h"
|
||||||
#import "ChatSecureCoreCompat-Swift.h"
|
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||||
|
|
||||||
|
|
||||||
@interface OTRDatabaseManager ()
|
@interface OTRDatabaseManager ()
|
||||||
|
@ -123,7 +124,6 @@
|
||||||
}
|
}
|
||||||
return keyData;
|
return keyData;
|
||||||
};
|
};
|
||||||
options.cipherCompatability = YapDatabaseCipherCompatability_Version3;
|
|
||||||
_databaseDirectory = [directory copy];
|
_databaseDirectory = [directory copy];
|
||||||
if (!_databaseDirectory) {
|
if (!_databaseDirectory) {
|
||||||
_databaseDirectory = [[self class] defaultYapDatabaseDirectory];
|
_databaseDirectory = [[self class] defaultYapDatabaseDirectory];
|
||||||
|
@ -134,14 +134,17 @@
|
||||||
}
|
}
|
||||||
NSString *databasePath = [self.databaseDirectory stringByAppendingPathComponent:name];
|
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.
|
// Stop trying to setup up the database. Something went wrong. Most likely the password is incorrect.
|
||||||
if (self.database == nil) {
|
if (self.database == nil) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.database.connectionDefaults.objectCacheLimit = 10000;
|
self.database.defaultObjectPolicy = YapDatabasePolicyShare;
|
||||||
|
self.database.defaultObjectCacheLimit = 10000;
|
||||||
|
|
||||||
[self setupConnections];
|
[self setupConnections];
|
||||||
|
|
||||||
|
@ -193,7 +196,7 @@
|
||||||
|
|
||||||
|
|
||||||
NSString *name = [YapDatabaseConstants extensionName:DatabaseExtensionNameMessageQueueBrokerViewName];
|
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
|
//Register Buddy username & displayName FTS and corresponding view
|
||||||
|
@ -296,17 +299,15 @@
|
||||||
return [[NSFileManager defaultManager] fileExistsAtPath:[self defaultYapDatabasePathWithName:OTRYapDatabaseName]];
|
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) {
|
if (rememeber) {
|
||||||
self.inMemoryPassphrase = nil;
|
self.inMemoryPassphrase = nil;
|
||||||
result = [SAMKeychain setPassword:passphrase forService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName error:error];
|
[SAMKeychain setPassword:passphrase forService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName error:error];
|
||||||
} else {
|
} else {
|
||||||
[SAMKeychain deletePasswordForService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName];
|
[SAMKeychain deletePasswordForService:kOTRServiceName account:OTRYapDatabasePassphraseAccountName];
|
||||||
self.inMemoryPassphrase = passphrase;
|
self.inMemoryPassphrase = passphrase;
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasPassphrase
|
- (BOOL)hasPassphrase
|
|
@ -14,7 +14,7 @@
|
||||||
#import "OTRIncomingMessage.h"
|
#import "OTRIncomingMessage.h"
|
||||||
#import "OTRLog.h"
|
#import "OTRLog.h"
|
||||||
#import "OTROutgoingMessage.h"
|
#import "OTROutgoingMessage.h"
|
||||||
#import "ChatSecureCoreCompat-Swift.h"
|
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||||
|
|
||||||
NSString *OTRArchiveFilteredConversationsName = @"OTRFilteredConversationsName";
|
NSString *OTRArchiveFilteredConversationsName = @"OTRFilteredConversationsName";
|
||||||
NSString *OTRBuddyFilteredConversationsName = @"OTRBuddyFilteredConversationsName";
|
NSString *OTRBuddyFilteredConversationsName = @"OTRBuddyFilteredConversationsName";
|
|
@ -21,7 +21,9 @@
|
||||||
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
|
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
@import Foundation;
|
@import Foundation;
|
||||||
@class OTRKit, OTRDataHandler, OTRFingerprint;
|
@import OTRKit;
|
||||||
|
|
||||||
|
@class OTRPushTLVHandler;
|
||||||
|
|
||||||
extern NSString * _Nonnull const OTRMessageStateDidChangeNotification;
|
extern NSString * _Nonnull const OTRMessageStateDidChangeNotification;
|
||||||
extern NSString * _Nonnull const OTRWillStartGeneratingPrivateKeyNotification;
|
extern NSString * _Nonnull const OTRWillStartGeneratingPrivateKeyNotification;
|
||||||
|
@ -41,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@property (nonatomic, strong, readonly) OTRKit *otrKit;
|
@property (nonatomic, strong, readonly) OTRKit *otrKit;
|
||||||
@property (nonatomic, strong, readonly) OTRDataHandler *dataHandler;
|
@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
|
* 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 "OTRMediaServer.h"
|
||||||
#import "OTRDatabaseManager.h"
|
#import "OTRDatabaseManager.h"
|
||||||
#import "OTRLog.h"
|
#import "OTRLog.h"
|
||||||
|
#import "OTRPushTLVHandler.h"
|
||||||
#import "OTRXMPPManager.h"
|
#import "OTRXMPPManager.h"
|
||||||
#import "OTRYapMessageSendAction.h"
|
#import "OTRYapMessageSendAction.h"
|
||||||
#import "ChatSecureCoreCompat-Swift.h"
|
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||||
|
|
||||||
@import AVFoundation;
|
@import AVFoundation;
|
||||||
@import XMPPFramework;
|
@import XMPPFramework;
|
||||||
|
@ -66,6 +67,7 @@ NSString *const OTRMessageStateKey = @"OTREncryptionManagerMessageStateKey";
|
||||||
_otrFingerprintCache = [[NSCache alloc] init];
|
_otrFingerprintCache = [[NSCache alloc] init];
|
||||||
_otrKit = [[OTRKit alloc] initWithDelegate:self dataPath:nil];
|
_otrKit = [[OTRKit alloc] initWithDelegate:self dataPath:nil];
|
||||||
_dataHandler = [[OTRDataHandler alloc] initWithOTRKit:self.otrKit delegate:self];
|
_dataHandler = [[OTRDataHandler alloc] initWithOTRKit:self.otrKit delegate:self];
|
||||||
|
_pushTLVHandler = [[OTRPushTLVHandler alloc] initWithOTRKit:self.otrKit delegate:nil];
|
||||||
_readConnection = OTRDatabaseManager.shared.readConnection;
|
_readConnection = OTRDatabaseManager.shared.readConnection;
|
||||||
NSArray *protectPaths = @[self.otrKit.privateKeyPath, self.otrKit.fingerprintsPath, self.otrKit.instanceTagsPath];
|
NSArray *protectPaths = @[self.otrKit.privateKeyPath, self.otrKit.fingerprintsPath, self.otrKit.instanceTagsPath];
|
||||||
for (NSString *path in protectPaths) {
|
for (NSString *path in protectPaths) {
|
||||||
|
@ -246,7 +248,7 @@ NSString *const OTRMessageStateKey = @"OTREncryptionManagerMessageStateKey";
|
||||||
} completionBlock:^{
|
} completionBlock:^{
|
||||||
if (!buddy) { return; }
|
if (!buddy) { return; }
|
||||||
message.buddyUniqueId = buddy.uniqueId;
|
message.buddyUniqueId = buddy.uniqueId;
|
||||||
[[OTRProtocolManager sharedInstance] sendMessage:message];
|
[OTRProtocolManager.shared sendMessage:message];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
|
||||||
//#865
|
//#865
|
||||||
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
|
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
|
||||||
buddyUniqueId:(NSString *)buddyUniqueId
|
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;
|
completionQueue:(nullable dispatch_queue_t)completionQueue;
|
||||||
|
|
||||||
- (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem
|
- (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem
|
||||||
|
@ -43,10 +43,8 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
|
||||||
buddyUniqueId:(NSString *)buddyUniqueId
|
buddyUniqueId:(NSString *)buddyUniqueId
|
||||||
error:(NSError* __autoreleasing *)error;
|
error:(NSError* __autoreleasing *)error;
|
||||||
|
|
||||||
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId;
|
+ (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId;
|
||||||
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash;
|
+ (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash;
|
||||||
|
|
||||||
- (void)vacuum:(dispatch_block_t)completion;
|
|
||||||
|
|
||||||
@property (class, nonatomic, readonly) OTRMediaFileManager *shared;
|
@property (class, nonatomic, readonly) OTRMediaFileManager *shared;
|
||||||
|
|
|
@ -52,10 +52,7 @@ NSString *const kOTRRootMediaDirectory = @"media";
|
||||||
- (BOOL)setupWithPath:(NSString *)path password:(NSString *)password
|
- (BOOL)setupWithPath:(NSString *)path password:(NSString *)password
|
||||||
{
|
{
|
||||||
_ioCipher = [[IOCipher alloc] initWithPath:path password:password];
|
_ioCipher = [[IOCipher alloc] initWithPath:path password:password];
|
||||||
if (!_ioCipher) {
|
return _ioCipher != nil;
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return [_ioCipher setCipherCompatibility:3];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)copyDataFromFilePath:(NSString *)filePath
|
- (void)copyDataFromFilePath:(NSString *)filePath
|
||||||
|
@ -134,7 +131,7 @@ completionQueue:(nullable dispatch_queue_t)completionQueue {
|
||||||
//#865
|
//#865
|
||||||
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
|
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
|
||||||
buddyUniqueId:(NSString *)buddyUniqueId
|
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 {
|
completionQueue:(nullable dispatch_queue_t)completionQueue {
|
||||||
if (!completionQueue) {
|
if (!completionQueue) {
|
||||||
completionQueue = dispatch_get_main_queue();
|
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
|
@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")
|
@objc public static let DeviceListUpdateNotificationName = Notification.Name(rawValue: "DeviceListUpdateNotification")
|
||||||
|
|
||||||
public let signalEncryptionManager:OTRAccountSignalEncryptionManager
|
open let signalEncryptionManager:OTRAccountSignalEncryptionManager
|
||||||
public let omemoStorageManager:OTROMEMOStorageManager
|
open let omemoStorageManager:OTROMEMOStorageManager
|
||||||
@objc public let accountYapKey:String
|
@objc open let accountYapKey:String
|
||||||
@objc public let databaseConnection:YapDatabaseConnection
|
@objc open let databaseConnection:YapDatabaseConnection
|
||||||
@objc open weak var omemoModule:OMEMOModule?
|
@objc open weak var omemoModule:OMEMOModule?
|
||||||
@objc open weak var omemoModuleQueue:DispatchQueue?
|
@objc open weak var omemoModuleQueue:DispatchQueue?
|
||||||
@objc open var callbackQueue:DispatchQueue
|
@objc open var callbackQueue:DispatchQueue
|
||||||
@objc public let workQueue:DispatchQueue
|
@objc open let workQueue:DispatchQueue
|
||||||
@objc public let messageStorage: MessageStorage
|
@objc open let messageStorage: MessageStorage
|
||||||
@objc public let roomManager: OTRXMPPRoomManager
|
@objc open let roomManager: OTRXMPPRoomManager
|
||||||
|
|
||||||
private var roomStorage: RoomStorage {
|
private var roomStorage: RoomStorage {
|
||||||
return roomManager.roomStorage
|
return roomManager.roomStorage
|
||||||
|
@ -295,7 +295,7 @@ import SignalProtocolObjC
|
||||||
//Strong self work here
|
//Strong self work here
|
||||||
var buddies: [OTRXMPPBuddy] = []
|
var buddies: [OTRXMPPBuddy] = []
|
||||||
self.databaseConnection.read { (transaction) in
|
self.databaseConnection.read { (transaction) in
|
||||||
buddies = buddyKeys.compactMap({ key in
|
buddies = buddyKeys.flatMap({ key in
|
||||||
OTRXMPPBuddy.fetchObject(withUniqueID: key, transaction: transaction)
|
OTRXMPPBuddy.fetchObject(withUniqueID: key, transaction: transaction)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -310,7 +310,10 @@ import SignalProtocolObjC
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
//Create the encrypted payload
|
//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.
|
// 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
|
let encryptClosure:(OMEMODevice) -> (OMEMOKeyData?) = { device in
|
||||||
|
@ -331,7 +334,7 @@ import SignalProtocolObjC
|
||||||
4. Remove optional values
|
4. Remove optional values
|
||||||
*/
|
*/
|
||||||
let buddyKeyDataArray = buddyKeys.flatMap({ key in
|
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
|
// 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
|
let ourDevicesKeyData = self.omemoStorageManager.getDevicesForOurAccount(trustedOnly: true).filter({ (device) -> Bool in
|
||||||
return device.deviceId.uint32Value != self.signalEncryptionManager.registrationId
|
return device.deviceId.uint32Value != self.signalEncryptionManager.registrationId
|
||||||
}).map(encryptClosure).compactMap{ $0 }
|
}).map(encryptClosure).flatMap{ $0 }
|
||||||
|
|
||||||
// Combine teh two arrays for all key data
|
// Combine teh two arrays for all key data
|
||||||
let keyDataArray = ourDevicesKeyData + buddyKeyDataArray
|
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)
|
let groupMessage = self.omemoModule?.message(forKeyData: keyDataArray, iv: ivData, to: destinationJID, payload: finalPayload, elementId: message.remoteMessageId)
|
||||||
{
|
{
|
||||||
groupMessage.addAttribute(withName: "type", stringValue: "groupchat")
|
groupMessage.addAttribute(withName: "type", stringValue: "groupchat")
|
||||||
groupMessage.addReceiptRequest()
|
|
||||||
self.omemoModule?.xmppStream?.send(groupMessage)
|
self.omemoModule?.xmppStream?.send(groupMessage)
|
||||||
} else if message is OTROutgoingMessage {
|
} else if message is OTROutgoingMessage {
|
||||||
self.omemoModule?.sendKeyData(keyDataArray, iv: ivData, to: destinationJID, payload: finalPayload, elementId: message.remoteMessageId)
|
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/>.
|
// along with ChatSecure. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
@class OTROutgoingMessage, OTRBuddy, OTRAccount;
|
@class OTROutgoingMessage, OTRBuddy, OTRAccount;
|
||||||
|
@protocol PushControllerProtocol;
|
||||||
|
|
||||||
typedef NS_ENUM(int, OTRProtocolType) {
|
typedef NS_ENUM(int, OTRProtocolType) {
|
||||||
OTRProtocolTypeNone = 0,
|
OTRProtocolTypeNone = 0,
|
|
@ -30,16 +30,16 @@
|
||||||
@class OTRAccount, OTRXMPPAccount, OTRBuddy, OTROutgoingMessage, PushController, OTRXMPPManager;
|
@class OTRAccount, OTRXMPPAccount, OTRBuddy, OTROutgoingMessage, PushController, OTRXMPPManager;
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
@interface OTRProtocolManager : NSObject
|
@interface _OTRProtocolManager : NSObject
|
||||||
|
|
||||||
@property (atomic, readonly) NSUInteger numberOfConnectedProtocols;
|
@property (atomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
|
||||||
@property (atomic, readonly) NSUInteger numberOfConnectingProtocols;
|
|
||||||
|
@property (atomic, readonly) NSArray<id<OTRProtocol>> *allProtocols;
|
||||||
|
|
||||||
- (BOOL)existsProtocolForAccount:(OTRAccount *)account;
|
- (BOOL)existsProtocolForAccount:(OTRAccount *)account;
|
||||||
- (nullable id <OTRProtocol>)protocolForAccount:(OTRAccount *)account;
|
- (nullable id <OTRProtocol>)protocolForAccount:(OTRAccount *)account;
|
||||||
- (nullable OTRXMPPManager*)xmppManagerForAccount:(OTRAccount *)account;
|
- (nullable OTRXMPPManager*)xmppManagerForAccount:(OTRAccount *)account;
|
||||||
- (void)removeProtocolForAccount:(OTRAccount *)account;
|
- (void)removeProtocolForAccount:(OTRAccount *)account;
|
||||||
- (void)setProtocol:(id <OTRProtocol>)protocol forAccount:(OTRAccount *)account;
|
|
||||||
|
|
||||||
- (BOOL)isAccountConnected:(OTRAccount *)account;
|
- (BOOL)isAccountConnected:(OTRAccount *)account;
|
||||||
|
|
||||||
|
@ -47,8 +47,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
- (void)loginAccount:(OTRAccount *)account userInitiated:(BOOL)userInitiated;
|
- (void)loginAccount:(OTRAccount *)account userInitiated:(BOOL)userInitiated;
|
||||||
- (void)loginAccounts:(NSArray<OTRAccount*> *)accounts;
|
- (void)loginAccounts:(NSArray<OTRAccount*> *)accounts;
|
||||||
- (void)goAwayForAllAccounts;
|
- (void)goAwayForAllAccounts;
|
||||||
- (void)disconnectAllAccounts;
|
|
||||||
- (void)disconnectAllAccountsSocketOnly:(BOOL)socketOnly timeout:(NSTimeInterval)timeout completionBlock:(nullable void (^)())completionBlock;
|
|
||||||
|
|
||||||
- (void)sendMessage:(OTROutgoingMessage *)message;
|
- (void)sendMessage:(OTROutgoingMessage *)message;
|
||||||
|
|
||||||
|
@ -58,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
+ (instancetype)sharedInstance; // Singleton method
|
+ (instancetype)sharedInstance; // Singleton method
|
||||||
|
|
||||||
/** Convenience for sharedInstance */
|
/** Convenience for sharedInstance */
|
||||||
@property (class, nonatomic, readonly) OTRProtocolManager *shared;
|
//@property (class, nonatomic, readonly) OTRProtocolManager *shared;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
|
@ -26,23 +26,24 @@
|
||||||
#import "OTRIncomingMessage.h"
|
#import "OTRIncomingMessage.h"
|
||||||
#import "OTROutgoingMessage.h"
|
#import "OTROutgoingMessage.h"
|
||||||
#import "OTRConstants.h"
|
#import "OTRConstants.h"
|
||||||
|
#import "OTROAuthRefresher.h"
|
||||||
|
#import "OTROAuthXMPPAccount.h"
|
||||||
#import "OTRDatabaseManager.h"
|
#import "OTRDatabaseManager.h"
|
||||||
|
#import "OTRPushTLVHandler.h"
|
||||||
#import <BBlock/NSObject+BBlock.h>
|
#import <BBlock/NSObject+BBlock.h>
|
||||||
@import YapDatabase;
|
@import YapDatabase;
|
||||||
|
|
||||||
@import KVOController;
|
@import KVOController;
|
||||||
@import OTRAssets;
|
@import OTRAssets;
|
||||||
#import "OTRLog.h"
|
#import "OTRLog.h"
|
||||||
#import "ChatSecureCoreCompat-Swift.h"
|
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||||
#import "OTRXMPPPresenceSubscriptionRequest.h"
|
#import "OTRXMPPPresenceSubscriptionRequest.h"
|
||||||
|
|
||||||
@interface OTRProtocolManager ()
|
@interface _OTRProtocolManager ()
|
||||||
@property (atomic, readwrite) NSUInteger numberOfConnectedProtocols;
|
//@property (atomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
|
||||||
@property (atomic, readwrite) NSUInteger numberOfConnectingProtocols;
|
|
||||||
@property (nonatomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation OTRProtocolManager
|
@implementation _OTRProtocolManager
|
||||||
|
|
||||||
-(instancetype)init
|
-(instancetype)init
|
||||||
{
|
{
|
||||||
|
@ -50,53 +51,36 @@
|
||||||
if(self)
|
if(self)
|
||||||
{
|
{
|
||||||
_protocolManagers = [[NSMutableDictionary alloc] init];
|
_protocolManagers = [[NSMutableDictionary alloc] init];
|
||||||
|
|
||||||
_numberOfConnectedProtocols = 0;
|
|
||||||
_numberOfConnectingProtocols = 0;
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSArray<id<OTRProtocol>> *)allProtocols {
|
||||||
|
return self.protocolManagers.allValues;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)removeProtocolForAccount:(OTRAccount *)account
|
- (void)removeProtocolForAccount:(OTRAccount *)account
|
||||||
{
|
{
|
||||||
NSParameterAssert(account);
|
NSParameterAssert(account);
|
||||||
if (!account) { return; }
|
if (!account) { return; }
|
||||||
id<OTRProtocol> protocol = nil;
|
id<OTRProtocol> protocol = nil;
|
||||||
@synchronized (self) {
|
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
|
||||||
}
|
|
||||||
if (protocol && [protocol respondsToSelector:@selector(disconnect)]) {
|
if (protocol && [protocol respondsToSelector:@selector(disconnect)]) {
|
||||||
[protocol disconnect];
|
[protocol disconnect];
|
||||||
}
|
}
|
||||||
[self.KVOController unobserve:protocol];
|
[self.protocolManagers removeObjectForKey:account.uniqueId];
|
||||||
@synchronized (self) {
|
|
||||||
[self.protocolManagers removeObjectForKey:account.uniqueId];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addProtocol:(id<OTRProtocol>)protocol forAccount:(OTRAccount *)account
|
- (void)addProtocol:(id<OTRProtocol>)protocol forAccount:(OTRAccount *)account
|
||||||
{
|
{
|
||||||
@synchronized (self) {
|
[self.protocolManagers setObject:protocol forKey:account.uniqueId];
|
||||||
[self.protocolManagers setObject:protocol forKey:account.uniqueId];
|
|
||||||
}
|
|
||||||
[self.KVOController observe:protocol keyPath:NSStringFromSelector(@selector(loginStatus)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld action:@selector(protocolDidChange:)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)existsProtocolForAccount:(OTRAccount *)account
|
- (BOOL)existsProtocolForAccount:(OTRAccount *)account
|
||||||
{
|
{
|
||||||
NSParameterAssert(account.uniqueId);
|
NSParameterAssert(account.uniqueId);
|
||||||
if (!account.uniqueId) { return NO; }
|
if (!account.uniqueId) { return NO; }
|
||||||
@synchronized (self) {
|
return [self.protocolManagers objectForKey:account.uniqueId] != nil;
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id <OTRProtocol>)protocolForAccount:(OTRAccount *)account
|
- (id <OTRProtocol>)protocolForAccount:(OTRAccount *)account
|
||||||
|
@ -104,14 +88,12 @@
|
||||||
NSParameterAssert(account);
|
NSParameterAssert(account);
|
||||||
if (!account.uniqueId) { return nil; }
|
if (!account.uniqueId) { return nil; }
|
||||||
id <OTRProtocol> protocol = nil;
|
id <OTRProtocol> protocol = nil;
|
||||||
@synchronized (self) {
|
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
if(!protocol)
|
||||||
if(!protocol)
|
{
|
||||||
{
|
protocol = [[[account protocolClass] alloc] initWithAccount:account];
|
||||||
protocol = [[[account protocolClass] alloc] initWithAccount:account];
|
if (protocol && account.uniqueId) {
|
||||||
if (protocol && account.uniqueId) {
|
[self addProtocol:protocol forAccount:account];
|
||||||
[self addProtocol:protocol forAccount:account];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return protocol;
|
return protocol;
|
||||||
|
@ -133,7 +115,22 @@
|
||||||
if (!account) { return; }
|
if (!account) { return; }
|
||||||
id <OTRProtocol> protocol = [self protocolForAccount:account];
|
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
|
- (void)loginAccount:(OTRAccount *)account
|
||||||
|
@ -149,89 +146,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)goAwayForAllAccounts {
|
- (void)goAwayForAllAccounts {
|
||||||
@synchronized (self) {
|
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) {
|
||||||
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) {
|
if ([protocol isKindOfClass:[OTRXMPPManager class]]) {
|
||||||
if ([protocol isKindOfClass:[OTRXMPPManager class]]) {
|
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
||||||
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
[xmpp goAway];
|
||||||
[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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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)isAccountConnected:(OTRAccount *)account;
|
||||||
{
|
{
|
||||||
BOOL connected = NO;
|
BOOL connected = NO;
|
||||||
id <OTRProtocol> protocol = nil;
|
id <OTRProtocol> protocol = nil;
|
||||||
@synchronized (self) {
|
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
||||||
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
|
||||||
}
|
|
||||||
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
||||||
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
||||||
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
||||||
|
@ -250,7 +177,7 @@
|
||||||
OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction];
|
OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction];
|
||||||
account = [OTRAccount fetchObjectWithUniqueID:buddy.accountUniqueId transaction:transaction];
|
account = [OTRAccount fetchObjectWithUniqueID:buddy.accountUniqueId transaction:transaction];
|
||||||
} completionBlock:^{
|
} completionBlock:^{
|
||||||
OTRProtocolManager * protocolManager = [OTRProtocolManager sharedInstance];
|
OTRProtocolManager * protocolManager = [OTRProtocolManager shared];
|
||||||
id<OTRProtocol> protocol = [protocolManager protocolForAccount:account];
|
id<OTRProtocol> protocol = [protocolManager protocolForAccount:account];
|
||||||
[protocol sendMessage:message];
|
[protocol sendMessage:message];
|
||||||
}];
|
}];
|
||||||
|
@ -264,7 +191,7 @@
|
||||||
if (otrFingerprint.length == 40) {
|
if (otrFingerprint.length == 40) {
|
||||||
message = [message stringByAppendingFormat:@"\n%@", otrFingerprint];
|
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];
|
NSMutableArray<OTRAccount*> *accounts = [NSMutableArray array];
|
||||||
[OTRDatabaseManager.shared.readConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
[OTRDatabaseManager.shared.readConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||||
NSArray<OTRAccount*> *allAccounts = [OTRAccount allAccountsWithTransaction:transaction];
|
NSArray<OTRAccount*> *allAccounts = [OTRAccount allAccountsWithTransaction:transaction];
|
||||||
|
@ -289,7 +216,7 @@
|
||||||
title = account.username;
|
title = account.username;
|
||||||
}
|
}
|
||||||
UIAlertAction *action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
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];
|
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 "OTRIntSetting.h"
|
||||||
#import "OTRCertificateSetting.h"
|
#import "OTRCertificateSetting.h"
|
||||||
#import "OTRUtilities.h"
|
#import "OTRUtilities.h"
|
||||||
#import "ChatSecureCoreCompat-Swift.h"
|
#import <ChatSecureCore/ChatSecureCore-Swift.h>
|
||||||
|
|
||||||
#import "OTRUtilities.h"
|
#import "OTRUtilities.h"
|
||||||
|
|
||||||
|
@ -64,11 +64,11 @@
|
||||||
[settingsGroups addObject:accountsGroup];
|
[settingsGroups addObject:accountsGroup];
|
||||||
|
|
||||||
if (OTRBranding.allowsDonation) {
|
if (OTRBranding.allowsDonation) {
|
||||||
NSString *donateTitle = nil;
|
NSString *donateTitle = DONATE_STRING();
|
||||||
if (TransactionObserver.hasValidReceipt) {
|
if (TransactionObserver.hasValidReceipt) {
|
||||||
donateTitle = [NSString stringWithFormat:@"%@ ✅", DONATE_STRING()];
|
donateTitle = [NSString stringWithFormat:@"%@ ✅", DONATE_STRING()];
|
||||||
} else {
|
} else {
|
||||||
donateTitle = [NSString stringWithFormat:@"%@ 🎁", DONATE_STRING()];
|
donateTitle = [NSString stringWithFormat:@"%@ 🆕", DONATE_STRING()];
|
||||||
}
|
}
|
||||||
OTRDonateSetting *donateSetting = [[OTRDonateSetting alloc] initWithTitle:donateTitle description:nil];
|
OTRDonateSetting *donateSetting = [[OTRDonateSetting alloc] initWithTitle:donateTitle description:nil];
|
||||||
//donateSetting.imageName = @"29-heart.png";
|
//donateSetting.imageName = @"29-heart.png";
|
||||||
|
@ -101,18 +101,16 @@
|
||||||
description:ALLOW_DB_PASSPHRASE_BACKUP_DESCRIPTION_STRING()
|
description:ALLOW_DB_PASSPHRASE_BACKUP_DESCRIPTION_STRING()
|
||||||
settingsKey:kOTRSettingKeyAllowDBPassphraseBackup];
|
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]];
|
OTRViewSetting *pushViewSetting = [[OTRViewSetting alloc] initWithTitle:CHATSECURE_PUSH_STRING() description:nil viewControllerClass:[EnablePushViewController class]];
|
||||||
pushViewSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
pushViewSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||||
OTRSettingsGroup *pushGroup = [[OTRSettingsGroup alloc] initWithTitle:PUSH_TITLE_STRING() settings:@[pushViewSetting]];
|
OTRSettingsGroup *pushGroup = [[OTRSettingsGroup alloc] initWithTitle:PUSH_TITLE_STRING() settings:@[pushViewSetting]];
|
||||||
[settingsGroups addObject:pushGroup];
|
[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];
|
OTRSettingsGroup *chatSettingsGroup = [[OTRSettingsGroup alloc] initWithTitle:CHAT_STRING() settings:chatSettings];
|
||||||
[settingsGroups addObject:chatSettingsGroup];
|
[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*/
|
/** Generate a new account key*/
|
||||||
func generateNewIdenityKeyPairForAccountKey(_ accountKey:String) -> OTRAccountSignalIdentity
|
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.
|
* This class implements the SignalStore protocol. One OTRSignalStorageManager should be created per account key/collection.
|
||||||
*/
|
*/
|
||||||
open class OTRSignalStorageManager: NSObject {
|
open class OTRSignalStorageManager: NSObject {
|
||||||
public let accountKey:String
|
open let accountKey:String
|
||||||
public let databaseConnection:YapDatabaseConnection
|
open let databaseConnection:YapDatabaseConnection
|
||||||
open weak var delegate:OTRSignalStorageManagerDelegate?
|
open weak var delegate:OTRSignalStorageManagerDelegate?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -232,7 +232,7 @@ open class OTRSignalStorageManager: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = YapDatabaseQuery(string: "WHERE (OTRYapDatabaseSignalPreKeyAccountKeySecondaryIndexColumnName) = ?", parameters: ["\(self.accountKey)"])
|
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 {
|
guard let preKey = object as? OTRSignalPreKey else {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -7,14 +7,12 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
@import Foundation;
|
@import Foundation;
|
||||||
@class CPAProxyManager;
|
@import CPAProxy;
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
@interface OTRTorManager : NSObject
|
@interface OTRTorManager : NSObject
|
||||||
|
|
||||||
@property (nonatomic, strong, nullable) CPAProxyManager *torManager;
|
@property (nonatomic, strong) CPAProxyManager *torManager;
|
||||||
|
|
||||||
+ (instancetype) sharedInstance;
|
+ (instancetype) sharedInstance;
|
||||||
|
|
||||||
@end
|
@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 {
|
@objc open class OTRXMPPChangeAvatar: NSObject {
|
||||||
|
|
||||||
open weak var xmppvCardTempModule:XMPPvCardTempModule?
|
open weak var xmppvCardTempModule:XMPPvCardTempModule?
|
||||||
public let photoData:Data
|
open let photoData:Data
|
||||||
fileprivate let workQueue = DispatchQueue(label: "OTRXMPPChangeAvatar-workQueue", attributes: [])
|
fileprivate let workQueue = DispatchQueue(label: "OTRXMPPChangeAvatar-workQueue", attributes: [])
|
||||||
|
|
||||||
fileprivate var waitingForVCardFetch:Bool = false
|
fileprivate var waitingForVCardFetch:Bool = false
|
|
@ -7,7 +7,8 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import YapDatabase
|
import YapDatabase.YapDatabaseFullTextSearch
|
||||||
|
import YapDatabase.YapDatabaseSearchResultsView
|
||||||
|
|
||||||
open class OTRYapExtensions:NSObject {
|
open class OTRYapExtensions:NSObject {
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ open class OTRYapExtensions:NSObject {
|
||||||
let usernameColumnName = BuddyFTSColumnName.username.name()
|
let usernameColumnName = BuddyFTSColumnName.username.name()
|
||||||
let displayNameColumnName = BuddyFTSColumnName.displayName.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 {
|
guard let buddy = object as? OTRBuddy else {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import YapDatabase
|
import YapDatabase.YapDatabaseView
|
||||||
|
|
||||||
@objc public protocol OTRYapViewHandlerDelegateProtocol:NSObjectProtocol {
|
@objc public protocol OTRYapViewHandlerDelegateProtocol:NSObjectProtocol {
|
||||||
|
|
|
@ -10,13 +10,12 @@ import Foundation
|
||||||
import ChatSecure_Push_iOS
|
import ChatSecure_Push_iOS
|
||||||
import YapDatabase
|
import YapDatabase
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import OTRKit
|
|
||||||
|
|
||||||
@objc public protocol PushControllerProtocol {
|
@objc public protocol PushControllerProtocol {
|
||||||
|
|
||||||
func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void)
|
func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void)
|
||||||
func receiveRemoteNotification(_ notification:[AnyHashable: Any], completion: @escaping (_ buddy:OTRBuddy?, _ 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 {
|
@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.
|
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
|
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 storage: PushStorageProtocol
|
||||||
let write = connections?.write {
|
|
||||||
let storage = PushStorage(databaseConnection: write)
|
|
||||||
finishStorageSetup(storage: storage)
|
|
||||||
_storage = storage
|
|
||||||
}
|
|
||||||
return _storage
|
|
||||||
}
|
|
||||||
var apiClient : Client
|
var apiClient : Client
|
||||||
var callbackQueue = OperationQueue()
|
var callbackQueue = OperationQueue()
|
||||||
|
var otrListener: PushOTRListener?
|
||||||
let timeBufffer:TimeInterval = 60*60*24
|
let timeBufffer:TimeInterval = 60*60*24
|
||||||
var pubsubEndpoint: NSString?
|
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.apiClient = Client(baseUrl: baseURL, urlSessionConfiguration: sessionConfiguration, account: nil)
|
||||||
|
self.storage = PushStorage(databaseConnection: databaseConnection)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
// 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
|
||||||
private func finishStorageSetup(storage: PushStorageProtocol) {
|
let readConnection = OTRDatabaseManager.shared.database?.newConnection()
|
||||||
var account: Account? = nil;
|
var account: Account? = nil;
|
||||||
connections?.read.asyncRead({ (transaction) in
|
readConnection?.asyncRead({ (transaction) in
|
||||||
account = storage.thisDevicePushAccount()
|
account = self.storage.thisDevicePushAccount()
|
||||||
}, completionBlock: {
|
}, completionBlock: {
|
||||||
self.apiClient.account = account
|
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?) {
|
public func deactivate(completion: (()->())?, callbackQueue: DispatchQueue?) {
|
||||||
apiClient.unregister { (success, error) in
|
apiClient.unregister { (success, error) in
|
||||||
PushController.setPushPreference(.disabled)
|
PushController.setPushPreference(.disabled)
|
||||||
self.storage?.deleteEverything(completion: completion, callbackQueue: callbackQueue)
|
self.storage.deleteEverything(completion: completion, callbackQueue: callbackQueue)
|
||||||
self.apiClient.account = nil
|
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
|
self.apiClient.registerNewUser(username, password: password, email: nil) {[weak self] (account, error) -> Void in
|
||||||
if let newAccount = account {
|
if let newAccount = account {
|
||||||
self?.apiClient.account = newAccount
|
self?.apiClient.account = newAccount
|
||||||
self?.storage?.saveThisAccount(newAccount)
|
self?.storage.saveThisAccount(newAccount)
|
||||||
self?.callbackQueue.addOperation({ () -> Void in
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(true, nil)
|
completion(true, nil)
|
||||||
})
|
})
|
||||||
|
@ -164,14 +158,14 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
|
|
||||||
- returns: The push storage object that controls storing and retrieving push tokens
|
- 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
|
return self.storage
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc open func registerThisDevice(_ apns:String, completion:@escaping (_ success: Bool, _ error: Error?) -> Void) {
|
@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
|
self.apiClient.registerDevice(apns, name: nil, deviceID: nil) {[weak self] (device, error) -> Void in
|
||||||
if let newDevice = device {
|
if let newDevice = device {
|
||||||
self?.storage?.saveThisDevice(newDevice)
|
self?.storage.saveThisDevice(newDevice)
|
||||||
self?.callbackQueue.addOperation({ () -> Void in
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(true, nil)
|
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) {
|
@objc open func updateThisDevice(_ apns:String, completion:@escaping (_ success: Bool, _ error: Error?) -> Void) {
|
||||||
DispatchQueue.global().async {[weak self] () -> Void in
|
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
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
|
|
||||||
completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil))
|
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
|
self?.apiClient.updateDevice(id, APNSToken: apns, name: device.name, deviceID: device.id, completion: {[weak self] (device, error) -> Void in
|
||||||
if let newDevice = device {
|
if let newDevice = device {
|
||||||
self?.storage?.saveThisDevice(newDevice)
|
self?.storage.saveThisDevice(newDevice)
|
||||||
self?.callbackQueue.addOperation({ () -> Void in
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(true, nil)
|
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) {
|
@objc open func getNewPushToken(_ buddyKey:String?, completion:@escaping (_ token:TokenContainer?,_ error:NSError?) -> Void) {
|
||||||
DispatchQueue.global().async {[weak self] () -> Void in
|
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
|
self?.updateUnusedTokenStore({[weak self] (success, error) -> Void in
|
||||||
if success {
|
if success {
|
||||||
self?.getNewPushToken(buddyKey, completion: completion)
|
self?.getNewPushToken(buddyKey, completion: completion)
|
||||||
|
@ -253,11 +247,11 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self?.storage?.removeUnusedToken(tokenContainer)
|
self?.storage.removeUnusedToken(tokenContainer)
|
||||||
if let buddyKey = buddyKey {
|
if let buddyKey = buddyKey {
|
||||||
self?.storage?.associateBuddy(tokenContainer, buddyKey: buddyKey)
|
self?.storage.associateBuddy(tokenContainer, buddyKey: buddyKey)
|
||||||
} else {
|
} else {
|
||||||
self?.storage?.saveUsedToken(tokenContainer)
|
self?.storage.saveUsedToken(tokenContainer)
|
||||||
}
|
}
|
||||||
self?.callbackQueue.addOperation({ () -> Void in
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(tokenContainer, nil)
|
completion(tokenContainer, nil)
|
||||||
|
@ -276,7 +270,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tokenContainer.pushToken = newToken
|
tokenContainer.pushToken = newToken
|
||||||
self?.storage?.saveUnusedToken(tokenContainer)
|
self?.storage.saveUnusedToken(tokenContainer)
|
||||||
self?.callbackQueue.addOperation({ () -> Void in
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(true,nil)
|
completion(true,nil)
|
||||||
})
|
})
|
||||||
|
@ -299,7 +293,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
@objc open func updateUnusedTokenStore(_ completion:@escaping (_ success:Bool,_ error:Error?) -> Void) {
|
@objc open func updateUnusedTokenStore(_ completion:@escaping (_ success:Bool,_ error:Error?) -> Void) {
|
||||||
|
|
||||||
DispatchQueue.global().async {[weak self] () -> Void in
|
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
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil))
|
completion(false, NSError.chatSecureError(PushError.noPushDevice, userInfo: nil))
|
||||||
})
|
})
|
||||||
|
@ -308,11 +302,11 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
|
|
||||||
var tokensToCreate:UInt = 0
|
var tokensToCreate:UInt = 0
|
||||||
|
|
||||||
guard let unusedTokens = self?.storage?.numberUnusedTokens() else {
|
guard let unusedTokens = self?.storage.numberUnusedTokens() else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let minimumCount = self?.storage?.unusedTokenStoreMinimum() else {
|
guard let minimumCount = self?.storage.unusedTokenStoreMinimum() else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +363,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
tokenContainer.pushToken = token
|
tokenContainer.pushToken = token
|
||||||
tokenContainer.endpoint = endpointURL
|
tokenContainer.endpoint = endpointURL
|
||||||
tokenContainer.buddyKey = buddyKey
|
tokenContainer.buddyKey = buddyKey
|
||||||
self?.storage?.saveUsedToken(tokenContainer)
|
self?.storage.saveUsedToken(tokenContainer)
|
||||||
self?.callbackQueue.addOperation({ () -> Void in
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(true, nil)
|
completion(true, nil)
|
||||||
})
|
})
|
||||||
|
@ -412,7 +406,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
public func receiveRemoteNotification(_ notification: [AnyHashable : Any], completion: @escaping (OTRBuddy?, NSError?) -> Void) {
|
public func receiveRemoteNotification(_ notification: [AnyHashable : Any], completion: @escaping (OTRBuddy?, NSError?) -> Void) {
|
||||||
do {
|
do {
|
||||||
let message = try Deserializer.messageFromPushDictionary(notification)
|
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
|
self.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(nil, NSError.chatSecureError(PushError.noBuddyFound, userInfo: nil))
|
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) {
|
@objc open func sendKnock(_ buddyKey:String, completion:@escaping (_ success:Bool, _ error:NSError?) -> Void) {
|
||||||
DispatchQueue.global().async {[weak self] () -> Void in
|
DispatchQueue.global().async {[weak self] () -> Void in
|
||||||
do {
|
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
|
self?.callbackQueue.addOperation({ () -> Void in
|
||||||
completion(false, NSError.chatSecureError(PushError.noTokensFound, userInfo: nil))
|
completion(false, NSError.chatSecureError(PushError.noTokensFound, userInfo: nil))
|
||||||
})
|
})
|
||||||
|
@ -464,7 +458,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
|
|
||||||
if ((error as NSError?)?.code == 404) {
|
if ((error as NSError?)?.code == 404) {
|
||||||
// Token was revoked or was never valid.
|
// 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
|
// Retry and see if we have another token to use or will error out with noTokensFound
|
||||||
self?.sendKnock(buddyKey, completion: completion)
|
self?.sendKnock(buddyKey, completion: completion)
|
||||||
}
|
}
|
||||||
|
@ -547,7 +541,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
//MARK: OTRPushTLVHandlerDelegate
|
//MARK: OTRPushTLVHandlerDelegate
|
||||||
@objc open func receivePush(_ tlvData: Data!, username: String!, accountName: String!, protocolString: String!, fingerprint:OTRFingerprint!) {
|
@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 {
|
guard let buddyKey = buddy?.uniqueId else {
|
||||||
//Error fetching buddy
|
//Error fetching buddy
|
||||||
|
@ -566,7 +560,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't store tokens for Tor accounts
|
// 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 {
|
if account?.accountType == OTRAccountType.xmppTor {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -584,7 +578,7 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
|
|
||||||
//MARK: Push Preferences
|
//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 {
|
guard let value = UserDefaults.standard.object(forKey: kOTRPushEnabledKey) as? NSNumber else {
|
||||||
return .undefined
|
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
|
var bool = false
|
||||||
if preference == .enabled {
|
if preference == .enabled {
|
||||||
bool = true
|
bool = true
|
||||||
|
@ -607,79 +601,79 @@ open class PushController: NSObject, PushControllerProtocol {
|
||||||
//MARK: Utility
|
//MARK: Utility
|
||||||
|
|
||||||
/// If callbackQueue is nil, it will complete on main queue
|
/// If callbackQueue is nil, it will complete on main queue
|
||||||
public func gatherPushInfo(completion: @escaping (PushInfo?) -> (), callbackQueue: DispatchQueue = DispatchQueue.main) {
|
public func gatherPushInfo(completion: @escaping (PushInfo) -> (), callbackQueue: DispatchQueue?) {
|
||||||
guard let storage = self.storage else {
|
|
||||||
callbackQueue.async {
|
|
||||||
completion(nil)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var pubsubEndpoint: String?
|
var pubsubEndpoint: String?
|
||||||
var pushPermitted = false
|
var pushPermitted = false
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
group.enter()
|
group.enter()
|
||||||
DispatchQueue.main.async {
|
pushPermitted = PushController.canReceivePushNotifications() // This will be async in a later version when we do iOS 10 refactor
|
||||||
PushController.canReceivePushNotifications(completion: { (enabled) in
|
|
||||||
pushPermitted = enabled
|
|
||||||
group.leave()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
group.enter()
|
group.enter()
|
||||||
|
group.leave()
|
||||||
getPubsubEndpoint { (endpoint, error) in
|
getPubsubEndpoint { (endpoint, error) in
|
||||||
pubsubEndpoint = endpoint
|
pubsubEndpoint = endpoint
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
group.notify(queue: .main) {
|
let queue = callbackQueue ?? DispatchQueue.main
|
||||||
let device = storage.thisDevice()
|
group.notify(queue: DispatchQueue.global(qos: .default)) {
|
||||||
|
let device = self.storage.thisDevice()
|
||||||
let newPushInfo = PushInfo(
|
let newPushInfo = PushInfo(
|
||||||
pushAPIURL: self.apiClient.baseUrl,
|
pushAPIURL: self.apiClient.baseUrl,
|
||||||
hasPushAccount: storage.hasPushAccount(),
|
hasPushAccount: self.storage.hasPushAccount(),
|
||||||
numUsedTokens: storage.numberUsedTokens(),
|
numUsedTokens: self.storage.numberUsedTokens(),
|
||||||
numUnusedTokens: storage.numberUnusedTokens(),
|
numUnusedTokens: self.storage.numberUnusedTokens(),
|
||||||
pushPermitted: pushPermitted,
|
pushPermitted: pushPermitted,
|
||||||
pubsubEndpoint: pubsubEndpoint,
|
pubsubEndpoint: pubsubEndpoint,
|
||||||
device: device)
|
device: device)
|
||||||
callbackQueue.async {
|
queue.async {
|
||||||
completion(newPushInfo)
|
completion(newPushInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public static func registerForPushNotifications() {
|
@objc open static func registerForPushNotifications() {
|
||||||
let center = UNUserNotificationCenter.current()
|
if #available(iOS 10.0, *) {
|
||||||
center.requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { (granted, error) in
|
let center = UNUserNotificationCenter.current()
|
||||||
DispatchQueue.main.async(execute: {
|
center.requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { (granted, error) in
|
||||||
// TODO: Handle push registration error
|
DispatchQueue.main.async(execute: {
|
||||||
let app = UIApplication.shared
|
// TODO: Handle push registration error
|
||||||
NotificationCenter.default.post(name: Notification.Name(rawValue: OTRUserNotificationsChanged), object: app.delegate, userInfo:nil)
|
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 {
|
} 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 ChatSecure_Push_iOS
|
||||||
import YapDatabase
|
import YapDatabase
|
||||||
|
|
||||||
@objc public protocol PushStorageProtocol: AnyObject {
|
@objc public protocol PushStorageProtocol: class {
|
||||||
func thisDevicePushAccount() -> Account?
|
func thisDevicePushAccount() -> Account?
|
||||||
func hasPushAccount() -> Bool
|
func hasPushAccount() -> Bool
|
||||||
func saveThisAccount(_ account:Account)
|
func saveThisAccount(_ account:Account)
|
||||||
|
@ -132,9 +132,11 @@ class PushStorage: NSObject, PushStorageProtocol {
|
||||||
func unusedToken() -> TokenContainer? {
|
func unusedToken() -> TokenContainer? {
|
||||||
var tokenContainer:TokenContainer? = nil
|
var tokenContainer:TokenContainer? = nil
|
||||||
self.databaseConnection.read { (transaction) -> Void in
|
self.databaseConnection.read { (transaction) -> Void in
|
||||||
transaction.iterateKeysAndObjects(inCollection: PushYapCollections.unusedTokenCollection.rawValue, using: { (key, tc: TokenContainer, stop) -> Void in
|
transaction.enumerateKeysAndObjects(inCollection: PushYapCollections.unusedTokenCollection.rawValue, using: { (key, object, stop) -> Void in
|
||||||
tokenContainer = tc
|
if let tc = object as? TokenContainer {
|
||||||
stop = true
|
tokenContainer = tc
|
||||||
|
}
|
||||||
|
stop.initialize(to: true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return tokenContainer
|
return tokenContainer
|
||||||
|
@ -234,16 +236,18 @@ class PushStorage: NSObject, PushStorageProtocol {
|
||||||
self.databaseConnection.asyncReadWrite({ (transaction) in
|
self.databaseConnection.asyncReadWrite({ (transaction) in
|
||||||
let collection = PushYapCollections.unusedTokenCollection.rawValue
|
let collection = PushYapCollections.unusedTokenCollection.rawValue
|
||||||
var removeKeyArray:[String] = []
|
var removeKeyArray:[String] = []
|
||||||
transaction.iterateKeysAndObjects(inCollection: collection, using: { (key, token: TokenContainer, stop) in
|
transaction.enumerateKeysAndObjects(inCollection: collection, using: { (key, object, stop) in
|
||||||
//Check that there is an expires date otherwise remove
|
if let token = object as? TokenContainer {
|
||||||
guard let expiresDate = token.pushToken?.expires else {
|
//Check that there is an expires date otherwise remove
|
||||||
removeKeyArray.append(token.uniqueId)
|
guard let expiresDate = token.pushToken?.expires else {
|
||||||
return
|
removeKeyArray.append(token.uniqueId)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Check that the date is farther in the future than currentDate + timeBuffer
|
// Check that the date is farther in the future than currentDate + timeBuffer
|
||||||
if (Date(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .orderedDescending ) {
|
if (Date(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .orderedDescending ) {
|
||||||
removeKeyArray.append(token.uniqueId)
|
removeKeyArray.append(token.uniqueId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -177,7 +177,9 @@ extension ServerCheck: XMPPPushDelegate {
|
||||||
public func pushModule(_ module: XMPPPushModule, readyWithCapabilities caps: XMLElement, jid: XMPPJID) {
|
public func pushModule(_ module: XMPPPushModule, readyWithCapabilities caps: XMLElement, jid: XMPPJID) {
|
||||||
// This _should_ be handled elsewhere in OTRServerCapabilities
|
// This _should_ be handled elsewhere in OTRServerCapabilities
|
||||||
// Not sure why it's not working properly
|
// Not sure why it's not working properly
|
||||||
result.capabilities?[.XEP0357]?.status = .Available
|
if var caps = result.capabilities {
|
||||||
|
caps[.XEP0357]?.status = .Available
|
||||||
|
}
|
||||||
checkReady()
|
checkReady()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,11 +22,11 @@ open class ShareControllerURLSource: NSObject, UIActivityItemSource {
|
||||||
return self.url!
|
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
|
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()
|
var name = SOMEONE_STRING()
|
||||||
if let displayName = account?.username {
|
if let displayName = account?.username {
|
||||||
name = displayName
|
name = displayName
|
||||||
|
@ -38,7 +38,7 @@ open class ShareControllerURLSource: NSObject, UIActivityItemSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
open class ShareController: NSObject {
|
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)])
|
let fingerprintTypes = Set([NSNumber(value: OTRFingerprintType.OTR.rawValue as Int32)])
|
||||||
|
|
||||||
account.generateShareURL(withFingerprintTypes: fingerprintTypes, completion: { (url: URL?, error: Error?) -> Void in
|
account.generateShareURL(withFingerprintTypes: fingerprintTypes, completion: { (url: URL?, error: Error?) -> Void in
|
||||||
|
@ -48,7 +48,7 @@ open class ShareController: NSObject {
|
||||||
|
|
||||||
let qrCodeActivity = OTRQRCodeActivity()
|
let qrCodeActivity = OTRQRCodeActivity()
|
||||||
let activityViewController = UIActivityViewController(activityItems: [self.getShareSource(account, url: url)], applicationActivities: [qrCodeActivity])
|
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 ppc = activityViewController.popoverPresentationController {
|
||||||
if let view = sender as? UIView {
|
if let view = sender as? UIView {
|
||||||
ppc.sourceView = view
|
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)
|
return ShareControllerURLSource(account: account, url: url)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -49,8 +49,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
if ([super activate:aXmppStream])
|
if ([super activate:aXmppStream])
|
||||||
{
|
{
|
||||||
[self performBlock:^{
|
[self performBlock:^{
|
||||||
[self.capabilities addDelegate:self delegateQueue:self->moduleQueue];
|
[self.capabilities addDelegate:self delegateQueue:moduleQueue];
|
||||||
self->_tracker = [[XMPPIDTracker alloc] initWithStream:aXmppStream dispatchQueue:self->moduleQueue];
|
_tracker = [[XMPPIDTracker alloc] initWithStream:aXmppStream dispatchQueue:moduleQueue];
|
||||||
}];
|
}];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -60,8 +60,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
|
|
||||||
- (void) deactivate {
|
- (void) deactivate {
|
||||||
[self performBlock:^{
|
[self performBlock:^{
|
||||||
[self->_tracker removeAllIDs];
|
[_tracker removeAllIDs];
|
||||||
self->_tracker = nil;
|
_tracker = nil;
|
||||||
[self.capabilities removeDelegate:self];
|
[self.capabilities removeDelegate:self];
|
||||||
self.discoveredServices = nil;
|
self.discoveredServices = nil;
|
||||||
}];
|
}];
|
||||||
|
@ -74,7 +74,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
- (BOOL) autoDiscoverServices {
|
- (BOOL) autoDiscoverServices {
|
||||||
__block BOOL discover = NO;
|
__block BOOL discover = NO;
|
||||||
[self performBlock:^{
|
[self performBlock:^{
|
||||||
discover = self->_autoDiscoverServices;
|
discover = _autoDiscoverServices;
|
||||||
}];
|
}];
|
||||||
return discover;
|
return discover;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
- (void) setAutoDiscoverServices:(BOOL)autoDiscoverServices {
|
- (void) setAutoDiscoverServices:(BOOL)autoDiscoverServices {
|
||||||
[self performBlockAsync:^{
|
[self performBlockAsync:^{
|
||||||
[self willChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))];
|
[self willChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))];
|
||||||
self->_autoDiscoverServices = autoDiscoverServices;
|
_autoDiscoverServices = autoDiscoverServices;
|
||||||
[self didChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))];
|
[self didChangeValueForKey:NSStringFromSelector(@selector(autoDiscoverServices))];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
- (void) setDiscoveredServices:(NSArray<NSXMLElement *> * _Nullable)discoveredServices {
|
- (void) setDiscoveredServices:(NSArray<NSXMLElement *> * _Nullable)discoveredServices {
|
||||||
[self performBlockAsync:^{
|
[self performBlockAsync:^{
|
||||||
[self willChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))];
|
[self willChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))];
|
||||||
self->_discoveredServices = [discoveredServices copy];
|
_discoveredServices = [discoveredServices copy];
|
||||||
[self didChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))];
|
[self didChangeValueForKey:NSStringFromSelector(@selector(discoveredServices))];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
- (nullable NSArray<NSXMLElement *>*) discoveredServices {
|
- (nullable NSArray<NSXMLElement *>*) discoveredServices {
|
||||||
__block NSArray<NSXMLElement *> *services = nil;
|
__block NSArray<NSXMLElement *> *services = nil;
|
||||||
[self performBlock:^{
|
[self performBlock:^{
|
||||||
services = self->_discoveredServices;
|
services = _discoveredServices;
|
||||||
}];
|
}];
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
- (void) setAllCapabilities:(NSDictionary<XMPPJID *,NSXMLElement *> * _Nullable)allCapabilities {
|
- (void) setAllCapabilities:(NSDictionary<XMPPJID *,NSXMLElement *> * _Nullable)allCapabilities {
|
||||||
[self performBlockAsync:^{
|
[self performBlockAsync:^{
|
||||||
[self willChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))];
|
[self willChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))];
|
||||||
self->_allCapabilities = [allCapabilities copy];
|
_allCapabilities = [allCapabilities copy];
|
||||||
[self didChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))];
|
[self didChangeValueForKey:NSStringFromSelector(@selector(allCapabilities))];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
- (nullable NSDictionary<XMPPJID*, NSXMLElement *> *) allCapabilities {
|
- (nullable NSDictionary<XMPPJID*, NSXMLElement *> *) allCapabilities {
|
||||||
__block NSDictionary<XMPPJID*, NSXMLElement *> *caps = nil;
|
__block NSDictionary<XMPPJID*, NSXMLElement *> *caps = nil;
|
||||||
[self performBlock:^{
|
[self performBlock:^{
|
||||||
caps = self->_allCapabilities;
|
caps = _allCapabilities;
|
||||||
}];
|
}];
|
||||||
return caps;
|
return caps;
|
||||||
}
|
}
|
||||||
|
@ -122,8 +122,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
- (NSXMLElement*) streamFeatures {
|
- (NSXMLElement*) streamFeatures {
|
||||||
__block NSXMLElement *features = nil;
|
__block NSXMLElement *features = nil;
|
||||||
[self performBlock:^{
|
[self performBlock:^{
|
||||||
if (self->xmppStream.state >= STATE_XMPP_POST_NEGOTIATION) {
|
if (xmppStream.state >= STATE_XMPP_POST_NEGOTIATION) {
|
||||||
features = [[self->xmppStream.rootElement elementForName:@"stream:features"] copy];
|
features = [[xmppStream.rootElement elementForName:@"stream:features"] copy];
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
return features;
|
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.
|
// This is a public method, so it may be invoked on any thread/queue.
|
||||||
[self performBlockAsync:^{
|
[self performBlockAsync:^{
|
||||||
if (self->_hasRequestedServices) return; // We've already requested services
|
if (_hasRequestedServices) return; // We've already requested services
|
||||||
if (self->_discoveredServices) { // We've already discovered the services
|
if (_discoveredServices) { // We've already discovered the services
|
||||||
[self->multicastDelegate serverCapabilities:self didDiscoverServices:self->_discoveredServices];
|
[multicastDelegate serverCapabilities:self didDiscoverServices:_discoveredServices];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString *toStr = self->xmppStream.myJID.domain;
|
NSString *toStr = xmppStream.myJID.domain;
|
||||||
NSXMLElement *query = [NSXMLElement elementWithName:@"query"
|
NSXMLElement *query = [NSXMLElement elementWithName:@"query"
|
||||||
xmlns:XMPPDiscoverItemsNamespace];
|
xmlns:XMPPDiscoverItemsNamespace];
|
||||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"get"
|
XMPPIQ *iq = [XMPPIQ iqWithType:@"get"
|
||||||
to:[XMPPJID jidWithString:toStr]
|
to:[XMPPJID jidWithString:toStr]
|
||||||
elementID:[self->xmppStream generateUUID]
|
elementID:[xmppStream generateUUID]
|
||||||
child:query];
|
child:query];
|
||||||
if (!iq) {
|
if (!iq) {
|
||||||
XMPPLogInfo(@"OTRServerCapabilities: Could not discover services for stream: %@", self->xmppStream);
|
XMPPLogInfo(@"OTRServerCapabilities: Could not discover services for stream: %@", xmppStream);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
XMPPLogInfo(@"OTRServerCapabilities: Discovering services for domain %@...", toStr);
|
XMPPLogInfo(@"OTRServerCapabilities: Discovering services for domain %@...", toStr);
|
||||||
|
@ -173,8 +173,8 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
selector:@selector(handleDiscoverServicesQueryIQ:withInfo:)
|
selector:@selector(handleDiscoverServicesQueryIQ:withInfo:)
|
||||||
timeout:15];
|
timeout:15];
|
||||||
|
|
||||||
[self->xmppStream sendElement:iq];
|
[xmppStream sendElement:iq];
|
||||||
self->_hasRequestedServices = YES;
|
_hasRequestedServices = YES;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,11 +184,11 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
*/
|
*/
|
||||||
- (void)fetchAllCapabilities {
|
- (void)fetchAllCapabilities {
|
||||||
[self performBlockAsync:^{
|
[self performBlockAsync:^{
|
||||||
if (self->xmppStream.state != STATE_XMPP_CONNECTED) {
|
if (xmppStream.state != STATE_XMPP_CONNECTED) {
|
||||||
XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not connected. %@", self);
|
XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not connected. %@", self);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (![self->xmppStream isAuthenticated]) {
|
if (![xmppStream isAuthenticated]) {
|
||||||
XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not authenticated. %@", self);
|
XMPPLogError(@"OTRServerCapabilities: fetchAllCapabilities error - not authenticated. %@", self);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -209,11 +209,11 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
// [self.allJIDs addObject:myJID];
|
// [self.allJIDs addObject:myJID];
|
||||||
[self.allJIDs addObject:myJID.domainJID];
|
[self.allJIDs addObject:myJID.domainJID];
|
||||||
}
|
}
|
||||||
if (!self->_autoDiscoverServices) {
|
if (!_autoDiscoverServices) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[self discoverServices];
|
[self discoverServices];
|
||||||
if (!self->_autoFetchAllCapabilities) {
|
if (!_autoFetchAllCapabilities) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[self fetchCapabilitiesForJIDs:self.allJIDs];
|
[self fetchCapabilitiesForJIDs:self.allJIDs];
|
||||||
|
@ -241,7 +241,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
- (void)handleDiscoverServicesQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info
|
- (void)handleDiscoverServicesQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info
|
||||||
{
|
{
|
||||||
[self performBlockAsync:^{
|
[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;
|
NSError *error = nil;
|
||||||
if (!iq) {
|
if (!iq) {
|
||||||
NSDictionary *dict = @{NSLocalizedDescriptionKey : @"The request timed out.",
|
NSDictionary *dict = @{NSLocalizedDescriptionKey : @"The request timed out.",
|
||||||
|
@ -262,15 +262,15 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
XMPPLogError(@"OTRServerCapabilities: Error discovering services for domain %@: %@", self->xmppStream.myJID.domain, error);
|
XMPPLogError(@"OTRServerCapabilities: Error discovering services for domain %@: %@", xmppStream.myJID.domain, error);
|
||||||
[self->multicastDelegate serverCapabilitiesFailedToDiscoverServices:self
|
[multicastDelegate serverCapabilitiesFailedToDiscoverServices:self
|
||||||
withError:error];
|
withError:error];
|
||||||
|
|
||||||
// Deal with the race condition where we've already fetched your JID and server caps
|
// Deal with the race condition where we've already fetched your JID and server caps
|
||||||
// but for whatever reason fetching services fails.
|
// but for whatever reason fetching services fails.
|
||||||
NSSet *allCaps = [NSSet setWithArray:self.allCapabilities.allKeys];
|
NSSet *allCaps = [NSSet setWithArray:self.allCapabilities.allKeys];
|
||||||
if ([self.allJIDs isEqualToSet:allCaps]) {
|
if ([self.allJIDs isEqualToSet:allCaps]) {
|
||||||
[self->multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities];
|
[multicastDelegate serverCapabilities:self didDiscoverCapabilities:self.allCapabilities];
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -279,19 +279,16 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
xmlns:XMPPDiscoverItemsNamespace];
|
xmlns:XMPPDiscoverItemsNamespace];
|
||||||
|
|
||||||
NSArray<NSXMLElement*> *items = [query elementsForName:@"item"];
|
NSArray<NSXMLElement*> *items = [query elementsForName:@"item"];
|
||||||
if (!items) {
|
|
||||||
items = @[];
|
|
||||||
}
|
|
||||||
self.discoveredServices = [items copy];
|
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
|
// Recursively fetch service capabilities if needed
|
||||||
if (!self->_autoFetchAllCapabilities) {
|
if (!_autoFetchAllCapabilities) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
NSSet<XMPPJID*> *jids = [self jidsFromItems:self->_discoveredServices];
|
NSSet<XMPPJID*> *jids = [self jidsFromItems:_discoveredServices];
|
||||||
[self.allJIDs unionSet:jids];
|
[self.allJIDs unionSet:jids];
|
||||||
[self fetchCapabilitiesForJIDs:jids];
|
[self fetchCapabilitiesForJIDs:jids];
|
||||||
}];
|
}];
|
||||||
|
@ -343,7 +340,7 @@ static NSString *const OTRServerCapabilitiesErrorDomain = @"OTRServerCapabilitie
|
||||||
}
|
}
|
||||||
[newCaps setObject:caps forKey:jid];
|
[newCaps setObject:caps forKey:jid];
|
||||||
self.allCapabilities = newCaps;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
id <XMPPCapabilitiesStorage> storage = self.capabilities.xmppCapabilitiesStorage;
|
id <XMPPCapabilitiesStorage> storage = self.capabilities.xmppCapabilitiesStorage;
|
||||||
BOOL fetched = [storage areCapabilitiesKnownForJID:jid xmppStream:self->xmppStream];
|
BOOL fetched = [storage areCapabilitiesKnownForJID:jid xmppStream:xmppStream];
|
||||||
if (fetched) {
|
if (fetched) {
|
||||||
NSXMLElement *capabilities = [storage capabilitiesForJID:jid xmppStream:self->xmppStream];
|
NSXMLElement *capabilities = [storage capabilitiesForJID:jid xmppStream:xmppStream];
|
||||||
if (capabilities) {
|
if (capabilities) {
|
||||||
[newCaps setObject:capabilities forKey:jid];
|
[newCaps setObject:capabilities forKey:jid];
|
||||||
*stop = YES;
|
*stop = YES;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue