├── .gitignore ├── LICENSE ├── README.md ├── core ├── auth.js ├── conf.js ├── imageOps.js ├── rpc.js └── rs.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 buhe 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 Qiniu SDK 2 | 3 | 纯JavaScript实现的Qiniu SDK, 4 | 5 | ##安装 6 | npm i react-native-qiniu --save 7 | 8 | ##使用方法 9 | 10 | ```javascript 11 | import Qiniu,{Auth,ImgOps,Conf,Rs,Rpc} from 'react-native-qiniu'; 12 | Conf.ACCESS_KEY = 13 | Conf.SECRET_KEY = 14 | //强烈不建议在客户端保存 AK 和 SK ,反编译后 hacker 获取到可以对你的资源为所欲为,建议通过安全渠道从服务器端获取。 15 | 16 | //upload file to Qiniu 17 | var putPolicy = new Auth.PutPolicy2( 18 | {scope: ":"} 19 | ); 20 | var uptoken = putPolicy.token(); 21 | let formInput = { 22 | key : "", 23 | // formInput对象如何配置请参考七牛官方文档“直传文件”一节 24 | } 25 | Rpc.uploadFile(, uptoken, formInput); 26 | 27 | //download private file 28 | var getPolicy = new Auth.GetPolicy(); 29 | let url = getPolicy.makeRequest('http://7xp19y.com2.z0.glb.qiniucdn.com/5.jpg'); 30 | //fetch from this url 31 | 32 | //image sync operation 33 | var imgInfo = new ImgOps.ImageView(1,100,200); 34 | let url = imgInfo.makeRequest('http://7xoaqn.com2.z0.glb.qiniucdn.com/16704/6806d20a359f43c88f1cb3c59980e5ef'); 35 | //fetch from this url 36 | 37 | //image info 38 | var self = this; 39 | var imgInfo = new ImgOps.ImageInfo(); 40 | let url = imgInfo.makeRequest('http://7xoaqn.com2.z0.glb.qiniucdn.com/16704/6806d20a359f43c88f1cb3c59980e5ef'); 41 | fetch(url).then((response) => { 42 | return response.text(); 43 | }).then((responseText) => { 44 | self.setState({info: responseText}); 45 | }).catch((error) => { 46 | console.warn(error); 47 | }); 48 | 49 | //resource operation 50 | //stat info 51 | var self = this; 52 | Rs.stat(, response.text()) 54 | .then((responseText) => { 55 | self.setState({info: responseText}); 56 | }) 57 | .catch((error) => { 58 | console.warn(error); 59 | }); 60 | ``` 61 | 62 | ##进行中 63 | 64 | [RoadMap](https://github.com/qiniu/react-native-sdk/issues/1) 65 | 66 | ##Release Note 67 | ###0.1.0 68 | - [x] 上传文件 69 | - [x] 私有库中文件下载功能 70 | - [x] Image Ops 71 | - [x] 资源管理 72 | 73 | ###0.1.1 74 | - 重构 upload 方法 (CaveyChan) 75 | 76 | ###0.2.0 77 | - es6 style 78 | 79 | ###0.3.0 80 | - 增加 progress callback 81 | 82 | ##Contributor 83 | - CaveyChan 84 | - laukey 85 | - cdmalcl 86 | 87 | 88 | ##相关文章 89 | 90 | [React Native 文件上传 和 react-native-qiniu](https://medium.com/@bugu1986/react-native-%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0-%E5%92%8C-react-native-qiniu-4b3f7335090e#.ooux7ospa) 91 | -------------------------------------------------------------------------------- /core/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buhe on 16/4/12. 3 | */ 4 | import base64 from 'base-64'; 5 | import CryptoJS from "crypto-js"; 6 | import conf from "./conf.js"; 7 | import parse from 'url-parse'; 8 | 9 | function urlsafeBase64Encode(jsonFlags) { 10 | var encoded = base64.encode(jsonFlags); 11 | return base64ToUrlSafe(encoded); 12 | }; 13 | 14 | function base64ToUrlSafe(v) { 15 | return v.replace(/\//g, '_').replace(/\+/g, '-'); 16 | }; 17 | 18 | function hmacSha1(encodedFlags, secretKey) { 19 | var encoded = CryptoJS.HmacSHA1(encodedFlags, secretKey).toString(CryptoJS.enc.Base64); 20 | return encoded; 21 | }; 22 | 23 | function generateAccessToken(url, body) { 24 | var u = parse(url, true); 25 | 26 | var path = u.pathname; 27 | var access = path + '\n'; 28 | 29 | if (body) { 30 | access += body; 31 | } 32 | 33 | var digest = hmacSha1(access, conf.SECRET_KEY); 34 | var safeDigest = base64ToUrlSafe(digest); 35 | let token = 'QBox ' + conf.ACCESS_KEY + ':' + safeDigest; 36 | //console.log(token); 37 | return token; 38 | }; 39 | 40 | class PutPolicy2 { 41 | constructor(putPolicyObj) { 42 | if (typeof putPolicyObj !== 'object') { 43 | return false; 44 | } 45 | 46 | this.scope = putPolicyObj.scope || null; 47 | this.expires = putPolicyObj.expires || 3600; 48 | this.insertOnly = putPolicyObj.insertOnly || null; 49 | 50 | this.saveKey = putPolicyObj.saveKey || null; 51 | this.endUser = putPolicyObj.endUser || null; 52 | 53 | this.returnUrl = putPolicyObj.returnUrl || null; 54 | this.returnBody = putPolicyObj.returnBody || null; 55 | 56 | this.callbackUrl = putPolicyObj.callbackUrl || null; 57 | this.callbackHost = putPolicyObj.callbackHost || null; 58 | this.callbackBody = putPolicyObj.callbackBody || null; 59 | this.callbackBodyType = putPolicyObj.callbackBodyType || null; 60 | 61 | this.persistentOps = putPolicyObj.persistentOps || null; 62 | this.persistentNotifyUrl = putPolicyObj.persistentNotifyUrl || null; 63 | this.persistentPipeline = putPolicyObj.persistentPipeline || null; 64 | 65 | this.fsizeLimit = putPolicyObj.fsizeLimit || null; 66 | 67 | this.fsizeMin = putPolicyObj.fsizeMin || null; 68 | 69 | this.detectMime = putPolicyObj.detectMime || null; 70 | 71 | this.mimeLimit = putPolicyObj.mimeLimit || null; 72 | } 73 | 74 | token() { 75 | var flags = this.getFlags(); 76 | var encodedFlags = urlsafeBase64Encode(JSON.stringify(flags)); 77 | var encoded = hmacSha1(encodedFlags, conf.SECRET_KEY); 78 | var encodedSign = base64ToUrlSafe(encoded); 79 | var uploadToken = conf.ACCESS_KEY + ':' + encodedSign + ':' + encodedFlags; 80 | return uploadToken; 81 | } 82 | 83 | getFlags() { 84 | var flags = {}; 85 | var attrs = ['scope', 'insertOnly', 'saveKey', 'endUser', 'returnUrl', 'returnBody', 'callbackUrl', 'callbackHost', 'callbackBody', 'callbackBodyType', 'callbackFetchKey', 'persistentOps', 'persistentNotifyUrl', 'persistentPipeline', 'fsizeLimit', 'fsizeMin', 'detectMime', 'mimeLimit']; 86 | 87 | for (var i = attrs.length - 1; i >= 0; i--) { 88 | if (this[attrs[i]] !== null) { 89 | flags[attrs[i]] = this[attrs[i]]; 90 | } 91 | } 92 | 93 | flags['deadline'] = this.expires + Math.floor(Date.now() / 1000); 94 | 95 | return flags; 96 | } 97 | } 98 | 99 | class GetPolicy { 100 | constructor(expires) { 101 | this.expires = expires || 3600; 102 | } 103 | 104 | makeRequest(baseUrl) { 105 | var deadline = this.expires + Math.floor(Date.now() / 1000); 106 | 107 | if (baseUrl.indexOf('?') >= 0) { 108 | baseUrl += '&e='; 109 | } else { 110 | baseUrl += '?e='; 111 | } 112 | baseUrl += deadline; 113 | 114 | var signature = hmacSha1(baseUrl, conf.SECRET_KEY); 115 | var encodedSign = base64ToUrlSafe(signature); 116 | var downloadToken = conf.ACCESS_KEY + ':' + encodedSign; 117 | 118 | return baseUrl + '&token=' + downloadToken; 119 | } 120 | } 121 | 122 | export default {urlsafeBase64Encode,generateAccessToken,PutPolicy2,GetPolicy} 123 | -------------------------------------------------------------------------------- /core/conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buhe on 16/4/12. 3 | */ 4 | let ACCESS_KEY = ''; 5 | let SECRET_KEY = ''; 6 | 7 | let UP_HOST = 'http://upload.qiniu.com'; 8 | let RS_HOST = 'http://rs.qbox.me'; 9 | let RSF_HOST = 'http://rsf.qbox.me'; 10 | let API_HOST = 'http://api.qiniu.com'; 11 | let RPC_TIMEOUT = 3600000; // default rpc timeout: one hour 12 | 13 | export default { 14 | ACCESS_KEY,SECRET_KEY,UP_HOST,RS_HOST,RSF_HOST,API_HOST,RPC_TIMEOUT 15 | } -------------------------------------------------------------------------------- /core/imageOps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buhe on 16/4/12. 3 | */ 4 | import util from './auth'; 5 | import rpc from './rpc'; 6 | import conf from './conf'; 7 | 8 | class ImageView { 9 | constructor(mode, width, height, quality, format) { 10 | this.mode = mode || 1; 11 | this.width = width || 0; 12 | this.height = height || 0; 13 | this.quality = quality || 0; 14 | this.format = format || null; 15 | } 16 | 17 | makeRequest(url) { 18 | url += '?imageView2/' + this.mode; 19 | 20 | if (this.width > 0) { 21 | url += '/w/' + this.width; 22 | } 23 | 24 | if (this.height > 0) { 25 | url += '/h/' + this.height; 26 | } 27 | 28 | if (this.quality > 0) { 29 | url += '/q/' + this.quality; 30 | } 31 | 32 | if (this.format) { 33 | url += '/format/' + this.format; 34 | } 35 | 36 | return url; 37 | } 38 | } 39 | 40 | class ImageInfo { 41 | makeRequest(url) { 42 | return url + '?imageInfo' 43 | } 44 | } 45 | 46 | class Exif { 47 | makeRequest(url) { 48 | return url + '?exif' 49 | } 50 | } 51 | 52 | export default {ImageView,ImageInfo,Exif} 53 | -------------------------------------------------------------------------------- /core/rpc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buhe on 16/4/12. 3 | */ 4 | import conf from './conf.js'; 5 | 6 | /** 7 | * 直传文件 8 | * formInput对象如何配置请参考七牛官方文档“直传文件”一节 9 | */ 10 | function uploadFile(uri, token, formInput, onprogress) { 11 | return new Promise((resolve, reject)=> { 12 | if (typeof uri != 'string' || uri == '' || typeof formInput.key == 'undefined') { 13 | reject && reject(null); 14 | return; 15 | } 16 | if (uri[0] == '/') { 17 | uri = "file://" + uri; 18 | } 19 | var xhr = new XMLHttpRequest(); 20 | xhr.open('POST', conf.UP_HOST); 21 | 22 | xhr.timeout = conf.RPC_TIMEOUT; 23 | 24 | xhr.onload = () => { 25 | if (xhr.status !== 200) { 26 | reject && reject(xhr); 27 | return; 28 | } 29 | 30 | resolve && resolve(xhr); 31 | }; 32 | 33 | xhr.ontimeout = (e) => { 34 | reject && reject(e); 35 | return; 36 | } 37 | 38 | xhr.onerror = (e)=>{ 39 | reject && reject(e); 40 | return; 41 | } 42 | 43 | var formdata = new FormData(); 44 | formdata.append("key", formInput.key); 45 | formdata.append("token", token); 46 | if (typeof formInput.type == 'undefined') 47 | formInput.type = 'application/octet-stream'; 48 | if (typeof formInput.name == 'undefined') { 49 | var filePath = uri.split("/"); 50 | if (filePath.length > 0) 51 | formInput.name = filePath[filePath.length - 1]; 52 | else 53 | formInput.name = ""; 54 | } 55 | formdata.append("file", {uri: uri, type: formInput.type, name: formInput.name}); 56 | xhr.upload.onprogress = (event) => { 57 | onprogress && onprogress(event, xhr); 58 | }; 59 | xhr.send(formdata); 60 | }); 61 | } 62 | 63 | //发送管理和fop命令,总之就是不上传文件 64 | function post(uri, adminToken, content) { 65 | var headers = { 66 | 'Content-Type': 'application/x-www-form-urlencoded', 67 | }; 68 | let payload = { 69 | headers: headers, 70 | method: 'POST', 71 | dataType: 'json', 72 | timeout: conf.RPC_TIMEOUT, 73 | }; 74 | if (typeof content === 'undefined') { 75 | payload.headers['Content-Length'] = 0; 76 | } else { 77 | //carry data 78 | payload.body = content; 79 | } 80 | 81 | if (adminToken) { 82 | headers['Authorization'] = adminToken; 83 | } 84 | 85 | return fetch(uri, payload); 86 | } 87 | 88 | export default {uploadFile, post} 89 | -------------------------------------------------------------------------------- /core/rs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buhe on 16/4/12. 3 | */ 4 | import util from './auth.js'; 5 | import conf from './conf.js'; 6 | import rpc from './rpc.js'; 7 | 8 | function stat(bucket, key) { 9 | var encodedEntryUri = getEncodedEntryUri(bucket, key); 10 | var uri = conf.RS_HOST + '/stat/' + encodedEntryUri; 11 | var digest = util.generateAccessToken(uri, null); 12 | 13 | return rpc.post(uri, digest); 14 | } 15 | 16 | function remove(bucket, key) { 17 | /* 18 | * func (this Client) Delete(bucket, key string) (err error) 19 | * */ 20 | var encodedEntryUri = getEncodedEntryUri(bucket, key); 21 | var uri = conf.RS_HOST + '/delete/' + encodedEntryUri; 22 | var digest = util.generateAccessToken(uri, null); 23 | return rpc.post(uri, digest); 24 | } 25 | 26 | function move(bucketSrc, keySrc, bucketDest, keyDest) { 27 | var encodedEntryURISrc = getEncodedEntryUri(bucketSrc, keySrc); 28 | var encodedEntryURIDest = getEncodedEntryUri(bucketDest, keyDest); 29 | var uri = conf.RS_HOST + '/move/' + encodedEntryURISrc + '/' + encodedEntryURIDest; 30 | var digest = util.generateAccessToken(uri, null); 31 | return rpc.post(uri, digest); 32 | } 33 | 34 | function forceMove(bucketSrc, keySrc, bucketDest, keyDest, force) { 35 | 36 | var encodedEntryURISrc = getEncodedEntryUri(bucketSrc, keySrc); 37 | var encodedEntryURIDest = getEncodedEntryUri(bucketDest, keyDest); 38 | var uri = conf.RS_HOST + '/move/' + encodedEntryURISrc + '/' + encodedEntryURIDest + '/force/' + force; 39 | 40 | var digest = util.generateAccessToken(uri, null); 41 | return rpc.post(uri, digest); 42 | } 43 | 44 | function copy(bucketSrc, keySrc, bucketDest, keyDest) { 45 | var encodedEntryURISrc = getEncodedEntryUri(bucketSrc, keySrc); 46 | var encodedEntryURIDest = getEncodedEntryUri(bucketDest, keyDest); 47 | var uri = conf.RS_HOST + '/copy/' + encodedEntryURISrc + '/' + encodedEntryURIDest; 48 | 49 | var digest = util.generateAccessToken(uri, null); 50 | return rpc.post(uri, digest); 51 | } 52 | 53 | function forceCopy(bucketSrc, keySrc, bucketDest, keyDest, force) { 54 | 55 | var encodedEntryURISrc = getEncodedEntryUri(bucketSrc, keySrc); 56 | var encodedEntryURIDest = getEncodedEntryUri(bucketDest, keyDest); 57 | var uri = conf.RS_HOST + '/copy/' + encodedEntryURISrc + '/' + encodedEntryURIDest + '/force/' + force; 58 | 59 | var digest = util.generateAccessToken(uri, null); 60 | return rpc.post(uri, digest); 61 | } 62 | 63 | 64 | function fetch(url, bucket, key) { 65 | var bucketUri = getEncodedEntryUri(bucket, key); 66 | var fetchUrl = util.urlsafeBase64Encode(url); 67 | var digest = util.generateAccessToken(uri, null); 68 | return rpc.post(uri, digest); 69 | } 70 | 71 | function batchStat(entries) { 72 | return fileHandle('stat', entries); 73 | } 74 | 75 | function batchDelete(entries) { 76 | return fileHandle('delete', entries); 77 | } 78 | 79 | function batchMove(entries) { 80 | return fileHandle('move', entries); 81 | } 82 | 83 | function forceBatchMove(entries, force) { 84 | 85 | return fileHandleForce('move', entries, force); 86 | 87 | } 88 | 89 | function batchCopy(entries) { 90 | return fileHandle('copy', entries); 91 | } 92 | 93 | function forceBatchCopy(entries, force) { 94 | 95 | return fileHandleForce('copy', entries, force); 96 | 97 | } 98 | 99 | 100 | function fileHandle(op, entries) { 101 | var body = ''; 102 | for (var i in entries) { 103 | body += entries[i].toStr(op); 104 | } 105 | 106 | var uri = conf.RS_HOST + '/batch'; 107 | var digest = util.generateAccessToken(uri, body); 108 | return rpc.post(uri,digest, body); 109 | } 110 | 111 | function fileHandleForce(op, entries, force) { 112 | var body = ''; 113 | for (var i in entries) { 114 | body += entries[i].toStr(op, force); 115 | } 116 | 117 | console.log(body); 118 | var uri = conf.RS_HOST + '/batch'; 119 | var digest = util.generateAccessToken(uri, body); 120 | return rpc.post(uri, digest,body); 121 | } 122 | 123 | function getEncodedEntryUri(bucket, key) { 124 | return util.urlsafeBase64Encode(bucket + (key ? ':' + key : '')); 125 | } 126 | 127 | class EntryPathPair { 128 | constructor(src, dest) { 129 | this.src = src || null; 130 | this.dest = dest || null; 131 | } 132 | 133 | toStr(op, force) { 134 | if (typeof(force) == 'undefined') { 135 | 136 | return 'op=/' + op + '/' + this.src.encode() + '/' + this.dest.encode() + '&'; 137 | 138 | } else { 139 | 140 | return 'op=/' + op + '/' + this.src.encode() + '/' + this.dest.encode() + '/force/' + force + '&'; 141 | } 142 | } 143 | 144 | } 145 | 146 | class BatchItemRet { 147 | constructor(error, code) { 148 | this.error = error || null; 149 | this.code = code || null; 150 | } 151 | } 152 | 153 | class BatchStatItemRet { 154 | constructor(data, error, code) { 155 | this.data = data; 156 | this.error = error; 157 | this.code = code; 158 | } 159 | } 160 | 161 | 162 | class Entry { 163 | constructor(hash, fsize, putTime, mimeType, endUser) { 164 | this.hash = hash || null; 165 | this.fsize = fsize || null; 166 | this.putTime = putTime || null; 167 | this.mimeType = mimeType || null; 168 | this.endUser = endUser || null; 169 | } 170 | } 171 | 172 | class EntryPath { 173 | constructor(bucket, key) { 174 | this.bucket = bucket || null; 175 | this.key = key || null; 176 | } 177 | 178 | encode() { 179 | return getEncodedEntryUri(this.bucket, this.key); 180 | } 181 | 182 | toStr(op) { 183 | return 'op=/' + op + '/' + getEncodedEntryUri(this.bucket, this.key) + '&'; 184 | } 185 | } 186 | 187 | export default { 188 | stat, 189 | remove, 190 | move, 191 | forceMove, 192 | copy, 193 | forceCopy, 194 | fetch, 195 | batchStat, 196 | batchDelete, 197 | batchMove, 198 | forceBatchMove, 199 | batchCopy, 200 | forceBatchCopy, 201 | EntryPathPair, 202 | BatchItemRet, 203 | BatchStatItemRet, 204 | Entry, 205 | EntryPath 206 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buhe on 16/4/12. 3 | */ 4 | import rpc from './core/rpc'; 5 | import auth from './core/auth'; 6 | import rs from './core/rs'; 7 | import imgOps from './core/imageOps'; 8 | import conf from './core/conf'; 9 | 10 | module.exports = { 11 | // Components 12 | Rpc:rpc, 13 | Auth:auth, 14 | Rs:rs, 15 | ImgOps:imgOps, 16 | Conf:conf 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-qiniu", 3 | "version": "0.3.0", 4 | "description": "Qiniu React Native SDK ,A pure javascript implements.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "React", 11 | "Native", 12 | "Qiniu", 13 | "SDK" 14 | ], 15 | "author": "buhe", 16 | "license": "MIT", 17 | "dependencies": { 18 | "base-64": "^0.1.0", 19 | "crypto-js": "^3.1.6", 20 | "url-parse": "^1.1.1" 21 | } 22 | } 23 | --------------------------------------------------------------------------------