AppRTCDemo(iOS): remote-video reliability fixes
Previously GAE Channel callbacks would be handled by JS string-encoding the
payload into a URL. Unfortunately this is limited to the (undocumented,
silently problematic) maximum URL length UIWebView supports. Replaced this
scheme by a notification from JS to ObjC and a getter from ObjC to JS (which
happens out-of-line to avoid worrying about UIWebView's re-entrancy, or lack
thereof). Part of this change also moved from a combination of: JSON,
URL-escaping, and ad-hoc :-separated values to simply JSON.
Also incidentally:
- Removed outdated TODO about onRenegotiationNeeded, which is unneeded
- Move handling of PeerConnection callbacks to the main queue to avoid having
to think about concurrency too hard.
- Replaced a bunch of NSOrderedSame with isEqualToString for clearer code and
not having to worry about the fact that [nil compare:@"foo"]==NSOrderedSame
is always true (yay ObjC!).
- Auto-scroll messages view.
BUG=3117
R=noahric@google.com
Review URL: https://webrtc-codereview.appspot.com/10899006
git-svn-id: http://webrtc.googlecode.com/svn/trunk@5814 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m
index 8400778..e8077b9 100644
--- a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m
+++ b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m
@@ -64,19 +64,23 @@
}
- (void)peerConnectionOnError:(RTCPeerConnection*)peerConnection {
- NSLog(@"PCO onError.");
- NSAssert(NO, @"PeerConnection failed.");
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ NSLog(@"PCO onError.");
+ NSAssert(NO, @"PeerConnection failed.");
+ });
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
signalingStateChanged:(RTCSignalingState)stateChanged {
- NSLog(@"PCO onSignalingStateChange: %d", stateChanged);
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ NSLog(@"PCO onSignalingStateChange: %d", stateChanged);
+ });
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
addedStream:(RTCMediaStream*)stream {
- NSLog(@"PCO onAddStream.");
dispatch_async(dispatch_get_main_queue(), ^(void) {
+ NSLog(@"PCO onAddStream.");
NSAssert([stream.audioTracks count] <= 1,
@"Expected at most 1 audio stream");
NSAssert([stream.videoTracks count] <= 1,
@@ -90,49 +94,57 @@
- (void)peerConnection:(RTCPeerConnection*)peerConnection
removedStream:(RTCMediaStream*)stream {
- NSLog(@"PCO onRemoveStream.");
+ dispatch_async(dispatch_get_main_queue(),
+ ^(void) { NSLog(@"PCO onRemoveStream."); });
}
- (void)peerConnectionOnRenegotiationNeeded:(RTCPeerConnection*)peerConnection {
- NSLog(@"PCO onRenegotiationNeeded.");
- // TODO(hughv): Handle this.
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ NSLog(@"PCO onRenegotiationNeeded - ignoring because AppRTC has a "
+ "predefined negotiation strategy");
+ });
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
gotICECandidate:(RTCICECandidate*)candidate {
- NSLog(@"PCO onICECandidate.\n Mid[%@] Index[%d] Sdp[%@]",
- candidate.sdpMid,
- candidate.sdpMLineIndex,
- candidate.sdp);
- NSDictionary* json = @{
- @"type" : @"candidate",
- @"label" : [NSNumber numberWithInt:candidate.sdpMLineIndex],
- @"id" : candidate.sdpMid,
- @"candidate" : candidate.sdp
- };
- NSError* error;
- NSData* data =
- [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
- if (!error) {
- [_delegate sendData:data];
- } else {
- NSAssert(NO,
- @"Unable to serialize JSON object with error: %@",
- error.localizedDescription);
- }
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ NSLog(@"PCO onICECandidate.\n Mid[%@] Index[%d] Sdp[%@]",
+ candidate.sdpMid,
+ candidate.sdpMLineIndex,
+ candidate.sdp);
+ NSDictionary* json = @{
+ @"type" : @"candidate",
+ @"label" : [NSNumber numberWithInt:candidate.sdpMLineIndex],
+ @"id" : candidate.sdpMid,
+ @"candidate" : candidate.sdp
+ };
+ NSError* error;
+ NSData* data =
+ [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
+ if (!error) {
+ [_delegate sendData:data];
+ } else {
+ NSAssert(NO,
+ @"Unable to serialize JSON object with error: %@",
+ error.localizedDescription);
+ }
+ });
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
iceGatheringChanged:(RTCICEGatheringState)newState {
- NSLog(@"PCO onIceGatheringChange. %d", newState);
+ dispatch_async(dispatch_get_main_queue(),
+ ^(void) { NSLog(@"PCO onIceGatheringChange. %d", newState); });
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
iceConnectionChanged:(RTCICEConnectionState)newState {
- NSLog(@"PCO onIceConnectionChange. %d", newState);
- if (newState == RTCICEConnectionConnected)
- [self displayLogMessage:@"ICE Connection Connected."];
- NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!");
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ NSLog(@"PCO onIceConnectionChange. %d", newState);
+ if (newState == RTCICEConnectionConnected)
+ [self displayLogMessage:@"ICE Connection Connected."];
+ NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!");
+ });
}
- (void)displayLogMessage:(NSString*)message {
@@ -198,6 +210,7 @@
}
- (void)displayLogMessage:(NSString*)message {
+ NSAssert([NSThread isMainThread], @"Called off main thread!");
NSLog(@"%@", message);
[self.viewController displayText:message];
}
@@ -263,7 +276,7 @@
[lms addAudioTrack:[self.peerConnectionFactory audioTrackWithID:@"ARDAMSa0"]];
[self.peerConnection addStream:lms constraints:constraints];
- [self displayLogMessage:@"onICEServers - add local stream."];
+ [self displayLogMessage:@"onICEServers - added local stream."];
}
#pragma mark - GAEMessageHandler methods
@@ -286,24 +299,15 @@
[self displayLogMessage:@"PC - createOffer."];
}
-- (void)onMessage:(NSString*)data {
- NSString* message = [self unHTMLifyString:data];
- NSError* error;
- NSDictionary* objects = [NSJSONSerialization
- JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
- options:0
- error:&error];
- NSAssert(!error,
- @"%@",
- [NSString stringWithFormat:@"Error: %@", error.description]);
- NSAssert([objects count] > 0, @"Invalid JSON object");
- NSString* value = [objects objectForKey:@"type"];
+- (void)onMessage:(NSDictionary*)messageData {
+ NSString* type = messageData[@"type"];
+ NSAssert(type, @"Missing type: %@", messageData);
[self displayLogMessage:[NSString stringWithFormat:@"GAE onMessage type - %@",
- value]];
- if ([value compare:@"candidate"] == NSOrderedSame) {
- NSString* mid = [objects objectForKey:@"id"];
- NSNumber* sdpLineIndex = [objects objectForKey:@"label"];
- NSString* sdp = [objects objectForKey:@"candidate"];
+ type]];
+ if ([type isEqualToString:@"candidate"]) {
+ NSString* mid = messageData[@"id"];
+ NSNumber* sdpLineIndex = messageData[@"label"];
+ NSString* sdp = messageData[@"candidate"];
RTCICECandidate* candidate =
[[RTCICECandidate alloc] initWithMid:mid
index:sdpLineIndex.intValue
@@ -313,16 +317,16 @@
} else {
[self.peerConnection addICECandidate:candidate];
}
- } else if (([value compare:@"offer"] == NSOrderedSame) ||
- ([value compare:@"answer"] == NSOrderedSame)) {
- NSString* sdpString = [objects objectForKey:@"sdp"];
+ } else if ([type isEqualToString:@"offer"] ||
+ [type isEqualToString:@"answer"]) {
+ NSString* sdpString = messageData[@"sdp"];
RTCSessionDescription* sdp = [[RTCSessionDescription alloc]
- initWithType:value
+ initWithType:type
sdp:[APPRTCAppDelegate preferISAC:sdpString]];
[self.peerConnection setRemoteDescriptionWithDelegate:self
sessionDescription:sdp];
[self displayLogMessage:@"PC - setRemoteDescription."];
- } else if ([value compare:@"bye"] == NSOrderedSame) {
+ } else if ([type isEqualToString:@"bye"]) {
[self closeVideoUI];
UIAlertView* alertView =
[[UIAlertView alloc] initWithTitle:@"Remote end hung up"
@@ -332,7 +336,7 @@
otherButtonTitles:nil];
[alertView show];
} else {
- NSAssert(NO, @"Invalid message: %@", data);
+ NSAssert(NO, @"Invalid message: %@", messageData);
}
}
@@ -342,8 +346,8 @@
}
- (void)onError:(int)code withDescription:(NSString*)description {
- [self displayLogMessage:[NSString stringWithFormat:@"GAE onError: %@",
- description]];
+ [self displayLogMessage:[NSString stringWithFormat:@"GAE onError: %d, %@",
+ code, description]];
[self closeVideoUI];
}
@@ -400,8 +404,8 @@
[newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
[newMLine addObject:isac16kRtpMap];
for (; origPartIndex < [origMLineParts count]; ++origPartIndex) {
- if ([isac16kRtpMap compare:[origMLineParts objectAtIndex:origPartIndex]] !=
- NSOrderedSame) {
+ if (![isac16kRtpMap
+ isEqualToString:[origMLineParts objectAtIndex:origPartIndex]]) {
[newMLine addObject:[origMLineParts objectAtIndex:origPartIndex]];
}
}
@@ -415,20 +419,20 @@
- (void)peerConnection:(RTCPeerConnection*)peerConnection
didCreateSessionDescription:(RTCSessionDescription*)origSdp
error:(NSError*)error {
- if (error) {
- [self displayLogMessage:@"SDP onFailure."];
- NSAssert(NO, error.description);
- return;
- }
-
- [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."];
- RTCSessionDescription* sdp = [[RTCSessionDescription alloc]
- initWithType:origSdp.type
- sdp:[APPRTCAppDelegate preferISAC:origSdp.description]];
- [self.peerConnection setLocalDescriptionWithDelegate:self
- sessionDescription:sdp];
- [self displayLogMessage:@"PC setLocalDescription."];
dispatch_async(dispatch_get_main_queue(), ^(void) {
+ if (error) {
+ [self displayLogMessage:@"SDP onFailure."];
+ NSAssert(NO, error.description);
+ return;
+ }
+ [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."];
+ RTCSessionDescription* sdp = [[RTCSessionDescription alloc]
+ initWithType:origSdp.type
+ sdp:[APPRTCAppDelegate preferISAC:origSdp.description]];
+ [self.peerConnection setLocalDescriptionWithDelegate:self
+ sessionDescription:sdp];
+
+ [self displayLogMessage:@"PC setLocalDescription."];
NSDictionary* json = @{@"type" : sdp.type, @"sdp" : sdp.description};
NSError* error;
NSData* data =
@@ -442,14 +446,14 @@
- (void)peerConnection:(RTCPeerConnection*)peerConnection
didSetSessionDescriptionWithError:(NSError*)error {
- if (error) {
- [self displayLogMessage:@"SDP onFailure."];
- NSAssert(NO, error.description);
- return;
- }
-
- [self displayLogMessage:@"SDP onSuccess() - possibly drain candidates"];
dispatch_async(dispatch_get_main_queue(), ^(void) {
+ if (error) {
+ [self displayLogMessage:@"SDP onFailure."];
+ NSAssert(NO, error.description);
+ return;
+ }
+
+ [self displayLogMessage:@"SDP onSuccess() - possibly drain candidates"];
if (!self.client.initiator) {
if (self.peerConnection.remoteDescription &&
!self.peerConnection.localDescription) {
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCViewController.h b/talk/examples/ios/AppRTCDemo/APPRTCViewController.h
index c42a372..f5fcee4 100644
--- a/talk/examples/ios/AppRTCDemo/APPRTCViewController.h
+++ b/talk/examples/ios/AppRTCDemo/APPRTCViewController.h
@@ -32,9 +32,9 @@
// The view controller that is displayed when AppRTCDemo is loaded.
@interface APPRTCViewController : UIViewController<UITextFieldDelegate>
-@property(weak, nonatomic) IBOutlet UITextField* textField;
-@property(weak, nonatomic) IBOutlet UITextView* textInstructions;
-@property(weak, nonatomic) IBOutlet UITextView* textOutput;
+@property(weak, nonatomic) IBOutlet UITextField* roomInput;
+@property(weak, nonatomic) IBOutlet UITextView* instructionsView;
+@property(weak, nonatomic) IBOutlet UITextView* logView;
@property(weak, nonatomic) IBOutlet UIView* blackView;
@property(nonatomic, strong) APPRTCVideoView* remoteVideoView;
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCViewController.m b/talk/examples/ios/AppRTCDemo/APPRTCViewController.m
index 3cebb72..0ac9282 100644
--- a/talk/examples/ios/AppRTCDemo/APPRTCViewController.m
+++ b/talk/examples/ios/AppRTCDemo/APPRTCViewController.m
@@ -41,8 +41,8 @@
[super viewDidLoad];
self.statusBarOrientation =
[UIApplication sharedApplication].statusBarOrientation;
- self.textField.delegate = self;
- [self.textField becomeFirstResponder];
+ self.roomInput.delegate = self;
+ [self.roomInput becomeFirstResponder];
}
- (void)viewDidLayoutSubviews {
@@ -59,18 +59,20 @@
- (void)displayText:(NSString*)text {
dispatch_async(dispatch_get_main_queue(), ^(void) {
NSString* output =
- [NSString stringWithFormat:@"%@\n%@", self.textOutput.text, text];
- self.textOutput.text = output;
+ [NSString stringWithFormat:@"%@\n%@", self.logView.text, text];
+ self.logView.text = output;
+ [self.logView
+ scrollRangeToVisible:NSMakeRange([self.logView.text length], 0)];
});
}
- (void)resetUI {
- [self.textField resignFirstResponder];
- self.textField.text = nil;
- self.textField.hidden = NO;
- self.textInstructions.hidden = NO;
- self.textOutput.hidden = YES;
- self.textOutput.text = nil;
+ [self.roomInput resignFirstResponder];
+ self.roomInput.text = nil;
+ self.roomInput.hidden = NO;
+ self.instructionsView.hidden = NO;
+ self.logView.hidden = YES;
+ self.logView.text = nil;
self.blackView.hidden = YES;
[_remoteVideoView renderVideoTrackInterface:nil];
@@ -145,8 +147,8 @@
return;
}
textField.hidden = YES;
- self.textInstructions.hidden = YES;
- self.textOutput.hidden = NO;
+ self.instructionsView.hidden = YES;
+ self.logView.hidden = NO;
// TODO(hughv): Instead of launching a URL with apprtc scheme, change to
// prepopulating the textField with a valid URL missing the room. This allows
// the user to have the simplicity of just entering the room or the ability to
diff --git a/talk/examples/ios/AppRTCDemo/GAEChannelClient.h b/talk/examples/ios/AppRTCDemo/GAEChannelClient.h
index 49a928d..8c7d5d3 100644
--- a/talk/examples/ios/AppRTCDemo/GAEChannelClient.h
+++ b/talk/examples/ios/AppRTCDemo/GAEChannelClient.h
@@ -33,7 +33,7 @@
@protocol GAEMessageHandler<NSObject>
- (void)onOpen;
-- (void)onMessage:(NSString *)data;
+- (void)onMessage:(NSDictionary*)data;
- (void)onClose;
- (void)onError:(int)code withDescription:(NSString *)description;
diff --git a/talk/examples/ios/AppRTCDemo/GAEChannelClient.m b/talk/examples/ios/AppRTCDemo/GAEChannelClient.m
index 1b5e559..fcd0787 100644
--- a/talk/examples/ios/AppRTCDemo/GAEChannelClient.m
+++ b/talk/examples/ios/AppRTCDemo/GAEChannelClient.m
@@ -63,41 +63,54 @@
#pragma mark - UIWebViewDelegate method
++ (NSDictionary*)jsonStringToDictionary:(NSString*)str {
+ NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
+ NSError* error;
+ NSDictionary* dict =
+ [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
+ NSAssert(!error, @"Invalid JSON? %@", str);
+ return dict;
+}
+
- (BOOL)webView:(UIWebView*)webView
shouldStartLoadWithRequest:(NSURLRequest*)request
navigationType:(UIWebViewNavigationType)navigationType {
NSString* scheme = [request.URL scheme];
- if ([scheme compare:@"js-frame"] != NSOrderedSame) {
+ NSAssert(scheme, @"scheme is nil: %@", request);
+ if (![scheme isEqualToString:@"js-frame"]) {
return YES;
}
- NSString* resourceSpecifier = [request.URL resourceSpecifier];
- NSRange range = [resourceSpecifier rangeOfString:@":"];
- NSString* method;
- NSString* message;
- if (range.length == 0 && range.location == NSNotFound) {
- method = resourceSpecifier;
- } else {
- method = [resourceSpecifier substringToIndex:range.location];
- message = [resourceSpecifier substringFromIndex:range.location + 1];
- }
+
dispatch_async(dispatch_get_main_queue(), ^(void) {
- if ([method compare:@"onopen"] == NSOrderedSame) {
+ NSString* queuedMessage = [webView
+ stringByEvaluatingJavaScriptFromString:@"popQueuedMessage();"];
+ NSAssert([queuedMessage length], @"Empty queued message from JS");
+
+ NSDictionary* queuedMessageDict =
+ [GAEChannelClient jsonStringToDictionary:queuedMessage];
+ NSString* method = queuedMessageDict[@"type"];
+ NSAssert(method, @"Missing method: %@", queuedMessageDict);
+ NSDictionary* payload = queuedMessageDict[@"payload"]; // May be nil.
+
+ if ([method isEqualToString:@"onopen"]) {
[self.delegate onOpen];
- } else if ([method compare:@"onmessage"] == NSOrderedSame) {
- [self.delegate onMessage:message];
- } else if ([method compare:@"onclose"] == NSOrderedSame) {
+ } else if ([method isEqualToString:@"onmessage"]) {
+ NSDictionary* payloadData =
+ [GAEChannelClient jsonStringToDictionary:payload[@"data"]];
+ [self.delegate onMessage:payloadData];
+ } else if ([method isEqualToString:@"onclose"]) {
[self.delegate onClose];
- } else if ([method compare:@"onerror"] == NSOrderedSame) {
- // TODO(hughv): Get error.
- int code = -1;
- NSString* description = message;
- [self.delegate onError:code withDescription:description];
+ } else if ([method isEqualToString:@"onerror"]) {
+ NSNumber* codeNumber = payload[@"code"];
+ int code = [codeNumber intValue];
+ NSAssert([codeNumber isEqualToNumber:[NSNumber numberWithInt:code]],
+ @"Unexpected non-integral code: %@", payload);
+ [self.delegate onError:code withDescription:payload[@"description"]];
} else {
- NSAssert(
- NO, @"Invalid message sent from UIWebView: %@", resourceSpecifier);
+ NSAssert(NO, @"Invalid message sent from UIWebView: %@", queuedMessage);
}
});
- return YES;
+ return NO;
}
@end
diff --git a/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib b/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib
index 92d2ade..62807fe 100644
--- a/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib
+++ b/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib
@@ -178,7 +178,7 @@
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
- <string key="label">textField</string>
+ <string key="label">roomInput</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="546385578"/>
</object>
@@ -186,7 +186,7 @@
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
- <string key="label">textInstructions</string>
+ <string key="label">instructionsView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="176994284"/>
</object>
@@ -194,7 +194,7 @@
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
- <string key="label">textOutput</string>
+ <string key="label">logView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="634862110"/>
</object>
@@ -660,25 +660,25 @@
<string key="superclassName">UIViewController</string>
<dictionary class="NSMutableDictionary" key="outlets">
<string key="blackView">UIView</string>
- <string key="textField">UITextField</string>
- <string key="textInstructions">UITextView</string>
- <string key="textOutput">UITextView</string>
+ <string key="roomInput">UITextField</string>
+ <string key="instructionsView">UITextView</string>
+ <string key="logView">UITextView</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
<object class="IBToOneOutletInfo" key="blackView">
<string key="name">blackView</string>
<string key="candidateClassName">UIView</string>
</object>
- <object class="IBToOneOutletInfo" key="textField">
- <string key="name">textField</string>
+ <object class="IBToOneOutletInfo" key="roomInput">
+ <string key="name">roomInput</string>
<string key="candidateClassName">UITextField</string>
</object>
- <object class="IBToOneOutletInfo" key="textInstructions">
- <string key="name">textInstructions</string>
+ <object class="IBToOneOutletInfo" key="instructionsView">
+ <string key="name">instructionsView</string>
<string key="candidateClassName">UITextView</string>
</object>
- <object class="IBToOneOutletInfo" key="textOutput">
- <string key="name">textOutput</string>
+ <object class="IBToOneOutletInfo" key="logView">
+ <string key="name">logView</string>
<string key="candidateClassName">UITextView</string>
</object>
</dictionary>
diff --git a/talk/examples/ios/AppRTCDemo/ios_channel.html b/talk/examples/ios/AppRTCDemo/ios_channel.html
index a55b8f4..86846dd 100644
--- a/talk/examples/ios/AppRTCDemo/ios_channel.html
+++ b/talk/examples/ios/AppRTCDemo/ios_channel.html
@@ -6,10 +6,11 @@
Helper HTML that redirects Google AppEngine's Channel API to Objective C.
This is done by hosting this page in an iOS application. The hosting
class creates a UIWebView control and implements the UIWebViewDelegate
- protocol. Then when there is a channel message, it is encoded in an IFRAME.
- That IFRAME is added to the DOM which triggers a navigation event
- |shouldStartLoadWithRequest| in Objective C which can then be routed in the
- application as desired.
+ protocol. Then when there is a channel message it is queued in JS,
+ and an IFRAME is added to the DOM, triggering a navigation event
+ |shouldStartLoadWithRequest| in Objective C which can then fetch the
+ message using |popQueuedMessage|. This queuing is necessary to avoid URL
+ length limits in UIWebView (which are undocumented).
-->
<body onbeforeunload="closeSocket()" onload="openSocket()">
<script type="text/javascript">
@@ -38,6 +39,10 @@
var channel = null;
var socket = null;
+ // In-order queue of messages to be delivered to ObjectiveC.
+ // Each is a JSON.stringify()'d dictionary containing a 'type'
+ // field and optionally a 'payload'.
+ var messageQueue = [];
function openSocket() {
if (!QueryString.token || !QueryString.token.match(/^[A-z0-9_-]+$/)) {
@@ -52,17 +57,13 @@
sendMessageToObjC("onopen");
},
'onmessage': function(msg) {
- sendMessageToObjC("onmessage:" +
- encodeURIComponent(JSON.stringify(msg.data)));
+ sendMessageToObjC("onmessage", msg);
},
'onclose': function() {
sendMessageToObjC("onclose");
},
'onerror': function(err) {
- sendMessageToObjC("onerror:" +
- encodeURIComponent(JSON.stringify(err.code)) +
- ":message:" +
- encodeURIComponent(JSON.stringify(err.description)));
+ sendMessageToObjC("onerror", err);
}
});
}
@@ -73,9 +74,10 @@
// Add an IFRAME to the DOM to trigger a navigation event. Then remove
// it as it is no longer needed. Only one event is generated.
- function sendMessageToObjC(message) {
+ function sendMessageToObjC(type, payload) {
+ messageQueue.push(JSON.stringify({'type': type, 'payload': payload}));
var iframe = document.createElement("IFRAME");
- iframe.setAttribute("src", "js-frame:" + message);
+ iframe.setAttribute("src", "js-frame:");
// For some reason we need to set a non-empty size for the iOS6
// simulator...
iframe.setAttribute("height", "1px");
@@ -83,6 +85,10 @@
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
}
+
+ function popQueuedMessage() {
+ return messageQueue.shift();
+ }
</script>
</body>
</html>