Skip to content

[add] Image source headers handling (Single Component) #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/react-native-web-examples/pages/image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ const dataBase64Svg =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0nMjAwJyBoZWlnaHQ9JzIwMCcgZmlsbD0iIzAwMDAwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwMCAxMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGQ9Ik0yNS44NjcsNDguODUzQzMyLjgwNiw1MC4xNzYsNDYuNDYsNTIuNSw2MS4yMTUsNTIuNWgwLjAwNWM5LjcxLDAsMTguNDAxLTEuMDU3LDI1LjkzOC0yLjkxMyAgIGMwLjE1OS0wLjA0NiwwLjM1LTAuMTM1LDAuNTY1LTAuMTg3YzAuMjgyLTAuMDcyLDAuNTY1LTAuMTY0LDAuODQ0LTAuMjM4YzMuMTg0LTAuOTY0LDIuNTc3LTMuMDUxLDIuMTk5LTMuODUyICAgYy00LjE2Ni03LjcxOS0xNS4wODYtMjMuNDE1LTM1LjAyOC0yMy40MTVjLTIyLjE2OSwwLTMwLjI2MiwxMC42MzUtMzMuMTQsMTkuNTg5QzIyLjU0NSw0Mi4zMzMsMjIuNDA3LDQ3LjEzNSwyNS44NjcsNDguODUzeiAgICBNMjguNjc2LDM4LjAzMmMwLjAxMy0wLjAzNiwwLjYxNC0xLjYyNiwxLjkyMy0xLjAwOGMxLjEzMywwLjUzNSwwLjk2MSwxLjU2MywwLjg4NywxLjg1Yy0wLjAwNywwLjAyNC0wLjAxNCwwLjA0OC0wLjAyMSwwLjA3MyAgIGMwLDAuMDAxLTAuMDAxLDAuMDA0LTAuMDAxLDAuMDA0bDAsMGMtMC4yNDksMC45MjktMC40MDQsMi4wODYtMC4wMTcsMi44NmMwLjE2LDAuMzE5LDAuNDkyLDAuNzY4LDEuNTQyLDAuOTg3bDAuMzY2LDAuMDc3ICAgYzIwLjgxNiw0LjM2LDM2LDIuOTMzLDQ1LjY3OCwwLjYyNmwtMC4wMDQsMC4wMDJjMCwwLDAuMDA1LTAuMDAyLDAuMDA3LTAuMDAzYzAuMjEyLTAuMDUsMC40MjEtMC4xMDEsMC42MjgtMC4xNTIgICBjMC41MDktMC4wNSwxLjE3MywwLjA3OCwxLjM5OSwxYzAuMzUxLDEuNDI0LTAuOTczLDEuODk1LTEuMjE3LDEuOTY5Yy01LjMyNSwxLjI3OS0xMi4yNjYsMi4zMDYtMjAuODM1LDIuMzA3ICAgYy03LjUwNSwwLTE2LjI1NS0wLjc4Ny0yNi4yNTctMi44ODJsLTAuMzY0LTAuMDc3Yy0yLjEyLTAuNDQyLTMuMTExLTEuNjMzLTMuNTY5LTIuNTU1QzI3Ljk4NSw0MS40MjEsMjguMjgxLDM5LjQxNiwyOC42NzYsMzguMDMyICAgeiI+PC9wYXRoPjxjaXJjbGUgY3g9IjEwLjQ5MyIgY3k9IjIzLjQ1NSIgcj0iMC42MTkiPjwvY2lyY2xlPjxwYXRoIGQ9Ik0yLjA4LDI4LjMwOGMwLjY3Ni0wLjE3OCwwLjk4My0wLjM1MiwxLjE3NC0wLjVDNC42OSwyNi42OSw2LjUsMjcuNDgzLDcuNSwyOC4zNTd2MC4wMDJjMCwwLDEuNzExLDEuMjM1LDAuNzM3LDIuMjAyICAgYy0wLjk3NCwwLjk2NS0yLjMxOSwwLjAwNi0yLjMxOSwwLjAwNmwwLjAzNSwwLjAxNmMtMC4zMjctMC4yMDMtMC42LTAuNTYxLTAuNzgtMC41ODRjLTAuMzcsMC4yNi0wLjg3NiwwLjUtMS40NzYsMC41SDMuNyAgIGMwLDAtMS4zNDUsMC43MDksMC4xNzgsMS42NTJjMC4wMDEsMC4wMDEsMC4wMDIsMC4wNzIsMC4wMDQsMC4wNzNjMy45MzksMi4zNDIsOC4yNzEsNS43MDEsOC4yNzEsOC44OCAgIGMwLDAuNjkxLDAuMiwxNy4wNDIsMTcuNjI2LDI0LjczOWwwLjk2NywwLjQ0MmwtMC4xLDEuMDU5Yy0wLjQyMSw0LjM5LDEuMTQ1LDEwLjE5MSwxMC45OTMsMTIuODg4bDAuMTEzLDAuMDM4ICAgYzAuMDY3LDAuMDIzLDYuNzMyLDIuNDI5LDEwLjkwNywyLjQyOWMxLjU4NCwwLDIuMTU1LTAuMzUyLDIuMjQzLTAuNTYxYzAuMDg1LTAuMjAyLDAuNjEyLTIuMTY0LTYuMzMyLTkuMzg3bDAuMDAyLTAuMTgzICAgYzAsMC0yLjQ3Ny0zLjA3LDEuNTMzLTMuMDdjMC4wMSwwLDAuMDE5LDAsMC4wMjksMGMxLjI4NSwwLDIuNjA4LDAuMjE1LDMuOTgsMC4xODRjNC43NzEtMC4xMTcsOS4zMTYtMC40MjUsMTMuNTA2LTEuMDk2ICAgbDAuNDc0LTAuMDI4bDAuNjY4LDAuMTU4YzkuNjUxLDQuOTQ4LDE2LjczOCw3LjcxNiwxOS43MzgsNy43MTZ2MC4wMDZjMCwwLDAuMTY0LDAuMDExLDAuMjMsMC4wMDQgICBjLTAuMTg5LTAuNzIzLTIuMjMtMi44LTcuMjMtOS4wNzl2MC4wMjFjMCwwLTEuNTEyLTEuNjU4LDAuNzk3LTIuNjUzYzAuMDYzLTAuMDI2LDAuMDA4LDAuMDIzLDAuMDYtMC4wMDEgICBjOC42MzktMy41MDksMTMuNTAxLTguMjA0LDE1LjQxMS0xMS43NzVjMS4xNDUtMi4xMjksMC4yMDYtMi43ODQtMC42NTktMi45NzZjLTAuMzE3LTAuMDM4LTAuNjM0LTAuMDYyLTAuOTEyLTAuMDYyICAgYy0wLjIwNSwwLTAuMzc5LDAuMDEtMC41MjgsMC4wMjdsLTMuMTQzLDEuMjE0QzgzLjczMiw1My45MjYsNzMuMjE4LDU1LjUsNjEuMjIsNTUuNWMtMC4wMDIsMC0wLjAwNSwwLTAuMDA1LDAgICBjLTE1LjEyOCwwLTI5LjEwMS0yLjQzMi0zNi4wODMtMy43NzFsLTAuMTczLTAuMTExbC0wLjE2LTAuMTI2Yy01Ljg1OC0yLjY4MS01LjEzNy0xMC4yMDItNS4xMDMtMTAuNTE5bDAuMDYtMC4zICAgYzAuODk1LTIuODM4LDIuNDY3LTYuMzUyLDUuMjEzLTkuNzE5Yy0xLjgwOC0xLjM2OS00LjU5LTQuMTg4LTQuNDMtOC40OTRjMC4wNDYtMS4yNDQtMC40ODYtMi41MDgtMS40OTgtMy41NTkgICBjLTEuNDk4LTEuNTU1LTMuNzg1LTIuNDQ2LTYuMjc0LTIuNDQ2Yy0xLjc3LDAtMy41NTMsMC40NDItNS4yOTMsMS4zMTRjLTQuMDYxLDIuMDM1LTQuODU1LDQuNzM2LTUuNjkyLDcuNTk2ICAgYy0wLjEzNiwwLjQ2OC0wLjI4NCwwLjkzOS0wLjQzOCwxLjQxYy0wLjAwNiwwLjAxOS0wLjAyMiwwLjAzNS0wLjAyOCwwLjA1NkMwLjgzMywyOC40MjMsMS42OTEsMjguMzksMi4wOCwyOC4zMDh6IE0xMC40OTMsMTkuOTA4ICAgYzEuOTU2LDAsMy41NDgsMS41OTEsMy41NDgsMy41NDdjMCwxLjk1Ny0xLjU5MiwzLjU0OC0zLjU0OCwzLjU0OGMtMS45NTcsMC0zLjU0OC0xLjU5Mi0zLjU0OC0zLjU0OCAgIEM2Ljk0NCwyMS40OTksOC41MzYsMTkuOTA4LDEwLjQ5MywxOS45MDh6Ij48L3BhdGg+PC9nPjwvc3ZnPg==';
const dataSvg =
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>';
const sourceWithHeaders = {
uri: placeholder,
headers: {
'x-token': '0012345'
}
};
const sourceWithHeadersAndRedirect = {
uri: source,
headers: {
'x-token': '0012345'
}
};

function Divider() {
return <View style={styles.divider} />;
Expand Down Expand Up @@ -114,6 +126,17 @@ export default function ImagePage() {
/>
</View>
</View>
<Divider />
<View style={styles.row}>
<View style={styles.column}>
<Text style={[styles.text]}>With Headers</Text>
<Image source={sourceWithHeaders} style={styles.image} />
</View>
<View style={styles.column}>
<Text style={[styles.text]}>Headers & Redirect</Text>
<Image source={sourceWithHeadersAndRedirect} style={styles.image} />
</View>
</View>
</Example>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,14 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`]
>
<div
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
style="filter: url(#tint-57);"
style="filter: url(#tint-56);"
/>
<svg
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
>
<defs>
<filter
id="tint-57"
id="tint-56"
>
<feflood
flood-color="blue"
Expand Down Expand Up @@ -378,7 +378,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
>
<div
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-56);"
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-55);"
/>
<img
alt=""
Expand All @@ -391,7 +391,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
>
<defs>
<filter
id="tint-56"
id="tint-55"
>
<feflood
flood-color="red"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const originalImage = window.Image;
describe('components/Image', () => {
beforeEach(() => {
ImageUriCache._entries = {};
ImageLoader.load = jest.fn((source, onLoad, onError) => {
onLoad();
return { source, cancel: jest.fn() };
});
window.Image = jest.fn(() => ({}));
});

Expand Down Expand Up @@ -107,9 +111,6 @@ describe('components/Image', () => {
describe('prop "onLoad"', () => {
test('is called after image is loaded from network', () => {
jest.useFakeTimers();
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
onLoad();
});
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
Expand All @@ -127,9 +128,6 @@ describe('components/Image', () => {

test('is called after image is loaded from cache', () => {
jest.useFakeTimers();
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
onLoad();
});
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
Expand Down Expand Up @@ -261,11 +259,6 @@ describe('components/Image', () => {

test('is set immediately if the image was preloaded', () => {
const uri = 'https://yahoo.com/favicon.ico';
ImageLoader.load = jest
.fn()
.mockImplementationOnce((_, onLoad, onError) => {
onLoad();
});
return Image.prefetch(uri).then(() => {
const source = { uri };
const { container } = render(<Image source={source} />, {
Expand Down Expand Up @@ -311,8 +304,9 @@ describe('components/Image', () => {
let loadCallback;
ImageLoader.load = jest
.fn()
.mockImplementationOnce((_, onLoad, onError) => {
.mockImplementationOnce((source, onLoad, onError) => {
loadCallback = onLoad;
return { source, cancel: jest.fn() };
});
const { container } = render(
<Image defaultSource={{ uri: defaultUri }} source={{ uri }} />
Expand Down
50 changes: 36 additions & 14 deletions packages/react-native-web/src/exports/Image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @flow
*/

import type { LoadRequest } from '../../modules/ImageLoader';
import type { ImageProps } from './types';

import * as React from 'react';
Expand Down Expand Up @@ -146,6 +147,15 @@ function resolveAssetUri(source): ?string {
return uri;
}

function hasSourceDiff(a, b) {
if (a.uri !== b.uri) return true;

const headersA = a.headers || {};
const headersB = b.headers || {};

return JSON.stringify(headersA) !== JSON.stringify(headersB);
}

interface ImageStatics {
getSize: (
uri: string,
Expand Down Expand Up @@ -201,7 +211,11 @@ const Image: React.AbstractComponent<
const hasTextAncestor = React.useContext(TextAncestorContext);
const hiddenImageRef = React.useRef(null);
const filterRef = React.useRef(_filterId++);
const requestRef = React.useRef(null);
const requestRef = React.useRef<LoadRequest>({
source: { uri: '' },
cancel: () => {},
requestId: -1
});
const shouldDisplaySource =
state === LOADED || (state === LOADING && defaultSource == null);
const [flatStyle, _resizeMode, filter, tintColor] = getFlatStyle(
Expand Down Expand Up @@ -258,16 +272,27 @@ const Image: React.AbstractComponent<
// Image loading
const uri = resolveAssetUri(source);
React.useEffect(() => {
abortPendingRequest();

if (uri != null) {
const nextSource = { uri, headers: undefined };
if (source && source.headers != null) {
nextSource.headers = source.headers;
}

if (!hasSourceDiff(nextSource, requestRef.current.source)) return;

requestRef.current.cancel();
updateState(LOADING);
if (onLoadStart) {
onLoadStart();
}

requestRef.current = ImageLoader.load(
uri,
const makeRequest = nextSource.headers
? ImageLoader.loadWithHeaders
: ImageLoader.load;

requestRef.current = makeRequest(
// $FlowFixMe
nextSource,
function load(e) {
updateState(LOADED);
if (onLoad) {
Expand All @@ -291,17 +316,14 @@ const Image: React.AbstractComponent<
}
}
);
} else {
requestRef.current.source = { uri: '' };
requestRef.current.cancel();
}
}, [uri, updateState, onError, onLoad, onLoadEnd, onLoadStart, source]);

function abortPendingRequest() {
if (requestRef.current != null) {
ImageLoader.abort(requestRef.current);
requestRef.current = null;
}
}

return abortPendingRequest;
}, [uri, requestRef, updateState, onError, onLoad, onLoadEnd, onLoadStart]);
// Run the cleanup function on unmount
React.useEffect(() => requestRef.current.cancel, []);

return (
<View
Expand Down
82 changes: 75 additions & 7 deletions packages/react-native-web/src/modules/ImageLoader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const ImageLoader = {
) {
let complete = false;
const interval = setInterval(callback, 16);
const requestId = ImageLoader.load(uri, callback, errorCallback);
const { requestId } = ImageLoader.load({ uri }, callback, errorCallback);

function callback() {
const image = requests[`${requestId}`];
Expand Down Expand Up @@ -118,13 +118,26 @@ const ImageLoader = {
has(uri: string): boolean {
return ImageUriCache.has(uri);
},
load(uri: string, onLoad: Function, onError: Function): number {
load(
source: { uri: string },
onLoad: Function,
onError: Function
): LoadRequest {
id += 1;
const image = new window.Image();
image.onerror = onError;
image.onload = (e) => {
image.onload = (nativeEvent) => {
// avoid blocking the main thread
const onDecode = () => onLoad({ nativeEvent: e });
const onDecode = () => {
// Append `source` to match RN's ImageLoadEvent interface
nativeEvent.source = {
uri: image.src,
width: image.naturalWidth,
height: image.naturalHeight
};

onLoad({ nativeEvent });
};
if (typeof image.decode === 'function') {
// Safari currently throws exceptions when decoding svgs.
// We want to catch that error and allow the load handler
Expand All @@ -134,14 +147,58 @@ const ImageLoader = {
setTimeout(onDecode, 0);
}
};
image.src = uri;
image.src = source.uri;
requests[`${id}`] = image;
return id;

return {
source,
cancel: () => ImageLoader.abort(id),
requestId: id
};
},
loadWithHeaders(
source: ImageSource,
onLoad: Function,
onError: Function
): LoadRequest {
let loadRequest: LoadRequest;
let uri: string;
const abortCtrl = new AbortController();
const request = new Request(source.uri, {
headers: source.headers,
signal: abortCtrl.signal
});
request.headers.append('accept', 'image/*');

fetch(request)
.then((response) => response.blob())
.then((blob) => {
uri = URL.createObjectURL(blob);
loadRequest = ImageLoader.load({ uri }, onLoad, onError);
})
.catch((error) => {
if (error.name !== 'AbortError' && onError) {
onError(error);
}
});

return {
source,
get requestId() {
if (loadRequest) return loadRequest.requestId;
return -1;
},
cancel: () => {
abortCtrl.abort();
if (loadRequest) loadRequest.cancel();
URL.revokeObjectURL(uri);
}
};
},
prefetch(uri: string): Promise<void> {
return new Promise((resolve, reject) => {
ImageLoader.load(
uri,
{ uri },
() => {
// Add the uri to the cache so it can be immediately displayed when used
// but also immediately remove it to correctly reflect that it has no active references
Expand All @@ -164,4 +221,15 @@ const ImageLoader = {
}
};

export type LoadRequest = {|
requestId: number,
source: ImageSource | { uri: string },
cancel: Function
|};

type ImageSource = {
uri: string,
headers: { [key: string]: string }
};

export default ImageLoader;