Merge pull request #71 from loki-project/session-restore

Session restore
pull/82/head
gmbnt 5 years ago committed by GitHub
commit 8209fa1383
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1 +1 @@
Subproject commit 80b9ef94d107a4c2794164537da30eb4482af49a
Subproject commit 3a470c921a599097807df0cdbff4b2f238c91f37

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
2400888E239F30A600305217 /* SessionRestorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2400888D239F30A600305217 /* SessionRestorationView.swift */; };
241C6314231F64C000B4198E /* JazzIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C630E231F5AAC00B4198E /* JazzIcon.swift */; };
241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */; };
241C6316231F64CE00B4198E /* UIColor+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6310231F5C4400B4198E /* UIColor+Helper.swift */; };
@ -709,6 +710,7 @@
0F94C85CB0B235DA37F68ED0 /* Pods_SignalShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1C93CF3971B64E8B6C1F9AC1 /* Pods-SignalShareExtension.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.test.xcconfig"; sourceTree = "<group>"; };
1CE3CD5C23334683BDD3D78C /* Pods-Signal.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.test.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.test.xcconfig"; sourceTree = "<group>"; };
2400888D239F30A600305217 /* SessionRestorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRestorationView.swift; sourceTree = "<group>"; };
241C630E231F5AAC00B4198E /* JazzIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JazzIcon.swift; sourceTree = "<group>"; };
241C6310231F5C4400B4198E /* UIColor+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helper.swift"; sourceTree = "<group>"; };
241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Rounding.swift"; sourceTree = "<group>"; };
@ -2783,6 +2785,7 @@
B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */,
B85357C023A1B81900AAF6CD /* SeedReminderViewDelegate.swift */,
B8BB82B82394911B00BA5194 /* Separator.swift */,
2400888D239F30A600305217 /* SessionRestorationView.swift */,
B8CCF638239721E20091D419 /* TabBar.swift */,
B8BB82B423947F2D00BA5194 /* TextField.swift */,
);
@ -3980,6 +3983,7 @@
B8162F0322891AD600D46544 /* FriendRequestView.swift in Sources */,
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */,
34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */,
2400888E239F30A600305217 /* SessionRestorationView.swift in Sources */,
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */,
4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */,
34EA69402194933900702471 /* MediaDownloadView.swift in Sources */,

@ -114,10 +114,10 @@ final class FriendRequestView : UIView {
let format: String = {
switch (message.friendRequestStatus) {
case .none, .sendingOrFailed: preconditionFailure()
case .pending: return NSLocalizedString("%@ sent you a message request", comment: "")
case .accepted: return NSLocalizedString("You've accepted %@'s message request", comment: "")
case .declined: return NSLocalizedString("You've declined %@'s message request", comment: "")
case .expired: return NSLocalizedString("%@'s message request has expired", comment: "")
case .pending: return NSLocalizedString("%@ sent you a session request", comment: "")
case .accepted: return NSLocalizedString("You've accepted %@'s session request", comment: "")
case .declined: return NSLocalizedString("You've declined %@'s session request", comment: "")
case .expired: return NSLocalizedString("%@'s session request has expired", comment: "")
default: preconditionFailure()
}
}()
@ -130,10 +130,10 @@ final class FriendRequestView : UIView {
switch (message.friendRequestStatus) {
case .none: preconditionFailure()
case .sendingOrFailed: return nil
case .pending: return NSLocalizedString("You've sent %@ a message request", comment: "")
case .accepted: return NSLocalizedString("%@ accepted your message request", comment: "")
case .pending: return NSLocalizedString("You've sent %@ a session request", comment: "")
case .accepted: return NSLocalizedString("%@ accepted your session request", comment: "")
case .declined: preconditionFailure()
case .expired: return NSLocalizedString("Your message request to %@ has expired", comment: "")
case .expired: return NSLocalizedString("Your session request to %@ has expired", comment: "")
default: preconditionFailure()
}
}()

@ -0,0 +1,84 @@
@objc(LKSessionRestorationView)
final class SessionRestorationView : UIView {
private let thread: TSThread
@objc public var onRestore: (() -> Void)?
@objc public var onDismiss: (() -> Void)?
// MARK: Lifecycle
@objc init(thread: TSThread) {
self.thread = thread;
super.init(frame: CGRect.zero)
initialize()
}
required init?(coder: NSCoder) { fatalError("Using SessionRestorationView.init(coder:) isn't allowed. Use SessionRestorationView.init(thread:) instead.") }
override init(frame: CGRect) { fatalError("Using SessionRestorationView.init(frame:) isn't allowed. Use SessionRestorationView.init(thread:) instead.") }
private func initialize() {
// Set up background
backgroundColor = Colors.modalBackground
layer.cornerRadius = Values.modalCornerRadius
layer.masksToBounds = false
layer.borderColor = Colors.modalBorder.cgColor
layer.borderWidth = Values.borderThickness
layer.shadowColor = UIColor.black.cgColor
layer.shadowRadius = 8
layer.shadowOpacity = 0.64
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize)
titleLabel.text = NSLocalizedString("Session Out of Sync", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.textAlignment = .center
// Set up explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("Would you like to restore your session?", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
// Set up restore button
let restoreButton = UIButton()
restoreButton.set(.height, to: Values.mediumButtonHeight)
restoreButton.layer.cornerRadius = Values.modalButtonCornerRadius
restoreButton.backgroundColor = Colors.accent
restoreButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
restoreButton.setTitleColor(Colors.text, for: UIControl.State.normal)
restoreButton.setTitle(NSLocalizedString("Restore", comment: ""), for: UIControl.State.normal)
restoreButton.addTarget(self, action: #selector(restore), for: UIControl.Event.touchUpInside)
// Set up dismiss button
let dismissButton = UIButton()
dismissButton.set(.height, to: Values.mediumButtonHeight)
dismissButton.layer.cornerRadius = Values.modalButtonCornerRadius
dismissButton.backgroundColor = Colors.buttonBackground
dismissButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
dismissButton.setTitleColor(Colors.text, for: UIControl.State.normal)
dismissButton.setTitle(NSLocalizedString("Dismiss", comment: ""), for: UIControl.State.normal)
dismissButton.addTarget(self, action: #selector(dismiss), for: UIControl.Event.touchUpInside)
// Set up button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ dismissButton, restoreButton ])
buttonStackView.axis = .horizontal
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.distribution = .fillEqually
// Set up main stack view
let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, buttonStackView ])
mainStackView.axis = .vertical
mainStackView.spacing = Values.smallSpacing
addSubview(mainStackView)
mainStackView.pin(to: self, withInset: Values.mediumSpacing)
// Update explanation label if possible
if let contactID = thread.contactIdentifier() {
let displayName = DisplayNameUtilities.getPrivateChatDisplayName(for: contactID) ?? contactID
explanationLabel.text = String(format: NSLocalizedString("Would you like to restore your session with %@?", comment: ""), displayName)
}
}
// MARK: Interaction
@objc private func restore() { onRestore?() }
@objc private func dismiss() { onDismiss?() }
}

@ -176,6 +176,7 @@ final class RestoreVC : UIViewController {
databaseConnection.setObject(seed.toHexString(), forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
databaseConnection.setObject(keyPair, forKey: OWSPrimaryStorageIdentityKeyStoreIdentityKey, inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = keyPair.hexEncodedPublicKey
OWSPrimaryStorage.shared().setRestorationTime(Date().timeIntervalSince1970)
mnemonicTextField.resignFirstResponder()
Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in
let displayNameVC = DisplayNameVC()

@ -431,12 +431,8 @@ typedef void (^SystemMessageActionBlock)(void);
}];
case TSErrorMessageMissingKeyId:
case TSErrorMessageNoSession:
return nil;
case TSErrorMessageInvalidMessage:
return [SystemMessageAction actionWithTitle:NSLocalizedString(@"FINGERPRINT_SHRED_KEYMATERIAL_BUTTON", @"")
block:^{
[weakSelf.delegate tappedCorruptedMessage:message];
}];
return nil;
case TSErrorMessageDuplicateMessage:
case TSErrorMessageInvalidVersion:
return nil;

@ -162,6 +162,7 @@ typedef enum : NSUInteger {
@property (nonatomic) NSCache *cellMediaCache;
@property (nonatomic) LKConversationTitleView *headerView;
@property (nonatomic, nullable) UIView *bannerView;
@property (nonatomic, nullable) UIView *restoreSessionBannerView;
@property (nonatomic, nullable) OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration;
// Back Button Unread Count
@ -261,7 +262,7 @@ typedef enum : NSUInteger {
_recordVoiceNoteAudioActivity = [[OWSAudioActivity alloc] initWithAudioDescription:audioActivityDescription behavior:OWSAudioBehavior_PlayAndRecord];
self.scrollContinuity = kScrollContinuityBottom;
_currentMentionStartIndex = -1;
_mentions = [NSMutableArray new];
_oldText = @"";
@ -418,6 +419,10 @@ typedef enum : NSUInteger {
selector:@selector(handleThreadFriendRequestStatusChangedNotification:)
name:NSNotification.threadFriendRequestStatusChanged
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleThreadSessionRestoreDevicesChangedNotifiaction:)
name:NSNotification.threadSessionRestoreDevicesChanged
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleCalculatingPoWNotification:)
name:NSNotification.calculatingPoW
@ -509,6 +514,17 @@ typedef enum : NSUInteger {
[self resetContentAndLayout];
}
- (void)handleThreadSessionRestoreDevicesChangedNotifiaction:(NSNotification *)notification
{
// Check thread
NSString *threadID = (NSString *)notification.object;
if (![threadID isEqualToString:self.thread.uniqueId]) { return; }
// Ensure thread instance is up to date
[self.thread reload];
// Update UI
[self updateSessionRestoreBanner];
}
- (void)peekSetup
{
_peek = YES;
@ -547,7 +563,7 @@ typedef enum : NSUInteger {
selector:@selector(reloadTimerDidFire)
userInfo:nil
repeats:YES];
[LKAPI populateUserHexEncodedPublicKeyCacheIfNeededFor:thread.uniqueId in:nil];
}
@ -622,7 +638,7 @@ typedef enum : NSUInteger {
[super viewDidLoad];
[self createContents];
[self registerCellClasses];
[self createConversationScrollButtons];
@ -641,20 +657,20 @@ typedef enum : NSUInteger {
[self loadDraftInCompose];
[self applyTheme];
[self.conversationViewModel viewDidLoad];
// Loki: Set gradient background
self.collectionView.backgroundColor = UIColor.clearColor;
LKGradient *gradient = LKGradients.defaultLokiBackground;
self.view.backgroundColor = UIColor.clearColor;
[self.view setGradient:gradient];
// Loki: Set navigation bar background color
UINavigationBar *navigationBar = self.navigationController.navigationBar;
[navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
navigationBar.shadowImage = [UIImage new];
[navigationBar setTranslucent:NO];
navigationBar.barTintColor = LKColors.navigationBarBackground;
// Loki: Set up navigation bar buttons
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Back", "") style:UIBarButtonItemStylePlain target:nil action:nil];
backButton.tintColor = LKColors.text;
@ -662,7 +678,7 @@ typedef enum : NSUInteger {
UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"Gear"] style:UIBarButtonItemStylePlain target:self action:@selector(showConversationSettings)];
settingsButton.tintColor = LKColors.text;
self.navigationItem.rightBarButtonItem = settingsButton;
if (self.thread.isGroupThread) {
TSGroupThread *thread = (TSGroupThread *)self.thread;
if (thread.isRSSFeed) { return; }
@ -719,7 +735,7 @@ typedef enum : NSUInteger {
[self.progressIndicatorView autoPinEdgeToSuperviewEdge:ALEdgeTop];
[self.progressIndicatorView autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
[self.progressIndicatorView autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
[self.collectionView applyScrollViewInsetsFix];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _collectionView);
@ -728,7 +744,7 @@ typedef enum : NSUInteger {
self.inputToolbar.inputTextViewDelegate = self;
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _inputToolbar);
[self updateInputToolbar];
self.loadMoreHeader = [UILabel new];
self.loadMoreHeader.text = NSLocalizedString(@"CONVERSATION_VIEW_LOADING_MORE_MESSAGES", @"Indicates that the app is loading more messages in this conversation.");
self.loadMoreHeader.textColor = [LKColors.text colorWithAlphaComponent:0.8];
@ -835,6 +851,7 @@ typedef enum : NSUInteger {
OWSLogDebug(@"viewWillAppear");
[self ensureBannerState];
[self updateSessionRestoreBanner];
[super viewWillAppear:animated];
@ -992,6 +1009,38 @@ typedef enum : NSUInteger {
return [result copy];
}
- (void)updateSessionRestoreBanner {
BOOL isContactThread = [self.thread isKindOfClass:[TSContactThread class]];
BOOL shouldRemoveBanner = !isContactThread;
if (isContactThread) {
TSContactThread *thread = (TSContactThread *)self.thread;
if (thread.sessionRestoreDevices.count > 0) {
if (self.restoreSessionBannerView == nil) {
LKSessionRestorationView *bannerView = [[LKSessionRestorationView alloc] initWithThread:thread];
[self.view addSubview:bannerView];
[bannerView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:LKValues.mediumSpacing];
[bannerView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:LKValues.largeSpacing];
[bannerView autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:LKValues.mediumSpacing];
[self.view layoutSubviews];
self.restoreSessionBannerView = bannerView;
[bannerView setOnRestore:^{
[self restoreSession];
}];
[bannerView setOnDismiss:^{
[thread removeAllSessionRestoreDevicesWithTransaction:nil];
}];
}
} else {
shouldRemoveBanner = true;
}
}
if (shouldRemoveBanner && self.restoreSessionBannerView) {
[self.restoreSessionBannerView removeFromSuperview];
self.restoreSessionBannerView = nil;
}
}
- (void)ensureBannerState
{
// This method should be called rarely, so it's simplest to discard and
@ -1163,6 +1212,27 @@ typedef enum : NSUInteger {
}];
}
- (void)restoreSession {
if ([self.thread isKindOfClass:[TSContactThread class]]) {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
TSContactThread *thread = (TSContactThread *)self.thread;
NSArray *devices = thread.sessionRestoreDevices;
for (NSString *device in devices) {
if (device.length == 0) { continue; }
OWSMessageSend *sessionRestoreMessage = [messageSender getSessionRestoreMessageForHexEncodedPublicKey:device];
if (sessionRestoreMessage) {
dispatch_async(OWSDispatch.sendingQueue, ^{
[messageSender sendMessage:sessionRestoreMessage];
});
}
}
[[[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageType:TSInfoMessageTypeLokiSessionResetInProgress] save];
thread.sessionResetState = TSContactThreadSessionResetStateRequestReceived;
[thread save];
[thread removeAllSessionRestoreDevicesWithTransaction:nil];
}
}
- (void)noLongerVerifiedBannerViewWasTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
@ -1333,7 +1403,7 @@ typedef enum : NSUInteger {
// - Begin presenting another view, e.g. swipe-left for details or swipe-right to go back, but quit part way, so that you remain on the conversation view
// - toolbar will be not be visible unless we reaquire first responder.
if (!self.isFirstResponder) {
// We don't have to worry about the input toolbar being visible if the inputToolbar.textView is first responder
// In fact doing so would unnecessarily dismiss the keyboard which is probably not desirable and at least
// a distracting animation.
@ -1481,7 +1551,7 @@ typedef enum : NSUInteger {
- (void)updateBarButtonItems
{
return; // Loki: Re-enable later?
self.navigationItem.hidesBackButton = NO;
if (self.customBackButton) {
self.navigationItem.leftBarButtonItem = self.customBackButton;
@ -1508,7 +1578,7 @@ typedef enum : NSUInteger {
UIButton *callButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *image = [[UIImage imageNamed:@"button_phone_white"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[callButton setImage:image forState:UIControlStateNormal];
if (OWSWindowManager.sharedManager.hasCall) {
callButton.enabled = NO;
callButton.userInteractionEnabled = NO;
@ -1520,7 +1590,7 @@ typedef enum : NSUInteger {
}
UIEdgeInsets imageEdgeInsets = UIEdgeInsetsZero;
// We normally would want to use left and right insets that ensure the button
// is square and the icon is centered. However UINavigationBar doesn't offer us
// control over the margins and spacing of its content, and the buttons end up
@ -1604,7 +1674,7 @@ typedef enum : NSUInteger {
isAttachmentButtonHidden = false;
}
[self.inputToolbar setUserInteractionEnabled:isEnabled];
NSString *placeholderText = isEnabled ? NSLocalizedString(@"Message", "") : NSLocalizedString(@"Pending message request", "");
NSString *placeholderText = isEnabled ? NSLocalizedString(@"Message", "") : NSLocalizedString(@"Pending session request", "");
[self.inputToolbar setPlaceholderText:placeholderText];
[self.inputToolbar setAttachmentButtonHidden:isAttachmentButtonHidden];
}
@ -1873,14 +1943,14 @@ typedef enum : NSUInteger {
// Before 2.13 we didn't track the recipient id in the identity change error.
OWSLogWarn(@"Ignoring tap on legacy nonblocking identity change since it has no signal id");
return;
} else {
OWSLogInfo(@"Assuming tap on legacy nonblocking identity change corresponds to current contact thread: %@",
self.thread.contactIdentifier);
signalIdParam = self.thread.contactIdentifier;
}
}
NSString *signalId = signalIdParam;
[self showFingerprintWithRecipientId:signalId];
@ -2408,7 +2478,7 @@ typedef enum : NSUInteger {
self.audioAttachmentPlayer =
[[OWSAudioPlayer alloc] initWithMediaUrl:attachmentStream.originalMediaURL audioBehavior:OWSAudioBehavior_AudioMessagePlayback delegate:viewItem];
// Associate the player with this media adapter.
self.audioAttachmentPlayer.owner = viewItem;
[self.audioAttachmentPlayer play];
@ -4261,11 +4331,11 @@ typedef enum : NSUInteger {
[searchBar setPositionAdjustment:UIOffsetMake(4, 0) forSearchBarIcon:UISearchBarIconSearch];
[searchBar setSearchTextPositionAdjustment:UIOffsetMake(2, 0)];
[searchBar setPositionAdjustment:UIOffsetMake(-4, 0) forSearchBarIcon:UISearchBarIconClear];
// Note: setting a searchBar as the titleView causes UIKit to render the navBar
// *slightly* taller (44pt -> 56pt)
self.navigationItem.titleView = searchBar;
[self updateBarButtonItems];
// Hack so that the ResultsBar stays on the screen when dismissing the search field
@ -4743,7 +4813,7 @@ typedef enum : NSUInteger {
targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
if (@available(iOS 13, *)) {
} else {
if (self.menuActionsViewController != nil) {
NSValue *_Nullable contentOffset = [self contentOffsetForMenuActionInteraction];

@ -2573,20 +2573,20 @@
"Calculating proof of work" = "Calculating proof of work";
"Failed to calculate proof of work." = "Failed to calculate proof of work.";
"Share Public Key" = "Share Public Key";
"%@ sent you a message request" = "%@ sent you a message request";
"%@ sent you a session request" = "%@ sent you a session request";
"Accept" = "Accept";
"Decline" = "Decline";
"Pending message request" = "Pending message request";
"Pending session request" = "Pending session request";
"New Message" = "New Message";
"Secure session reset in progress" = "Secure session reset in progress";
"Secure session reset done" = "Secure session reset done";
"Session" = "Session";
"You've sent %@ a message request" = "You've sent %@ a message request";
"You've declined %@'s message request" = "You've declined %@'s message request";
"You've accepted %@'s message request" = "You've accepted %@'s message request";
"%@ accepted your message request" = "%@ accepted your message request";
"%@'s message request has expired" = "%@'s message request has expired";
"Your message request to %@ has expired" = "Your message request to %@ has expired";
"You've sent %@ a session request" = "You've sent %@ a session request";
"You've declined %@'s session request" = "You've declined %@'s session request";
"You've accepted %@'s session request" = "You've accepted %@'s session request";
"%@ accepted your session request" = "%@ accepted your session request";
"%@'s session request has expired" = "%@'s session request has expired";
"Your session request to %@ has expired" = "Your session request to %@ has expired";
"Show Seed" = "Show Seed";
"Your Seed" = "Your Seed";
"Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications." = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
@ -2671,6 +2671,8 @@
"Your device was unlinked successfully" = "Your device was unlinked successfully";
"Unnamed Device" = "Unnamed Device";
"Linked device (%@)" = "Linked device (%@)";
"Restore session" = "Restore session";
"Would you like to start a new session with %@?" = "Would you like to start a new session with %@?";
// MARK: - Redesign
"Messages" = "Messages";
@ -2772,3 +2774,8 @@
"Are you sure? This cannot be undone." = "Are you sure? This cannot be undone.";
"When enabled, messages between you and %@ will disappear after they have been seen." = "When enabled, messages between you and %@ will disappear after they have been seen.";
"This will be your name when you use Session." = "This will be your name when you use Session.";
"Session Out of Sync" = "Session Out of Sync";
"Would you like to restore your session?" = "Would you like to restore your session?";
"Would you like to restore your session with %@?" = "Would you like to restore your session with %@?";
"Restore" = "Restore";
"Dismiss" = "Dismiss";

@ -264,6 +264,15 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
return [self syncContactsForSignalAccounts:@[ signalAccount ]];
}
- (AnyPromise *)syncContact:(NSString *)hexEncodedPubKey transaction:(YapDatabaseReadTransaction *)transaction
{
TSContactThread *thread = [TSContactThread getThreadWithContactId:hexEncodedPubKey transaction:transaction];
if (thread != nil && thread.isContactFriend) {
return [self syncContactsForSignalAccounts:@[[[SignalAccount alloc] initWithRecipientId:hexEncodedPubKey]]];
}
return [AnyPromise promiseWithValue:@1];
}
- (AnyPromise *)syncAllContacts
{
NSMutableArray<SignalAccount *> *friends = @[].mutableCopy;

@ -148,6 +148,7 @@ message DataMessage {
END_SESSION = 1;
EXPIRATION_TIMER_UPDATE = 2;
PROFILE_KEY_UPDATE = 4;
SESSION_RESTORE = 64;
UNLINK_DEVICE = 128;
}

@ -22,6 +22,7 @@ extern NSString *const TSContactThreadPrefix;
// Loki: The current session reset state for this thread
@property (atomic) TSContactThreadSessionResetState sessionResetState;
@property (atomic, readonly) NSArray<NSString *> *sessionRestoreDevices;
@property (nonatomic) BOOL hasDismissedOffers;
@ -47,6 +48,11 @@ extern NSString *const TSContactThreadPrefix;
+ (NSString *)conversationColorNameForRecipientId:(NSString *)recipientId
transaction:(YapDatabaseReadTransaction *)transaction;
#pragma mark - Loki Session Restore
- (void)addSessionRestoreDevice:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction;
- (void)removeAllSessionRestoreDevicesWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction;
@end
NS_ASSUME_NONNULL_END

@ -8,6 +8,7 @@
#import "NotificationsProtocol.h"
#import "OWSIdentityManager.h"
#import "SSKEnvironment.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <YapDatabase/YapDatabaseConnection.h>
#import <YapDatabase/YapDatabaseTransaction.h>
@ -26,6 +27,7 @@ NSString *const TSContactThreadPrefix = @"c";
// No session reset ongoing
_sessionResetState = TSContactThreadSessionResetStateNone;
_sessionRestoreDevices = @[];
return self;
}
@ -107,6 +109,34 @@ NSString *const TSContactThreadPrefix = @"c";
return [self stableColorNameForNewConversationWithString:recipientId];
}
#pragma mark - Loki Session Restore
- (void)addSessionRestoreDevice:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction
{
NSMutableSet *set = [[NSMutableSet alloc] initWithArray:_sessionRestoreDevices];
[set addObject:hexEncodedPublicKey];
[self setSessionRestoreDevices:set.allObjects transaction:transaction];
}
- (void)removeAllSessionRestoreDevicesWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction
{
[self setSessionRestoreDevices:@[] transaction:transaction];
}
- (void)setSessionRestoreDevices:(NSArray<NSString *> *)sessionRestoreDevices transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction {
_sessionRestoreDevices = sessionRestoreDevices;
void (^postNotification)() = ^() {
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.threadSessionRestoreDevicesChanged object:self.uniqueId];
};
if (transaction == nil) {
[self save];
[self.dbReadWriteConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{ postNotification(); }];
} else {
[self saveWithTransaction:transaction];
[transaction.connection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{ postNotification(); }];
}
}
@end
NS_ASSUME_NONNULL_END

@ -37,6 +37,11 @@ NS_ASSUME_NONNULL_BEGIN
- (NSString *_Nullable)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction;
- (void)updateMessageIDCollectionByPruningMessagesWithIDs:(NSSet<NSString *> *)targetMessageIDs in:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(updateMessageIDCollectionByPruningMessagesWithIDs(_:in:));
# pragma mark - Restoration
- (void)setRestorationTime:(NSTimeInterval)time;
- (NSTimeInterval)getRestorationTime;
@end
NS_ASSUME_NONNULL_END

@ -186,4 +186,16 @@
[transaction removeObjectsForKeys:serverIDs inCollection:LKMessageIDCollection];
}
# pragma mark - Restoration
#define LKGeneralCollection @"Loki"
- (void)setRestorationTime:(NSTimeInterval)time {
[self.dbReadWriteConnection setDouble:time forKey:@"restoration_time" inCollection:LKGeneralCollection];
}
- (NSTimeInterval)getRestorationTime {
return [self.dbReadConnection doubleForKey:@"restoration_time" inCollection:LKGeneralCollection defaultValue:0];
}
@end

@ -0,0 +1,8 @@
#import "LKFriendRequestMessage.h"
NS_SWIFT_NAME(SessionRestoreMessage)
@interface LKSessionRestoreMessage : LKFriendRequestMessage
- (instancetype)initWithThread:(TSThread *)thread;
@end

@ -0,0 +1,23 @@
#import "LKSessionRestoreMessage.h"
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
@implementation LKSessionRestoreMessage
#pragma mark Initialization
- (instancetype)initWithThread:(TSThread *)thread {
return [self initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray<NSString *> new]
expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil];
}
#pragma mark Building
- (nullable SSKProtoDataMessageBuilder *)dataMessageBuilder
{
SSKProtoDataMessageBuilder *builder = super.dataMessageBuilder;
if (builder == nil) { return nil; }
[builder setFlags:SSKProtoDataMessageFlagsSessionRestore];
return builder;
}
@end

@ -6,6 +6,7 @@ public extension Notification.Name {
public static let messageFriendRequestStatusChanged = Notification.Name("messageFriendRequestStatusChanged")
public static let threadDeleted = Notification.Name("threadDeleted")
public static let dataNukeRequested = Notification.Name("dataNukeRequested")
public static let threadSessionRestoreDevicesChanged = Notification.Name("threadSessionRestoreDevicesChanged")
// Message statuses
public static let calculatingPoW = Notification.Name("calculatingPoW")
public static let contactingNetwork = Notification.Name("contactingNetwork")
@ -23,6 +24,7 @@ public extension Notification.Name {
@objc public static let messageFriendRequestStatusChanged = Notification.Name.messageFriendRequestStatusChanged.rawValue as NSString
@objc public static let threadDeleted = Notification.Name.threadDeleted.rawValue as NSString
@objc public static let dataNukeRequested = Notification.Name.dataNukeRequested.rawValue as NSString
@objc public static let threadSessionRestoreDevicesChanged = Notification.Name.threadSessionRestoreDevicesChanged.rawValue as NSString
// Message statuses
@objc public static let calculatingPoW = Notification.Name.calculatingPoW.rawValue as NSString
@objc public static let contactingNetwork = Notification.Name.contactingNetwork.rawValue as NSString

@ -89,8 +89,9 @@ NSUInteger TSErrorMessageSchemaVersion = 1;
withTransaction:(YapDatabaseReadWriteTransaction *)transaction
failedMessageType:(TSErrorMessageType)errorMessageType
{
NSString *source = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source;
TSContactThread *contactThread =
[TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction];
[TSContactThread getOrCreateThreadWithContactId:source transaction:transaction];
// Legit usage of senderTimestamp. We don't actually currently surface it in the UI, but it serves as
// a reference to the envelope which we failed to process.

@ -678,16 +678,34 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes
OWSAssertDebug(errorMessage);
if (errorMessage != nil) {
[errorMessage saveWithTransaction:transaction];
[self handleSessionRestoreForErrorMessage:errorMessage envelope:envelope transaction:transaction];
[self notifyUserForErrorMessage:errorMessage envelope:envelope transaction:transaction];
}
}];
}
- (void)handleSessionRestoreForErrorMessage:(TSErrorMessage *)errorMessage
envelope:(SSKProtoEnvelope *)envelope
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
NSString *hexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source;
TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
// Trigger a session restore prompt if we get specific errors
if (errorMessage.errorType == TSErrorMessageNoSession ||
errorMessage.errorType == TSErrorMessageInvalidMessage ||
errorMessage.errorType == TSErrorMessageInvalidKeyException) {
// We want to store the source device's public key into the session restore in case it's a secondary device message
[((TSContactThread *)contactThread) addSessionRestoreDevice:envelope.source transaction:transaction];
}
}
- (void)notifyUserForErrorMessage:(TSErrorMessage *)errorMessage
envelope:(SSKProtoEnvelope *)envelope
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction];
NSString *hexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source;
TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
[SSKEnvironment.shared.notificationsManager notifyUserForErrorMessage:errorMessage
thread:contactThread
transaction:transaction];

@ -447,6 +447,16 @@ NS_ASSUME_NONNULL_BEGIN
OWSFailDebug(@"Failed to create a pre key bundle.");
}
[self.primaryStorage setPreKeyBundle:bundle forContact:envelope.source transaction:transaction];
// Loki: If we received a friend request, but we were already friends with this user, then reset the session
if (envelope.type == SSKProtoEnvelopeTypeFriendRequest) {
TSContactThread *thread = [TSContactThread getThreadWithContactId:envelope.source transaction:transaction];
if (thread && thread.isContactFriend) {
[self resetSessionWithContact:envelope.source transaction:transaction];
// Let our other devices know that we have reset the session
[SSKEnvironment.shared.syncManager syncContact:envelope.source transaction:transaction];
}
}
}
// Loki: Handle address message if needed
@ -535,7 +545,10 @@ NS_ASSUME_NONNULL_BEGIN
OWSFail(@"Missing transaction.");
return;
}
// Loki: Don't process session restore messages
if ((dataMessage.flags & SSKProtoDataMessageFlagsSessionRestore) != 0) { return; }
if ([self isDataMessageBlocked:dataMessage envelope:envelope]) {
NSString *logMessage = [NSString stringWithFormat:@"Ignoring blocked message from sender: %@", envelope.source];
if (dataMessage.group) {
@ -1081,23 +1094,21 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction];
[self resetSessionWithContact:envelope.source transaction:transaction];
}
- (void)resetSessionWithContact:(NSString *)hexEncodedPublicKey
transaction:(YapDatabaseReadWriteTransaction *)transaction {
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
// MJK TODO - safe to remove senderTimestamp
[[[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp
inThread:thread
messageType:TSInfoMessageTypeLokiSessionResetInProgress] saveWithTransaction:transaction];
/* Loki: Original code
* ================
[[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageType:TSInfoMessageTypeSessionDidEnd] saveWithTransaction:transaction];
* ================
*/
// Loki: Archive all our sessions
// Ref: SignalServiceKit/Loki/Docs/SessionReset.md
[self.primaryStorage archiveAllSessionsForContact:envelope.source protocolContext:transaction];
[self.primaryStorage archiveAllSessionsForContact:hexEncodedPublicKey protocolContext:transaction];
// Loki: Set our session reset state
thread.sessionResetState = TSContactThreadSessionResetStateRequestReceived;
@ -1107,13 +1118,7 @@ NS_ASSUME_NONNULL_BEGIN
LKEphemeralMessage *emptyMessage = [[LKEphemeralMessage alloc] initInThread:thread];
[self.messageSenderJobQueue addMessage:emptyMessage transaction:transaction];
NSLog(@"[Loki] Session reset received from %@.", envelope.source);
/* Loki: Original code
* ================
[self.primaryStorage deleteAllSessionsForContact:envelope.source protocolContext:transaction];
* ================
*/
NSLog(@"[Loki] Session reset received from %@.", hexEncodedPublicKey);
}
- (void)handleExpirationTimerUpdateMessageWithEnvelope:(SSKProtoEnvelope *)envelope
@ -1717,10 +1722,6 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
message.friendRequestStatus = LKMessageFriendRequestStatusPending; // Don't save yet. This is done in finalizeIncomingMessage:thread:masterThread:envelope:transaction.
} else {
// This can happen if Alice and Bob have a session, Bob deletes his app, restores from seed, and then sends a friend request to Alice again.
// TODO: Re-enable when seed restoration is done
// [self handleEndSessionMessageWithEnvelope:envelope dataMessage:data transaction:transaction];
}
}

@ -10,7 +10,7 @@
#import "OWSBackgroundTask.h"
#import "OWSBatchMessageProcessor.h"
#import "OWSMessageDecrypter.h"
#import "OWSPrimaryStorage.h"
#import "OWSPrimaryStorage+Loki.h"
#import "OWSQueues.h"
#import "OWSStorage.h"
#import "OWSIdentityManager.h"
@ -404,6 +404,16 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
return;
}
// Loki: Ignore any friend requests that we got before restoration
uint64_t restorationTime = [NSNumber numberWithDouble:[OWSPrimaryStorage.sharedManager getRestorationTime]].unsignedLongLongValue;
if (envelope.type == SSKProtoEnvelopeTypeFriendRequest && envelope.timestamp < restorationTime * 1000) {
[LKLogger print:@"[Loki] Ignoring friend request received before restoration."];
dispatch_async(self.serialQueue, ^{
completion(YES);
});
return;
}
// We use the original envelope for this check;
// the decryption process might rewrite the envelope.
BOOL wasReceivedByUD = [self wasReceivedByUD:envelope];

@ -97,6 +97,7 @@ NS_SWIFT_NAME(MessageSender)
success:(void (^)(void))successHandler
failure:(void (^)(NSError *error))failureHandler;
- (OWSMessageSend *)getSessionRestoreMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey;
- (OWSMessageSend *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey;
- (void)sendMessage:(OWSMessageSend *)messageSend;

@ -45,6 +45,7 @@
#import "TSThread.h"
#import "TSContactThread.h"
#import "LKFriendRequestMessage.h"
#import "LKSessionRestoreMessage.h"
#import "LKDeviceLinkMessage.h"
#import "LKAddressMessage.h"
#import <AxolotlKit/AxolotlExceptions.h>
@ -915,15 +916,32 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return deviceMessages;
}
- (OWSMessageSend *)getSessionRestoreMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey
{
__block TSContactThread *thread;
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
// Force hide slave device thread
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:hexEncodedPublicKey in:transaction];
thread.isForceHidden = masterHexEncodedPublicKey != nil && ![masterHexEncodedPublicKey isEqualToString:hexEncodedPublicKey];
[thread saveWithTransaction:transaction];
}];
if (thread == nil) { return nil; }
LKSessionRestoreMessage *message = [[LKSessionRestoreMessage alloc] initWithThread:thread];
message.skipSave = YES;
SignalRecipient *recipient = [[SignalRecipient alloc] initWithUniqueId:hexEncodedPublicKey];
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
return [[OWSMessageSend alloc] initWithMessage:message thread:thread recipient:recipient senderCertificate:nil udAccess:nil localNumber:userHexEncodedPublicKey success:^{ } failure:^(NSError *error) { }];
}
- (OWSMessageSend *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey
{
__block TSContactThread *thread;
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSContactThread fetchObjectWithUniqueID:[TSContactThread threadIdFromContactId:hexEncodedPublicKey] transaction:transaction];
if (thread == nil) {
thread = [[TSContactThread alloc] initWithContactId:hexEncodedPublicKey];
}
thread.isForceHidden = YES;
thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
// Force hide slave device thread
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:hexEncodedPublicKey in:transaction];
thread.isForceHidden = masterHexEncodedPublicKey != nil && ![masterHexEncodedPublicKey isEqualToString:hexEncodedPublicKey];
[thread saveWithTransaction:transaction];
}];
LKFriendRequestMessage *message = [[LKFriendRequestMessage alloc] initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"Please accept to enable messages to be synced across devices" attachmentIds:[NSMutableArray new]
@ -1316,6 +1334,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// Update the thread
[message.thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestSent withTransaction:transaction];
[message.thread removeOldOutgoingFriendRequestMessagesIfNeededWithTransaction:transaction];
if ([message.thread isKindOfClass:[TSContactThread class]]) {
[((TSContactThread *) message.thread) removeAllSessionRestoreDevicesWithTransaction:transaction];
}
// Update the message
[message saveFriendRequestStatus:LKMessageFriendRequestStatusPending withTransaction:transaction];
NSTimeInterval expirationInterval = 72 * kHourInterval;

@ -3433,6 +3433,7 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder {
case endSession = 1
case expirationTimerUpdate = 2
case profileKeyUpdate = 4
case sessionRestore = 64
case unlinkDevice = 128
}
@ -3441,6 +3442,7 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder {
case .endSession: return .endSession
case .expirationTimerUpdate: return .expirationTimerUpdate
case .profileKeyUpdate: return .profileKeyUpdate
case .sessionRestore: return .sessionRestore
case .unlinkDevice: return .unlinkDevice
}
}
@ -3450,6 +3452,7 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder {
case .endSession: return .endSession
case .expirationTimerUpdate: return .expirationTimerUpdate
case .profileKeyUpdate: return .profileKeyUpdate
case .sessionRestore: return .sessionRestore
case .unlinkDevice: return .unlinkDevice
}
}

@ -881,6 +881,7 @@ struct SignalServiceProtos_DataMessage {
case endSession // = 1
case expirationTimerUpdate // = 2
case profileKeyUpdate // = 4
case sessionRestore // = 64
case unlinkDevice // = 128
init() {
@ -892,6 +893,7 @@ struct SignalServiceProtos_DataMessage {
case 1: self = .endSession
case 2: self = .expirationTimerUpdate
case 4: self = .profileKeyUpdate
case 64: self = .sessionRestore
case 128: self = .unlinkDevice
default: return nil
}
@ -902,6 +904,7 @@ struct SignalServiceProtos_DataMessage {
case .endSession: return 1
case .expirationTimerUpdate: return 2
case .profileKeyUpdate: return 4
case .sessionRestore: return 64
case .unlinkDevice: return 128
}
}
@ -3478,6 +3481,7 @@ extension SignalServiceProtos_DataMessage.Flags: SwiftProtobuf._ProtoNameProvidi
1: .same(proto: "END_SESSION"),
2: .same(proto: "EXPIRATION_TIMER_UPDATE"),
4: .same(proto: "PROFILE_KEY_UPDATE"),
64: .same(proto: "SESSION_RESTORE"),
128: .same(proto: "UNLINK_DEVICE"),
]
}

@ -19,6 +19,12 @@ public class OWSMockSyncManager: NSObject, OWSSyncManagerProtocol {
return AnyPromise()
}
@objc public func syncContact(_ hexEncodedPubKey: String, transaction: YapDatabaseReadTransaction) -> AnyPromise {
Logger.info("")
return AnyPromise()
}
@objc public func syncAllContacts() -> AnyPromise {
Logger.info("")

@ -6,6 +6,7 @@ NS_ASSUME_NONNULL_BEGIN
@class AnyPromise;
@class SignalAccount;
@class YapDatabaseReadTransaction;
@protocol OWSSyncManagerProtocol <NSObject>
@ -13,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN
- (AnyPromise *)syncLocalContact __attribute__((warn_unused_result));
- (AnyPromise *)syncContact:(NSString *)hexEncodedPubKey transaction:(YapDatabaseReadTransaction *)transaction;
- (AnyPromise *)syncAllContacts __attribute__((warn_unused_result));
- (AnyPromise *)syncContactsForSignalAccounts:(NSArray<SignalAccount *> *)signalAccounts __attribute__((warn_unused_result));

Loading…
Cancel
Save