Dropzone Component error when trying to access file in Safari

I’ve been trying to get the Dropzone upload to work in Safari, after many different attempts, I came to the conclusion that there seems to be an issue in the way the file object is passed to the extension. The implementation works in Chrome (including CORS).

The docs state the limitation:

At present, DropZone does not offer image upload preview capabilities. The use of object URLs directly in an image component is not possible due to the extension and host operating on separate domains.

Which makes sense, as the app extension is running with the null origin. However in our tests, the fetch call also cannot access the file object, this completely prevents the upload from working in Safari.

I always get the following error in the console:

Not allowed to load local resource: blob:null/994fe27e-2379-495d-a58c-750dbc1245cb

Cannot load blob:null/994fe27e-2379-495d-a58c-750dbc1245cb due to access control checks

I’ve tried many approaches, including:

  • Uploading to Staged Media URL as suggested in this Guide. With the URL https://shopify-staged-uploads.storage.googleapis.com/ (passes CORS checks, but still gets the same error when the browser tries to read the file when passed to fetch)
  • This comment form Shopify staff suggest using XHR. Which actually works during dev mode, but once the extension is published, XHR access is not supported.
  • Many different variations of fetch calls (PUT with body, POST with FormData)

Here is a simplified extension that can be used to reproduce the issue:

import {BlockStack, DropZone, reactExtension, TextBlock, Banner, Image, useApi } from "@shopify/ui-extensions-react/customer-account";
import {useState} from 'react';

export default reactExtension(
    "customer-account.order-status.block.render",
    () => <CustomerUpload/>
);

function CustomerUpload() {
    const {i18n} = useApi();

    const [image, setImage] = useState(null);

    return (
        <Banner>
            <BlockStack inlineAlignment="center">
                <TextBlock>Basic Upload</TextBlock>
                <DropZone
                    onInput={async ([file]) => {

                        // Uploading provided file to a server using fetch API
                        const formData = new FormData();
                        formData.append('file', new File([file], file.name));

                        try {
                            // WORKS with any api that has CORS enabled   EXCEPT in Safari
                            // Access-Control-Allow-Origin: *
                            //
                            // To reproduce issue hardcoded incorrect URL
                            // CHROME: fetch throws a CORS error (expected)
                            // SAFARI: the browser already throws error on fetch (no cors is initiated)
                            const url = 'https://shopify.com/upload';

                            const uploadResponse = await fetch(url, {
                                method: 'POST',
                                body: formData
                            });

                            const uploadData = await uploadResponse.json();

                            console.debug('Upload response:', uploadResponse);

                            setImage(uploadData.url);

                        } catch (error) {
                            console.error('Upload failed:', error);
                        }
                    }}
                ></DropZone>
                {image ? <Image source={image} /> : null}
            </BlockStack>
        </Banner>
    );
}

Note: the example has a fixed URL that is expected to fail CORS checks, but the Safari issue already happens before CORS checks are applied. Valid CORS URLs work fine in Chrome, but not in Safari

Please if anyone at Shopify can take a look at this issue, I would really appreciate it!

Hey @michael,

What’s the error you’re getting when using XHR?

Does your extension have access to network_access?
(Please don’t forget to also activate it in the partner dashboard as described in the docs).

What version of Safari are you testing with? And are talking about iOS Safari or desktop or both?

Thank you!

Hi @Robin-Shopify thanks for your reply.

The error is the following:

TypeError: undefined is not a constructor (evaluating 'new XMLHttpRequest')

It seems XMLHttpRequest is not available in the context. The docs also only mention the use of “fetch” for network access. Please confirm that this is the case.

The extension has network_access enabled (and activated) - the code works fine in other browsers.

Testing with Safari Version 18.5 on macOS - but i0S has the same issue.

@michael, can you share the XMLHttpRequest based code you’ve tried too please?

the code works fine in other browsers.

The XMLHttpRequest or the fetch version? Or both?

Thank you!

I think XHR does not work in any of the browsers (might have to re-test though - the other browsers use the good code path which uses fetch directly)

Fetch solution works in all Browsers except Safari.

The code that uses XHR as a fallback:

                       // get signed URL for POST upload
                        urls = await getSignedUploadUrl(file, settings);

                        uploadUrl = urls.upload;

                        const form = new FormData();
 
             
                        form.append('file', new File([file._file], file._file.name, {type: file._file.type}));

                        await new Promise((resolve, reject) => {

                       
                            let xhr = new XMLHttpRequest();
                            xhr.open("POST", uploadUrl, true);

                            xhr.onload = function () {
                                // GCS return 204 on success
                                if (xhr.status === 200 || xhr.status === 204) {
                                    resolve();
                                } else {
                                    reject(new Error(`Upload failed with status ${xhr.status}: ${xhr.statusText}`));
                                }
                            };

                            xhr.onerror = function () {
                                reject(new Error(`Upload failed with status ${xhr.status}: ${xhr.statusText}`));
                            };

                            xhr.send(form);
                        });

@Robin-Shopify
Are you able to test my code example form the intital post?

@micheal I could reproduce your initial issue. Thank you for reporting, we will investigate this further.

Glad this was reproducible and thanks for looking into it, please keep me posted if you find a solution.

Responding to issues is what makes the Shopify platform great :folded_hands:

@Robin-Shopify any updates on this issue? If you could share a timeline for a potential fix that would be much appreciated.

We already have this component live in production and are getting more tickets about this issue. Thanks :folded_hands:

@michael we’re still working on a proper, long-term fix, but it should already be working for you at the moment. Is this not the case? I mean the version that uses fetch.

@Robin-Shopify the Fetch version does not work in Safari, we always get this error:

Not allowed to load local resource: blob:null/994fe27e-2379-495d-a58c-750dbc1245cb

Cannot load blob:null/994fe27e-2379-495d-a58c-750dbc1245cb due to access control checks

Does fetch work on your end? In the first post i’ve provided the simplest version to reproduce the issue, if you can share a working version that works on Safari that would be much appreciated.

const formData = new FormData();
formData.append("file", new File([file], file.name, { type: file.type }));


await fetch("https://your-url.com/upload", {
  method: "POST",
  body: formData,
});

This code works fine for me currently. In Safari and Chrome.

That’s not working for you?

@Robin-Shopify i’ve now tested my previous code and now it suddenly works again in Safari. I’m 100% sure that the code previously always resulted in the “Not allowed to load local resource” error. So something must have changed, maybe the file origin was updated so that the sandboxed extension has more permissions to access the file.

Code like this previously threw an error in Safari:

const buffer   = await file.arrayBuffer();

This now works as well, so the issue has been resolved :+1:

Thanks again for looking into this :clap:

I just deployed the extension and tested it again, it turns out the issue is still there when the Dropzone component is used on the purchase.thank-you.block.render target.

I get this error when fetch is trying to access the file:

I can reproduce it in dev mode as well. @Robin-Shopify please try to reproduce it on the thank you page target :folded_hands:

@Robin-Shopify were you able to test my example code in the initial post on the thank-you page target?

Update: based on recent changes on the Shopify side, it now seems to work on the Thank you page :clap:

In my test, it was required to use the FormData object and passing the file reference without wrapping it in new File:

const formData = new FormData();
formData.append('file', file));

  try {
     const url = 'https://shopify.com/upload';

     const uploadResponse = await fetch(url, {
           method: 'POST',
           body: formData
    });

    const uploadData = await uploadResponse.json();

    console.debug('Upload response:', uploadResponse);

    setImage(uploadData.url);

  } catch (error) {
        console.error('Upload failed:', error);
  }