blob: 853496f81b54381677904569abf98f99962b5fab [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
2 * libjingle
3 * Copyright 2013, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000028#if !defined(__has_feature) || !__has_feature(objc_arc)
29#error "This file requires ARC support."
30#endif
31
henrike@webrtc.org28e20752013-07-10 00:45:36 +000032#import "APPRTCAppClient.h"
33
34#import <dispatch/dispatch.h>
35
36#import "GAEChannelClient.h"
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000037#import "RTCICEServer.h"
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +000038#import "RTCMediaConstraints.h"
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000039#import "RTCPair.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000040
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000041@implementation APPRTCAppClient {
42 dispatch_queue_t _backgroundQueue;
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +000043 GAEChannelClient* _gaeChannel;
44 NSURL* _postMessageURL;
45 BOOL _verboseLogging;
46 __weak id<GAEMessageHandler> _messageHandler;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000047}
henrike@webrtc.org28e20752013-07-10 00:45:36 +000048
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +000049- (instancetype)initWithDelegate:(id<APPRTCAppClientDelegate>)delegate
50 messageHandler:(id<GAEMessageHandler>)handler {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000051 if (self = [super init]) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000052 _delegate = delegate;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000053 _messageHandler = handler;
fischman@webrtc.org7c82ada2014-04-30 00:17:47 +000054 _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue",
55 DISPATCH_QUEUE_SERIAL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000056 // Uncomment to see Request/Response logging.
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000057 // _verboseLogging = YES;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000058 }
59 return self;
60}
61
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000062- (void)connectToRoom:(NSURL*)url {
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +000063 NSString* urlString =
64 [[url absoluteString] stringByAppendingString:@"&t=json"];
65 NSURL* requestURL = [NSURL URLWithString:urlString];
66 NSURLRequest* request = [NSURLRequest requestWithURL:requestURL];
67 [self sendURLRequest:request
68 completionHandler:^(NSError* error,
69 NSHTTPURLResponse* httpResponse,
70 NSData* responseData) {
71 int statusCode = [httpResponse statusCode];
72 [self logVerbose:[NSString stringWithFormat:
73 @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@",
74 [httpResponse URL],
75 statusCode,
76 [httpResponse allHeaderFields]]];
77 NSAssert(statusCode == 200,
78 @"Invalid response of %d received while connecting to: %@",
79 statusCode,
80 urlString);
81 if (statusCode != 200) {
82 return;
83 }
84 [self handleResponseData:responseData
85 forRoomRequest:request];
86 }];
henrike@webrtc.org28e20752013-07-10 00:45:36 +000087}
88
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000089- (void)sendData:(NSData*)data {
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +000090 NSParameterAssert([data length] > 0);
91 NSString* message = [NSString stringWithUTF8String:[data bytes]];
92 [self logVerbose:[NSString stringWithFormat:@"Send message:\n%@", message]];
93 if (!_postMessageURL) {
94 return;
95 }
96 NSMutableURLRequest* request =
97 [NSMutableURLRequest requestWithURL:_postMessageURL];
98 request.HTTPMethod = @"POST";
99 [request setHTTPBody:data];
100 [self sendURLRequest:request
101 completionHandler:^(NSError* error,
102 NSHTTPURLResponse* httpResponse,
103 NSData* responseData) {
104 int status = [httpResponse statusCode];
105 NSString* response = [responseData length] > 0 ?
106 [NSString stringWithUTF8String:[responseData bytes]] :
107 nil;
108 NSAssert(status == 200,
109 @"Bad response [%d] to message: %@\n\n%@",
110 status,
111 message,
112 response);
113 }];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000114}
115
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000116#pragma mark - Private
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000117
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000118- (void)logVerbose:(NSString*)message {
119 if (_verboseLogging) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000120 NSLog(@"%@", message);
121 }
122}
123
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000124- (void)handleResponseData:(NSData*)responseData
125 forRoomRequest:(NSURLRequest*)request {
126 NSDictionary* roomJSON = [self parseJSONData:responseData];
127 [self logVerbose:[NSString stringWithFormat:@"Room JSON:\n%@", roomJSON]];
128 NSParameterAssert(roomJSON);
129 if (roomJSON[@"error"]) {
130 NSArray* errorMessages = roomJSON[@"error_messages"];
131 NSMutableString* message = [NSMutableString string];
132 for (NSString* errorMessage in errorMessages) {
133 [message appendFormat:@"%@\n", errorMessage];
134 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000135 [self.delegate appClient:self didErrorWithMessage:message];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000136 return;
137 }
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000138 NSString* pcConfig = roomJSON[@"pc_config"];
139 NSData* pcConfigData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding];
140 NSDictionary* pcConfigJSON = [self parseJSONData:pcConfigData];
141 [self logVerbose:[NSString stringWithFormat:@"PCConfig JSON:\n%@",
142 pcConfigJSON]];
143 NSParameterAssert(pcConfigJSON);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000144
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000145 NSArray* iceServers = [self parseICEServersForPCConfigJSON:pcConfigJSON];
146 [self requestTURNServerForICEServers:iceServers
147 turnServerUrl:roomJSON[@"turn_url"]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000148
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000149 _initiator = [roomJSON[@"initiator"] boolValue];
150 [self logVerbose:[NSString stringWithFormat:@"Initiator: %d", _initiator]];
151 _postMessageURL = [self parsePostMessageURLForRoomJSON:roomJSON
152 request:request];
153 [self logVerbose:[NSString stringWithFormat:@"POST message URL:\n%@",
154 _postMessageURL]];
155 _videoConstraints = [self parseVideoConstraintsForRoomJSON:roomJSON];
156 [self logVerbose:[NSString stringWithFormat:@"Media constraints:\n%@",
157 _videoConstraints]];
158 NSString* token = roomJSON[@"token"];
159 [self logVerbose:
160 [NSString stringWithFormat:@"About to open GAE with token: %@",
161 token]];
162 _gaeChannel =
163 [[GAEChannelClient alloc] initWithToken:token
164 delegate:_messageHandler];
165}
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000166
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000167- (NSDictionary*)parseJSONData:(NSData*)data {
168 NSError* error = nil;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000169 NSDictionary* json =
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000170 [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000171 NSAssert(!error, @"Unable to parse. %@", error.localizedDescription);
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000172 return json;
173}
174
175- (NSArray*)parseICEServersForPCConfigJSON:(NSDictionary*)pcConfigJSON {
176 NSMutableArray* result = [NSMutableArray array];
177 NSArray* iceServers = pcConfigJSON[@"iceServers"];
178 for (NSDictionary* iceServer in iceServers) {
179 NSString* url = iceServer[@"urls"];
180 NSString* username = pcConfigJSON[@"username"];
181 NSString* credential = iceServer[@"credential"];
182 username = username ? username : @"";
183 credential = credential ? credential : @"";
184 [self logVerbose:[NSString stringWithFormat:@"url [%@] - credential [%@]",
185 url,
186 credential]];
187 RTCICEServer* server =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000188 [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url]
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000189 username:username
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000190 password:credential];
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000191 [result addObject:server];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000192 }
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000193 return result;
194}
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000195
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000196- (NSURL*)parsePostMessageURLForRoomJSON:(NSDictionary*)roomJSON
197 request:(NSURLRequest*)request {
198 NSString* requestUrl = [[request URL] absoluteString];
199 NSRange queryRange = [requestUrl rangeOfString:@"?"];
200 NSString* baseUrl = [requestUrl substringToIndex:queryRange.location];
201 NSString* roomKey = roomJSON[@"room_key"];
202 NSParameterAssert([roomKey length] > 0);
203 NSString* me = roomJSON[@"me"];
204 NSParameterAssert([me length] > 0);
205 NSString* postMessageUrl =
206 [NSString stringWithFormat:@"%@/message?r=%@&u=%@", baseUrl, roomKey, me];
207 return [NSURL URLWithString:postMessageUrl];
208}
209
210- (RTCMediaConstraints*)parseVideoConstraintsForRoomJSON:
211 (NSDictionary*)roomJSON {
212 NSString* mediaConstraints = roomJSON[@"media_constraints"];
213 RTCMediaConstraints* constraints = nil;
214 if ([mediaConstraints length] > 0) {
215 NSData* constraintsData =
216 [mediaConstraints dataUsingEncoding:NSUTF8StringEncoding];
217 NSDictionary* constraintsJSON = [self parseJSONData:constraintsData];
218 id video = constraintsJSON[@"video"];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000219 if ([video isKindOfClass:[NSDictionary class]]) {
220 NSDictionary* mandatory = video[@"mandatory"];
221 NSMutableArray* mandatoryContraints =
222 [NSMutableArray arrayWithCapacity:[mandatory count]];
223 [mandatory enumerateKeysAndObjectsUsingBlock:^(
224 id key, id obj, BOOL* stop) {
225 [mandatoryContraints addObject:[[RTCPair alloc] initWithKey:key
226 value:obj]];
227 }];
228 // TODO(tkchin): figure out json formats for optional constraints.
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000229 constraints =
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000230 [[RTCMediaConstraints alloc]
231 initWithMandatoryConstraints:mandatoryContraints
232 optionalConstraints:nil];
233 } else if ([video isKindOfClass:[NSNumber class]] && [video boolValue]) {
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000234 constraints = [[RTCMediaConstraints alloc] init];
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000235 }
236 }
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000237 return constraints;
238}
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000239
tkchin@webrtc.org013bdf82014-06-06 22:29:10 +0000240- (void)requestTURNServerWithUrl:(NSString*)turnServerUrl
241 completionHandler:
242 (void (^)(RTCICEServer* turnServer))completionHandler {
243 NSURL* turnServerURL = [NSURL URLWithString:turnServerUrl];
244 NSMutableURLRequest* request =
245 [NSMutableURLRequest requestWithURL:turnServerURL];
246 [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
247 [request addValue:@"https://apprtc.appspot.com"
248 forHTTPHeaderField:@"origin"];
249 [self sendURLRequest:request
250 completionHandler:^(NSError* error,
251 NSHTTPURLResponse* response,
252 NSData* responseData) {
253 if (error) {
254 NSLog(@"Unable to get TURN server.");
255 completionHandler(nil);
256 return;
257 }
258 NSDictionary* json = [self parseJSONData:responseData];
259 NSString* username = json[@"username"];
260 NSString* password = json[@"password"];
261 NSArray* uris = json[@"uris"];
262 NSParameterAssert([uris count] > 0);
263 RTCICEServer* turnServer =
264 [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:uris[0]]
265 username:username
266 password:password];
267 completionHandler(turnServer);
268 }];
269}
270
271- (void)requestTURNServerForICEServers:(NSArray*)iceServers
272 turnServerUrl:(NSString*)turnServerUrl {
273 BOOL isTurnPresent = NO;
274 for (RTCICEServer* iceServer in iceServers) {
275 if ([[iceServer.URI scheme] isEqualToString:@"turn"]) {
276 isTurnPresent = YES;
277 break;
278 }
279 }
280 if (!isTurnPresent) {
281 [self requestTURNServerWithUrl:turnServerUrl
282 completionHandler:^(RTCICEServer* turnServer) {
283 NSArray* servers = iceServers;
284 if (turnServer) {
285 servers = [servers arrayByAddingObject:turnServer];
286 }
287 NSLog(@"ICE servers:\n%@", servers);
288 [self.delegate appClient:self didReceiveICEServers:servers];
289 }];
290 } else {
291 NSLog(@"ICE servers:\n%@", iceServers);
292 dispatch_async(dispatch_get_main_queue(), ^{
293 [self.delegate appClient:self didReceiveICEServers:iceServers];
294 });
295 }
296}
297
298- (void)sendURLRequest:(NSURLRequest*)request
299 completionHandler:(void (^)(NSError* error,
300 NSHTTPURLResponse* httpResponse,
301 NSData* responseData))completionHandler {
302 dispatch_async(_backgroundQueue, ^{
303 NSError* error = nil;
304 NSURLResponse* response = nil;
305 NSData* responseData = [NSURLConnection sendSynchronousRequest:request
306 returningResponse:&response
307 error:&error];
308 NSParameterAssert(!response ||
309 [response isKindOfClass:[NSHTTPURLResponse class]]);
310 if (error) {
311 NSLog(@"Failed URL request for:%@\nError:%@", request, error);
312 }
313 dispatch_async(dispatch_get_main_queue(), ^{
314 NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
315 completionHandler(error, httpResponse, responseData);
316 });
317 });
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000318}
319
320@end