mirror of https://github.com/oxen-io/session-ios
Merge branch 'dev' into server-deletion
commit
af3e74b73b
@ -1 +1 @@
|
||||
Subproject commit 960a820c76a95a64cfb1c6ad721c68f73a8f27b9
|
||||
Subproject commit 8b30c2d91fe7f9743350dd30521b5ca74e78766c
|
@ -1,49 +0,0 @@
|
||||
import PromiseKit
|
||||
|
||||
internal extension LokiAPI {
|
||||
|
||||
private static let receivedMessageHashValuesKey = "receivedMessageHashValuesKey"
|
||||
private static let receivedMessageHashValuesCollection = "receivedMessageHashValuesCollection"
|
||||
|
||||
internal static func getLastMessageHashValue(for target: LokiAPITarget) -> String? {
|
||||
var result: String? = nil
|
||||
// Uses a read/write connection because getting the last message hash value also removes expired messages as needed
|
||||
// TODO: This shouldn't be the case; a getter shouldn't have an unexpected side effect
|
||||
storage.dbReadWriteConnection.readWrite { transaction in
|
||||
result = storage.getLastMessageHash(forServiceNode: target.address, transaction: transaction)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal static func setLastMessageHashValue(for target: LokiAPITarget, hashValue: String, expirationDate: UInt64) {
|
||||
storage.dbReadWriteConnection.readWrite { transaction in
|
||||
storage.setLastMessageHash(forServiceNode: target.address, hash: hashValue, expiresAt: expirationDate, transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
internal static func getReceivedMessageHashValues() -> Set<String>? {
|
||||
var result: Set<String>? = nil
|
||||
storage.dbReadConnection.read { transaction in
|
||||
result = transaction.object(forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection) as! Set<String>?
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal static func setReceivedMessageHashValues(to receivedMessageHashValues: Set<String>) {
|
||||
storage.dbReadWriteConnection.readWrite { transaction in
|
||||
transaction.setObject(receivedMessageHashValues, forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal extension Promise {
|
||||
|
||||
internal func recoveringNetworkErrorsIfNeeded() -> Promise<T> {
|
||||
return recover() { error -> Promise<T> in
|
||||
switch error {
|
||||
case NetworkManagerError.taskError(_, let underlyingError): throw underlyingError
|
||||
default: throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
import PromiseKit
|
||||
|
||||
private typealias Callback = () -> Void
|
||||
|
||||
public extension LokiAPI {
|
||||
private static var isLongPolling = false
|
||||
private static var shouldStopPolling = false
|
||||
private static var usedSnodes = [LokiAPITarget]()
|
||||
private static var cancels = [Callback]()
|
||||
|
||||
/// Start long polling.
|
||||
/// This will send a notification if new messages were received
|
||||
@objc public static func startLongPollingIfNeeded() {
|
||||
guard !isLongPolling else { return }
|
||||
isLongPolling = true
|
||||
shouldStopPolling = false
|
||||
|
||||
print("[Loki] Started long polling.")
|
||||
|
||||
longPoll()
|
||||
}
|
||||
|
||||
/// Stop long polling
|
||||
@objc public static func stopLongPolling() {
|
||||
shouldStopPolling = true
|
||||
isLongPolling = false
|
||||
usedSnodes.removeAll()
|
||||
cancelAllPromises()
|
||||
|
||||
print("[Loki] Stopped long polling.")
|
||||
}
|
||||
|
||||
/// The long polling loop
|
||||
private static func longPoll() {
|
||||
// This is here so we can stop the infinite loop
|
||||
guard !shouldStopPolling else { return }
|
||||
|
||||
getSwarm(for: userHexEncodedPublicKey).then { _ -> Guarantee<[Result<Void>]> in
|
||||
var promises = [Promise<Void>]()
|
||||
let connections = 3
|
||||
for i in 0..<connections {
|
||||
let (promise, cancel) = openConnection()
|
||||
promises.append(promise)
|
||||
cancels.append(cancel)
|
||||
}
|
||||
return when(resolved: promises)
|
||||
}.done { _ in
|
||||
// Since all promises are complete, we can clear the cancels
|
||||
cancelAllPromises()
|
||||
|
||||
// Keep long polling until it is stopped
|
||||
longPoll()
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
|
||||
private static func cancelAllPromises() {
|
||||
cancels.forEach { cancel in cancel() }
|
||||
cancels.removeAll()
|
||||
}
|
||||
|
||||
private static func getUnusedSnodes() -> [LokiAPITarget] {
|
||||
let snodes = LokiAPI.swarmCache[userHexEncodedPublicKey] ?? []
|
||||
return snodes.filter { !usedSnodes.contains($0) }
|
||||
}
|
||||
|
||||
/// Open a connection to an unused snode and get messages from it
|
||||
private static func openConnection() -> (Promise<Void>, cancel: Callback) {
|
||||
var isCancelled = false
|
||||
|
||||
let cancel = {
|
||||
isCancelled = true
|
||||
}
|
||||
|
||||
func connectToNextSnode() -> Promise<Void> {
|
||||
guard let nextSnode = getUnusedSnodes().first else {
|
||||
// We don't have anymore unused snodes
|
||||
return Promise.value(())
|
||||
}
|
||||
|
||||
// Add the snode to the used array
|
||||
usedSnodes.append(nextSnode)
|
||||
|
||||
func getMessagesInfinitely(from target: LokiAPITarget) -> Promise<Void> {
|
||||
// The only way to exit the infinite loop is to throw an error 3 times or cancel
|
||||
return getRawMessages(from: target, usingLongPolling: true).then { rawResponse -> Promise<Void> in
|
||||
// Check if we need to abort
|
||||
guard !isCancelled else { throw PMKError.cancelled }
|
||||
|
||||
// Process the messages
|
||||
let messages = parseRawMessagesResponse(rawResponse, from: target)
|
||||
|
||||
// Send our messages as a notification
|
||||
NotificationCenter.default.post(name: .newMessagesReceived, object: nil, userInfo: ["messages": messages])
|
||||
|
||||
// Continue fetching if we haven't cancelled
|
||||
return getMessagesInfinitely(from: target)
|
||||
}.retryingIfNeeded(maxRetryCount: 3)
|
||||
}
|
||||
|
||||
// Keep getting messages for this snode
|
||||
// If we errored out then connect to the next snode
|
||||
return getMessagesInfinitely(from: nextSnode).recover { _ -> Promise<Void> in
|
||||
// Cancelled, so just return successfully
|
||||
guard !isCancelled else { return Promise.value(()) }
|
||||
|
||||
// Connect to the next snode if we haven't cancelled
|
||||
// We also need to remove the cached snode so we don't contact it again
|
||||
dropIfNeeded(nextSnode, hexEncodedPublicKey: userHexEncodedPublicKey)
|
||||
return connectToNextSnode()
|
||||
}
|
||||
}
|
||||
|
||||
// Keep connecting to snodes
|
||||
return (connectToNextSnode(), cancel)
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
import PromiseKit
|
||||
|
||||
@objc(LKLongPoller)
|
||||
public final class LokiLongPoller : NSObject {
|
||||
private let onMessagesReceived: ([SSKProtoEnvelope]) -> Void
|
||||
private let storage = OWSPrimaryStorage.shared()
|
||||
private var hasStarted = false
|
||||
private var hasStopped = false
|
||||
private var connections = Set<Promise<Void>>()
|
||||
private var usedSnodes = Set<LokiAPITarget>()
|
||||
|
||||
// MARK: Settings
|
||||
private let connectionCount = 3
|
||||
private let retryInterval: TimeInterval = 4
|
||||
|
||||
// MARK: Convenience
|
||||
private var userHexEncodedPublicKey: String { return OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey }
|
||||
|
||||
// MARK: Initialization
|
||||
@objc public init(onMessagesReceived: @escaping ([SSKProtoEnvelope]) -> Void) {
|
||||
self.onMessagesReceived = onMessagesReceived
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: Public API
|
||||
@objc public func startIfNeeded() {
|
||||
guard !hasStarted else { return }
|
||||
print("[Loki] Started long polling.")
|
||||
hasStarted = true
|
||||
hasStopped = false
|
||||
openConnections()
|
||||
}
|
||||
|
||||
@objc public func stopIfNeeded() {
|
||||
guard !hasStopped else { return }
|
||||
print("[Loki] Stopped long polling.")
|
||||
hasStarted = false
|
||||
hasStopped = true
|
||||
usedSnodes.removeAll()
|
||||
}
|
||||
|
||||
// MARK: Private API
|
||||
private func openConnections() {
|
||||
guard !hasStopped else { return }
|
||||
LokiAPI.getSwarm(for: userHexEncodedPublicKey).then { [weak self] _ -> Guarantee<[Result<Void>]> in
|
||||
guard let strongSelf = self else { return Guarantee.value([Result<Void>]()) }
|
||||
strongSelf.usedSnodes.removeAll()
|
||||
let connections: [Promise<Void>] = (0..<strongSelf.connectionCount).map { _ in
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
strongSelf.openConnectionToNextSnode(seal: seal)
|
||||
return promise
|
||||
}
|
||||
strongSelf.connections = Set(connections)
|
||||
return when(resolved: connections)
|
||||
}.ensure { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
Timer.scheduledTimer(withTimeInterval: strongSelf.retryInterval, repeats: false) { _ in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.openConnections()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func openConnectionToNextSnode(seal: Resolver<Void>) {
|
||||
let swarm = LokiAPI.swarmCache[userHexEncodedPublicKey] ?? []
|
||||
let userHexEncodedPublicKey = self.userHexEncodedPublicKey
|
||||
let unusedSnodes = Set(swarm).subtracting(usedSnodes)
|
||||
if !unusedSnodes.isEmpty {
|
||||
let nextSnode = unusedSnodes.randomElement()!
|
||||
usedSnodes.insert(nextSnode)
|
||||
print("[Loki] Opening long polling connection to \(nextSnode).")
|
||||
longPoll(nextSnode, seal: seal).catch { [weak self] error in
|
||||
print("[Loki] Long polling connection to \(nextSnode) failed; dropping it and switching to next snode.")
|
||||
LokiAPI.dropIfNeeded(nextSnode, hexEncodedPublicKey: userHexEncodedPublicKey)
|
||||
self?.openConnectionToNextSnode(seal: seal)
|
||||
}
|
||||
} else {
|
||||
seal.fulfill(())
|
||||
}
|
||||
}
|
||||
|
||||
private func longPoll(_ target: LokiAPITarget, seal: Resolver<Void>) -> Promise<Void> {
|
||||
return LokiAPI.getRawMessages(from: target, usingLongPolling: true).then { [weak self] rawResponse -> Promise<Void> in
|
||||
guard let strongSelf = self, !strongSelf.hasStopped else { return Promise.value(()) }
|
||||
let messages = LokiAPI.parseRawMessagesResponse(rawResponse, from: target)
|
||||
strongSelf.onMessagesReceived(messages)
|
||||
return strongSelf.longPoll(target, seal: seal)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
|
||||
## Privacy Policy
|
||||
|
||||
Loki Messenger is a client application that interacts with the “Service Node” network, which is not wholly owned or run by LAG Foundation Ltd (the publishers of Loki Messenger). This privacy policy can only apply between the user and LAG Foundation Ltd and is limited to any information that LAG Foundation Ltd collects by providing the Loki Messenger client application. Your messages are always encrypted when they travel over the internet, so they can never be shared or viewed (in plaintext) by anyone but yourself and the intended recipients.
|
||||
|
||||
### Information you provide
|
||||
|
||||
Messages. LAG Foundation and the Service Node network cannot decrypt or otherwise access the content of your messages. The Service Node network queues end-to-end encrypted messages on its servers for delivery to devices that are temporarily offline (e.g. a phone whose battery has died) or can not otherwise be reached. Your message history is stored on your own device or devices.
|
||||
|
||||
User Support. If you contact any member of the Loki Messenger team, any personal data you may share with us is kept only for the purposes of researching the issue and contacting you about your case.
|
||||
|
||||
### Information that is or may be automatically collected
|
||||
|
||||
Messages. Due to the decentralised nature of the Loki Messenger, your encrypted messages will be stored on the Service Node network. Although no one, including the Service Nodes, can see the contents of the messages, in beta non Lokinet integrated Loki Messenger your metadata may be collected by Service Nodes on the network. This may include who messages are being sent to, who they are being sent by, IP addresses, and public key information. No personally identifying information is included in messages, however if Service Node operators are able to identify which is your specific public key (account number) through some other means (for instance, by posting it on Twitter), they may be able to link you to your Messenger identity. LAG Foundation Ltd is only responsible for a small subset of the Service Node network and can not guarantee that your metadata is not being tracked when using the Beta Loki Messenger version. In this version of the Loki Messenger, there is limited metadata privacy protection. We will ensure that this is made clear in the app, until an update which includes much stronger protections is released.
|
||||
|
||||
|
||||
|
||||
Usage Information. the Loki Messenger beta may automatically send us usage information periodically. All personally identifying information is stripped from this data that you provide to us. This data is critical to us to understand how we can improve the Loki Messenger by understanding how users interact with the app, and what is going wrong when the app stops working. This data includes: how often you use the app, how many people you are talking to, how many messages you are sending, how long you spend in the app each day, and any error messages that the app encounters. We do not collect your IP address, public key, contact list, conversation history, or any other type of personal information. When the Full version of Loki messenger is released this data collection will become opt in.
|
||||
|
||||
|
||||
|
||||
You are able to audit how this data is collected in the app by visiting [https://github.com/loki-project/loki-messenger-ios](https://github.com/loki-project/loki-messenger-ios) and inspecting the code of our analytics module.
|
||||
|
||||
### Information we may share
|
||||
|
||||
Instances where LAG Foundation Ltd may need to share your data:
|
||||
|
||||
- To meet any applicable law, regulation, legal process or enforceable governmental request.
|
||||
|
||||
- To enforce applicable Terms, including investigation of potential violations.
|
||||
|
||||
- To detect, prevent, or otherwise address fraud, security, or technical issues.
|
||||
|
||||
- To protect against harm to the rights, property, or safety of LAG Foundation Ltd, our users, or the public as required or permitted by law.
|
||||
|
||||
|
||||
Note: The LAG can only share the data it has obtained, which is very minimal even in the beta program, when Loki Messenger is released live such data will be anonymised before collection limiting the usefulness of such data for purposes other than analysing our broad userbase.
|
||||
|
||||
Third Parties
|
||||
|
||||
LAG Foundation Ltd distributes the Loki messenger iOS application through two primary sources, the Apple Store and directly through Github. These third party service providers may also collect data on your download of and usage of Loki Messenger. This data collection is our of our hands, although it is information we can view and use as stated in this policy. For more information about how these platforms collect and use information, you should read their privacy policies prior to using them.
|
||||
|
||||
You may opt out of this data collection by building the iOS binaries from the repository [https://github.com/loki-project/loki-messenger-ios](https://github.com/loki-project/loki-messenger-ios) and sideloading the app onto a modified Apple device.
|
||||
|
||||
### Updates
|
||||
|
||||
We will update this privacy policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our Services confirms your acceptance of our updated Privacy Policy.
|
||||
|
||||
### Contact Us
|
||||
|
||||
If you have questions about our Privacy Policy please contact us at team@loki.network.
|
||||
|
||||
Last Updated 28/08/2019
|
Loading…
Reference in New Issue