1<html>
2  <head>
3    <script src="http://apprtc.appspot.com/_ah/channel/jsapi"></script>
4  </head>
5  <!--
6  Helper HTML that redirects Google AppEngine's Channel API to Objective C.
7  This is done by hosting this page in an iOS application.  The hosting
8  class creates a UIWebView control and implements the UIWebViewDelegate
9  protocol.  Then when there is a channel message it is queued in JS,
10  and an IFRAME is added to the DOM, triggering a navigation event
11  |shouldStartLoadWithRequest| in Objective C which can then fetch the
12  message using |popQueuedMessage|.  This queuing is necessary to avoid URL
13  length limits in UIWebView (which are undocumented).
14  -->
15  <body onbeforeunload="closeSocket()" onload="openSocket()">
16    <script type="text/javascript">
17      // QueryString is copy/pasta from
18      // chromium's chrome/test/data/media/html/utils.js.
19      var QueryString = function () {
20        // Allows access to query parameters on the URL; e.g., given a URL like:
21        //    http://<url>/my.html?test=123&bob=123
22        // parameters can now be accessed via QueryString.test or
23        // QueryString.bob.
24        var params = {};
25
26        // RegEx to split out values by &.
27        var r = /([^&=]+)=?([^&]*)/g;
28
29        // Lambda function for decoding extracted match values. Replaces '+'
30        // with space so decodeURIComponent functions properly.
31        function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); }
32
33        var match;
34        while (match = r.exec(window.location.search.substring(1)))
35          params[d(match[1])] = d(match[2]);
36
37        return params;
38      } ();
39
40      var channel = null;
41      var socket = null;
42      // In-order queue of messages to be delivered to ObjectiveC.
43      // Each is a JSON.stringify()'d dictionary containing a 'type'
44      // field and optionally a 'payload'.
45      var messageQueue = [];
46
47      function openSocket() {
48        if (!QueryString.token || !QueryString.token.match(/^[A-z0-9_-]+$/)) {
49          // Send error back to ObjC.  This will assert in GAEChannelClient.m.
50          sendMessageToObjC("JSError:Missing/malformed token parameter " +
51                            QueryString.token);
52          throw "Missing/malformed token parameter: " + QueryString.token;
53        }
54        channel = new goog.appengine.Channel(QueryString.token);
55        socket = channel.open({
56          'onopen': function() {
57            sendMessageToObjC("onopen");
58          },
59          'onmessage': function(msg) {
60            sendMessageToObjC("onmessage", msg);
61          },
62          'onclose': function() {
63            sendMessageToObjC("onclose");
64          },
65          'onerror': function(err) {
66            sendMessageToObjC("onerror", err);
67          }
68        });
69      }
70
71      function closeSocket() {
72        socket.close();
73      }
74
75      // Add an IFRAME to the DOM to trigger a navigation event.  Then remove
76      // it as it is no longer needed.  Only one event is generated.
77      function sendMessageToObjC(type, payload) {
78        messageQueue.push(JSON.stringify({'type': type, 'payload': payload}));
79        var iframe = document.createElement("IFRAME");
80        iframe.setAttribute("src", "js-frame:");
81        // For some reason we need to set a non-empty size for the iOS6
82        // simulator...
83        iframe.setAttribute("height", "1px");
84        iframe.setAttribute("width", "1px");
85        document.documentElement.appendChild(iframe);
86        iframe.parentNode.removeChild(iframe);
87      }
88
89      function popQueuedMessage() {
90        return messageQueue.shift();
91      }
92    </script>
93  </body>
94</html>
95