1 // Sfdc.canvas.parent 2 (function ($$) { 3 4 "use strict"; 5 6 var module = (function() { 7 8 var listening = false, 9 canvas = {}, 10 pversion, sid, salesforceBaseUrl; 11 12 function getFrame(id) { 13 // We need to get the index of the iframe in case there 14 // are multiple iframes on the page. 15 // Referencing the iframe via frames['canvas-frame'] is 16 // no good because the location is clobbered if you reference 17 // it in that way, which causes postMessage to break. 18 19 var fn = "canvas-frame-" + id; 20 21 var ifs = window.document.getElementsByTagName('iframe'); 22 for ( var i = 0, found = -1; ifs[i] && found == -1; i++){ 23 try { 24 if (ifs[i].name === fn){ 25 found = i; 26 } 27 } 28 catch (e) {} 29 } 30 return found!=-1?frames[found]:frames[fn]; 31 } 32 33 function callback(msgData, ctx) { 34 35 if ($$.isNil(ctx.id)) { 36 throw "ERROR: Was expecting an id"; 37 } 38 39 if (msgData) { 40 41 var type = msgData.body.type, 42 seq = msgData.seq; 43 44 // Update the version of the client. 45 canvas[ctx.id].cversion = msgData.clientVersion; 46 47 // THis is our local callback handler. 48 var lhandler = function(data, xmlHttp, config) { 49 50 var obj = {}, 51 ct = xmlHttp.getResponseHeader("Content-Type"); 52 53 // Got a response back from the server, send it (via xd) back to the client. 54 obj.seq = seq; 55 obj.parentVersion = pversion; 56 obj.clientVersion = canvas[ctx.id].cversion; 57 obj.payload = ct && data && (ct.indexOf("application/json") >= 0) ? Sfdc.JSON.parse(data) : data; 58 obj.status = xmlHttp.status; 59 obj.statusText = xmlHttp.statusText; 60 obj.responseHeaders = xmlHttp.getAllResponseHeaders(); 61 $$.xd.post(obj, $$.stripUrl(canvas[ctx.id].url), getFrame(ctx.id)); 62 }; 63 64 var requestUrl = null, config = null; 65 if (type === 'ctx') { 66 var frame = $$.byId("if-" + ctx.id); 67 var ver = ($$.isNil(canvas[ctx.id].cversion)) ? pversion : canvas[ctx.id].cversion; 68 requestUrl = "/services/data/v" + ver + "/platformconnect/canvascontext"; 69 config = { 70 success : lhandler, 71 failure : lhandler, 72 headers: {Authorization : "OAuth " + msgData.body.accessToken, Accept : "application/json"}, 73 contentType:"application/json", 74 method: "PUT", 75 data:Sfdc.JSON.stringify({ 76 environment:{ 77 dimensions: { 78 height: frame && frame.style.height || null, 79 width: frame && frame.style.width || null 80 }, 81 parameters : canvas[ctx.id].options.parameters 82 } 83 }) 84 }; 85 86 requestUrl = applySalesforceProxyConfig(requestUrl, config); 87 Sfdc.Ajax.request(requestUrl, config); 88 } 89 else if (type === 'ajax') { 90 msgData.body.config.success = lhandler; 91 msgData.body.config.failure = lhandler; 92 requestUrl = msgData.body.url; 93 config = msgData.body.config; 94 requestUrl = applySalesforceProxyConfig(requestUrl, config); 95 Sfdc.Ajax.request(requestUrl, config); 96 } 97 } 98 } 99 100 function listen() { 101 102 if (listening) { 103 $$.xd.remove(); 104 } 105 106 $$.xd.receive(callback, function(o, d) { 107 d = Sfdc.JSON.parse(d); 108 if ($$.isNil(d.body.config.client)) { 109 alert("It appears the canvas app is using an older version of the SDK. \n" + 110 "In the future we will support backwards compatibility, but for now \n" + 111 "Force.com Canvas is in Pilot and the canvas app developer must upgrade."); 112 return false; 113 } 114 var c = canvas[d.body.config.client.instanceId]; 115 if (!$$.isNil(c)) { 116 return {id : d.body.config.client.instanceId}; 117 } 118 return false; 119 }); 120 listening = true; 121 } 122 123 function getSignedRequest(id, namespace, canvas, elem, uid, options) { 124 125 var url = "/services/data/v" + pversion + "/platformconnect/signedrequest?canvas=" + canvas + ((!$$.isNil(namespace))? "&namespace="+namespace : ""); 126 var config = { 127 success: function(data) { 128 var obj = Sfdc.JSON.parse(data); 129 addSignedRequestFrame(id, obj.response, elem, uid, options); 130 }, 131 headers: {Authorization : "OAuth " + sid, Accept : "application/json"}, 132 contentType: "application/json", 133 method: "PUT", 134 data:Sfdc.JSON.stringify({ 135 canvasRequest:{ 136 context:{ 137 environment:{ 138 dimensions: { 139 height: options.height, 140 width: options.width 141 }, 142 parameters: options.parameters 143 } 144 }, 145 client : { 146 targetOrigin: document.location.protocol + "//" + document.location.host, 147 instanceId : id 148 } 149 } 150 }) 151 }; 152 153 url = applySalesforceProxyConfig(url, config); 154 155 Sfdc.Ajax.request(url, config); 156 } 157 158 function applySalesforceProxyConfig(url, requestConfig) { 159 var endPointUrl = null; 160 var proxyUrl = url; 161 if (!$$.isNil(salesforceBaseUrl)) { 162 endPointUrl = salesforceBaseUrl + url; 163 proxyUrl = location.protocol + "//" + location.hostname + ":" + location.port + "/services/proxy"; 164 requestConfig.headers["SalesforceProxy-Endpoint"] = endPointUrl; 165 } 166 return proxyUrl; 167 } 168 169 function trace(options){ 170 171 var theType = $$.isNil(options.type) ? null : options.type.toUpperCase(); 172 if ($$.isNil(theType) || ($$.indexOf(["FINISH","ERROR"],theType) === -1)){ 173 throw "PRECONDITION ERROR: Invalid event type (options.type)."; 174 } 175 176 if ($$.isNil(options.name)){ 177 throw "PRECONDITION ERROR: Event name is required (options.name)."; 178 } 179 180 if ($$.isNil(options.canvas)){ 181 throw "PRECONDITION ERROR: Canvas application name is required (options.canvas)."; 182 } 183 184 var url = "/services/data/v" + pversion + "/platformconnect/traceevent"; 185 186 var config = { 187 headers :{ 188 Authorization : "OAuth " + sid, 189 Accept : "application/json" 190 }, 191 contentType: "application/json", 192 data : Sfdc.JSON.stringify({ 193 eventName: options.name, 194 eventType: theType, 195 namespace: options.namespace, 196 canvas: options.canvas, 197 payload: options.payload, 198 authType: options.authType, 199 startTime: options.startTime, 200 endTime: new Date().getTime(), 201 parentVersion: pversion, 202 clientVersion: options.cversion 203 }) 204 }; 205 206 url = applySalesforceProxyConfig(url, config, options); 207 208 Sfdc.Ajax.post(url, function(data){}, config); 209 } 210 211 function addSignedRequestFrame(id, sr, parent, uid, options) { 212 213 var tmpForm, tmpInput, tmpSubmit; 214 215 tmpForm = document.createElement('form'); 216 tmpForm.setAttribute('name', "canvas-hidden-form"); 217 tmpForm.setAttribute('style', "display:none"); 218 tmpForm.setAttribute('action', canvas[id].url); 219 tmpForm.setAttribute('target', "canvas-frame-" + id); 220 tmpForm.setAttribute('method', "post"); 221 222 tmpInput = document.createElement('input'); 223 tmpInput.setAttribute('type', "text"); 224 tmpInput.setAttribute('name', "signed_request"); 225 tmpInput.setAttribute('id', "signed_request"); 226 tmpInput.value =sr; 227 228 tmpSubmit = document.createElement('input'); 229 tmpSubmit.setAttribute('type', "submit"); 230 tmpSubmit.setAttribute('post', "post"); 231 232 tmpForm.appendChild(tmpInput); 233 tmpForm.appendChild(tmpSubmit); 234 235 // Remove all child elements from the last time we rendered a canvas app 236 while ( parent.firstChild ) {parent.removeChild( parent.firstChild );} 237 parent.appendChild(tmpForm); 238 parent.appendChild(buildIFrame(id, uid,options)); 239 document.forms["canvas-hidden-form"].submit(); 240 } 241 242 function buildIFrame(id, uid, options){ 243 var rv = document.createElement('iframe'); 244 rv.setAttribute('id', uid); 245 rv.setAttribute('name', "canvas-frame-" + id); 246 rv.style.border=options.frameborder; 247 rv.style.width = options.width; 248 rv.style.height = options.height; 249 rv.style.scrolling = options.scrolling; 250 rv.onload = function(){ 251 if (options.startTime){ 252 trace({name:"render", 253 type:"FINISH", 254 namespace: canvas[id].app.namespace, 255 canvas: canvas[id].app.developerName, 256 startTime: canvas[id].app.startTime, 257 authType: canvas[id].app.authenticationType, 258 payload: { 259 canvasUrl: canvas[id].url 260 }, 261 cversion : canvas[id].cversion 262 }); 263 options.startTime = null; 264 } 265 }; 266 return rv; 267 } 268 269 function addFrame(id, parent, uid, options) { 270 var frameDoc, frameObj; 271 272 // Remove all child elements from the last time we rendered a canvas app 273 while ( parent.firstChild ) {parent.removeChild( parent.firstChild );} 274 275 frameObj = parent.appendChild(buildIFrame(id,uid,options)); 276 if (frameObj.contentDocument) { 277 frameDoc = frameObj.contentDocument; 278 } 279 else if (frameObj.contentWindow) { 280 frameDoc = frameObj.contentWindow.document; 281 } 282 frameDoc.location.replace(canvas[id].url); 283 } 284 285 function render(container, elem, options) { 286 var iframeDefaults = { 287 frameborder: '0', 288 width: '800px', 289 height: '900px', 290 scrolling: 'no' 291 }; 292 293 options = $$.extend(iframeDefaults, options || {}); 294 295 var id, uid, q, parentUrl, curl, instanceLabel, tempElem; 296 297 if ($$.isNil(container) || $$.isNil(container.app) || $$.isNil(container.app.canvasUrl) || $$.isNil(container.app.id)) { 298 throw "ERROR: insufficient container and/or app parameter information passed in"; 299 } 300 if ($$.isNil(container.version)) { 301 throw "ERROR: Parent version information not supplied"; 302 } 303 if ($$.isNil(container.sid)) { 304 throw "ERROR: SID not supplied"; 305 } 306 307 try { 308 instanceLabel = $$.isNil(options.instanceLabel) ? "" : "-" + options.instanceLabel; 309 id = container.app.id + instanceLabel; 310 canvas[id] = {}; 311 canvas[id].app = container.app; 312 canvas[id].cversion = null; 313 canvas[id].options = options; 314 315 pversion = container.version; 316 sid = container.sid; 317 curl = decodeURIComponent(container.app.canvasUrl); 318 319 // Capture the start time (ms) for trace. 320 options.startTime = new Date().getTime(); 321 322 uid = "if-" + id; 323 324 tempElem = $$.isObject(elem) ? elem : $$.byId(elem); 325 326 if ($$.isNil(tempElem)) { 327 alert("Container element not found [" + elem + "]"); 328 } 329 else { 330 331 parentUrl = document.location.protocol + "//" + document.location.host; 332 333 salesforceBaseUrl = options.salesforceBaseUrl; 334 335 if (container.app.authenticationType === "OAUTH") { 336 if (! $$.isNil(container.login) && "https://login.salesforce.com" !== container.login) { 337 q = $$.param({loginUrl : container.login}); 338 } 339 q += "#target_origin=" + encodeURIComponent(parentUrl) + "&instance_id=" + id ; 340 canvas[id].url = $$.query(curl, q); 341 addFrame(id, tempElem, uid, options); 342 } 343 else { 344 canvas[id].url = curl; 345 getSignedRequest(id, container.app.namespace,container.app.developerName, tempElem, uid, options); 346 } 347 listen(canvas[id].url); 348 } 349 } 350 catch (e) { 351 alert("An Error occurred: ", e); 352 } 353 354 } 355 356 return { 357 render : render 358 }; 359 }()); 360 361 $$.module('Sfdc.canvas.parent', module); 362 363 }(Sfdc.canvas)); 364