blob: c412aef9007f0a23639989f5b5ead19f6a84baa6 [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
41@interface APPRTCAppClient ()
42
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000043@property(nonatomic, weak, readonly) id<GAEMessageHandler> messageHandler;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000044@property(nonatomic, copy) NSString* baseURL;
45@property(nonatomic, strong) GAEChannelClient* gaeChannel;
46@property(nonatomic, copy) NSString* postMessageUrl;
47@property(nonatomic, copy) NSString* pcConfig;
48@property(nonatomic, strong) NSMutableString* roomHtml;
49@property(atomic, strong) NSMutableArray* sendQueue;
50@property(nonatomic, copy) NSString* token;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000051
52@property(nonatomic, assign) BOOL verboseLogging;
53
54@end
55
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000056@implementation APPRTCAppClient {
57 dispatch_queue_t _backgroundQueue;
58}
henrike@webrtc.org28e20752013-07-10 00:45:36 +000059
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000060- (id)initWithDelegate:(id<APPRTCAppClientDelegate>)delegate
61 messageHandler:(id<GAEMessageHandler>)handler {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000062 if (self = [super init]) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000063 _delegate = delegate;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000064 _messageHandler = handler;
fischman@webrtc.org7c82ada2014-04-30 00:17:47 +000065 _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue",
66 DISPATCH_QUEUE_SERIAL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000067 _sendQueue = [NSMutableArray array];
68 // Uncomment to see Request/Response logging.
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000069 // _verboseLogging = YES;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000070 }
71 return self;
72}
73
74#pragma mark - Public methods
75
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000076- (void)connectToRoom:(NSURL*)url {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000077 self.roomHtml = [NSMutableString stringWithCapacity:20000];
78 NSURLRequest* request = [NSURLRequest requestWithURL:url];
henrike@webrtc.org28e20752013-07-10 00:45:36 +000079 [NSURLConnection connectionWithRequest:request delegate:self];
80}
81
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000082- (void)sendData:(NSData*)data {
fischman@webrtc.org7c82ada2014-04-30 00:17:47 +000083 [self maybeLogMessage:@"Send message"];
84
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000085 dispatch_async(_backgroundQueue, ^{
henrike@webrtc.org28e20752013-07-10 00:45:36 +000086 [self.sendQueue addObject:[data copy]];
fischman@webrtc.org7c82ada2014-04-30 00:17:47 +000087
88 if ([self.postMessageUrl length] < 1) {
89 return;
90 }
91 for (NSData* data in self.sendQueue) {
92 NSString* url =
93 [NSString stringWithFormat:@"%@/%@",
94 self.baseURL, self.postMessageUrl];
95 [self sendData:data withUrl:url];
96 }
97 [self.sendQueue removeAllObjects];
98 });
henrike@webrtc.org28e20752013-07-10 00:45:36 +000099}
100
101#pragma mark - Internal methods
102
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000103- (NSString*)findVar:(NSString*)name strippingQuotes:(BOOL)strippingQuotes {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000104 NSError* error;
105 NSString* pattern =
106 [NSString stringWithFormat:@".*\n *var %@ = ([^\n]*);\n.*", name];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000107 NSRegularExpression* regexp =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000108 [NSRegularExpression regularExpressionWithPattern:pattern
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000109 options:0
110 error:&error];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000111 NSAssert(!error,
112 @"Unexpected error compiling regex: ",
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000113 error.localizedDescription);
114
115 NSRange fullRange = NSMakeRange(0, [self.roomHtml length]);
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000116 NSArray* matches =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000117 [regexp matchesInString:self.roomHtml options:0 range:fullRange];
118 if ([matches count] != 1) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000119 NSString* format = @"%lu matches for %@ in %@";
120 NSString* message = [NSString stringWithFormat:format,
121 (unsigned long)[matches count], name, self.roomHtml];
122 [self.delegate appClient:self didErrorWithMessage:message];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000123 return nil;
124 }
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000125 NSRange matchRange = [matches[0] rangeAtIndex:1];
126 NSString* value = [self.roomHtml substringWithRange:matchRange];
127 if (strippingQuotes) {
128 NSAssert([value length] > 2,
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000129 @"Can't strip quotes from short string: [%@]",
130 value);
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000131 NSAssert(([value characterAtIndex:0] == '\'' &&
132 [value characterAtIndex:[value length] - 1] == '\''),
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000133 @"Can't strip quotes from unquoted string: [%@]",
134 value);
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000135 value = [value substringWithRange:NSMakeRange(1, [value length] - 2)];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000136 }
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000137 return value;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000138}
139
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000140- (void)maybeLogMessage:(NSString*)message {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000141 if (self.verboseLogging) {
142 NSLog(@"%@", message);
143 }
144}
145
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000146- (void)sendData:(NSData*)data withUrl:(NSString*)url {
147 NSMutableURLRequest* request =
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000148 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
149 request.HTTPMethod = @"POST";
150 [request setHTTPBody:data];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000151 NSURLResponse* response;
152 NSError* error;
153 NSData* responseData = [NSURLConnection sendSynchronousRequest:request
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000154 returningResponse:&response
155 error:&error];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000156 NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000157 int status = [httpResponse statusCode];
158 NSAssert(status == 200,
159 @"Bad response [%d] to message: %@\n\n%@",
160 status,
161 [NSString stringWithUTF8String:[data bytes]],
162 [NSString stringWithUTF8String:[responseData bytes]]);
163}
164
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000165- (void)updateICEServers:(NSMutableArray*)ICEServers
166 withTurnServer:(NSString*)turnServerUrl {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000167 if ([turnServerUrl length] < 1) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000168 [self.delegate appClient:self didReceiveICEServers:ICEServers];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000169 return;
170 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000171 dispatch_async(_backgroundQueue, ^(void) {
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000172 NSMutableURLRequest* request = [NSMutableURLRequest
173 requestWithURL:[NSURL URLWithString:turnServerUrl]];
174 [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
175 [request addValue:@"https://apprtc.appspot.com"
176 forHTTPHeaderField:@"origin"];
177 NSURLResponse* response;
178 NSError* error;
179 NSData* responseData = [NSURLConnection sendSynchronousRequest:request
180 returningResponse:&response
181 error:&error];
182 if (!error) {
183 NSDictionary* json =
184 [NSJSONSerialization JSONObjectWithData:responseData
185 options:0
186 error:&error];
187 NSAssert(!error, @"Unable to parse. %@", error.localizedDescription);
188 NSString* username = json[@"username"];
189 NSString* password = json[@"password"];
190 NSArray* uris = json[@"uris"];
191 for (int i = 0; i < [uris count]; ++i) {
192 NSString* turnServer = [uris objectAtIndex:i];
193 RTCICEServer* ICEServer =
194 [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:turnServer]
195 username:username
196 password:password];
197 NSLog(@"Added ICE Server: %@", ICEServer);
198 [ICEServers addObject:ICEServer];
199 }
200 } else {
201 NSLog(@"Unable to get TURN server. Error: %@", error.description);
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000202 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000203
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000204 dispatch_async(dispatch_get_main_queue(), ^(void) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000205 [self.delegate appClient:self didReceiveICEServers:ICEServers];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000206 });
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000207 });
208}
209
210#pragma mark - NSURLConnectionDataDelegate methods
211
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000212- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
213 NSString* roomHtml = [NSString stringWithUTF8String:[data bytes]];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000214 NSString* message =
215 [NSString stringWithFormat:@"Received %lu chars",
216 (unsigned long)[roomHtml length]];
217 [self maybeLogMessage:message];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000218 [self.roomHtml appendString:roomHtml];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000219}
220
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000221- (void)connection:(NSURLConnection*)connection
222 didReceiveResponse:(NSURLResponse*)response {
223 NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000224 int statusCode = [httpResponse statusCode];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000225 [self
226 maybeLogMessage:
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000227 [NSString stringWithFormat:
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000228 @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@",
229 [httpResponse URL],
230 statusCode,
231 [httpResponse allHeaderFields]]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000232 NSAssert(statusCode == 200, @"Invalid response of %d received.", statusCode);
233}
234
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000235- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000236 NSString* message =
237 [NSString stringWithFormat:@"finished loading %lu chars",
238 (unsigned long)[self.roomHtml length]];
239 [self maybeLogMessage:message];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000240 NSRegularExpression* fullRegex =
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000241 [NSRegularExpression regularExpressionWithPattern:@"room is full"
242 options:0
243 error:nil];
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000244 if ([fullRegex
245 numberOfMatchesInString:self.roomHtml
246 options:0
247 range:NSMakeRange(0, [self.roomHtml length])]) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000248 NSString* message = @"Room full, dropping peerconnection.";
249 [self.delegate appClient:self didErrorWithMessage:message];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000250 return;
251 }
252
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000253 NSString* fullUrl = [[[connection originalRequest] URL] absoluteString];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000254 NSRange queryRange = [fullUrl rangeOfString:@"?"];
255 self.baseURL = [fullUrl substringToIndex:queryRange.location];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000256 [self maybeLogMessage:[NSString
257 stringWithFormat:@"Base URL: %@", self.baseURL]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000258
fischman@webrtc.org82387e42014-02-10 18:47:11 +0000259 self.initiator = [[self findVar:@"initiator" strippingQuotes:NO] boolValue];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000260 self.token = [self findVar:@"channelToken" strippingQuotes:YES];
261 if (!self.token)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000262 return;
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000263 [self maybeLogMessage:[NSString stringWithFormat:@"Token: %@", self.token]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000264
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000265 NSString* roomKey = [self findVar:@"roomKey" strippingQuotes:YES];
266 NSString* me = [self findVar:@"me" strippingQuotes:YES];
267 if (!roomKey || !me)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000268 return;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000269 self.postMessageUrl =
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000270 [NSString stringWithFormat:@"/message?r=%@&u=%@", roomKey, me];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000271 [self maybeLogMessage:[NSString stringWithFormat:@"POST message URL: %@",
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000272 self.postMessageUrl]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000273
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000274 NSString* pcConfig = [self findVar:@"pcConfig" strippingQuotes:NO];
275 if (!pcConfig)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000276 return;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000277 [self maybeLogMessage:[NSString
278 stringWithFormat:@"PC Config JSON: %@", pcConfig]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000279
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000280 NSString* turnServerUrl = [self findVar:@"turnUrl" strippingQuotes:YES];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000281 if (turnServerUrl) {
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000282 [self maybeLogMessage:[NSString
283 stringWithFormat:@"TURN server request URL: %@",
284 turnServerUrl]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000285 }
286
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000287 NSError* error;
288 NSData* pcData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding];
289 NSDictionary* json =
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000290 [NSJSONSerialization JSONObjectWithData:pcData options:0 error:&error];
291 NSAssert(!error, @"Unable to parse. %@", error.localizedDescription);
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000292 NSArray* servers = [json objectForKey:@"iceServers"];
293 NSMutableArray* ICEServers = [NSMutableArray array];
294 for (NSDictionary* server in servers) {
295 NSString* url = [server objectForKey:@"urls"];
296 NSString* username = json[@"username"];
297 NSString* credential = [server objectForKey:@"credential"];
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000298 if (!username) {
299 username = @"";
300 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000301 if (!credential) {
302 credential = @"";
303 }
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000304 [self maybeLogMessage:[NSString
305 stringWithFormat:@"url [%@] - credential [%@]",
306 url,
307 credential]];
308 RTCICEServer* ICEServer =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000309 [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url]
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000310 username:username
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000311 password:credential];
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000312 NSLog(@"Added ICE Server: %@", ICEServer);
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000313 [ICEServers addObject:ICEServer];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000314 }
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000315 [self updateICEServers:ICEServers withTurnServer:turnServerUrl];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000316
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000317 NSString* mc = [self findVar:@"mediaConstraints" strippingQuotes:NO];
318 if (mc) {
319 error = nil;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000320 NSData* mcData = [mc dataUsingEncoding:NSUTF8StringEncoding];
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000321 json =
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000322 [NSJSONSerialization JSONObjectWithData:mcData options:0 error:&error];
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000323 NSAssert(!error, @"Unable to parse. %@", error.localizedDescription);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000324 id video = json[@"video"];
325 if ([video isKindOfClass:[NSDictionary class]]) {
326 NSDictionary* mandatory = video[@"mandatory"];
327 NSMutableArray* mandatoryContraints =
328 [NSMutableArray arrayWithCapacity:[mandatory count]];
329 [mandatory enumerateKeysAndObjectsUsingBlock:^(
330 id key, id obj, BOOL* stop) {
331 [mandatoryContraints addObject:[[RTCPair alloc] initWithKey:key
332 value:obj]];
333 }];
334 // TODO(tkchin): figure out json formats for optional constraints.
335 _videoConstraints =
336 [[RTCMediaConstraints alloc]
337 initWithMandatoryConstraints:mandatoryContraints
338 optionalConstraints:nil];
339 } else if ([video isKindOfClass:[NSNumber class]] && [video boolValue]) {
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000340 _videoConstraints = [[RTCMediaConstraints alloc] init];
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000341 }
342 }
343
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000344 [self
345 maybeLogMessage:[NSString
346 stringWithFormat:@"About to open GAE with token: %@",
347 self.token]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000348 self.gaeChannel =
349 [[GAEChannelClient alloc] initWithToken:self.token
350 delegate:self.messageHandler];
351}
352
353@end