1 /**
  2 *@namespace Sfdc.canvas.client
  3 *@name Sfdc.canvas.client
  4 */
  5 (function ($$) {
  6 
  7     "use strict";
  8 
  9     var pversion, cversion = "27.0";
 10 
 11     var module =   (function() /**@lends module */ {
 12         
 13         var purl, cbs = [], seq = 0;
 14 
 15         function startsWithHttp(u, d) {
 16             return  $$.isNil(u) ? u : (u.substring(0, 4) === "http") ? u : d;
 17         }
 18         /**
 19         * @description
 20         * @function
 21         * @returns The url of the Parent Window
 22         */
 23         function getTargetOrigin(to) {
 24 
 25             var h;
 26 
 27             if (to === "*") {return to;}
 28 
 29             if (!$$.isNil(to)) {
 30                 h = $$.stripUrl(to);
 31                 purl = startsWithHttp(h, purl);
 32                 if (purl) {return purl;}
 33             }
 34 
 35             // This relies on the parent passing it in. This may not be there as the client can do a redirect.
 36             h = document.location.hash;
 37             if (h) {
 38                 h = decodeURIComponent(h.replace(/^#/, ''));
 39                 purl = startsWithHttp(h, purl);
 40             }
 41             return purl;
 42         }
 43 
 44         function callbacker(data) {
 45 
 46             if (data) {
 47                 // If the server is telling us the access_token is invalid, wipe it clean.
 48                 if (data.status === 401 &&
 49                     $$.isArray(data.payload) &&
 50                     data.payload[0].errorCode &&
 51                     data.payload[0].errorCode === "INVALID_SESSION_ID") {
 52                     // Session has expired logout.
 53                     $$.oauth.logout();
 54                 }
 55                 if ($$.isFunction(cbs[data.seq])) {
 56                     cbs[data.seq](data);
 57                 }
 58                 else {
 59                     // This can happen when the user switches out canvas apps real quick,
 60                     // before the request from the last canvas app have finish processing.
 61                     // We will ignore any of these results as the canvas app is no longer active to
 62                     // respond to the results.
 63                 }
 64             }
 65         }
 66 
 67         function postit(clientscb, message) {
 68 
 69             var wrapped, to, c;
 70 
 71             // need to keep a mapping from request to callback, otherwise
 72             // wrong callbacks get called. Unfortunately, this is the only
 73             // way to handle this as postMessage acts more like topic/queue.
 74             // limit the sequencers to 100 avoid out of memory errors
 75 
 76             seq = (seq > 100) ? 0 : seq + 1;
 77             cbs[seq] = clientscb;
 78             wrapped = {seq : seq, src : "client", clientVersion : cversion, parentVersion: pversion, body : message};
 79 
 80             c  = message && message.config && message.config.client;
 81             to = getTargetOrigin($$.isNil(c) ? null : c.targetOrigin);
 82             if ($$.isNil(to)) {
 83                 throw "ERROR: targetOrigin was not supplied and was not found on the hash tag, this can result from a redirect or link to another page. " +
 84                     "Try setting the targetOrgin (example: targetOrigin : sr.context.environment.targetOrigin) " +
 85                     "when making an ajax request.";
 86             }
 87             $$.xd.post(wrapped, to, parent);
 88         }
 89 
 90         function validateClient(client, cb) {
 91 
 92             var msg;
 93 
 94             if ($$.isNil(client) || $$.isNil(client.oauthToken)) {
 95                 msg = {status : 401, statusText : "Unauthorized" , parentVersion : pversion, payload : "client or client.oauthToken not supplied"};
 96             }
 97             if ($$.isNil(client.instanceId) || $$.isNil(client.targetOrigin)) {
 98                 msg = {status : 400, statusText : "Bad Request" , parentVersion : pversion, payload : "client.instanceId or client.targetOrigin not supplied"};
 99             }
100             if (!$$.isNil(msg)) {
101                 if ($$.isFunction(cb)) {
102                     cb(msg);
103                     return false;
104                 }
105                 else {
106                     throw msg;
107                 }
108             }
109             return true;
110         }
111 
112         /**
113         * @description Get the context for the current user and organization
114         * @public
115         * @name Sfdc.canvas.client#ctx
116         * @function
117         * @param {Function} clientscb Callback function to run when the call to ctx is complete
118         * @param {String} token OAuth token to send. 
119         * @example
120         * // Gets context in the canvas app.
121         * 
122         * function callback(msg) {
123         *   if (msg.status !== 200) {
124         *     alert("Error: " + msg.status);
125         *     return;
126         *   }
127         *   alert("Payload: ", msg.payload);
128         * }
129         * var ctxlink = connect.byId("ctxlink");
130         * var oauthtoken = connect.oauth.token();
131         * ctxlink.onclick=function() {
132         *   connect.client.ctx(callback, oauthtoken)};
133         * }
134         */
135         function ctx(clientscb, client) {
136             client = client || $$.oauth.client();
137             if (validateClient(client, clientscb)) {
138                 var token = client.oauthToken;
139                 postit(clientscb, {type : "ctx", accessToken : token, config : {client : client}});
140             }
141         }
142         
143         /**
144         * @description Perform a cross-domain, asynchronous HTTP request.  
145             <br>Note:  this should not be used for same domain requests.
146         * @param {String} url The URL to which the request is sent
147         * @param {Object} settings A set of key/value pairs to configure the request.  
148             <br>The success setting is required at minimum and should be a callback function
149          * @config {String} [client] required client context {oauthToken: "", targetOrigin : "", instanceId : ""}
150          * @config {String} [contentType] "application/json"
151          * @config {String} [data] request body
152          * @config {String} [headers] request headers
153          * @config {String} [method="GET"] The type of Ajax Request to make
154          * @config {Function} [success] Callback for all responses from server (failure and success) . Signature: success(response); intersting fields: [response.data, response.responseHeaders, response.status, response.statusText}
155          * @config {Boolean} [async=true] Asyncronous: true is only supported at this time.
156          * @name Sfdc.canvas.client#ajax
157         * @function
158         * @throws illegalArgumentException if the URL is missing or the settings object does not contain a success callback function.
159         * @example
160         * //Posting To a Chatter Feed:
161         * var sr = JSON.parse('<%=signedRequestJson%>');
162         * var url = sr.context.links.chatterFeedsUrl+"/news/"
163         *                                   +sr.context.user.userId+"/feed-items";
164         * var body = {body : {messageSegments : [{type: "Text", text: "Some Chatter Post"}]}};
165         * connect.client.ajax(url,
166         *   {client : sr.client,
167         *     method: 'POST',
168         *     contentType: "application/json",
169         *     data: JSON.stringify(body),
170         *     success : function(data) {
171         *     if (201 === data.status) {
172         *          alert("Success"
173         *          } 
174         *     }
175         *   });
176         * @example
177         * // Gets a List of Chatter Users:
178         * // Paste the signed request string into a JavaScript object for easy access.
179         * var sr = JSON.parse('<%=signedRequestJson%>');
180         * // Reference the Chatter user's URL from Context.Links object.
181         * var chatterUsersUrl = sr.context.links.chatterUsersUrl;
182         *
183         * // Make an XHR call back to salesforce through the supplied browser proxy.
184         * connect.client.ajax(chatterUsersUrl,
185         *   {client : sr.client,
186         *   success : function(data){
187         *   // Make sure the status code is OK.
188         *   if (data.status === 200) {
189         *     // Alert with how many Chatter users were returned.
190         *     alert("Got back "  + data.payload.users.length +
191         *     " users"); // Returned 2 users
192         *    }
193         * })};
194         */
195          function ajax(url, settings) {
196 
197             var ccb, config, defaults;
198 
199             if (!url) {
200                 throw "PRECONDITION ERROR: url required with AJAX call";
201             }
202             if (!settings || !$$.isFunction(settings.success)) {
203                 throw "PRECONDITION ERROR: function: 'settings.success' missing.";
204             }
205             if (! validateClient(settings.client, settings.success)) {
206                 return;
207             }
208 
209 
210             ccb = settings.success;
211             defaults = {
212                 method: 'GET',
213                 async: true,
214                 contentType: "application/json",
215                 headers: {"Authorization" : "OAuth "  + settings.client.oauthToken,
216                     "Accept" : "application/json"},
217                 data: null
218             };
219             config = $$.extend(defaults, settings || {});
220 
221             // Remove any listeners as functions cannot get marshaled.
222             config.success = undefined;
223             config.failure = undefined;
224             // Don't allow the client to set "*" as the target origin.
225             if (config.client.targetOrigin === "*") {
226                 config.client.targetOrigin = null;
227             }
228             else {
229                 // We need to set this here so we can validate the origin when we receive the call back
230                 purl = startsWithHttp(config.targetOrigin, purl);
231             }
232             postit(ccb, {type : "ajax", accessToken : token, url : url, config : config});
233         }
234 
235         /**
236          * @description Stores or gets the oauth token in a local javascript variable. Note, if longer term
237          * (survive page refresh) storage is needed store the oauth token on the server side.
238          * @param {String} t oauth token, if supplied it will be stored in a volatile local JS variable.
239          * @returns {Object} the oauth token.
240          */
241 
242         function token(t) {
243             return $$.oauth.token(t);
244         }
245 
246         function version() {
247             return {clientVersion: cversion};
248         }
249 
250         $$.xd.receive(callbacker, getTargetOrigin);
251 
252         return {
253             ctx : ctx,
254             ajax : ajax,
255             token : token,
256             version : version
257         };
258     }());
259 
260     $$.module('Sfdc.canvas.client', module);
261 
262 }(Sfdc.canvas));
263