Merge branch 'dev' into server-deletion

pull/43/head
Mikunj 6 years ago
commit af3e74b73b

@ -1 +1 @@
Subproject commit 960a820c76a95a64cfb1c6ad721c68f73a8f27b9
Subproject commit 8b30c2d91fe7f9743350dd30521b5ca74e78766c

@ -28,7 +28,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC9FA9C4CB8DFE0155BD054D6E704C10"
BlueprintIdentifier = "11A6AC8599513843417D550671469823"
BuildableName = "SignalServiceKit.framework"
BlueprintName = "SignalServiceKit"
ReferencedContainer = "container:Pods/Pods.xcodeproj">

@ -30,7 +30,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -47,7 +47,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>11</string>
<string>14</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LOGS_EMAIL</key>

@ -8,6 +8,7 @@ extern NSString *const AppDelegateStoryboardMain;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
- (void)stopLongPollerIfNeeded;
- (void)createGroupChatsIfNeeded;
- (void)createRSSFeedsIfNeeded;
- (void)startGroupChatPollersIfNeeded;

@ -63,7 +63,10 @@ static NSTimeInterval launchStartedAt;
@property (nonatomic) BOOL hasInitialRootViewController;
@property (nonatomic) BOOL areVersionMigrationsComplete;
@property (nonatomic) BOOL didAppLaunchFail;
// Loki
@property (nonatomic) LKP2PServer *lokiP2PServer;
@property (nonatomic) LKLongPoller *lokiLongPoller;
@property (nonatomic) LKGroupChatPoller *lokiPublicChatPoller;
@property (nonatomic) LKRSSFeedPoller *lokiNewsFeedPoller;
@property (nonatomic) LKRSSFeedPoller *lokiMessengerUpdatesFeedPoller;
@ -176,7 +179,7 @@ static NSTimeInterval launchStartedAt;
[DDLog flushLog];
[LKAPI stopLongPolling];
[self stopLongPollerIfNeeded];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
@ -195,7 +198,8 @@ static NSTimeInterval launchStartedAt;
[DDLog flushLog];
[LKAPI stopLongPolling];
[self stopLongPollerIfNeeded];
if (self.lokiP2PServer) { [self.lokiP2PServer stop]; }
}
@ -762,7 +766,7 @@ static NSTimeInterval launchStartedAt;
[Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
// Loki: Start long polling
[LKAPI startLongPollingIfNeeded];
[self startLongPollerIfNeeded];
// Loki: Tell our friends that we are online
[LKP2PAPI broadcastOnlineStatus];
@ -1360,8 +1364,8 @@ static NSTimeInterval launchStartedAt;
// For non-legacy users, read receipts are on by default.
[self.readReceiptManager setAreReadReceiptsEnabled:YES];
// Start long polling
[LKAPI startLongPollingIfNeeded];
// Loki: Start long polling
[self startLongPollerIfNeeded];
}
}
@ -1407,23 +1411,6 @@ static NSTimeInterval launchStartedAt;
[UIViewController attemptRotationToDeviceOrientation];
}
#pragma mark - Long polling
- (void)handleNewMessagesReceived:(NSNotification *)notification
{
NSArray *messages = (NSArray *)notification.userInfo[@"messages"];
NSLog(@"[Loki] Received %lu messages through long polling.", messages.count);
for (SSKProtoEnvelope *envelope in messages) {
NSData *envelopeData = [envelope serializedDataAndReturnError:nil];
if (envelopeData != nil) {
[SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:envelopeData];
} else {
OWSFailDebug(@"Failed to deserialize envelope.");
}
}
}
#pragma mark - status bar touches
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
@ -1488,6 +1475,34 @@ static NSTimeInterval launchStartedAt;
#pragma mark - Loki
- (void)setUpLongPollerIfNeeded
{
if (self.lokiLongPoller != nil) { return; }
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
if (userHexEncodedPublicKey == nil) { return; }
self.lokiLongPoller = [[LKLongPoller alloc] initOnMessagesReceived:^(NSArray<SSKProtoEnvelope *> *messages) {
for (SSKProtoEnvelope *message in messages) {
NSData *data = [message serializedDataAndReturnError:nil];
if (data != nil) {
[SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:data];
} else {
NSLog(@"[Loki] Failed to deserialize envelope.");
}
}
}];
}
- (void)startLongPollerIfNeeded
{
[self setUpLongPollerIfNeeded];
[self.lokiLongPoller startIfNeeded];
}
- (void)stopLongPollerIfNeeded
{
[self.lokiLongPoller stopIfNeeded];
}
- (LKGroupChat *)lokiPublicChat
{
return [[LKGroupChat alloc] initWithServerID:LKGroupChatAPI.publicChatServerID server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true];

@ -7,7 +7,7 @@ public final class LokiGroupChatPoller : NSObject {
private var hasStarted = false
private let pollForNewMessagesInterval: TimeInterval = 4
private let pollForDeletedMessagesInterval: TimeInterval = 32 * 60
private let pollForDeletedMessagesInterval: TimeInterval = 20
private let storage = OWSPrimaryStorage.shared()
private let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey

@ -105,6 +105,10 @@ final class NewConversationViewController : OWSViewController, OWSQRScannerDeleg
let alert = UIAlertController(title: NSLocalizedString("Invalid Public Key", comment: ""), message: NSLocalizedString("Please check the public key you entered and try again.", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
} else if OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey == hexEncodedPublicKey {
let alert = UIAlertController(title: NSLocalizedString("Can't Start Conversation", comment: ""), message: NSLocalizedString("Please enter the public key of the person you'd like to message.", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
} else {
let thread = TSContactThread.getOrCreateThread(contactId: hexEncodedPublicKey)
SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)

@ -2,6 +2,7 @@
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "AppDelegate.h"
#import "AppSettingsViewController.h"
#import "AboutTableViewController.h"
#import "AdvancedSettingsTableViewController.h"
@ -533,7 +534,8 @@
[ThreadUtil deleteAllContent];
[SSKEnvironment.shared.identityManager clearIdentityKey];
[LKAPI clearRandomSnodePool];
[LKAPI stopLongPolling];
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate stopLongPollerIfNeeded];
[SSKEnvironment.shared.tsAccountManager resetForReregistration];
UIViewController *rootViewController = [[OnboardingController new] initialViewController];
OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:rootViewController];

@ -680,7 +680,8 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) {
[ThreadUtil deleteAllContent];
[SSKEnvironment.shared.identityManager clearIdentityKey];
[LKAPI clearRandomSnodePool];
[LKAPI stopLongPolling];
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate stopLongPollerIfNeeded];
[SSKEnvironment.shared.tsAccountManager resetForReregistration];
UIViewController *rootViewController = [[OnboardingController new] initialViewController];
OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:rootViewController];

@ -2617,3 +2617,5 @@
"Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and clicking \"Show QR Code\"." = "Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and clicking \"Show QR Code\".";
"Scan QR Code" = "Scan QR Code";
"Loki" = "Loki";
"Can't Start Conversation" = "Can't Start Conversation";
"Please enter the public key of the person you'd like to message." = "Please enter the public key of the person you'd like to message.";

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

@ -65,6 +65,7 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
/*
if ([self isVersion:previousVersion atLeast:@"1.0.2" andLessThan:@"2.0"]) {
OWSLogError(@"Migrating from RedPhone no longer supported. Quitting.");
// Not translating these as so few are affected.
@ -83,6 +84,7 @@ NS_ASSUME_NONNULL_BEGIN
[CurrentAppContext().frontmostViewController presentAlert:alert];
}
*/
if ([self isVersion:previousVersion atLeast:@"2.0.0" andLessThan:@"2.1.70"] && [self.tsAccountManager isRegistered]) {
[self clearVideoCache];

@ -253,7 +253,7 @@ message DataMessage {
repeated Contact contact = 9;
repeated Preview preview = 10;
optional LokiProfile profile = 101; // Loki: The current user's profile
optional PublicChatInfo publicChatInfo = 900; // Loki: Internal public chat info
optional PublicChatInfo publicChatInfo = 999; // Loki: Internal public chat info
}
message NullMessage {
@ -423,7 +423,7 @@ message GroupDetails {
optional bool blocked = 8;
}
// Internal type - DO NOT SEND
// Internal - DO NOT SEND
message PublicChatInfo {
optional uint64 serverId = 1;
optional uint64 serverID = 1;
}

@ -711,6 +711,8 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
return [AnyPromise promiseWithValue:@(1)];
}
return [AnyPromise promiseWithValue:@(1)];
NSDate *_Nullable updateRequestDate =
[self.dbConnection objectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey
inCollection:TSAccountManager_UserAccountCollection];

@ -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)
}
}

@ -173,4 +173,51 @@ public final class LokiAPI : NSObject {
return envelope
}
}
// MARK: Caching
private static let receivedMessageHashValuesKey = "receivedMessageHashValuesKey"
private static let receivedMessageHashValuesCollection = "receivedMessageHashValuesCollection"
private 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
}
private 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)
}
}
private 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
}
private static func setReceivedMessageHashValues(to receivedMessageHashValues: Set<String>) {
storage.dbReadWriteConnection.readWrite { transaction in
transaction.setObject(receivedMessageHashValues, forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection)
}
}
}
// MARK: Error Handling
private extension Promise {
fileprivate func recoveringNetworkErrorsIfNeeded() -> Promise<T> {
return recover() { error -> Promise<T> in
switch error {
case NetworkManagerError.taskError(_, let underlyingError): throw underlyingError
default: throw error
}
}
}
}

@ -28,13 +28,13 @@ public final class LokiGroupChatAPI : NSObject {
// MARK: Error
public enum Error : Swift.Error {
case tokenParsingFailed, tokenDecryptionFailed, messageParsingFailed, jsonParsingFailed
case tokenParsingFailed, tokenDecryptionFailed, messageParsingFailed, deletionParsingFailed, jsonParsingFailed
}
// MARK: Database
private static let authTokenCollection = "LokiGroupChatAuthTokenCollection"
private static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection"
private static let firstMessageServerIDCollection = "LokiGroupChatFirstMessageServerIDCollection"
private static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection"
private static func getAuthTokenFromDatabase(for server: String) -> String? {
var result: String? = nil
@ -64,17 +64,17 @@ public final class LokiGroupChatAPI : NSObject {
}
}
private static func getFirstMessageServerID(for group: UInt64, on server: String) -> UInt? {
private static func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt? {
var result: UInt? = nil
storage.dbReadConnection.read { transaction in
result = transaction.object(forKey: "\(server).\(group)", inCollection: firstMessageServerIDCollection) as! UInt?
result = transaction.object(forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection) as! UInt?
}
return result
}
private static func setFirstMessageServerID(for group: UInt64, on server: String, to newValue: UInt64) {
private static func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64) {
storage.dbReadWriteConnection.readWrite { transaction in
transaction.setObject(newValue, forKey: "\(server).\(group)", inCollection: firstMessageServerIDCollection)
transaction.setObject(newValue, forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection)
}
}
@ -146,9 +146,7 @@ public final class LokiGroupChatAPI : NSObject {
return nil
}
let lastMessageServerID = getLastMessageServerID(for: group, on: server)
let firstMessageServerID = getFirstMessageServerID(for: group, on: server)
if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: group, on: server, to: serverID) }
if serverID < (firstMessageServerID ?? UInt.max) { setFirstMessageServerID(for: group, on: server, to: serverID) }
return LokiGroupMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp)
}
}
@ -185,22 +183,27 @@ public final class LokiGroupChatAPI : NSObject {
public static func getDeletedMessageServerIDs(for group: UInt64, on server: String) -> Promise<[UInt64]> {
print("[Loki] Getting deleted messages for group chat with ID: \(group) on server: \(server).")
let firstMessageServerID = getFirstMessageServerID(for: group, on: server) ?? 0
let queryParameters = "is_deleted=true&since_id=\(firstMessageServerID)"
let url = URL(string: "\(server)/channels/\(group)/messages?\(queryParameters)")!
let queryParameters: String
if let lastDeletionServerID = getLastDeletionServerID(for: group, on: server) {
queryParameters = "since_id=\(lastDeletionServerID)"
} else {
queryParameters = "count=\(fallbackBatchCount)"
}
let url = URL(string: "\(server)/loki/v1/channel/\(group)/deletes?\(queryParameters)")!
let request = TSRequest(url: url)
return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject }.map { rawResponse in
guard let json = rawResponse as? JSON, let rawMessages = json["data"] as? [JSON] else {
guard let json = rawResponse as? JSON, let deletions = json["data"] as? [JSON] else {
print("[Loki] Couldn't parse deleted messages for group chat with ID: \(group) on server: \(server) from: \(rawResponse).")
throw Error.messageParsingFailed
throw Error.deletionParsingFailed
}
return rawMessages.flatMap { message in
guard let serverID = message["id"] as? UInt64 else {
print("[Loki] Couldn't parse deleted message for group chat with ID: \(group) on server: \(server) from: \(message).")
return deletions.flatMap { deletion in
guard let serverID = deletion["id"] as? UInt64, let messageServerID = deletion["message_id"] as? UInt64 else {
print("[Loki] Couldn't parse deleted message for group chat with ID: \(group) on server: \(server) from: \(deletion).")
return nil
}
let isDeleted = (message["is_deleted"] as? Bool ?? false)
return isDeleted ? serverID : nil
let lastDeletionServerID = getLastDeletionServerID(for: group, on: server)
if serverID > (lastDeletionServerID ?? 0) { setLastDeletionServerID(for: group, on: server, to: serverID) }
return messageServerID
}
}
}

@ -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)
}
}
}

@ -350,6 +350,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedGroupIdsKey = @"kOWSBlockingMan
// This method should only be called from within a synchronized block.
- (void)syncBlockListIfNecessary
{
/*
OWSAssertDebug(_blockedPhoneNumberSet);
// If we haven't yet successfully synced the current "block list" changes,
@ -376,6 +377,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedGroupIdsKey = @"kOWSBlockingMan
OWSLogInfo(@"retrying sync of block list");
[self sendBlockListSyncMessageWithPhoneNumbers:self.blockedPhoneNumbers groupIds:localBlockedGroupIds];
*/
}
- (void)sendBlockListSyncMessageWithPhoneNumbers:(NSArray<NSString *> *)blockedPhoneNumbers

@ -1402,6 +1402,11 @@ NS_ASSUME_NONNULL_BEGIN
thread:oldGroupThread
envelope:envelope
transaction:transaction];
if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) {
[self.primaryStorage setIDForMessageWithServerID:dataMessage.publicChatInfo.serverID to:incomingMessage.uniqueId in:transaction];
}
return incomingMessage;
}
default: {

@ -220,7 +220,7 @@ NS_ASSUME_NONNULL_BEGIN
NSString *path = [textSecureAccountsAPI stringByAppendingString:textSecureAttributesAPI];
NSString *authKey = self.tsAccountManager.serverAuthToken;
OWSAssertDebug(authKey.length > 0);
// OWSAssertDebug(authKey.length > 0);
NSString *_Nullable pin = [self.ows2FAManager pinCode];
NSDictionary<NSString *, id> *accountAttributes = [self accountAttributesWithPin:pin authKey:authKey];
@ -321,7 +321,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSDictionary<NSString *, id> *)accountAttributesWithPin:(nullable NSString *)pin
authKey:(NSString *)authKey
{
OWSAssertDebug(authKey.length > 0);
// OWSAssertDebug(authKey.length > 0);
uint32_t registrationId = [self.tsAccountManager getOrGenerateRegistrationId];
BOOL isManualMessageFetchEnabled = self.tsAccountManager.isManualMessageFetchEnabled;

@ -2502,7 +2502,7 @@ struct SignalServiceProtos_GroupDetails {
fileprivate var _storage = _StorageClass.defaultInstance
}
/// Internal type - DO NOT SEND
/// Internal - DO NOT SEND
struct SignalServiceProtos_PublicChatInfo {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
@ -3178,7 +3178,7 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf.
9: .same(proto: "contact"),
10: .same(proto: "preview"),
101: .same(proto: "profile"),
900: .same(proto: "publicChatInfo"),
999: .same(proto: "publicChatInfo"),
]
fileprivate class _StorageClass {
@ -3238,7 +3238,7 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf.
case 9: try decoder.decodeRepeatedMessageField(value: &_storage._contact)
case 10: try decoder.decodeRepeatedMessageField(value: &_storage._preview)
case 101: try decoder.decodeSingularMessageField(value: &_storage._profile)
case 900: try decoder.decodeSingularMessageField(value: &_storage._publicChatInfo)
case 999: try decoder.decodeSingularMessageField(value: &_storage._publicChatInfo)
default: break
}
}
@ -3281,7 +3281,7 @@ extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf.
try visitor.visitSingularMessageField(value: v, fieldNumber: 101)
}
if let v = _storage._publicChatInfo {
try visitor.visitSingularMessageField(value: v, fieldNumber: 900)
try visitor.visitSingularMessageField(value: v, fieldNumber: 999)
}
}
try unknownFields.traverse(visitor: &visitor)
@ -5157,7 +5157,7 @@ extension SignalServiceProtos_GroupDetails.Avatar: SwiftProtobuf.Message, SwiftP
extension SignalServiceProtos_PublicChatInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".PublicChatInfo"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "serverId"),
1: .same(proto: "serverID"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>CFBundleVersion</key>
<string>11</string>
<string>14</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSAppTransportSecurity</key>

@ -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…
Cancel
Save