blob: 9ac83ffc1513a1b508023c085ddafafd5e8d5155 [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
28#import "APPRTCAppClient.h"
29
30#import <dispatch/dispatch.h>
31
32#import "GAEChannelClient.h"
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000033#import "RTCICEServer.h"
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +000034#import "APPRTCAppDelegate.h"
35#import "RTCMediaConstraints.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000036
37@interface APPRTCAppClient ()
38
fischman@webrtc.orgd0f4c212013-08-20 22:16:55 +000039@property(nonatomic, strong) dispatch_queue_t backgroundQueue;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000040@property(nonatomic, copy) NSString* baseURL;
41@property(nonatomic, strong) GAEChannelClient* gaeChannel;
42@property(nonatomic, copy) NSString* postMessageUrl;
43@property(nonatomic, copy) NSString* pcConfig;
44@property(nonatomic, strong) NSMutableString* roomHtml;
45@property(atomic, strong) NSMutableArray* sendQueue;
46@property(nonatomic, copy) NSString* token;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000047
48@property(nonatomic, assign) BOOL verboseLogging;
49
50@end
51
52@implementation APPRTCAppClient
53
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000054- (id)initWithICEServerDelegate:(id<ICEServerDelegate>)delegate
55 messageHandler:(id<GAEMessageHandler>)handler {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000056 if (self = [super init]) {
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000057 _ICEServerDelegate = delegate;
58 _messageHandler = handler;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000059 _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue", NULL);
60 _sendQueue = [NSMutableArray array];
61 // Uncomment to see Request/Response logging.
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000062 // _verboseLogging = YES;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000063 }
64 return self;
65}
66
67#pragma mark - Public methods
68
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000069- (void)connectToRoom:(NSURL*)url {
70 NSURLRequest* request = [self getRequestFromUrl:url];
henrike@webrtc.org28e20752013-07-10 00:45:36 +000071 [NSURLConnection connectionWithRequest:request delegate:self];
72}
73
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000074- (void)sendData:(NSData*)data {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000075 @synchronized(self) {
76 [self maybeLogMessage:@"Send message"];
77 [self.sendQueue addObject:[data copy]];
78 }
79 [self requestQueueDrainInBackground];
80}
81
82#pragma mark - Internal methods
83
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000084- (NSString*)findVar:(NSString*)name strippingQuotes:(BOOL)strippingQuotes {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000085 NSError* error;
86 NSString* pattern =
87 [NSString stringWithFormat:@".*\n *var %@ = ([^\n]*);\n.*", name];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000088 NSRegularExpression* regexp =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000089 [NSRegularExpression regularExpressionWithPattern:pattern
henrike@webrtc.org28e20752013-07-10 00:45:36 +000090 options:0
91 error:&error];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000092 NSAssert(!error,
93 @"Unexpected error compiling regex: ",
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000094 error.localizedDescription);
95
96 NSRange fullRange = NSMakeRange(0, [self.roomHtml length]);
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +000097 NSArray* matches =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000098 [regexp matchesInString:self.roomHtml options:0 range:fullRange];
99 if ([matches count] != 1) {
100 [self showMessage:[NSString stringWithFormat:@"%d matches for %@ in %@",
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000101 [matches count],
102 name,
103 self.roomHtml]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000104 return nil;
105 }
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000106 NSRange matchRange = [matches[0] rangeAtIndex:1];
107 NSString* value = [self.roomHtml substringWithRange:matchRange];
108 if (strippingQuotes) {
109 NSAssert([value length] > 2,
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000110 @"Can't strip quotes from short string: [%@]",
111 value);
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000112 NSAssert(([value characterAtIndex:0] == '\'' &&
113 [value characterAtIndex:[value length] - 1] == '\''),
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000114 @"Can't strip quotes from unquoted string: [%@]",
115 value);
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000116 value = [value substringWithRange:NSMakeRange(1, [value length] - 2)];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000117 }
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000118 return value;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000119}
120
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000121- (NSURLRequest*)getRequestFromUrl:(NSURL*)url {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000122 self.roomHtml = [NSMutableString stringWithCapacity:20000];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000123 NSString* path =
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000124 [NSString stringWithFormat:@"https:%@", [url resourceSpecifier]];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000125 NSURLRequest* request =
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000126 [NSURLRequest requestWithURL:[NSURL URLWithString:path]];
127 return request;
128}
129
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000130- (void)maybeLogMessage:(NSString*)message {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000131 if (self.verboseLogging) {
132 NSLog(@"%@", message);
133 }
134}
135
136- (void)requestQueueDrainInBackground {
137 dispatch_async(self.backgroundQueue, ^(void) {
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000138 // TODO(hughv): This can block the UI thread. Fix.
139 @synchronized(self) {
140 if ([self.postMessageUrl length] < 1) {
141 return;
142 }
143 for (NSData* data in self.sendQueue) {
144 NSString* url =
145 [NSString stringWithFormat:@"%@/%@",
146 self.baseURL, self.postMessageUrl];
147 [self sendData:data withUrl:url];
148 }
149 [self.sendQueue removeAllObjects];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000150 }
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000151 });
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000152}
153
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000154- (void)sendData:(NSData*)data withUrl:(NSString*)url {
155 NSMutableURLRequest* request =
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000156 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
157 request.HTTPMethod = @"POST";
158 [request setHTTPBody:data];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000159 NSURLResponse* response;
160 NSError* error;
161 NSData* responseData = [NSURLConnection sendSynchronousRequest:request
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000162 returningResponse:&response
163 error:&error];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000164 NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000165 int status = [httpResponse statusCode];
166 NSAssert(status == 200,
167 @"Bad response [%d] to message: %@\n\n%@",
168 status,
169 [NSString stringWithUTF8String:[data bytes]],
170 [NSString stringWithUTF8String:[responseData bytes]]);
171}
172
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000173- (void)showMessage:(NSString*)message {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000174 NSLog(@"%@", message);
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000175 UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"Unable to join"
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000176 message:message
177 delegate:nil
178 cancelButtonTitle:@"OK"
179 otherButtonTitles:nil];
180 [alertView show];
181}
182
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000183- (void)updateICEServers:(NSMutableArray*)ICEServers
184 withTurnServer:(NSString*)turnServerUrl {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000185 if ([turnServerUrl length] < 1) {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000186 [self.ICEServerDelegate onICEServers:ICEServers];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000187 return;
188 }
189 dispatch_async(self.backgroundQueue, ^(void) {
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000190 NSMutableURLRequest* request = [NSMutableURLRequest
191 requestWithURL:[NSURL URLWithString:turnServerUrl]];
192 [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
193 [request addValue:@"https://apprtc.appspot.com"
194 forHTTPHeaderField:@"origin"];
195 NSURLResponse* response;
196 NSError* error;
197 NSData* responseData = [NSURLConnection sendSynchronousRequest:request
198 returningResponse:&response
199 error:&error];
200 if (!error) {
201 NSDictionary* json =
202 [NSJSONSerialization JSONObjectWithData:responseData
203 options:0
204 error:&error];
205 NSAssert(!error, @"Unable to parse. %@", error.localizedDescription);
206 NSString* username = json[@"username"];
207 NSString* password = json[@"password"];
208 NSArray* uris = json[@"uris"];
209 for (int i = 0; i < [uris count]; ++i) {
210 NSString* turnServer = [uris objectAtIndex:i];
211 RTCICEServer* ICEServer =
212 [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:turnServer]
213 username:username
214 password:password];
215 NSLog(@"Added ICE Server: %@", ICEServer);
216 [ICEServers addObject:ICEServer];
217 }
218 } else {
219 NSLog(@"Unable to get TURN server. Error: %@", error.description);
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000220 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000221
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000222 dispatch_async(dispatch_get_main_queue(), ^(void) {
223 [self.ICEServerDelegate onICEServers:ICEServers];
224 });
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000225 });
226}
227
228#pragma mark - NSURLConnectionDataDelegate methods
229
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000230- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
231 NSString* roomHtml = [NSString stringWithUTF8String:[data bytes]];
232 [self maybeLogMessage:[NSString stringWithFormat:@"Received %d chars",
233 [roomHtml length]]];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000234 [self.roomHtml appendString:roomHtml];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000235}
236
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000237- (void)connection:(NSURLConnection*)connection
238 didReceiveResponse:(NSURLResponse*)response {
239 NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000240 int statusCode = [httpResponse statusCode];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000241 [self
242 maybeLogMessage:
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000243 [NSString stringWithFormat:
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000244 @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@",
245 [httpResponse URL],
246 statusCode,
247 [httpResponse allHeaderFields]]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000248 NSAssert(statusCode == 200, @"Invalid response of %d received.", statusCode);
249}
250
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000251- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000252 [self maybeLogMessage:[NSString stringWithFormat:@"finished loading %d chars",
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000253 [self.roomHtml length]]];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000254 NSRegularExpression* fullRegex =
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000255 [NSRegularExpression regularExpressionWithPattern:@"room is full"
256 options:0
257 error:nil];
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000258 if ([fullRegex
259 numberOfMatchesInString:self.roomHtml
260 options:0
261 range:NSMakeRange(0, [self.roomHtml length])]) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000262 [self showMessage:@"Room full"];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000263 APPRTCAppDelegate* ad =
264 (APPRTCAppDelegate*)[[UIApplication sharedApplication] delegate];
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000265 [ad closeVideoUI];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000266 return;
267 }
268
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000269 NSString* fullUrl = [[[connection originalRequest] URL] absoluteString];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000270 NSRange queryRange = [fullUrl rangeOfString:@"?"];
271 self.baseURL = [fullUrl substringToIndex:queryRange.location];
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000272 [self maybeLogMessage:[NSString
273 stringWithFormat:@"Base URL: %@", self.baseURL]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000274
fischman@webrtc.org82387e42014-02-10 18:47:11 +0000275 self.initiator = [[self findVar:@"initiator" strippingQuotes:NO] boolValue];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000276 self.token = [self findVar:@"channelToken" strippingQuotes:YES];
277 if (!self.token)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000278 return;
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000279 [self maybeLogMessage:[NSString stringWithFormat:@"Token: %@", self.token]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000280
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000281 NSString* roomKey = [self findVar:@"roomKey" strippingQuotes:YES];
282 NSString* me = [self findVar:@"me" strippingQuotes:YES];
283 if (!roomKey || !me)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000284 return;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000285 self.postMessageUrl =
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000286 [NSString stringWithFormat:@"/message?r=%@&u=%@", roomKey, me];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000287 [self maybeLogMessage:[NSString stringWithFormat:@"POST message URL: %@",
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000288 self.postMessageUrl]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000289
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000290 NSString* pcConfig = [self findVar:@"pcConfig" strippingQuotes:NO];
291 if (!pcConfig)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000292 return;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000293 [self maybeLogMessage:[NSString
294 stringWithFormat:@"PC Config JSON: %@", pcConfig]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000295
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000296 NSString* turnServerUrl = [self findVar:@"turnUrl" strippingQuotes:YES];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000297 if (turnServerUrl) {
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000298 [self maybeLogMessage:[NSString
299 stringWithFormat:@"TURN server request URL: %@",
300 turnServerUrl]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000301 }
302
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000303 NSError* error;
304 NSData* pcData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding];
305 NSDictionary* json =
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000306 [NSJSONSerialization JSONObjectWithData:pcData options:0 error:&error];
307 NSAssert(!error, @"Unable to parse. %@", error.localizedDescription);
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000308 NSArray* servers = [json objectForKey:@"iceServers"];
309 NSMutableArray* ICEServers = [NSMutableArray array];
310 for (NSDictionary* server in servers) {
311 NSString* url = [server objectForKey:@"urls"];
312 NSString* username = json[@"username"];
313 NSString* credential = [server objectForKey:@"credential"];
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000314 if (!username) {
315 username = @"";
316 }
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000317 if (!credential) {
318 credential = @"";
319 }
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000320 [self maybeLogMessage:[NSString
321 stringWithFormat:@"url [%@] - credential [%@]",
322 url,
323 credential]];
324 RTCICEServer* ICEServer =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000325 [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url]
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000326 username:username
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000327 password:credential];
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000328 NSLog(@"Added ICE Server: %@", ICEServer);
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000329 [ICEServers addObject:ICEServer];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000330 }
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000331 [self updateICEServers:ICEServers withTurnServer:turnServerUrl];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000332
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000333 NSString* mc = [self findVar:@"mediaConstraints" strippingQuotes:NO];
334 if (mc) {
335 error = nil;
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000336 NSData* mcData = [mc dataUsingEncoding:NSUTF8StringEncoding];
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000337 json =
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000338 [NSJSONSerialization JSONObjectWithData:mcData options:0 error:&error];
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000339 NSAssert(!error, @"Unable to parse. %@", error.localizedDescription);
340 if ([[json objectForKey:@"video"] boolValue]) {
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000341 _videoConstraints = [[RTCMediaConstraints alloc] init];
henrike@webrtc.orgd3d6bce2014-03-10 20:41:22 +0000342 }
343 }
344
fischman@webrtc.org7fa1fcb2014-03-25 00:11:56 +0000345 [self
346 maybeLogMessage:[NSString
347 stringWithFormat:@"About to open GAE with token: %@",
348 self.token]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000349 self.gaeChannel =
350 [[GAEChannelClient alloc] initWithToken:self.token
351 delegate:self.messageHandler];
352}
353
354@end