Skip to content

Commit

Permalink
Add cache bypass because CORS headers are missing on a cache hit (tsa…
Browse files Browse the repository at this point in the history
…yen#129)

* add `cacheBust` and `imagePlaceholder` options (by @cnatis)
  • Loading branch information
cnatis authored and tsayen committed Jun 5, 2017
1 parent 6f43ce7 commit 8e68f3a
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 4 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ for JavaScript names of CSS properties.
A number between 0 and 1 indicating image quality (e.g. 0.92 => 92%) of the
JPEG image. Defaults to 1.0 (100%)

#### cacheBust

Set to true to append the current time as a query string to URL requests to enable cache busting. Defaults to false

#### imagePlaceholder

A data URL for a placeholder image that will be used when fetching an image fails. Defaults to undefined and will throw an error on failed images

## Browsers

It's tested on latest Chrome and Firefox (49 and 45 respectively at the time
Expand Down
14 changes: 13 additions & 1 deletion spec/dom-to-image.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -507,13 +507,25 @@
.then(done).catch(done);
});

it('should return empty result if cannot get resourse', function (done) {
it('should return empty result if cannot get resource', function (done) {
domtoimage.impl.util.getAndEncode(BASE_URL + 'util/not-found')
.then(function (resource) {
assert.equal(resource, '');
}).then(done).catch(done);
});

it('should return placeholder result if cannot get resource and placeholder is provided', function (done) {
var placeholder = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAMSURBVBhXY7h79y4ABTICmGnXPbMAAAAASUVORK5CYII=";
var original = domtoimage.impl.options.imagePlaceholder;
domtoimage.impl.options.imagePlaceholder = placeholder;
domtoimage.impl.util.getAndEncode(BASE_URL + 'util/not-found')
.then(function (resource) {
var placeholderData = placeholder.split(/,/)[1];
assert.equal(resource, placeholderData);
domtoimage.impl.options.imagePlaceholder = original;
}).then(done).catch(done);
});

it('should parse extension', function () {
var parse = domtoimage.impl.util.parseExtension;

Expand Down
55 changes: 52 additions & 3 deletions src/dom-to-image.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
var fontFaces = newFontFaces();
var images = newImages();

// Default impl options
var defaultOptions = {
// Default is to fail on error, no placeholder
imagePlaceholder: undefined,
// Default cache bust is false, it will use the cache
cacheBust: false
};

var domtoimage = {
toSvg: toSvg,
toPng: toPng,
Expand All @@ -16,7 +24,8 @@
fontFaces: fontFaces,
images: images,
util: util,
inliner: inliner
inliner: inliner,
options: {}
}
};

Expand All @@ -37,10 +46,13 @@
* @param {Object} options.style - an object whose properties to be copied to node's style before rendering.
* @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only),
defaults to 1.0.
* @param {String} options.imagePlaceholder - dataURL to use as a placeholder for failed images, default behaviour is to fail fast on images we can't fetch
* @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url
* @return {Promise} - A promise that is fulfilled with a SVG image data URL
* */
function toSvg(node, options) {
options = options || {};
copyOptions(options);
return Promise.resolve(node)
.then(function (node) {
return cloneNode(node, options.filter, true);
Expand Down Expand Up @@ -122,6 +134,21 @@
.then(util.canvasToBlob);
}

function copyOptions(options) {
// Copy options to impl options for use in impl
if(typeof(options.imagePlaceholder) === 'undefined') {
domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder;
} else {
domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder;
}

if(typeof(options.cacheBust) === 'undefined') {
domtoimage.impl.options.cacheBust = defaultOptions.cacheBust;
} else {
domtoimage.impl.options.cacheBust = options.cacheBust;
}
}

function draw(domNode, options) {
return toSvg(domNode, options)
.then(util.makeImage)
Expand Down Expand Up @@ -435,6 +462,11 @@

function getAndEncode(url) {
var TIMEOUT = 30000;
if(domtoimage.impl.options.cacheBust) {
// Cache bypass so we dont have CORS issues with cached images
// Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
url += ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime();
}

return new Promise(function (resolve) {
var request = new XMLHttpRequest();
Expand All @@ -446,11 +478,24 @@
request.open('GET', url, true);
request.send();

var placeholder;
if(domtoimage.impl.options.imagePlaceholder) {
var split = domtoimage.impl.options.imagePlaceholder.split(/,/);
if(split && split[1]) {
placeholder = split[1];
}
}

function done() {
if (request.readyState !== 4) return;

if (request.status !== 200) {
fail('cannot fetch resource: ' + url + ', status: ' + request.status);
if(placeholder) {
resolve(placeholder);
} else {
fail('cannot fetch resource: ' + url + ', status: ' + request.status);
}

return;
}

Expand All @@ -463,7 +508,11 @@
}

function timeout() {
fail('timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url);
if(placeholder) {
resolve(placeholder);
} else {
fail('timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url);
}
}

function fail(message) {
Expand Down

0 comments on commit 8e68f3a

Please sign in to comment.