| <html> |
| <head> |
| <script src="http://apprtc.appspot.com/_ah/channel/jsapi"></script> |
| </head> |
| <!-- |
| 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 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"> |
| // QueryString is copy/pasta from |
| // chromium's chrome/test/data/media/html/utils.js. |
| var QueryString = function () { |
| // Allows access to query parameters on the URL; e.g., given a URL like: |
| // http://<url>/my.html?test=123&bob=123 |
| // parameters can now be accessed via QueryString.test or |
| // QueryString.bob. |
| var params = {}; |
| |
| // RegEx to split out values by &. |
| var r = /([^&=]+)=?([^&]*)/g; |
| |
| // Lambda function for decoding extracted match values. Replaces '+' |
| // with space so decodeURIComponent functions properly. |
| function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); } |
| |
| var match; |
| while (match = r.exec(window.location.search.substring(1))) |
| params[d(match[1])] = d(match[2]); |
| |
| return params; |
| } (); |
| |
| 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_-]+$/)) { |
| // Send error back to ObjC. This will assert in GAEChannelClient.m. |
| sendMessageToObjC("JSError:Missing/malformed token parameter " + |
| QueryString.token); |
| throw "Missing/malformed token parameter: " + QueryString.token; |
| } |
| channel = new goog.appengine.Channel(QueryString.token); |
| socket = channel.open({ |
| 'onopen': function() { |
| sendMessageToObjC("onopen"); |
| }, |
| 'onmessage': function(msg) { |
| sendMessageToObjC("onmessage", msg); |
| }, |
| 'onclose': function() { |
| sendMessageToObjC("onclose"); |
| }, |
| 'onerror': function(err) { |
| sendMessageToObjC("onerror", err); |
| } |
| }); |
| } |
| |
| function closeSocket() { |
| socket.close(); |
| } |
| |
| // 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(type, payload) { |
| messageQueue.push(JSON.stringify({'type': type, 'payload': payload})); |
| var iframe = document.createElement("IFRAME"); |
| iframe.setAttribute("src", "js-frame:"); |
| // For some reason we need to set a non-empty size for the iOS6 |
| // simulator... |
| iframe.setAttribute("height", "1px"); |
| iframe.setAttribute("width", "1px"); |
| document.documentElement.appendChild(iframe); |
| iframe.parentNode.removeChild(iframe); |
| } |
| |
| function popQueuedMessage() { |
| return messageQueue.shift(); |
| } |
| </script> |
| </body> |
| </html> |