├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── index.d.ts
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | !**/*.xcodeproj
7 | !**/*.pbxproj
8 | !**/*.xcworkspacedata
9 | !**/*.xcsettings
10 | !**/*.xcscheme
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata
20 | *.xccheckout
21 | *.moved-aside
22 | DerivedData
23 | *.hmap
24 | *.ipa
25 | *.xcuserstate
26 | project.xcworkspace
27 |
28 | # Android/IntelliJ
29 | #
30 | build/
31 | .idea
32 | .gradle
33 | local.properties
34 |
35 | # node.js
36 | #
37 | node_modules/
38 | npm-debug.log
39 | yarn.lock
40 | yarn-error.log
41 |
42 | # others
43 | #
44 | .buckconfig
45 | .flowconfig
46 | .gitattributes
47 | .watchmanconfig
48 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | !**/*.xcodeproj
7 | !**/*.pbxproj
8 | !**/*.xcworkspacedata
9 | !**/*.xcsettings
10 | !**/*.xcscheme
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata
20 | *.xccheckout
21 | *.moved-aside
22 | DerivedData
23 | *.hmap
24 | *.ipa
25 | *.xcuserstate
26 | project.xcworkspace
27 |
28 | # Android/IJ
29 | #
30 | /build/
31 | .idea
32 | .gradle
33 | local.properties
34 |
35 | # node.js
36 | #
37 | node_modules/
38 | npm-debug.log
39 | yarn.lock
40 | yarn-error.log
41 |
42 | # others
43 | #
44 | .npmignore
45 | .buckconfig
46 | .flowconfig
47 | .gitattributes
48 | .gitignore
49 | .git
50 | .watchmanconfig
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Wonday (@wonday.org)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-image-cache-wrapper
2 | [](https://www.npmjs.com/package/react-native-image-cache-wrapper)
3 |
4 | The best react native image cache wrapper.
5 |
6 | ### Feature
7 |
8 | * the same usage with `````` and ``````
9 | * can set activity indicator
10 | * cache images with expiration
11 | * clear one cache or all cache files
12 | * support getSize() and prefetch()
13 | * support cache base64 data to local
14 |
15 | ### Installation
16 | We use [`rn-fetch-blob`](https://github.com/joltup/rn-fetch-blob) to handle file system access in this package,
17 | So you should install react-native-image-cache-wrapper and rn-fetch-blob both.
18 |
19 | ```bash
20 | npm install react-native-image-cache-wrapper --save
21 |
22 | npm install rn-fetch-blob --save
23 | react-native link rn-fetch-blob
24 | ```
25 | or use yarn
26 |
27 | ```
28 | yarn add react-native-image-cache-wrapper
29 | yarn add rn-fetch-blob
30 | ```
31 | *Notice: if you use RN 0.60+, please use rn-fetch-blob v0.10.16*
32 |
33 |
34 | ### ChangeLog
35 |
36 | v1.0.7
37 |
38 | 1. fix Strange error
39 |
40 | v1.0.6
41 |
42 | 1. add static method ```CachedImage.isUrlCached(url,success=(cachFile)=>void,fail=(error)=>void))```
43 | 2. add static method ```CachedImage.getCacheFilename(url)```
44 | 3. add static property ```CachedImage.cacheDir```, user can use to set customized cacheDir
45 |
46 | v1.0.5
47 |
48 | 1. fix for RN 0.59
49 |
50 | v1.0.4
51 |
52 | 1. fix Content-Length check
53 |
54 | v1.0.3
55 |
56 | 1. fix file successfully download check
57 |
58 | v1.0.2
59 |
60 | 1. use rn-fetch-blob instead of react-native-fetch-blob
61 |
62 | v1.0.0
63 |
64 | 1. initial release
65 |
66 | [[more]](https://github.com/wonday/react-native-image-cache-wrapper/releases)
67 |
68 |
69 | ### Configuration
70 |
71 | | Property | Type | Default | Description | FirstRelease |
72 | | ------------- |:-------------:|:----------------:| ------------------- | ------------ |
73 | | `````` or `````` properties | | | same with `````` and `````` | 1.0 |
74 | | expiration | number | 604800 | expiration seconds (0:no expiration, default cache a week) | 1.0 |
75 | | activityIndicator | Component | null | when loading show it as an indicator, you can use your component| 1.0 |
76 |
77 | ### Usage
78 |
79 | ```
80 | import CachedImage from 'react-native-image-cache-wrapper';
81 |
82 | render()
83 | {
84 | return (
85 |
86 |
87 |
88 |
89 | This is example with image background.
90 |
91 |
92 | );
93 | }
94 | ```
95 |
96 | ### Static Function
97 |
98 | **CachedImage.getSize(url, success=(width,height)=>void,fail=(error)=>void)**
99 |
100 | Get the image size, if no cache, will cache it.
101 |
102 | Example:
103 | ```
104 | import CachedImage from 'react-native-image-cache-wrapper';
105 |
106 | CachedImage.getSize("https://assets-cdn.github.com/images/modules/logos_page/Octocat.png",
107 | (width,height)=>{
108 | console.log("width:"+width+" height:"+height);
109 | },(error)=>{
110 | console.log("error:"+error);
111 | });
112 | ```
113 |
114 | **CachedImage.prefetch(url,expiration=0,success=(cachFile)=>void,fail=(error)=>void)**
115 |
116 | prefetch an image and cache it.
117 |
118 | Example:
119 | ```
120 | import CachedImage from 'react-native-image-cache-wrapper';
121 |
122 | // prefetch and cache image 3600 seconds
123 | CachedImage.prefetch("https://assets-cdn.github.com/images/modules/logos_page/Octocat.png", 3600,
124 | (cacheFile)=>{
125 | console.log("cache filename:"+cacheFile);
126 | },(error)=>{
127 | console.log("error:"+error);
128 | });
129 | ```
130 |
131 | **CachedImage.deleteCache(url)**
132 |
133 | delete a cache file.
134 |
135 | Example:
136 | ```
137 | import CachedImage from 'react-native-image-cache-wrapper';
138 |
139 | // prefetch and cache image 3600 seconds
140 | CachedImage.deleteCache("https://assets-cdn.github.com/images/modules/logos_page/Octocat.png");
141 | ```
142 |
143 | **CachedImage.clearCache()**
144 |
145 | clear all cache.
146 |
147 | Example:
148 | ```
149 | import CachedImage from 'react-native-image-cache-wrapper';
150 |
151 | // prefetch and cache image 3600 seconds
152 | CachedImage.clearCache();
153 | ```
154 |
155 | **CachedImage.isUrlCached(url,success=(cachFile)=>void,fail=(error)=>void))**
156 |
157 | check if a url is cached.
158 |
159 | Example:
160 | ```
161 | import CachedImage from 'react-native-image-cache-wrapper';
162 |
163 | // check if a url is cached.
164 | CachedImage.isUrlCached(url,(exists)=>{
165 | alert(exists);
166 | });
167 | ```
168 |
169 | **CachedImage.getCacheFilename(url)**
170 |
171 | make a cache filename.
172 |
173 | Example:
174 | ```
175 | import CachedImage from 'react-native-image-cache-wrapper';
176 |
177 | // check if a url is cached.
178 | let cachedFilename = CachedImage.getCacheFilename(url);
179 | ```
180 |
181 | **CachedImage.cacheDir**
182 |
183 | the property that can get/set cacheDir
184 |
185 | Example:
186 | ```
187 | import CachedImage from 'react-native-image-cache-wrapper';
188 |
189 | // check if a url is cached.
190 | CachedImage.cacheDir = RNFetchBlob.fs.dirs.CacheDir + "/CachedImage/";
191 | ```
192 |
193 |
194 |
195 |
196 |
197 |
198 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018-present, Wonday (@wonday.org)
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the MIT-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | */
8 |
9 | import * as React from 'react';
10 | import * as ReactNative from 'react-native';
11 |
12 | interface Props {
13 | source: any,
14 | defaultSource?: any,
15 | style?: any,
16 | resizeMode?: string,
17 | expiration?: number,
18 | activityIndicator?: any,
19 | onLoad?: () => void,
20 | onError?: (error: object) => void,
21 | }
22 |
23 | declare class CachedImage extends React.Component {
24 | }
25 |
26 | export default CachedImage;
27 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018-present, Wonday (@wonday.org)
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the MIT-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | */
8 | 'use strict';
9 |
10 | import {
11 | Image,
12 | ImageBackground,
13 | Platform,
14 | View
15 | } from 'react-native';
16 | import React, {Component} from 'react';
17 |
18 | import RNFetchBlob from 'rn-fetch-blob';
19 |
20 | const SHA1 = require('crypto-js/sha1');
21 |
22 | const defaultImageTypes = ['png', 'jpeg', 'jpg', 'gif', 'bmp', 'tiff', 'tif'];
23 |
24 | export default class CachedImage extends Component {
25 |
26 | static defaultProps = {
27 | expiration: 86400 * 7, // default cache a week
28 | activityIndicator: null, // default not show an activity indicator
29 | };
30 |
31 | static cacheDir = RNFetchBlob.fs.dirs.CacheDir + "/CachedImage/";
32 |
33 | static sameURL = []
34 | /**
35 | * delete a cache file
36 | * @param url
37 | */
38 | static deleteCache = url => {
39 | const cacheFile = _getCacheFilename(url);
40 | return _unlinkFile(cacheFile);
41 | };
42 |
43 | /**
44 | * clear all cache files
45 | */
46 | static clearCache = () => _unlinkFile(CachedImage.cacheDir);
47 |
48 | /**
49 | * check if a url is cached
50 | */
51 | static isUrlCached = (url: string, success: Function, failure: Function) => {
52 | const cacheFile = _getCacheFilename(url);
53 | RNFetchBlob.fs.exists(cacheFile)
54 | .then((exists) => {
55 | success && success(exists);
56 | })
57 | .catch((error) => {
58 | failure && failure(error);
59 | });
60 | };
61 |
62 | /**
63 | * make a cache filename
64 | * @param url
65 | * @returns {string}
66 | */
67 | static getCacheFilename = (url) => {
68 | return _getCacheFilename(url);
69 | }
70 |
71 | /**
72 | * Same as ReactNaive.Image.getSize only it will not download the image if it has a cached version
73 | * @param url
74 | * @param success callback (width,height)=>{}
75 | * @param failure callback (error:string)=>{}
76 | */
77 | static getSize = (url: string, success: Function, failure: Function) => {
78 |
79 | CachedImage.prefetch(url, 0,
80 | (cacheFile) => {
81 | if (Platform.OS === 'android') {
82 | url = "file://" + cacheFile;
83 | } else {
84 | url = cacheFile;
85 | }
86 | Image.getSize(url, success, failure);
87 | },
88 | (error) => {
89 | Image.getSize(url, success, failure);
90 | });
91 |
92 | };
93 |
94 | /**
95 | * prefech an image
96 | *
97 | * @param url
98 | * @param expiration if zero or not set, no expiration
99 | * @param success callback (cacheFile:string)=>{}
100 | * @param failure callback (error:string)=>{}
101 | */
102 | static prefetch = (url: string, expiration: number, success: Function, failure: Function) => {
103 |
104 | // source invalidate
105 | if (!url || url.toString() !== url) {
106 | failure && failure("no url.");
107 | return;
108 | }
109 |
110 | const cacheFile = _getCacheFilename(url);
111 | if(CachedImage.sameURL.includes(cacheFile)){
112 |
113 | success && success(cacheFile);
114 | return
115 | }
116 | CachedImage.sameURL.push(cacheFile)
117 |
118 | RNFetchBlob.fs.stat(cacheFile)
119 | .then((stats) => {
120 | // if exist and not expired then use it.
121 | if (!Boolean(expiration) || (expiration * 1000 + stats.lastModified) > (new Date().getTime())) {
122 | success && success(cacheFile);
123 | } else {
124 | _saveCacheFile(url, success, failure);
125 | }
126 | })
127 | .catch((error) => {
128 | // not exist
129 | // success && success(cacheFile)
130 |
131 | _saveCacheFile(url, success, failure);
132 | });
133 | };
134 |
135 | constructor(props) {
136 |
137 | super(props);
138 | this.state = {
139 | source: null,
140 | };
141 |
142 | this._useDefaultSource = false;
143 | this._downloading = false;
144 | this._mounted = false;
145 | }
146 |
147 | componentDidMount() {
148 | this._mounted = true;
149 | }
150 |
151 | componentWillReceiveProps(nextProps) {
152 | const { source } = nextProps;
153 |
154 | if (source !== this.props.source) { this.setState({...this.props, source: source}) }
155 | }
156 |
157 | componentWillUnmount() {
158 | this._mounted = false;
159 | }
160 |
161 | render() {
162 |
163 | if (this.props.source && this.props.source.uri) {
164 | if (!this.state.source && !this._downloading) {
165 | this._downloading = true;
166 | CachedImage.prefetch(this.props.source.uri,
167 | this.props.expiration,
168 | (cacheFile) => {
169 | setTimeout(() => {
170 | if (this._mounted) {
171 | this.setState({source: {uri: "file://" + cacheFile}});
172 | }
173 | this._downloading = false;
174 | }, 0);
175 | }, (error) => {
176 | // cache failed use original source
177 | if (this._mounted) {
178 | setTimeout(() => {
179 | this.setState({source: { uri: this.props.source.uri}});
180 | }, 0);
181 | }
182 | this._downloading = false;
183 | });
184 | }
185 | } else {
186 | this.state.source = this.props.source;
187 | }
188 |
189 | if (this.state.source) {
190 |
191 | const renderImage = (props, children) => (children != null ?
192 | {children} :
193 | );
194 |
195 | const result = renderImage({
196 | ...this.props,
197 | source: this.state.source,
198 | onError: (error) => {
199 | // error happened, delete cache
200 | if (this.props.source && this.props.source.uri) {
201 | CachedImage.deleteCache(this.props.source.uri);
202 | }
203 | if (this.props.onError) {
204 | this.props.onError(error);
205 | } else {
206 | if (!this._useDefaultSource && this.props.defaultSource) {
207 | this._useDefaultSource = true;
208 | setTimeout(() => {
209 | if(this.props.source && this.props.source.uri ){
210 | this.setState({source: this.props.source});
211 | }
212 | else
213 | this.setState({source: this.props.defaultSource});
214 | }, 0);
215 | }
216 | }
217 | }
218 | }, this.props.children);
219 |
220 | return (result);
221 | } else {
222 | return (
223 |
227 | {this.props.activityIndicator}
228 | );
229 | }
230 | }
231 | }
232 |
233 | async function _unlinkFile(file) {
234 | try {
235 | return await RNFetchBlob.fs.unlink(file);
236 | } catch (e) {
237 | }
238 | }
239 |
240 | /**
241 | * make a cache filename
242 | * @param url
243 | * @returns {string}
244 | */
245 | function _getCacheFilename(url) {
246 |
247 | if (!url || url.toString() !== url) return "";
248 |
249 | let ext = url.replace(/.+\./, "").toLowerCase();
250 | if (defaultImageTypes.indexOf(ext) === -1) ext = "png";
251 | let hash = SHA1(url);
252 | return CachedImage.cacheDir + hash + "." + ext;
253 | }
254 |
255 | /**
256 | * save a url or base64 data to local file
257 | *
258 | *
259 | * @param url
260 | * @param success callback (cacheFile:string)=>{}
261 | * @param failure callback (error:string)=>{}
262 | */
263 | async function _saveCacheFile(url: string, success: Function, failure: Function) {
264 |
265 | try {
266 | const isNetwork = !!(url && url.match(/^https?:\/\//));
267 | const isBase64 = !!(url && url.match(/^data:/));
268 | const cacheFile = _getCacheFilename(url);
269 |
270 | if (isNetwork) {
271 | const tempCacheFile = cacheFile + '.tmp';
272 | _unlinkFile(tempCacheFile);
273 | RNFetchBlob.config({
274 | // response data will be saved to this path if it has access right.
275 | path: tempCacheFile,
276 | })
277 | .fetch(
278 | 'GET',
279 | url
280 | )
281 | .then(async (res) => {
282 |
283 | if (res && res.respInfo && res.respInfo.headers && !res.respInfo.headers["Content-Encoding"] && !res.respInfo.headers["Transfer-Encoding"] && res.respInfo.headers["Content-Length"]) {
284 | const expectedContentLength = res.respInfo.headers["Content-Length"];
285 | let actualContentLength;
286 |
287 | try {
288 | const fileStats = await RNFetchBlob.fs.stat(res.path());
289 |
290 | if (!fileStats || !fileStats.size) {
291 | throw new Error("FileNotFound:"+url);
292 | }
293 |
294 | actualContentLength = fileStats.size;
295 | } catch (error) {
296 | throw new Error("DownloadFailed:"+url);
297 | }
298 |
299 | if (expectedContentLength != actualContentLength) {
300 | throw new Error("DownloadFailed:"+url);
301 | }
302 | }
303 |
304 | _unlinkFile(cacheFile);
305 | RNFetchBlob.fs
306 | .mv(tempCacheFile, cacheFile)
307 | .then(() => {
308 | success && success(cacheFile);
309 | })
310 | .catch(async (error) => {
311 | throw error;
312 | });
313 | })
314 | .catch(async (error) => {
315 | _unlinkFile(tempCacheFile);
316 | _unlinkFile(cacheFile);
317 | failure && failure(error);
318 | });
319 | } else if (isBase64) {
320 | let data = url.replace(/data:/i, '');
321 | RNFetchBlob.fs
322 | .writeFile(cacheFile, data, 'base64')
323 | .then(() => {
324 | success && success(cacheFile);
325 | })
326 | .catch(async (error) => {
327 | _unlinkFile(cacheFile);
328 | failure && failure(error);
329 | });
330 | } else {
331 | failure && failure(new Error("NotSupportedUrl"));
332 | }
333 | } catch (error) {
334 | failure && failure(error);
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-image-cache-wrapper",
3 | "version": "1.0.7",
4 | "description": "The best react native image cache wrapper.",
5 | "main": "index.js",
6 | "typings": "./index.d.ts",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/wonday/react-native-image-cache-wrapper.git"
10 | },
11 | "keywords": [
12 | "Cache",
13 | "Cached Image",
14 | "Image Cache",
15 | "Image",
16 | "ImageBackground",
17 | "react-component",
18 | "react-native",
19 | "android",
20 | "ios"
21 | ],
22 | "author": {
23 | "name": "Wonday",
24 | "url": "https://github.com/wonday"
25 | },
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/wonday/react-native-image-cache-wrapper/issues"
29 | },
30 | "dependencies": {
31 | "crypto-js": "^3.1.9-1"
32 | },
33 | "peerDependencies": {
34 | "rn-fetch-blob": "^0.10.12"
35 | }
36 | }
--------------------------------------------------------------------------------