From daac3afc7dd7d7df1eb4e31901695b3cc0b51765 Mon Sep 17 00:00:00 2001 From: Ari Autio Date: Thu, 8 Sep 2022 17:41:10 +0300 Subject: [PATCH] Fix download links to cross-origin images Fixes #25. Cross-origin images are downloaded with fetch. Preparing to publish `2.6.0` soon once additional fixes are ready. --- .gitignore | 4 +- README.md | 2 +- cypress/e2e/react-modal-image.cy.js | 23 +++- demo/src/index.html | 5 + demo/src/index.js | 183 +++++++++++++++------------- package.json | 2 +- src/Header.js | 46 ++++++- 7 files changed, 171 insertions(+), 94 deletions(-) diff --git a/.gitignore b/.gitignore index 340e46a..d190853 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ /node_modules /umd npm-debug.log* -.vscode \ No newline at end of file +.vscode +cypress/screenshots +cypress/downloads \ No newline at end of file diff --git a/README.md b/README.md index 6332d98..5d55d14 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cypress/e2e/react-modal-image.cy.js b/cypress/e2e/react-modal-image.cy.js index bf49422..0d35ff6 100644 --- a/cypress/e2e/react-modal-image.cy.js +++ b/cypress/e2e/react-modal-image.cy.js @@ -1,7 +1,7 @@ /// 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"); @@ -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() { @@ -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(); + }) }); diff --git a/demo/src/index.html b/demo/src/index.html index fd5fd65..3dd27f4 100644 --- a/demo/src/index.html +++ b/demo/src/index.html @@ -2,6 +2,11 @@ react-modal-image live demo +
diff --git a/demo/src/index.js b/demo/src/index.js index dbb878d..2aa112c 100644 --- a/demo/src/index.js +++ b/demo/src/index.js @@ -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 ( -
-

react-modal-image

- -

#1 with alt, small, medium and large props

- -
- -
-

^ click or inspect the image above

- -

#2 with small and large props defined only

- -
- -
-

^ click or inspect the image above

- -

#3 with small and medium props defined only

- -
- -
-

^ click or inspect the image above

- -

#4 with download and zoom -buttons hidden

- -
- -
-

^ click or inspect the image above

- -

#5 with transparent png shown in hotpink background

- -
- -
-

^ click or inspect the image above

- -

#6 with rotation -button displayed

- -
- -
-

^ click or inspect the image above

- -

Further info

- -

- Github -

-
- ); - } -} - -render(, document.querySelector("#demo")); +import pkg from "../../package.json" + +const Demo = () => ( +
+

Demo of react-modal-image@{pkg.version}

+ +

#1 with alt, small, medium and large props

+ +
+ +
+

^ click or inspect the image above

+ +

#2 with small and large props defined only

+ +
+ +
+

^ click or inspect the image above

+ +

#3 with small and medium props defined only

+ +
+ +
+

^ click or inspect the image above

+ +

#4 with download and zoom -buttons hidden

+ +
+ +
+

^ click or inspect the image above

+ +

#5 with transparent png shown in hotpink background

+ +
+ +
+

^ click or inspect the image above

+ +

#6 with rotation -button displayed

+ +
+ +
+

^ click or inspect the image above

+ +

#7 with images from external domain

+ +
+ +
+ +

Further info

+ +

+ Github +

+
+); + +// @ts-ignore +const root = createRoot(document.querySelector("#demo")); +root.render(); diff --git a/package.json b/package.json index e447264..cb3b2e3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Header.js b/src/Header.js index 40672a7..89a2ca7 100644 --- a/src/Header.js +++ b/src/Header.js @@ -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 + * + * `foo 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, @@ -16,7 +58,7 @@ const Header = ({
{enableDownload && ( - + )} @@ -27,7 +69,7 @@ const Header = ({ )} {enableRotate && ( - + )}