NCExternalSignalingController.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "NCExternalSignalingController.h"
  6. #import "NCAPIController.h"
  7. #import "NCDatabaseManager.h"
  8. #import "NCRoomsManager.h"
  9. #import "NCSettingsController.h"
  10. #import "WSMessage.h"
  11. #import "NextcloudTalk-Swift.h"
  12. static NSTimeInterval kInitialReconnectInterval = 1;
  13. static NSTimeInterval kMaxReconnectInterval = 16;
  14. static NSTimeInterval kWebSocketTimeoutInterval = 15;
  15. NSString * const NCExternalSignalingControllerDidUpdateParticipantsNotification = @"NCExternalSignalingControllerDidUpdateParticipantsNotification";
  16. NSString * const NCExternalSignalingControllerDidReceiveJoinOfParticipantNotification = @"NCExternalSignalingControllerDidReceiveJoinOfParticipant";
  17. NSString * const NCExternalSignalingControllerDidReceiveLeaveOfParticipantNotification = @"NCExternalSignalingControllerDidReceiveLeaveOfParticipant";
  18. NSString * const NCExternalSignalingControllerDidReceiveStartedTypingNotification = @"NCExternalSignalingControllerDidReceiveStartedTypingNotification";
  19. NSString * const NCExternalSignalingControllerDidReceiveStoppedTypingNotification = @"NCExternalSignalingControllerDidReceiveStoppedTypingNotification";
  20. @interface NCExternalSignalingController () <NSURLSessionWebSocketDelegate>
  21. @property (nonatomic, strong) NSURLSessionWebSocketTask *webSocket;
  22. @property (nonatomic, strong) NSString* serverUrl;
  23. @property (nonatomic, strong) NSString* ticket;
  24. @property (nonatomic, strong) NSString* resumeId;
  25. @property (nonatomic, strong) NSString* sessionId;
  26. @property (nonatomic, strong) NSString* userId;
  27. @property (nonatomic, strong) NSString* authenticationBackendUrl;
  28. @property (nonatomic, assign) BOOL helloResponseReceived;
  29. @property (nonatomic, assign) BOOL mcuSupport;
  30. @property (nonatomic, strong) NSMutableDictionary<NSString *, SignalingParticipant *>* participantsMap;
  31. @property (nonatomic, strong) NSMutableArray* pendingMessages;
  32. @property (nonatomic, assign) NSInteger messageId;
  33. @property (nonatomic, strong) WSMessage *helloMessage;
  34. @property (nonatomic, strong) NSMutableArray *messagesWithCompletionBlocks;
  35. @property (nonatomic, assign) NSInteger reconnectInterval;
  36. @property (nonatomic, strong) NSTimer *reconnectTimer;
  37. @property (nonatomic, assign) NSInteger disconnectTime;
  38. @end
  39. @implementation NCExternalSignalingController
  40. - (instancetype)initWithAccount:(TalkAccount *)account server:(NSString *)serverUrl andTicket:(NSString *)ticket
  41. {
  42. self = [super init];
  43. if (self) {
  44. _account = account;
  45. _userId = _account.userId;
  46. _authenticationBackendUrl = [[NCAPIController sharedInstance] authenticationBackendUrlForAccount:_account];
  47. [self setServer:serverUrl andTicket:ticket];
  48. }
  49. return self;
  50. }
  51. - (BOOL)hasMCU
  52. {
  53. return _mcuSupport;
  54. }
  55. - (NSString *)sessionId
  56. {
  57. return _sessionId;
  58. }
  59. - (void)setServer:(NSString *)serverUrl andTicket:(NSString *)ticket
  60. {
  61. _serverUrl = [self getWebSocketUrlForServer:serverUrl];
  62. _ticket = ticket;
  63. _reconnectInterval = kInitialReconnectInterval;
  64. _pendingMessages = [NSMutableArray new];
  65. [self connect];
  66. }
  67. - (NSString *)getWebSocketUrlForServer:(NSString *)serverUrl
  68. {
  69. NSString *wsUrl = [serverUrl copy];
  70. // Change to websocket protocol
  71. wsUrl = [wsUrl stringByReplacingOccurrencesOfString:@"https://" withString:@"wss://"];
  72. wsUrl = [wsUrl stringByReplacingOccurrencesOfString:@"http://" withString:@"ws://"];
  73. // Remove trailing slash
  74. if([wsUrl hasSuffix:@"/"]) {
  75. wsUrl = [wsUrl substringToIndex:[wsUrl length] - 1];
  76. }
  77. // Add spreed endpoint
  78. wsUrl = [wsUrl stringByAppendingString:@"/spreed"];
  79. return wsUrl;
  80. }
  81. #pragma mark - WebSocket connection
  82. - (void)connect
  83. {
  84. [self connect:NO];
  85. }
  86. - (void)forceConnect
  87. {
  88. [self connect:YES];
  89. }
  90. - (void)connect:(BOOL)force
  91. {
  92. BOOL forceConnect = force || [NCRoomsManager sharedInstance].callViewController;
  93. // Do not try to connect if the app is running in the background (unless forcing a connection or in a call)
  94. if (!forceConnect && [[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) {
  95. [NCUtils log:@"Trying to create websocket connection while app is in the background"];
  96. _disconnected = YES;
  97. return;
  98. }
  99. [self invalidateReconnectionTimer];
  100. _disconnected = NO;
  101. _messageId = 1;
  102. _messagesWithCompletionBlocks = [NSMutableArray new];
  103. _helloResponseReceived = NO;
  104. [NCUtils log:[NSString stringWithFormat:@"Connecting to: %@", _serverUrl]];
  105. NSURL *url = [NSURL URLWithString:_serverUrl];
  106. NSString *userAgent = [NSString stringWithFormat:@"Mozilla/5.0 (iOS) Nextcloud-Talk v%@",
  107. [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]];
  108. NSURLSession *wsSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
  109. NSMutableURLRequest *wsRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:kWebSocketTimeoutInterval];
  110. [wsRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"];
  111. if (self.resumeId) {
  112. NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970];
  113. // We are only allowed to resume a session 30s after disconnect
  114. if (!self.disconnectTime || (currentTimestamp - self.disconnectTime) >= 30) {
  115. [NCUtils log:@"We have a resumeId, but we disconnected outside of the 30s resume window. Connecting without resumeId."];
  116. self.resumeId = nil;
  117. }
  118. }
  119. NSURLSessionWebSocketTask *webSocket = [wsSession webSocketTaskWithRequest:wsRequest];
  120. _webSocket = webSocket;
  121. [_webSocket resume];
  122. [self receiveMessage];
  123. }
  124. - (void)reconnect
  125. {
  126. // Note: Make sure to call reconnect only from the main-thread!
  127. if (_reconnectTimer) {
  128. return;
  129. }
  130. [NCUtils log:[NSString stringWithFormat:@"Reconnecting to: %@", _serverUrl]];
  131. [self resetWebSocket];
  132. // Execute completion blocks on all messages
  133. for (WSMessage *message in self->_messagesWithCompletionBlocks) {
  134. [message executeCompletionBlockWithStatus:SendMessageSocketError];
  135. }
  136. [self setReconnectionTimer];
  137. }
  138. - (void)forceReconnect
  139. {
  140. dispatch_async(dispatch_get_main_queue(), ^{
  141. self->_resumeId = nil;
  142. self->_currentRoom = nil;
  143. [self reconnect];
  144. });
  145. }
  146. - (void)forceReconnectForRejoin
  147. {
  148. // In case we force reconnect in order to rejoin the call again, we need to keep the currently joined room.
  149. // In `helloResponseReceived` we determine that we were in a room and that the sessionId changed, in that case
  150. // we trigger a re-join in `NCRoomsManager` which takes care of re-joining.
  151. dispatch_async(dispatch_get_main_queue(), ^{
  152. NSDictionary *byeDict = @{
  153. @"type": @"bye",
  154. @"bye": @{}
  155. };
  156. // Close our current session. Don't leave the room, as that would defeat the above mentioned purpose
  157. [self sendMessage:byeDict withCompletionBlock:^(NSURLSessionWebSocketTask *task, NCExternalSignalingSendMessageStatus status) {
  158. self->_resumeId = nil;
  159. [self reconnect];
  160. }];
  161. });
  162. }
  163. - (void)disconnect
  164. {
  165. [NCUtils log:[NSString stringWithFormat:@"Disconnecting from: %@", _serverUrl]];
  166. self.disconnectTime = [[NSDate date] timeIntervalSince1970];
  167. dispatch_async(dispatch_get_main_queue(), ^{
  168. [self invalidateReconnectionTimer];
  169. [self resetWebSocket];
  170. });
  171. }
  172. - (void)resetWebSocket
  173. {
  174. [_webSocket cancel];
  175. _webSocket = nil;
  176. _helloResponseReceived = NO;
  177. [_helloMessage ignoreCompletionBlock];
  178. _helloMessage = nil;
  179. _disconnected = YES;
  180. }
  181. - (void)setReconnectionTimer
  182. {
  183. [self invalidateReconnectionTimer];
  184. // Wiggle interval a little bit to prevent all clients from connecting
  185. // simultaneously in case the server connection is interrupted.
  186. NSInteger interval = _reconnectInterval - (_reconnectInterval / 2) + arc4random_uniform((int)_reconnectInterval);
  187. NSLog(@"Reconnecting in %ld", (long)interval);
  188. dispatch_async(dispatch_get_main_queue(), ^{
  189. self->_reconnectTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(connect) userInfo:nil repeats:NO];
  190. });
  191. _reconnectInterval = _reconnectInterval * 2;
  192. if (_reconnectInterval > kMaxReconnectInterval) {
  193. _reconnectInterval = kMaxReconnectInterval;
  194. }
  195. }
  196. - (void)invalidateReconnectionTimer
  197. {
  198. [_reconnectTimer invalidate];
  199. _reconnectTimer = nil;
  200. }
  201. #pragma mark - WebSocket messages
  202. - (void)sendMessage:(NSDictionary *)jsonDict withCompletionBlock:(SendMessageCompletionBlock)block
  203. {
  204. WSMessage *wsMessage = [[WSMessage alloc] initWithMessage:jsonDict withCompletionBlock:block];
  205. // Add message as pending message if websocket is not connected
  206. if (!_helloResponseReceived && !wsMessage.isHelloMessage) {
  207. dispatch_async(dispatch_get_main_queue(), ^{
  208. if (wsMessage.isJoinMessage) {
  209. // We join a new room, so any message which wasn't send by now is not relevant for the new room anymore
  210. self->_pendingMessages = [NSMutableArray new];
  211. }
  212. [NCUtils log:@"Trying to send message before we received a hello response -> adding to pendingMessages"];
  213. [self->_pendingMessages addObject:wsMessage];
  214. });
  215. return;
  216. }
  217. [self sendMessage:wsMessage];
  218. }
  219. - (void)sendMessage:(WSMessage *)wsMessage
  220. {
  221. // Assign messageId and timeout to messages with completionBlocks
  222. if (wsMessage.completionBlock) {
  223. NSString *messageIdString = [NSString stringWithFormat: @"%ld", (long)_messageId++];
  224. wsMessage.messageId = messageIdString;
  225. if (wsMessage.isHelloMessage) {
  226. [_helloMessage ignoreCompletionBlock];
  227. _helloMessage = wsMessage;
  228. } else {
  229. [_messagesWithCompletionBlocks addObject:wsMessage];
  230. }
  231. }
  232. if (!wsMessage.webSocketMessage) {
  233. NSLog(@"Error creating websocket message");
  234. [wsMessage executeCompletionBlockWithStatus:SendMessageApplicationError];
  235. return;
  236. }
  237. [wsMessage sendMessageWithWebSocket:_webSocket];
  238. }
  239. - (void)sendHelloMessage
  240. {
  241. NSDictionary *helloDict = @{
  242. @"type": @"hello",
  243. @"hello": @{
  244. @"version": @"1.0",
  245. @"auth": @{
  246. @"url": _authenticationBackendUrl,
  247. @"params": @{
  248. @"userid": _userId,
  249. @"ticket": _ticket
  250. }
  251. }
  252. }
  253. };
  254. // Try to resume session
  255. if (_resumeId) {
  256. helloDict = @{
  257. @"type": @"hello",
  258. @"hello": @{
  259. @"version": @"1.0",
  260. @"resumeid": _resumeId
  261. }
  262. };
  263. }
  264. [NCUtils log:@"Sending hello message"];
  265. [self sendMessage:helloDict withCompletionBlock:^(NSURLSessionWebSocketTask *task, NCExternalSignalingSendMessageStatus status) {
  266. if (status == SendMessageSocketError && task == self->_webSocket) {
  267. [NCUtils log:[NSString stringWithFormat:@"Reconnecting from sendHelloMessage"]];
  268. [self reconnect];
  269. }
  270. }];
  271. }
  272. - (void)helloResponseReceived:(NSDictionary *)messageDict
  273. {
  274. _helloResponseReceived = YES;
  275. [NCUtils log:[NSString stringWithFormat:@"Hello received with %ld pending messages", _pendingMessages.count]];
  276. NSString *messageId = [messageDict objectForKey:@"id"];
  277. [self executeCompletionBlockForMessageId:messageId withStatus:SendMessageSuccess];
  278. NSDictionary *helloDict = [messageDict objectForKey:@"hello"];
  279. _resumeId = [helloDict objectForKey:@"resumeid"];
  280. NSString *newSessionId = [helloDict objectForKey:@"sessionid"];
  281. BOOL sessionChanged = _sessionId && ![_sessionId isEqualToString:newSessionId];
  282. _sessionId = newSessionId;
  283. NSArray *serverFeatures = [[helloDict objectForKey:@"server"] objectForKey:@"features"];
  284. for (NSString *feature in serverFeatures) {
  285. if ([feature isEqualToString:@"mcu"]) {
  286. _mcuSupport = YES;
  287. }
  288. }
  289. NSString *serverVersion = [[helloDict objectForKey:@"server"] objectForKey:@"version"];
  290. dispatch_async(dispatch_get_main_queue(), ^{
  291. BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"NCUpdateSignalingVersionTransaction" expirationHandler:nil];
  292. [[NCDatabaseManager sharedInstance] setExternalSignalingServerVersion:serverVersion forAccountId:self->_account.accountId];
  293. // Send pending messages
  294. for (WSMessage *wsMessage in self->_pendingMessages) {
  295. [self sendMessage:wsMessage];
  296. }
  297. self->_pendingMessages = [NSMutableArray new];
  298. [bgTask stopBackgroundTask];
  299. });
  300. // Re-join if user was in a room
  301. if (_currentRoom && sessionChanged) {
  302. sessionChanged = NO;
  303. [self.delegate externalSignalingControllerWillRejoinCall:self];
  304. [[NCRoomsManager sharedInstance] rejoinRoomForCall:_currentRoom completionBlock:^(NSString * _Nullable sessionId, NCRoom * _Nullable room, NSError * _Nullable error, NSInteger statusCode, NSString * _Nullable statusReason) {
  305. [self.delegate externalSignalingControllerShouldRejoinCall:self];
  306. }];
  307. }
  308. }
  309. - (void)errorResponseReceived:(NSDictionary *)messageDict
  310. {
  311. NSString *errorCode = [[messageDict objectForKey:@"error"] objectForKey:@"code"];
  312. NSString *messageId = [messageDict objectForKey:@"id"];
  313. [NCUtils log:[NSString stringWithFormat:@"Received error response %@", errorCode]];
  314. if ([errorCode isEqualToString:@"no_such_session"] || [errorCode isEqualToString:@"too_many_requests"]) {
  315. // We could not resume the previous session, but the websocket is still alive -> resend the hello message without a resumeId
  316. _resumeId = nil;
  317. [self sendHelloMessage];
  318. return;
  319. } else if ([errorCode isEqualToString:@"already_joined"]) {
  320. // We already joined this room on the signaling server
  321. NSDictionary *details = [[messageDict objectForKey:@"error"] objectForKey:@"details"];
  322. NSString *roomId = [[details objectForKey:@"room"] objectForKey:@"roomid"];
  323. // If we are aware that we were in this room before, we should treat this as a success
  324. if ([_currentRoom isEqualToString:roomId]) {
  325. [self executeCompletionBlockForMessageId:messageId withStatus:SendMessageSuccess];
  326. return;
  327. }
  328. }
  329. [self executeCompletionBlockForMessageId:messageId withStatus:SendMessageApplicationError];
  330. }
  331. - (void)joinRoom:(NSString *)roomId withSessionId:(NSString *)sessionId withFederation:(NSDictionary *)federationDict withCompletionBlock:(JoinRoomExternalSignalingCompletionBlock)block
  332. {
  333. if (_disconnected) {
  334. [NCUtils log:[NSString stringWithFormat:@"Joining room %@, but the websocket is disconnected.", roomId]];
  335. }
  336. if (_webSocket == nil) {
  337. [NCUtils log:[NSString stringWithFormat:@"Joining room %@, but the websocket is nil.", roomId]];
  338. }
  339. NSDictionary *messageDict = @{
  340. @"type": @"room",
  341. @"room": @{
  342. @"roomid": roomId,
  343. @"sessionid": sessionId
  344. }
  345. };
  346. if (federationDict) {
  347. messageDict = @{
  348. @"type": @"room",
  349. @"room": @{
  350. @"roomid": roomId,
  351. @"sessionid": sessionId,
  352. @"federation": federationDict
  353. }
  354. };
  355. }
  356. [self sendMessage:messageDict withCompletionBlock:^(NSURLSessionWebSocketTask *task, NCExternalSignalingSendMessageStatus status) {
  357. if (status == SendMessageSocketError && task == self->_webSocket) {
  358. // Reconnect if this is still the same socket we tried to send the message on
  359. [NCUtils log:[NSString stringWithFormat:@"Reconnect from joinRoom"]];
  360. // When we failed to join a room, we shouldn't try to resume a session but instead do a force reconnect
  361. [self forceReconnect];
  362. }
  363. if (block) {
  364. NSError *error = nil;
  365. if (status != SendMessageSuccess) {
  366. error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
  367. }
  368. block(error);
  369. }
  370. }];
  371. }
  372. - (void)leaveRoom:(NSString *)roomId
  373. {
  374. if ([_currentRoom isEqualToString:roomId]) {
  375. _currentRoom = nil;
  376. [self joinRoom:@"" withSessionId:@"" withFederation:nil withCompletionBlock:nil];
  377. } else {
  378. NSLog(@"External signaling: Not leaving because it's not room we joined");
  379. }
  380. }
  381. - (void)sendCallMessage:(NCSignalingMessage *)message
  382. {
  383. NSDictionary *messageDict = @{
  384. @"type": @"message",
  385. @"message": @{
  386. @"recipient": @{
  387. @"type": @"session",
  388. @"sessionid": message.to
  389. },
  390. @"data": [message functionDict]
  391. }
  392. };
  393. [self sendMessage:messageDict withCompletionBlock:nil];
  394. }
  395. - (void)sendSendOfferMessageWithSessionId:(NSString *)sessionId andRoomType:(NSString *)roomType
  396. {
  397. NSDictionary *messageDict = @{
  398. @"type": @"message",
  399. @"message": @{
  400. @"recipient": @{
  401. @"type": @"session",
  402. @"sessionid": sessionId
  403. },
  404. @"data": @{
  405. @"type": @"sendoffer",
  406. @"roomType": roomType
  407. }
  408. }
  409. };
  410. [self sendMessage:messageDict withCompletionBlock:nil];
  411. }
  412. - (void)sendRoomMessageOfType:(NSString *)messageType andRoomType:(NSString *)roomType
  413. {
  414. NSDictionary *messageDict = @{
  415. @"type": @"message",
  416. @"message": @{
  417. @"recipient": @{
  418. @"type": @"room"
  419. },
  420. @"data": @{
  421. @"type": messageType,
  422. @"roomType": roomType
  423. }
  424. }
  425. };
  426. [self sendMessage:messageDict withCompletionBlock:nil];
  427. }
  428. - (void)requestOfferForSessionId:(NSString *)sessionId andRoomType:(NSString *)roomType
  429. {
  430. NSDictionary *messageDict = @{
  431. @"type": @"message",
  432. @"message": @{
  433. @"recipient": @{
  434. @"type": @"session",
  435. @"sessionid": sessionId
  436. },
  437. @"data": @{
  438. @"type": @"requestoffer",
  439. @"roomType": roomType
  440. }
  441. }
  442. };
  443. [self sendMessage:messageDict withCompletionBlock:nil];
  444. }
  445. - (void)roomMessageReceived:(NSDictionary *)messageDict
  446. {
  447. NSString *newRoomId = [[messageDict objectForKey:@"room"] objectForKey:@"roomid"];
  448. // Only reset the participant map when the room actually changed
  449. // Otherwise we would loose participant information for example when a recording is started
  450. if (![_currentRoom isEqualToString:newRoomId]) {
  451. _participantsMap = [NSMutableDictionary new];
  452. _currentRoom = newRoomId;
  453. }
  454. NSString *messageId = [messageDict objectForKey:@"id"];
  455. [self executeCompletionBlockForMessageId:messageId withStatus:SendMessageSuccess];
  456. }
  457. - (void)eventMessageReceived:(NSDictionary *)eventDict
  458. {
  459. NSString *eventTarget = [eventDict objectForKey:@"target"];
  460. if ([eventTarget isEqualToString:@"room"]) {
  461. [self processRoomEvent:eventDict];
  462. } else if ([eventTarget isEqualToString:@"roomlist"]) {
  463. [self processRoomListEvent:eventDict];
  464. } else if ([eventTarget isEqualToString:@"participants"]) {
  465. [self processRoomParticipantsEvent:eventDict];
  466. } else {
  467. NSLog(@"Unsupported event target: %@", eventDict);
  468. }
  469. }
  470. - (void)processRoomEvent:(NSDictionary *)eventDict
  471. {
  472. NSString *eventType = [eventDict objectForKey:@"type"];
  473. if ([eventType isEqualToString:@"join"]) {
  474. NSArray *joins = [eventDict objectForKey:@"join"];
  475. for (NSDictionary *participantDict in joins) {
  476. SignalingParticipant *participant = [[SignalingParticipant alloc] initWithJoinDictionary:participantDict];
  477. if (!participant.isFederated && [participant.userId isEqualToString:_userId]) {
  478. NSLog(@"App user joined room.");
  479. } else {
  480. NSLog(@"Participant joined room.");
  481. // Only notify if another participant joined the room and not ourselves from a different device
  482. NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
  483. if (_currentRoom && participant.signalingSessionId){
  484. [userInfo setObject:_currentRoom forKey:@"roomToken"];
  485. [userInfo setObject:participant.signalingSessionId forKey:@"sessionId"];
  486. }
  487. [[NSNotificationCenter defaultCenter] postNotificationName:NCExternalSignalingControllerDidReceiveJoinOfParticipantNotification
  488. object:self
  489. userInfo:userInfo];
  490. }
  491. [_participantsMap setObject:participant forKey:participant.signalingSessionId];
  492. }
  493. } else if ([eventType isEqualToString:@"leave"]) {
  494. NSArray *leftSessions = [eventDict objectForKey:@"leave"];
  495. for (NSString *sessionId in leftSessions) {
  496. SignalingParticipant *participant = [self getParticipantFromSessionId:sessionId];
  497. [_participantsMap removeObjectForKey:sessionId];
  498. if ([participant.signalingSessionId isEqualToString:_sessionId] || (!participant.isFederated && [participant.userId isEqualToString:_userId])) {
  499. // Ignore own session
  500. continue;
  501. }
  502. NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
  503. if (_currentRoom && sessionId){
  504. [userInfo setObject:_currentRoom forKey:@"roomToken"];
  505. [userInfo setObject:sessionId forKey:@"sessionId"];
  506. if (participant.userId) {
  507. [userInfo setObject:participant.userId forKey:@"userId"];
  508. }
  509. }
  510. [[NSNotificationCenter defaultCenter] postNotificationName:NCExternalSignalingControllerDidReceiveLeaveOfParticipantNotification
  511. object:self
  512. userInfo:userInfo];
  513. }
  514. } else if ([eventType isEqualToString:@"message"]) {
  515. [self processRoomMessageEvent:[eventDict objectForKey:@"message"]];
  516. } else if ([eventType isEqualToString:@"switchto"]) {
  517. [self processSwitchToMessageEvent:[eventDict objectForKey:@"switchto"]];
  518. } else {
  519. NSLog(@"Unknown room event: %@", eventDict);
  520. }
  521. }
  522. - (void)processRoomMessageEvent:(NSDictionary *)messageDict
  523. {
  524. NSString *messageType = [[messageDict objectForKey:@"data"] objectForKey:@"type"];
  525. if ([messageType isEqualToString:@"chat"]) {
  526. NSLog(@"Chat message received.");
  527. } else if ([messageType isEqualToString:@"recording"]) {
  528. [self.delegate externalSignalingController:self didReceivedSignalingMessage:messageDict];
  529. } else {
  530. NSLog(@"Unknown room message type: %@", messageDict);
  531. }
  532. }
  533. - (void)processSwitchToMessageEvent:(NSDictionary *)messageDict
  534. {
  535. NSString *roomToken = [messageDict objectForKey:@"roomid"];
  536. if (roomToken.length > 0) {
  537. [self.delegate externalSignalingController:self shouldSwitchToCall:roomToken];
  538. } else {
  539. NSLog(@"Unknown switchTo message: %@", messageDict);
  540. }
  541. }
  542. - (void)processRoomListEvent:(NSDictionary *)eventDict
  543. {
  544. NSLog(@"Refresh room list.");
  545. }
  546. - (void)processRoomParticipantsEvent:(NSDictionary *)eventDict
  547. {
  548. NSString *eventType = [eventDict objectForKey:@"type"];
  549. if ([eventType isEqualToString:@"update"]) {
  550. //NSLog(@"Participant list changed: %@", [eventDict objectForKey:@"update"]);
  551. NSDictionary *updateDict = [eventDict objectForKey:@"update"];
  552. [self.delegate externalSignalingController:self didReceivedParticipantListMessage:updateDict];
  553. NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
  554. NSString *roomToken = [updateDict objectForKey:@"roomid"];
  555. if (roomToken){
  556. [userInfo setObject:roomToken forKey:@"roomToken"];
  557. }
  558. NSArray *users = [updateDict objectForKey:@"users"];
  559. if (users) {
  560. for (NSDictionary *userDict in users) {
  561. NSString *sessionId = [userDict objectForKey:@"sessionId"];
  562. SignalingParticipant *participant = [self getParticipantFromSessionId:sessionId];
  563. if (participant) {
  564. [participant updateWithUpdateDictionary:userDict];
  565. }
  566. }
  567. [userInfo setObject:users forKey:@"users"];
  568. }
  569. [[NSNotificationCenter defaultCenter] postNotificationName:NCExternalSignalingControllerDidUpdateParticipantsNotification
  570. object:self
  571. userInfo:userInfo];
  572. } else {
  573. NSLog(@"Unknown room event: %@", eventDict);
  574. }
  575. }
  576. - (void)messageReceived:(NSDictionary *)messageDict
  577. {
  578. NSString *messageType = [[messageDict objectForKey:@"data"] objectForKey:@"type"];
  579. if ([messageType isEqualToString:@"startedTyping"] || [messageType isEqualToString:@"stoppedTyping"]) {
  580. NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
  581. NSDictionary *sender = [messageDict objectForKey:@"sender"];
  582. NSString *fromSession = [sender objectForKey:@"sessionid"];
  583. NSString *fromUser = [sender objectForKey:@"userid"];
  584. if (_currentRoom && fromSession){
  585. [userInfo setObject:_currentRoom forKey:@"roomToken"];
  586. [userInfo setObject:fromSession forKey:@"sessionId"];
  587. if (fromUser) {
  588. [userInfo setObject:fromUser forKey:@"userId"];
  589. }
  590. SignalingParticipant *participant = [_participantsMap objectForKey:fromSession];
  591. if (participant) {
  592. BOOL isFederated = participant.isFederated;
  593. [userInfo setObject:@(isFederated) forKey:@"isFederated"];
  594. if (participant.displayName != nil) {
  595. [userInfo setObject:participant.displayName forKey:@"displayName"];
  596. }
  597. }
  598. }
  599. if ([messageType isEqualToString:@"startedTyping"]) {
  600. [[NSNotificationCenter defaultCenter] postNotificationName:NCExternalSignalingControllerDidReceiveStartedTypingNotification
  601. object:self
  602. userInfo:userInfo];
  603. } else {
  604. [[NSNotificationCenter defaultCenter] postNotificationName:NCExternalSignalingControllerDidReceiveStoppedTypingNotification
  605. object:self
  606. userInfo:userInfo];
  607. }
  608. } else {
  609. [self.delegate externalSignalingController:self didReceivedSignalingMessage:messageDict];
  610. }
  611. }
  612. #pragma mark - Completion blocks
  613. - (void)executeCompletionBlockForMessageId:(NSString *)messageId withStatus:(NCExternalSignalingSendMessageStatus)status
  614. {
  615. if (!messageId) {
  616. return;
  617. }
  618. dispatch_async(dispatch_get_main_queue(), ^{
  619. if ([self->_helloMessage.messageId isEqualToString:messageId]) {
  620. [self->_helloMessage executeCompletionBlockWithStatus:status];
  621. self->_helloMessage = nil;
  622. return;
  623. }
  624. for (WSMessage *message in self->_messagesWithCompletionBlocks) {
  625. if ([messageId isEqualToString:message.messageId]) {
  626. [message executeCompletionBlockWithStatus:status];
  627. [self->_messagesWithCompletionBlocks removeObject:message];
  628. break;
  629. }
  630. }
  631. });
  632. }
  633. #pragma mark - NSURLSessionWebSocketDelegate
  634. - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol
  635. {
  636. dispatch_async(dispatch_get_main_queue(), ^{
  637. if (webSocketTask != self->_webSocket) {
  638. return;
  639. }
  640. [NCUtils log:@"WebSocket Connected!"];
  641. self->_reconnectInterval = kInitialReconnectInterval;
  642. [self sendHelloMessage];
  643. });
  644. }
  645. - (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reason
  646. {
  647. dispatch_async(dispatch_get_main_queue(), ^{
  648. if (webSocketTask != self->_webSocket) {
  649. return;
  650. }
  651. [NCUtils log:[NSString stringWithFormat:@"WebSocket didCloseWithCode:%ld reason:%@", (long)closeCode, reason]];
  652. [self reconnect];
  653. });
  654. }
  655. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
  656. {
  657. dispatch_async(dispatch_get_main_queue(), ^{
  658. if (task != self->_webSocket) {
  659. return;
  660. }
  661. if (error) {
  662. [NCUtils log:[NSString stringWithFormat:@"WebSocket session didCompleteWithError: %@", error.description]];
  663. [self reconnect];
  664. }
  665. });
  666. }
  667. - (void)receiveMessage {
  668. __weak NCExternalSignalingController *weakSelf = self;
  669. __block NSURLSessionWebSocketTask *receivingWebSocket = _webSocket;
  670. [_webSocket receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage * _Nullable message, NSError * _Nullable error) {
  671. if (!error) {
  672. NSData *messageData = message.data;
  673. NSString *messageString = message.string;
  674. if (message.type == NSURLSessionWebSocketMessageTypeString) {
  675. messageData = [message.string dataUsingEncoding:NSUTF8StringEncoding];
  676. }
  677. if (message.type == NSURLSessionWebSocketMessageTypeData) {
  678. messageString = [[NSString alloc] initWithData:messageData encoding:NSUTF8StringEncoding];
  679. }
  680. //NSLog(@"WebSocket didReceiveMessage: %@", messageString);
  681. NSDictionary *messageDict = [weakSelf getWebSocketMessageFromJSONData:messageData];
  682. NSString *messageType = [messageDict objectForKey:@"type"];
  683. if ([messageType isEqualToString:@"hello"]) {
  684. [weakSelf helloResponseReceived:messageDict];
  685. } else if ([messageType isEqualToString:@"error"]) {
  686. [weakSelf errorResponseReceived:messageDict];
  687. } else if ([messageType isEqualToString:@"room"]) {
  688. [weakSelf roomMessageReceived:messageDict];
  689. } else if ([messageType isEqualToString:@"event"]) {
  690. [weakSelf eventMessageReceived:[messageDict objectForKey:@"event"]];
  691. } else if ([messageType isEqualToString:@"message"]) {
  692. [weakSelf messageReceived:[messageDict objectForKey:@"message"]];
  693. } else if ([messageType isEqualToString:@"control"]) {
  694. [weakSelf messageReceived:[messageDict objectForKey:@"control"]];
  695. }
  696. // Completion block for messageId should have been handled already at this point
  697. NSString *messageId = [messageDict objectForKey:@"id"];
  698. [weakSelf executeCompletionBlockForMessageId:messageId withStatus:SendMessageApplicationError];
  699. [weakSelf receiveMessage];
  700. } else {
  701. dispatch_async(dispatch_get_main_queue(), ^{
  702. // Only try to reconnect if the webSocket is still the one we tried to receive a message on
  703. if (receivingWebSocket != weakSelf.webSocket) {
  704. return;
  705. }
  706. [NCUtils log:[NSString stringWithFormat:@"WebSocket receiveMessageWithCompletionHandler error %@", error.description]];
  707. [weakSelf reconnect];
  708. });
  709. }
  710. }];
  711. }
  712. - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
  713. {
  714. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  715. NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
  716. completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
  717. } else {
  718. completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
  719. }
  720. }
  721. #pragma mark - Utils
  722. - (SignalingParticipant *)getParticipantFromSessionId:(NSString *)sessionId
  723. {
  724. return [_participantsMap objectForKey:sessionId];
  725. }
  726. - (NSMutableDictionary *)getParticipantMap
  727. {
  728. return _participantsMap;
  729. }
  730. - (NSDictionary *)getWebSocketMessageFromJSONData:(NSData *)jsonData
  731. {
  732. NSError *error;
  733. NSDictionary* messageDict = [NSJSONSerialization JSONObjectWithData:jsonData
  734. options:kNilOptions
  735. error:&error];
  736. if (!messageDict) {
  737. NSLog(@"Error parsing websocket message: %@", error);
  738. }
  739. return messageDict;
  740. }
  741. @end