Skip to content

Commit

Permalink
Fix download links to cross-origin images
Browse files Browse the repository at this point in the history
Fixes #25. Cross-origin images are downloaded with fetch.

Preparing to publish `2.6.0` soon once additional fixes are ready.
  • Loading branch information
aautio committed Sep 8, 2022
1 parent bd2c3e8 commit daac3af
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 94 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
/node_modules
/umd
npm-debug.log*
.vscode
.vscode
cypress/screenshots
cypress/downloads
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ A _lightweight_ React component providing modal image Lightbox.
- Image alt shown as title of lightbox
- Background color of transparent images can be overridden.

You need to bring your own `Set` polyfill if you use old Internet Explorers.
You need to bring your own `Set` and `fetch` polyfills if you use old Internet Explorers.

## Simple API

Expand Down
23 changes: 21 additions & 2 deletions cypress/e2e/react-modal-image.cy.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="cypress" />

describe("react-modal-image", function() {
it("can find six images in the demo", function() {
it("can find seven images in the demo", function() {
cy.visit("demo/dist/index.html");

cy.contains("react-modal-image");
Expand All @@ -11,8 +11,9 @@ describe("react-modal-image", function() {
cy.contains("#4 with download and zoom -buttons hidden");
cy.contains("#5 with transparent png shown in hotpink background");
cy.contains("#6 with rotation -button displayed");
cy.contains("#7 with images from external domain");

cy.get("img").should("have.length", 6);
cy.get("img").should("have.length", 7);
});

it("can open and close the three first lightboxes", function() {
Expand Down Expand Up @@ -115,4 +116,22 @@ describe("react-modal-image", function() {

cy.get("span.__react_modal_image__icon_menu").children().last().click();
});

it("can download from external domain", function() {
cy.visit("demo/dist/index.html");

cy.get("#react-modal-image-img").should("not.exist");

cy.get(`img:nth(${6})`).click();
cy.get("#react-modal-image-img");

// trigger download
cy.get("span.__react_modal_image__icon_menu").children().first().click();

const downloadsFolder = Cypress.config("downloadsFolder");
cy.readFile(downloadsFolder + "/aaa.png").should("exist");

// close
cy.get("span.__react_modal_image__icon_menu").children().last().click();
})
});
5 changes: 5 additions & 0 deletions demo/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
<html>
<head>
<title>react-modal-image live demo</title>
<style>
div#demo > div > div {
max-width: 400px;
}
</style>
</head>
<body>
<div id="demo" />
Expand Down
183 changes: 96 additions & 87 deletions demo/src/index.js
Original file line number Diff line number Diff line change
@@ -1,90 +1,99 @@
import React, { Component } from "react";
import { render } from "react-dom";
import React from "react";
import { createRoot } from 'react-dom/client';

import ModalImage from "../../src";

class Demo extends Component {
render() {
return (
<div>
<h1>react-modal-image</h1>

<h2>#1 with alt, small, medium and large props</h2>

<div style={{ maxWidth: "400px" }}>
<ModalImage
alt="Here is the caption"
small="example_img_small.jpg"
medium="example_img_medium.jpg"
large="example_img_large.jpg"
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#2 with small and large props defined only</h2>

<div style={{ maxWidth: "400px" }}>
<ModalImage
small="example_img_small.jpg"
large="example_img_large.jpg"
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#3 with small and medium props defined only</h2>

<div style={{ maxWidth: "400px" }}>
<ModalImage
small="example_img_small.jpg"
medium="example_img_medium.jpg"
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#4 with download and zoom -buttons hidden</h2>

<div style={{ maxWidth: "400px" }}>
<ModalImage
small="example_img_small.jpg"
large="example_img_large.jpg"
hideDownload={true}
hideZoom={true}
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#5 with transparent png shown in hotpink background</h2>

<div style={{ maxWidth: "400px" }}>
<ModalImage
small="example_transparent_heart.png"
large="example_transparent_heart.png"
hideDownload={true}
hideZoom={true}
imageBackgroundColor="hotpink"
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#6 with rotation -button displayed</h2>

<div style={{ maxWidth: "400px" }}>
<ModalImage
small="example_img_small.jpg"
large="example_img_large.jpg"
showRotate={true}
/>
</div>
<p>^ click or inspect the image above</p>

<h2>Further info</h2>

<p>
<a href="https://github.com/aautio/react-modal-image">Github</a>
</p>
</div>
);
}
}

render(<Demo />, document.querySelector("#demo"));
import pkg from "../../package.json"

const Demo = () => (
<div>
<h1>Demo of react-modal-image@{pkg.version}</h1>

<h2>#1 with alt, small, medium and large props</h2>

<div>
<ModalImage
alt="Here is the caption"
small="example_img_small.jpg"
medium="example_img_medium.jpg"
large="example_img_large.jpg"
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#2 with small and large props defined only</h2>

<div>
<ModalImage
small="example_img_small.jpg"
large="example_img_large.jpg"
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#3 with small and medium props defined only</h2>

<div>
<ModalImage
small="example_img_small.jpg"
medium="example_img_medium.jpg"
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#4 with download and zoom -buttons hidden</h2>

<div>
<ModalImage
small="example_img_small.jpg"
large="example_img_large.jpg"
hideDownload={true}
hideZoom={true}
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#5 with transparent png shown in hotpink background</h2>

<div>
<ModalImage
small="example_transparent_heart.png"
large="example_transparent_heart.png"
hideDownload={true}
hideZoom={true}
imageBackgroundColor="hotpink"
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#6 with rotation -button displayed</h2>

<div>
<ModalImage
small="example_img_small.jpg"
large="example_img_large.jpg"
showRotate={true}
/>
</div>
<p>^ click or inspect the image above</p>

<h2>#7 with images from external domain</h2>

<div>
<ModalImage
small="https://dummyimage.com/420x200/000/aaa"
large="https://dummyimage.com/640x360/000/aaa"
/>
</div>

<h2>Further info</h2>

<p>
<a href="https://github.com/aautio/react-modal-image">Github</a>
</p>
</div>
);

// @ts-ignore
const root = createRoot(document.querySelector("#demo"));
root.render(<Demo />);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-modal-image",
"version": "2.5.0",
"version": "2.6.0",
"description": "Lightweight Lightbox React Component",
"main": "lib/index.js",
"module": "es/index.js",
Expand Down
46 changes: 44 additions & 2 deletions src/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,48 @@ import React from "react";

import { ZoomInIcon, ZoomOutIcon, DownloadIcon, CloseIcon, RotateIcon } from "./icons";

function isSameOrigin(href) {
// @ts-ignore
return document.location.hostname !== new URL(href, document.location).hostname
}

/**
* Triggers image download from cross origin URLs
*
* `<a href="..." download>foo</a> works only for same-origin URLs.
* Further info: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download
*/

const crossOriginDownload = href => event => {
if (!isSameOrigin(href)) {
// native download will be triggered by `download` attribute
return
}

// else proceed to use `fetch` for cross origin image download

event.preventDefault();

fetch(href)
.then(res => {
if (!res.ok) {
console.error("Failed to download image, HTTP status " + res.status + " from " + href)
}

return res.blob().then(blob => {
let tmpAnchor = document.createElement("a")
tmpAnchor.setAttribute("download", href.split("/").pop())
tmpAnchor.href = URL.createObjectURL(blob)
tmpAnchor.click()
})
})
.catch(err => {
console.error(err)
console.error("Failed to download image from " + href)
})
};


const Header = ({
image,
alt,
Expand All @@ -16,7 +58,7 @@ const Header = ({
<div className="__react_modal_image__header">
<span className="__react_modal_image__icon_menu">
{enableDownload && (
<a href={image} download>
<a href={image} download onClick={crossOriginDownload(image)}>
<DownloadIcon />
</a>
)}
Expand All @@ -27,7 +69,7 @@ const Header = ({
)}
{enableRotate && (
<a onClick={toggleRotate}>
<RotateIcon/>
<RotateIcon />
</a>
)}
<a onClick={onClose}>
Expand Down

0 comments on commit daac3af

Please sign in to comment.