1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
//
// TAPConnectionManager.m
// TapTalk
//
// Created by Dominic Vedericho on 09/08/18.
// Copyright © 2018 Moselo. All rights reserved.
//
#import "TAPConnectionManager.h"
#import <AFNetworking/AFNetworking.h>
#define kSocketAutomaticallyReconnect YES
#define kSocketReconnectDelay 0.5f
#define kSocketReconnectMaximumMultiplier 60.0f
@interface TAPConnectionManager () <SRWebSocketDelegate>
@property (strong, nonatomic) SRWebSocket *webSocket;
@property (strong, nonatomic) NSString *socketURL;
@property (nonatomic) NSInteger reconnectAttempt;
@property (nonatomic) BOOL isShouldReconnect;
@property (strong, nonatomic) NSMutableArray *delegatesArray;
- (void)tryToReconnect;
- (void)reconnect;
@end
@implementation TAPConnectionManager
#pragma mark - Lifecycle
+ (TAPConnectionManager *)sharedManager {
static TAPConnectionManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[self alloc] init];
});
return sharedManager;
}
- (id)init {
self = [super init];
if (self) {
_tapConnectionStatus = TAPConnectionManagerStatusTypeNotConnected;
_socketURL = [[NSString alloc] init];
_delegatesArray = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}
#pragma mark - Delegate
#pragma mark SRWebSocket
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
#ifdef DEBUG
NSLog(@"Socket Open");
#endif
_reconnectAttempt = 0;
_webSocket = webSocket;
_tapConnectionStatus = TAPConnectionManagerStatusTypeConnected;
[[NSNotificationCenter defaultCenter] postNotificationName:TAP_NOTIFICATION_SOCKET_CONNECTED object:nil];
for (id delegate in self.delegatesArray) {
if ([delegate respondsToSelector:@selector(connectionManagerDidConnected)]) {
[delegate connectionManagerDidConnected];
}
}
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
#ifdef DEBUG
NSLog(@"Socket Receive Emit");
#endif
NSString *messageString = [[NSString alloc] initWithData:message encoding:NSUTF8StringEncoding];
//Seperate combined message from server if exist
NSArray *messageArray = [messageString componentsSeparatedByString:@"\n"];
//Loop for combined message from server
for (NSString *currentMessage in messageArray) {
NSDictionary *messageDictionary = [TAPUtil jsonObjectFromString:currentMessage];
NSString *eventName = [messageDictionary objectForKey:@"eventName"];
NSDictionary *dataDictionary = [messageDictionary objectForKey:@"data"];
for (id delegate in self.delegatesArray) {
if ([delegate respondsToSelector:@selector(connectionManagerDidReceiveNewEmit:parameter:)]) {
[delegate connectionManagerDidReceiveNewEmit:eventName parameter:dataDictionary];
}
}
}
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
#ifdef DEBUG
NSLog(@"Socket Fail with Error: %@", [error description]);
#endif
[[NSNotificationCenter defaultCenter] postNotificationName:TAP_NOTIFICATION_SOCKET_RECEIVE_ERROR object:error];
_tapConnectionStatus = TAPConnectionManagerStatusTypeDisconnected;
for (id delegate in self.delegatesArray) {
if ([delegate respondsToSelector:@selector(connectionManagerDidReceiveError:)]) {
[delegate connectionManagerDidReceiveError:error];
}
}
[self tryToReconnect];
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean {
#ifdef DEBUG
NSLog(@"Socket Close with Code: %li Reason:%@ Clean:%@", code, reason, STRING_FROM_BOOL(wasClean));
#endif
[[NSNotificationCenter defaultCenter] postNotificationName:TAP_NOTIFICATION_SOCKET_DISCONNECTED object:nil];
_tapConnectionStatus = TAPConnectionManagerStatusTypeDisconnected;
for (id delegate in self.delegatesArray) {
if ([delegate respondsToSelector:@selector(connectionManagerDidDisconnectedWithCode:reason:cleanClose:)]) {
if (reason == nil) {
reason = @"";
}
[delegate connectionManagerDidDisconnectedWithCode:code reason:reason cleanClose:wasClean];
}
}
[self tryToReconnect];
}
#pragma mark - Custom Method
- (void)connect {
#ifdef DEBUG
NSLog(@"ConnectionManager Connect");
#endif
if (self.tapConnectionStatus != TAPConnectionManagerStatusTypeDisconnected && self.tapConnectionStatus != TAPConnectionManagerStatusTypeNotConnected) {
return;
}
_tapConnectionStatus = TAPConnectionManagerStatusTypeConnecting;
_isShouldReconnect = kSocketAutomaticallyReconnect;
[[NSNotificationCenter defaultCenter] postNotificationName:TAP_NOTIFICATION_SOCKET_CONNECTING object:nil];
for (id delegate in self.delegatesArray) {
if ([delegate respondsToSelector:@selector(connectionManagerIsConnecting)]) {
[delegate connectionManagerIsConnecting];
}
}
_webSocket.delegate = nil;
_webSocket = nil;
[self validateToken];
NSString *authorizationValueString = [NSString stringWithFormat:@"Bearer %@", [TAPDataManager getAccessToken]];
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.socketURL]];
NSString *encodedAppKey = [[TAPNetworkManager sharedManager] getAppKey];
[urlRequest addValue:encodedAppKey forHTTPHeaderField:@"App-Key"];
[urlRequest addValue:[[UIDevice currentDevice] identifierForVendor].UUIDString forHTTPHeaderField:@"Device-Identifier"];
[urlRequest addValue:[[UIDevice currentDevice] model] forHTTPHeaderField:@"Device-Model"];
[urlRequest addValue:@"ios" forHTTPHeaderField:@"Device-Platform"];
[urlRequest addValue:[[UIDevice currentDevice] systemVersion] forHTTPHeaderField:@"Device-OS-Version"];
[urlRequest addValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forHTTPHeaderField:@"App-Version"];
[urlRequest addValue:@"ios" forHTTPHeaderField:@"User-Agent"];
[urlRequest addValue:authorizationValueString forHTTPHeaderField:@"Authorization"];
SRWebSocket *webSocket = [[SRWebSocket alloc] initWithURLRequest:urlRequest];
webSocket.delegate = self;
[webSocket open];
}
- (void)reconnect {
#ifdef DEBUG
NSLog(@"ConnectionManager Reconnect");
#endif
[[NSNotificationCenter defaultCenter] postNotificationName:TAP_NOTIFICATION_SOCKET_RECONNECTING object:nil];
for (id delegate in self.delegatesArray) {
if ([delegate respondsToSelector:@selector(connectionManagerIsReconnecting)]) {
[delegate connectionManagerIsReconnecting];
}
}
[self connect];
}
- (void)sendEmit:(NSString *)eventName parameters:(NSDictionary *)parameterDictionary {
#ifdef DEBUG
NSLog(@"ConnectionManager Send Emit: %@\nParameter: %@", eventName, parameterDictionary);
#endif
NSMutableDictionary *emitDictionary = [NSMutableDictionary dictionary];
[emitDictionary setObject:eventName forKey:@"eventName"];
[emitDictionary setObject:parameterDictionary forKey:@"data"];
NSString *emitString = [TAPUtil jsonStringFromObject:emitDictionary];
NSData *emitData = [emitString dataUsingEncoding:NSUTF8StringEncoding];
[self.webSocket send:emitData];
}
- (void)disconnect {
#ifdef DEBUG
NSLog(@"ConnectionManager Disconnect");
#endif
if (self.webSocket == nil) {
return;
}
_isShouldReconnect = NO;
[self.webSocket close];
_webSocket.delegate = nil;
_webSocket = nil;
_tapConnectionStatus = TAPConnectionManagerStatusTypeDisconnected;
[[NSNotificationCenter defaultCenter] postNotificationName:TAP_NOTIFICATION_SOCKET_DISCONNECTED object:nil];
for (id delegate in self.delegatesArray) {
if ([delegate respondsToSelector:@selector(connectionManagerDidDisconnectedWithCode:reason:cleanClose:)]) {
[delegate connectionManagerDidDisconnectedWithCode:1 reason:@"User close connection" cleanClose:YES];
}
}
}
- (void)tryToReconnect {
if ([TapTalk sharedInstance].instanceState == TapTalkInstanceStateInactive) {
//Don't reconnect if apps is in background and doesn't have background sequence task
_reconnectAttempt = 0;
return;
}
if (self.isShouldReconnect) {
_reconnectAttempt++;
CGFloat reconnectDelay = self.reconnectAttempt * kSocketReconnectDelay;
if (reconnectDelay > kSocketReconnectMaximumMultiplier) {
//Set maximum reconnect delay if excedeed
reconnectDelay = kSocketReconnectMaximumMultiplier;
}
[self performSelector:@selector(reconnect) withObject:nil afterDelay:reconnectDelay];
}
}
- (TAPConnectionManagerStatusType)getSocketConnectionStatus {
return self.tapConnectionStatus;
}
- (void)validateToken {
NSString *accessToken = [TAPDataManager getAccessToken];
if (accessToken == nil || [accessToken isEqualToString:@""]) {
return;
}
[TAPDataManager callAPIValidateAccessTokenAndAutoRefreshSuccess:^{
#ifdef DEBUG
NSLog(@"Token Validated");
#endif
} failure:^(NSError *error) {
}];
}
- (void)setSocketURLString:(NSString *)urlString {
NSString *formattedSocketURLString;
if ([urlString hasPrefix:@"https"]) {
formattedSocketURLString = [urlString stringByReplacingOccurrencesOfString:@"https" withString:@"wss"];
}
else if ([urlString hasPrefix:@"http"]) {
formattedSocketURLString = [urlString stringByReplacingOccurrencesOfString:@"http" withString:@"wss"];
}
_socketURL = [NSString stringWithFormat:@"%@/connect", formattedSocketURLString];
}
- (void)addDelegate:(id)delegate {
if ([self.delegatesArray containsObject:delegate]) {
return;
}
NSLog(@"[WARNING] ConnectionManager - Do not forget to remove the delegate object, since an object can't weak retained in an array, also please remove this delegate before dealloc or the delegate will always retained");
[self.delegatesArray addObject:delegate];
}
- (void)removeDelegate:(id)delegate {
[self.delegatesArray removeObject:delegate];
}
@end