Skip to content

Commit

Permalink
Address browser compatibility issues
Browse files Browse the repository at this point in the history
* Added shim for .forEach to allow Encoding polyfill to work
* Re-order browser environment loading in build to fix Date.now issue
* Remove restriction in IE mode requests preventing alternate
  content-type requests from occurring which should have never been
  implemented to begin with, no reason to restrict them as the
  Content-Type travels through the body as a form encoded piece
* Fix Etag issue around how etags should be quoted
* Various unit tests improvements around skipping attachment tests,
  activity definition corrections, switch for above change to content
  type handling, regex undefined handling in URL parsing
  • Loading branch information
brianjmiller committed Sep 21, 2016
1 parent df99555 commit cf20663
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 208 deletions.
4 changes: 2 additions & 2 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ module.exports = function(grunt) {
bower;

browserFileList.push(
"src/Environment/Browser.js",
// needed because IE10 doesn't support Uint8ClampedArray
// which is required by CryptoJS for typedarray support
"node_modules/js-polyfills/typedarray.js",
// needed because IE10 doesn't have ArrayBuffer slice
"node_modules/arraybuffer-slice/index.js",
// needed for IE and Safari for TextDecoder/TextEncoder
"node_modules/text-encoding/lib/encoding.js",
"src/Environment/Browser.js"
"node_modules/text-encoding/lib/encoding.js"
);
nodeFileList.push(
"src/Environment/Node.js"
Expand Down
50 changes: 27 additions & 23 deletions src/Environment/Browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,33 @@ TinCan client library
};
}

//
// Add .forEach implementation for supporting our string encoding polyfill
// imported from js-polyfills to avoid bringing in the whole es5 shim
// for now, a rewrite probably moves all shims out of the main build or at
// least Environment file and leverages more of them
//

// ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
// From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
if (!Array.prototype.forEach) {
/* jshint freeze:false,bitwise:false */
Array.prototype.forEach = function (fun /*, thisp */) {
if (this === void 0 || this === null) { throw new TypeError(); }

var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function") { throw new TypeError(); }

var thisp = arguments[1], i;
for (i = 0; i < len; i += 1) {
if (i in t) {
fun.call(thisp, t[i], i, t);
}
}
};
}

/* Detect CORS and XDR support */
env.hasCORS = false;
env.useXDR = false;
Expand Down Expand Up @@ -232,18 +259,6 @@ TinCan client library
}

if (fullRequest.length >= MAX_REQUEST_LENGTH) {
// This may change based upon what content is supported in IE Mode
if (typeof headers["Content-Type"] !== "undefined" && headers["Content-Type"] !== "application/json") {
err = new Error("Unsupported content type for IE Mode request");
if (typeof cfg.callback !== "undefined") {
cfg.callback(err, null);
}
return {
err: err,
xhr: null
};
}

if (typeof cfg.method === "undefined") {
err = new Error("method must not be undefined for an IE Mode Request conversion");
if (typeof cfg.callback !== "undefined") {
Expand Down Expand Up @@ -359,17 +374,6 @@ TinCan client library
xhr: null
};
}
if (typeof headers["Content-Type"] !== "undefined" && headers["Content-Type"] !== "application/json") {
err = new Error("Unsupported content type for IE Mode request");
if (cfg.callback) {
cfg.callback(err, null);
return null;
}
return {
err: err,
xhr: null
};
}

// method has to go on querystring, and nothing else,
// and the actual method is then always POST
Expand Down
66 changes: 48 additions & 18 deletions src/LRS.js
Original file line number Diff line number Diff line change
Expand Up @@ -1236,20 +1236,25 @@ TinCan client library
);
if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") {
result.etag = xhr.getResponseHeader("ETag");
} else {
}
else {
//
// either XHR didn't have getResponseHeader (probably cause it is an IE
// XDomainRequest object which doesn't) or not populated by LRS so create
// the hash ourselves
//
result.etag = TinCan.Utils.getSHA1String(xhr.responseText);
// the LRS is responsible for quoting the Etag value so we need to mimic
// that behavior here as well
//
result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\"";
}

if (typeof xhr.contentType !== "undefined") {
// most likely an XDomainRequest which has .contentType,
// for the ones that it supports
result.contentType = xhr.contentType;
} else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
}
else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
result.contentType = xhr.getResponseHeader("Content-Type");
}

Expand Down Expand Up @@ -1280,19 +1285,24 @@ TinCan client library
);
if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") {
requestResult.state.etag = requestResult.xhr.getResponseHeader("ETag");
} else {
}
else {
//
// either XHR didn't have getResponseHeader (probably cause it is an IE
// XDomainRequest object which doesn't) or not populated by LRS so create
// the hash ourselves
//
requestResult.state.etag = TinCan.Utils.getSHA1String(requestResult.xhr.responseText);
// the LRS is responsible for quoting the Etag value so we need to mimic
// that behavior here as well
//
requestResult.state.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\"";
}
if (typeof requestResult.xhr.contentType !== "undefined") {
// most likely an XDomainRequest which has .contentType
// for the ones that it supports
requestResult.state.contentType = requestResult.xhr.contentType;
} else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
}
else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
requestResult.state.contentType = requestResult.xhr.getResponseHeader("Content-Type");
}
if (TinCan.Utils.isApplicationJSON(requestResult.state.contentType)) {
Expand Down Expand Up @@ -1662,19 +1672,24 @@ TinCan client library
);
if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") {
result.etag = xhr.getResponseHeader("ETag");
} else {
}
else {
//
// either XHR didn't have getResponseHeader (probably cause it is an IE
// XDomainRequest object which doesn't) or not populated by LRS so create
// the hash ourselves
//
result.etag = TinCan.Utils.getSHA1String(xhr.responseText);
// the LRS is responsible for quoting the Etag value so we need to mimic
// that behavior here as well
//
result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\"";
}
if (typeof xhr.contentType !== "undefined") {
// most likely an XDomainRequest which has .contentType
// for the ones that it supports
result.contentType = xhr.contentType;
} else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
}
else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
result.contentType = xhr.getResponseHeader("Content-Type");
}
if (TinCan.Utils.isApplicationJSON(result.contentType)) {
Expand Down Expand Up @@ -1705,19 +1720,24 @@ TinCan client library
);
if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") {
requestResult.profile.etag = requestResult.xhr.getResponseHeader("ETag");
} else {
}
else {
//
// either XHR didn't have getResponseHeader (probably cause it is an IE
// XDomainRequest object which doesn't) or not populated by LRS so create
// the hash ourselves
//
requestResult.profile.etag = TinCan.Utils.getSHA1String(requestResult.xhr.responseText);
// the LRS is responsible for quoting the Etag value so we need to mimic
// that behavior here as well
//
requestResult.profile.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\"";
}
if (typeof requestResult.xhr.contentType !== "undefined") {
// most likely an XDomainRequest which has .contentType
// for the ones that it supports
requestResult.profile.contentType = requestResult.xhr.contentType;
} else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
}
else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
requestResult.profile.contentType = requestResult.xhr.getResponseHeader("Content-Type");
}
if (TinCan.Utils.isApplicationJSON(requestResult.profile.contentType)) {
Expand Down Expand Up @@ -1966,19 +1986,24 @@ TinCan client library
);
if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") {
result.etag = xhr.getResponseHeader("ETag");
} else {
}
else {
//
// either XHR didn't have getResponseHeader (probably cause it is an IE
// XDomainRequest object which doesn't) or not populated by LRS so create
// the hash ourselves
//
result.etag = TinCan.Utils.getSHA1String(xhr.responseText);
// the LRS is responsible for quoting the Etag value so we need to mimic
// that behavior here as well
//
result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\"";
}
if (typeof xhr.contentType !== "undefined") {
// most likely an XDomainRequest which has .contentType
// for the ones that it supports
result.contentType = xhr.contentType;
} else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
}
else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
result.contentType = xhr.getResponseHeader("Content-Type");
}
if (TinCan.Utils.isApplicationJSON(result.contentType)) {
Expand Down Expand Up @@ -2009,19 +2034,24 @@ TinCan client library
);
if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") {
requestResult.profile.etag = requestResult.xhr.getResponseHeader("ETag");
} else {
}
else {
//
// either XHR didn't have getResponseHeader (probably cause it is an IE
// XDomainRequest object which doesn't) or not populated by LRS so create
// the hash ourselves
//
requestResult.profile.etag = TinCan.Utils.getSHA1String(requestResult.xhr.responseText);
// the LRS is responsible for quoting the Etag value so we need to mimic
// that behavior here as well
//
requestResult.profile.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\"";
}
if (typeof requestResult.xhr.contentType !== "undefined") {
// most likely an XDomainRequest which has .contentType
// for the ones that it supports
requestResult.profile.contentType = requestResult.xhr.contentType;
} else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
}
else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
requestResult.profile.contentType = requestResult.xhr.getResponseHeader("Content-Type");
}
if (TinCan.Utils.isApplicationJSON(requestResult.profile.contentType)) {
Expand Down
10 changes: 10 additions & 0 deletions test/js/BrowserPrep.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,14 @@ var TinCanTest,
};

TinCanTest.testAttachments = true;

//
// can't support attachments with content in browsers that don't support
// an XHR2 implementation, essentially IE < 10, so set a flag to skip
// testing related functionality
//
if (! ("withCredentials" in new XMLHttpRequest())) {
TinCanTest.testAttachments = false;
alert("Not testing attachments with content");
}
}());
10 changes: 7 additions & 3 deletions test/js/unit/Activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,16 @@
name: "activity with definition (raw definition)",
instanceConfig: {
id: "http://TestActivity",
definition: { name: "Test" }
definition: {
name: {
en: "Test"
}
}
},
toString: (new TinCan.ActivityDefinition({ name: "Test" })).toString(),
toString: (new TinCan.ActivityDefinition({ name: { en: "Test" } })).toString(),
checkProps: {
id: "http://TestActivity",
definition: new TinCan.ActivityDefinition({ name: "Test" })
definition: new TinCan.ActivityDefinition({ name: { en: "Test" } })
}
}
],
Expand Down
49 changes: 27 additions & 22 deletions test/js/unit/Attachment.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,34 +135,39 @@
checkProps: {
fileUrl: "http://id.tincanapi.com/attachment/test-attachment.pdf"
}
},
{
name: "Attachment with string content",
instanceConfig: {
content: "test text content"
},
checkProps: {
sha2: "889f4b4a820461e25c2431acab679831f7eed2fc25f42a809769045527e7a73b",
length: 17,
content: TinCan.Utils.stringToArrayBuffer("test text content")
}
},
{
name: "Attachment with binary content",
instanceConfig: {
content: TinCan.Utils.stringToArrayBuffer("test text content")
},
checkProps: {
sha2: "889f4b4a820461e25c2431acab679831f7eed2fc25f42a809769045527e7a73b",
length: 17,
content: TinCan.Utils.stringToArrayBuffer("test text content")
}
}
],
i,
obj,
result;

if (TinCanTest.testAttachments) {
set.push(
{
name: "Attachment with string content",
instanceConfig: {
content: "test text content"
},
checkProps: {
sha2: "889f4b4a820461e25c2431acab679831f7eed2fc25f42a809769045527e7a73b",
length: 17,
content: TinCan.Utils.stringToArrayBuffer("test text content")
}
},
{
name: "Attachment with binary content",
instanceConfig: {
content: TinCan.Utils.stringToArrayBuffer("test text content")
},
checkProps: {
sha2: "889f4b4a820461e25c2431acab679831f7eed2fc25f42a809769045527e7a73b",
length: 17,
content: TinCan.Utils.stringToArrayBuffer("test text content")
}
}
);
}

for (i = 0; i < set.length; i += 1) {
row = set[i];
obj = new TinCan.Attachment(row.instanceConfig);
Expand Down
Loading

0 comments on commit cf20663

Please sign in to comment.