Skip to content

Commit

Permalink
WebSocketが手動操作以外で切断された場合に自動で再接続を試みるようにした, fix #24
Browse files Browse the repository at this point in the history
  • Loading branch information
shibafu528 committed Feb 11, 2022
1 parent 19b1e1f commit 75979ed
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 11 deletions.
8 changes: 8 additions & 0 deletions Cocotodon.xcodeproj/project.pbxproj
Expand Up @@ -38,6 +38,7 @@
5D54E6C125138D840067EAB4 /* FlippedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D54E6C025138D840067EAB4 /* FlippedView.m */; };
5D6EFB5F27AD6E80004A5FBF /* DONStatusContentAnchor.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D6EFB5E27AD6E80004A5FBF /* DONStatusContentAnchor.m */; };
5D6EFB6227B63651004A5FBF /* TimelineTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D6EFB6127B63651004A5FBF /* TimelineTableView.m */; };
5D6EFB6527B64D5C004A5FBF /* DONAutoReconnectStreaming.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D6EFB6427B64D5C004A5FBF /* DONAutoReconnectStreaming.m */; };
5D6F5E292515CAEE00018EBA /* PostBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D6F5E282515CAEE00018EBA /* PostBox.m */; };
5D76A1AD2674C20E0024C882 /* AcknowledgementsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D76A1AC2674C20E0024C882 /* AcknowledgementsViewController.m */; };
5D76A1B126752C210024C882 /* Pods-Cocotodon-acknowledgements.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5D76A1B026752C190024C882 /* Pods-Cocotodon-acknowledgements.plist */; };
Expand Down Expand Up @@ -187,6 +188,9 @@
5D6EFB5E27AD6E80004A5FBF /* DONStatusContentAnchor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DONStatusContentAnchor.m; sourceTree = "<group>"; };
5D6EFB6027B63651004A5FBF /* TimelineTableView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TimelineTableView.h; sourceTree = "<group>"; };
5D6EFB6127B63651004A5FBF /* TimelineTableView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TimelineTableView.m; sourceTree = "<group>"; };
5D6EFB6327B64D5C004A5FBF /* DONAutoReconnectStreaming.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DONAutoReconnectStreaming.h; sourceTree = "<group>"; };
5D6EFB6427B64D5C004A5FBF /* DONAutoReconnectStreaming.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DONAutoReconnectStreaming.m; sourceTree = "<group>"; };
5D6EFB6627B65097004A5FBF /* DONAutoReconnectStreamingDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DONAutoReconnectStreamingDelegate.h; sourceTree = "<group>"; };
5D6F5E272515CAEE00018EBA /* PostBox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PostBox.h; sourceTree = "<group>"; };
5D6F5E282515CAEE00018EBA /* PostBox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PostBox.m; sourceTree = "<group>"; };
5D76A1AB2674C20E0024C882 /* AcknowledgementsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AcknowledgementsViewController.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -456,6 +460,9 @@
5DA74AAF2518F9BB00A1CF38 /* DONWebSocketStreaming.h */,
5DA74AB02518F9BB00A1CF38 /* DONWebSocketStreaming.m */,
5D45116F2519010E006CA130 /* DONStreamingEventDelegate.h */,
5D6EFB6327B64D5C004A5FBF /* DONAutoReconnectStreaming.h */,
5D6EFB6427B64D5C004A5FBF /* DONAutoReconnectStreaming.m */,
5D6EFB6627B65097004A5FBF /* DONAutoReconnectStreamingDelegate.h */,
);
path = Mastodon;
sourceTree = "<group>";
Expand Down Expand Up @@ -1028,6 +1035,7 @@
5D54E6C125138D840067EAB4 /* FlippedView.m in Sources */,
5D7AC8A625029CA70042009D /* MRBPin.m in Sources */,
5D94871B25173AE8009066C3 /* DONStatus.m in Sources */,
5D6EFB6527B64D5C004A5FBF /* DONAutoReconnectStreaming.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
6 changes: 3 additions & 3 deletions Cocotodon/MainViewController.m
Expand Up @@ -47,7 +47,7 @@ - (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
}];
}];
};
homeVC.streamingInitiator = ^DONWebSocketStreaming *(TimelineViewController<DONStreamingEventDelegate> *vc) {
homeVC.streamingInitiator = ^DONWebSocketStreaming *(id<DONStreamingEventDelegate> vc) {
return [App.client userStreamingViaWebSocketWithDelegate:vc];
};
[self.tabVC addTabViewItem:[NSTabViewItem tabViewItemWithViewController:homeVC label:@"ホーム"]];
Expand All @@ -64,7 +64,7 @@ - (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
}];
}];
};
localVC.streamingInitiator = ^DONWebSocketStreaming *(TimelineViewController<DONStreamingEventDelegate> *vc) {
localVC.streamingInitiator = ^DONWebSocketStreaming *(id<DONStreamingEventDelegate> vc) {
return [App.client localPublicStreamingViaWebSocketWithDelegate:vc];
};
[self.tabVC addTabViewItem:[NSTabViewItem tabViewItemWithViewController:localVC label:@"ローカル"]];
Expand All @@ -81,7 +81,7 @@ - (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
}];
}];
};
federatedVC.streamingInitiator = ^DONWebSocketStreaming *(TimelineViewController<DONStreamingEventDelegate> *vc) {
federatedVC.streamingInitiator = ^DONWebSocketStreaming *(id<DONStreamingEventDelegate> vc) {
return [App.client publicStreamingViaWebSocketWithDelegate:vc];
};
[self.tabVC addTabViewItem:[NSTabViewItem tabViewItemWithViewController:federatedVC label:@"連合"]];
Expand Down
2 changes: 1 addition & 1 deletion Cocotodon/Timeline/TimelineViewController.h
Expand Up @@ -8,7 +8,7 @@
@interface TimelineViewController : NSViewController

@property (nonatomic, copy) AnyPromise* (^timelineReloader)(TimelineViewController *vc);
@property (nonatomic, copy) DONWebSocketStreaming* (^streamingInitiator)(TimelineViewController<DONStreamingEventDelegate> *vc);
@property (nonatomic, copy) DONWebSocketStreaming* (^streamingInitiator)(id<DONStreamingEventDelegate> vc);

- (IBAction)reply:(id)sender;
- (IBAction)favorite:(id)sender;
Expand Down
17 changes: 14 additions & 3 deletions Cocotodon/Timeline/TimelineViewController.m
Expand Up @@ -7,6 +7,7 @@
#import "PostBox.h"
#import "ExpandableCellView.h"
#import "DONEmojiExpander.h"
#import "DONAutoReconnectStreaming.h"
#import "MainWindowController.h"
#import "IntentManager.h"
#import "ThreadWindow.h"
Expand All @@ -16,11 +17,12 @@

// ----------

@interface TimelineViewController () <NSTextViewDelegate, NSTableViewDelegate, NSTableViewDataSource, NSMenuDelegate, DONStreamingEventDelegate>
@interface TimelineViewController () <NSTextViewDelegate, NSTableViewDelegate, NSTableViewDataSource, NSMenuDelegate, DONAutoReconnectStreamingDelegate>

@property (nonatomic, weak) IBOutlet NSTableView *tableView;

@property (nonatomic) DONWebSocketStreaming *userStream;
@property (nonatomic) DONAutoReconnectStreaming *autoReconnect;

@property (nonatomic) NSArray<DONStatus*> *statuses;

Expand All @@ -47,7 +49,8 @@ - (void)viewDidLoad {
self.presentationMode = NO;

if (self.streamingInitiator) {
self.userStream = self.streamingInitiator(self);
self.autoReconnect = [[DONAutoReconnectStreaming alloc] initWithDelegate:self];
self.userStream = self.streamingInitiator(self.autoReconnect);
}

[self reload:nil];
Expand Down Expand Up @@ -161,13 +164,21 @@ - (void)donStreamingDidFailWithError:(NSError *)error {
});
}

- (void)donStreamingDidCompleteWithError:(NSError *)error {
- (void)donStreamingDidCompleteWithCloseCode:(NSURLSessionWebSocketCloseCode)closeCode error:(NSError *)error {
NSLog(@"ws close: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[self updateToolbarStreamingItem];
});
}

- (void)donStreamingShouldReconnect:(DONAutoReconnectStreaming *)autoReconnect {
NSLog(@"ws reconnect");
[self.userStream connect];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateToolbarStreamingItem];
});
}

- (void)updateToolbarStreamingItem {
NSString *sym = self.userStream.isConnected ? @"bolt.fill" : @"bolt.slash";
MainWindowController *wc = (MainWindowController*) self.view.window.windowController;
Expand Down
20 changes: 20 additions & 0 deletions DonKit/Mastodon/DONAutoReconnectStreaming.h
@@ -0,0 +1,20 @@
//
// Copyright (c) 2022 shibafu
//

#import <Foundation/Foundation.h>
#import "DONAutoReconnectStreamingDelegate.h"

NS_ASSUME_NONNULL_BEGIN

@interface DONAutoReconnectStreaming : NSObject <DONStreamingEventDelegate>

@property (nonatomic, weak) id<DONAutoReconnectStreamingDelegate> delegate;

- (instancetype)init __attribute__((unavailable("init is not available")));

- (instancetype)initWithDelegate:(id<DONAutoReconnectStreamingDelegate>)delegate;

@end

NS_ASSUME_NONNULL_END
62 changes: 62 additions & 0 deletions DonKit/Mastodon/DONAutoReconnectStreaming.m
@@ -0,0 +1,62 @@
//
// Copyright (c) 2022 shibafu
//

#import "DONAutoReconnectStreaming.h"

@interface DONAutoReconnectStreaming ()

@property (nonatomic) NSInteger retryCount;

@end

@implementation DONAutoReconnectStreaming

- (instancetype)initWithDelegate:(id<DONAutoReconnectStreamingDelegate>)delegate {
if (self = [super init]) {
_delegate = delegate;
_retryCount = 0;
}
return self;
}

- (void)donStreamingDidConnect {
if ([self.delegate respondsToSelector:@selector(donStreamingDidConnect)]) {
[self.delegate donStreamingDidConnect];
}
self.retryCount = 0; // reset retry counter
}

- (void)donStreamingDidReceiveUpdate:(nonnull DONStatus *)status {
[self.delegate donStreamingDidReceiveUpdate:status];
}

- (void)donStreamingDidReceiveNotification:(nonnull DONMastodonNotification *)notification {
[self.delegate donStreamingDidReceiveNotification:notification];
}

- (void)donStreamingDidFailWithError:(nonnull NSError *)error {
[self.delegate donStreamingDidFailWithError:error];
}

- (void)donStreamingDidCompleteWithCloseCode:(NSURLSessionWebSocketCloseCode)closeCode error:(NSError *)error {
[self.delegate donStreamingDidCompleteWithCloseCode:(NSURLSessionWebSocketCloseCode)closeCode error:error];
if (closeCode == NSURLSessionWebSocketCloseCodeNormalClosure) {
// cancel retry
self.retryCount = 0;
} else {
// try reconnect
int timeToSleep = MIN(2 << self.retryCount, 60);
self.retryCount += 1;
NSLog(@"streaming finish with error. try reconnect after %d sec.", timeToSleep);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeToSleep * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.retryCount == 0) {
// cancelled
return;
}
[self.delegate donStreamingShouldReconnect:self];
});
}
}

@end
17 changes: 17 additions & 0 deletions DonKit/Mastodon/DONAutoReconnectStreamingDelegate.h
@@ -0,0 +1,17 @@
//
// Copyright (c) 2022 shibafu
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@class DONAutoReconnectStreaming;

@protocol DONAutoReconnectStreamingDelegate <DONStreamingEventDelegate>

- (void)donStreamingShouldReconnect:(DONAutoReconnectStreaming *)autoReconnect;

@end

NS_ASSUME_NONNULL_END
6 changes: 5 additions & 1 deletion DonKit/Mastodon/DONStreamingEventDelegate.h
Expand Up @@ -15,7 +15,11 @@ NS_ASSUME_NONNULL_BEGIN

- (void)donStreamingDidFailWithError:(NSError*)error;

- (void)donStreamingDidCompleteWithError:(NSError*)error;
- (void)donStreamingDidCompleteWithCloseCode:(NSURLSessionWebSocketCloseCode)closeCode error:(NSError * _Nullable)error;

@optional

- (void)donStreamingDidConnect;

@end

Expand Down
24 changes: 21 additions & 3 deletions DonKit/Mastodon/DONWebSocketStreaming.m
Expand Up @@ -13,6 +13,7 @@ @interface DONWebSocketStreaming () <NSURLSessionDelegate, NSURLSessionTaskDeleg
@property (nonatomic) NSURLSessionWebSocketTask *task;
@property (nonatomic) NSTimer *pingTimer;
@property (nonatomic, weak) id <DONStreamingEventDelegate> delegate;
@property (nonatomic) BOOL sentConnectedEvent;

@end

Expand All @@ -26,6 +27,7 @@ - (instancetype)initWithConfiguration:(NSURLSessionConfiguration *)configuration
_task = nil;
_pingTimer = nil;
_delegate = delegate;
_sentConnectedEvent = NO;
}
return self;
}
Expand All @@ -41,7 +43,10 @@ - (void)connect {
[self disconnect];
}

self.task = [self.session webSocketTaskWithURL:self.endpoint];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.endpoint];
// (for debug) ertona.net supports X-Disconnect-After https://ertona.net/about/more
//[request setValue:@"5" forHTTPHeaderField:@"X-Disconnect-After"];
self.task = [self.session webSocketTaskWithRequest:request];
[self.task resume];
[self continiousReceive];
self.pingTimer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:@selector(ping:) userInfo:nil repeats:YES];
Expand All @@ -60,7 +65,9 @@ - (void)ping:(NSTimer*)timer {
[self.task sendPingWithPongReceiveHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"ws ping error: %@", error);
return;
}
[self didConnect];
}];
}

Expand All @@ -73,6 +80,7 @@ - (void)continiousReceive {
return;
}
} else {
[self didConnect];
switch (message.type) {
case NSURLSessionWebSocketMessageTypeData:
NSLog(@"Data received, ...why?: %@", message.data);
Expand Down Expand Up @@ -106,6 +114,15 @@ - (void)continiousReceive {
}];
}

- (void)didConnect {
if (!self.sentConnectedEvent) {
if ([self.delegate respondsToSelector:@selector(donStreamingDidConnect)]) {
[self.delegate donStreamingDidConnect];
}
self.sentConnectedEvent = YES;
}
}

- (void)didReceiveUpdate:(NSString*)payload {
NSError *error = nil;
id decodedPayload = [NSJSONSerialization JSONObjectWithData:[payload dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];
Expand Down Expand Up @@ -155,15 +172,16 @@ - (void)didReceiveFiltersChangedNotice:(NSString*)payload {
#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(@"ws connection closed: %@", error);
NSLog(@"ws connection closed: (close code = %ld) %@", self.task.closeCode, error);
self.isConnected = NO;
self.sentConnectedEvent = NO;
[self.pingTimer invalidate];
self.pingTimer = nil;
if (error) {
NSString *reason = [[NSString alloc] initWithData:self.task.closeReason encoding:NSUTF8StringEncoding];
NSLog(@"ws close code & reason: %ld %@", self.task.closeCode, reason);
}
[self.delegate donStreamingDidCompleteWithError:error];
[self.delegate donStreamingDidCompleteWithCloseCode:self.task.closeCode error:error];
}

#pragma mark - NSURLSessionDelegate
Expand Down

0 comments on commit 75979ed

Please sign in to comment.