|
|
|
|
/*!
|
|
|
|
|
* qiniu-js-sdk v1.0.16-beta
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2015 by Qiniu
|
|
|
|
|
* Released under GPL V2 License.
|
|
|
|
|
*
|
|
|
|
|
* GitHub: http://github.com/qiniu/js-sdk
|
|
|
|
|
*
|
|
|
|
|
* Date: 2016-5-31
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*global plupload ,mOxie*/
|
|
|
|
|
/*global ActiveXObject */
|
|
|
|
|
/*exported Qiniu */
|
|
|
|
|
/*exported QiniuJsSDK */
|
|
|
|
|
|
|
|
|
|
;(function( global ){
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates new cookie or removes cookie with negative expiration
|
|
|
|
|
* @param key The key or identifier for the store
|
|
|
|
|
* @param value Contents of the store
|
|
|
|
|
* @param exp Expiration - creation defaults to 30 days
|
|
|
|
|
*/
|
|
|
|
|
function createCookie(key, value, exp) {
|
|
|
|
|
var date = new Date();
|
|
|
|
|
date.setTime(date.getTime() + (exp * 24 * 60 * 60 * 1000));
|
|
|
|
|
var expires = "; expires=" + date.toGMTString();
|
|
|
|
|
document.cookie = key + "=" + value + expires + "; path=/";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns contents of cookie
|
|
|
|
|
* @param key The key or identifier for the store
|
|
|
|
|
*/
|
|
|
|
|
function readCookie(key) {
|
|
|
|
|
var nameEQ = key + "=";
|
|
|
|
|
var ca = document.cookie.split(';');
|
|
|
|
|
for (var i = 0, max = ca.length; i < max; i++) {
|
|
|
|
|
var c = ca[i];
|
|
|
|
|
while (c.charAt(0) === ' ') {
|
|
|
|
|
c = c.substring(1, c.length);
|
|
|
|
|
}
|
|
|
|
|
if (c.indexOf(nameEQ) === 0) {
|
|
|
|
|
return c.substring(nameEQ.length, c.length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if current browser is not support localStorage
|
|
|
|
|
// use cookie to make a polyfill
|
|
|
|
|
if ( !window.localStorage ) {
|
|
|
|
|
window.localStorage = {
|
|
|
|
|
setItem: function (key, value) {
|
|
|
|
|
createCookie(key, value, 30);
|
|
|
|
|
},
|
|
|
|
|
getItem: function (key) {
|
|
|
|
|
return readCookie(key);
|
|
|
|
|
},
|
|
|
|
|
removeItem: function (key) {
|
|
|
|
|
createCookie(key, '', -1);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function QiniuJsSDK() {
|
|
|
|
|
|
|
|
|
|
var that = this;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* detect IE version
|
|
|
|
|
* if current browser is not IE
|
|
|
|
|
* it will return false
|
|
|
|
|
* else
|
|
|
|
|
* it will return version of current IE browser
|
|
|
|
|
* @return {Number|Boolean} IE version or false
|
|
|
|
|
*/
|
|
|
|
|
this.detectIEVersion = function() {
|
|
|
|
|
var v = 4,
|
|
|
|
|
div = document.createElement('div'),
|
|
|
|
|
all = div.getElementsByTagName('i');
|
|
|
|
|
while (
|
|
|
|
|
div.innerHTML = '<!--[if gt IE ' + v + ']><i></i><![endif]-->',
|
|
|
|
|
all[0]
|
|
|
|
|
) {
|
|
|
|
|
v++;
|
|
|
|
|
}
|
|
|
|
|
return v > 4 ? v : false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var logger = {
|
|
|
|
|
MUTE: 0,
|
|
|
|
|
FATA: 1,
|
|
|
|
|
ERROR: 2,
|
|
|
|
|
WARN: 3,
|
|
|
|
|
INFO: 4,
|
|
|
|
|
DEBUG: 5,
|
|
|
|
|
TRACE: 6,
|
|
|
|
|
level: 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function log(type, args){
|
|
|
|
|
var header = "[qiniu-js-sdk]["+type+"]";
|
|
|
|
|
var msg = header;
|
|
|
|
|
for (var i = 0; i < args.length; i++) {
|
|
|
|
|
if (typeof args[i] === "string") {
|
|
|
|
|
msg += " " + args[i];
|
|
|
|
|
} else {
|
|
|
|
|
msg += " " + that.stringifyJSON(args[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (that.detectIEVersion()) {
|
|
|
|
|
// http://stackoverflow.com/questions/5538972/console-log-apply-not-working-in-ie9
|
|
|
|
|
//var log = Function.prototype.bind.call(console.log, console);
|
|
|
|
|
//log.apply(console, args);
|
|
|
|
|
console.log(msg);
|
|
|
|
|
}else{
|
|
|
|
|
args.unshift(header);
|
|
|
|
|
console.log.apply(console, args);
|
|
|
|
|
}
|
|
|
|
|
if (document.getElementById('qiniu-js-sdk-log')) {
|
|
|
|
|
document.getElementById('qiniu-js-sdk-log').innerHTML += '<p>'+msg+'</p>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function makeLogFunc(code){
|
|
|
|
|
var func = code.toLowerCase();
|
|
|
|
|
logger[func] = function(){
|
|
|
|
|
// logger[func].history = logger[func].history || [];
|
|
|
|
|
// logger[func].history.push(arguments);
|
|
|
|
|
if(window.console && window.console.log && logger.level>=logger[code]){
|
|
|
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
|
log(func,args);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (var property in logger){
|
|
|
|
|
if (logger.hasOwnProperty(property) && (typeof logger[property]) === "number" && !logger.hasOwnProperty(property.toLowerCase())) {
|
|
|
|
|
makeLogFunc(property);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var qiniuUploadUrl;
|
|
|
|
|
if (window.location.protocol === 'https:') {
|
|
|
|
|
qiniuUploadUrl = 'https://up.qbox.me';
|
|
|
|
|
} else {
|
|
|
|
|
qiniuUploadUrl = 'http://upload.qiniu.com';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* qiniu upload urls
|
|
|
|
|
* 'qiniuUploadUrls' is used to change target when current url is not avaliable
|
|
|
|
|
* @type {Array}
|
|
|
|
|
*/
|
|
|
|
|
var qiniuUploadUrls = [
|
|
|
|
|
"http://upload.qiniu.com",
|
|
|
|
|
"http://up.qiniu.com"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
var changeUrlTimes = 0;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* reset upload url
|
|
|
|
|
* if current page protocal is https
|
|
|
|
|
* it will always return 'https://up.qbox.me'
|
|
|
|
|
* else
|
|
|
|
|
* it will set 'qiniuUploadUrl' value with 'qiniuUploadUrls' looply
|
|
|
|
|
*/
|
|
|
|
|
this.resetUploadUrl = function(){
|
|
|
|
|
if (window.location.protocol === 'https:') {
|
|
|
|
|
qiniuUploadUrl = 'https://up.qbox.me';
|
|
|
|
|
} else {
|
|
|
|
|
var i = changeUrlTimes % qiniuUploadUrls.length;
|
|
|
|
|
qiniuUploadUrl = qiniuUploadUrls[i];
|
|
|
|
|
changeUrlTimes++;
|
|
|
|
|
}
|
|
|
|
|
logger.debug('resetUploadUrl: '+qiniuUploadUrl);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.resetUploadUrl();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* is image
|
|
|
|
|
* @param {String} url of a file
|
|
|
|
|
* @return {Boolean} file is a image or not
|
|
|
|
|
*/
|
|
|
|
|
this.isImage = function(url) {
|
|
|
|
|
url = url.split(/[?#]/)[0];
|
|
|
|
|
return (/\.(png|jpg|jpeg|gif|bmp)$/i).test(url);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* get file extension
|
|
|
|
|
* @param {String} filename
|
|
|
|
|
* @return {String} file extension
|
|
|
|
|
* @example
|
|
|
|
|
* input: test.txt
|
|
|
|
|
* output: txt
|
|
|
|
|
*/
|
|
|
|
|
this.getFileExtension = function(filename) {
|
|
|
|
|
var tempArr = filename.split(".");
|
|
|
|
|
var ext;
|
|
|
|
|
if (tempArr.length === 1 || (tempArr[0] === "" && tempArr.length === 2)) {
|
|
|
|
|
ext = "";
|
|
|
|
|
} else {
|
|
|
|
|
ext = tempArr.pop().toLowerCase(); //get the extension and make it lower-case
|
|
|
|
|
}
|
|
|
|
|
return ext;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* encode string by utf8
|
|
|
|
|
* @param {String} string to encode
|
|
|
|
|
* @return {String} encoded string
|
|
|
|
|
*/
|
|
|
|
|
this.utf8_encode = function(argString) {
|
|
|
|
|
// http://kevin.vanzonneveld.net
|
|
|
|
|
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
|
|
|
|
|
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
|
|
|
|
// + improved by: sowberry
|
|
|
|
|
// + tweaked by: Jack
|
|
|
|
|
// + bugfixed by: Onno Marsman
|
|
|
|
|
// + improved by: Yves Sucaet
|
|
|
|
|
// + bugfixed by: Onno Marsman
|
|
|
|
|
// + bugfixed by: Ulrich
|
|
|
|
|
// + bugfixed by: Rafal Kukawski
|
|
|
|
|
// + improved by: kirilloid
|
|
|
|
|
// + bugfixed by: kirilloid
|
|
|
|
|
// * example 1: this.utf8_encode('Kevin van Zonneveld');
|
|
|
|
|
// * returns 1: 'Kevin van Zonneveld'
|
|
|
|
|
|
|
|
|
|
if (argString === null || typeof argString === 'undefined') {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var string = (argString + ''); // .replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
|
|
|
var utftext = '',
|
|
|
|
|
start, end, stringl = 0;
|
|
|
|
|
|
|
|
|
|
start = end = 0;
|
|
|
|
|
stringl = string.length;
|
|
|
|
|
for (var n = 0; n < stringl; n++) {
|
|
|
|
|
var c1 = string.charCodeAt(n);
|
|
|
|
|
var enc = null;
|
|
|
|
|
|
|
|
|
|
if (c1 < 128) {
|
|
|
|
|
end++;
|
|
|
|
|
} else if (c1 > 127 && c1 < 2048) {
|
|
|
|
|
enc = String.fromCharCode(
|
|
|
|
|
(c1 >> 6) | 192, (c1 & 63) | 128
|
|
|
|
|
);
|
|
|
|
|
} else if (c1 & 0xF800 ^ 0xD800 > 0) {
|
|
|
|
|
enc = String.fromCharCode(
|
|
|
|
|
(c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128
|
|
|
|
|
);
|
|
|
|
|
} else { // surrogate pairs
|
|
|
|
|
if (c1 & 0xFC00 ^ 0xD800 > 0) {
|
|
|
|
|
throw new RangeError('Unmatched trail surrogate at ' + n);
|
|
|
|
|
}
|
|
|
|
|
var c2 = string.charCodeAt(++n);
|
|
|
|
|
if (c2 & 0xFC00 ^ 0xDC00 > 0) {
|
|
|
|
|
throw new RangeError('Unmatched lead surrogate at ' + (n - 1));
|
|
|
|
|
}
|
|
|
|
|
c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
|
|
|
|
|
enc = String.fromCharCode(
|
|
|
|
|
(c1 >> 18) | 240, ((c1 >> 12) & 63) | 128, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (enc !== null) {
|
|
|
|
|
if (end > start) {
|
|
|
|
|
utftext += string.slice(start, end);
|
|
|
|
|
}
|
|
|
|
|
utftext += enc;
|
|
|
|
|
start = end = n + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (end > start) {
|
|
|
|
|
utftext += string.slice(start, stringl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return utftext;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* encode data by base64
|
|
|
|
|
* @param {String} data to encode
|
|
|
|
|
* @return {String} encoded data
|
|
|
|
|
*/
|
|
|
|
|
this.base64_encode = function(data) {
|
|
|
|
|
// http://kevin.vanzonneveld.net
|
|
|
|
|
// + original by: Tyler Akins (http://rumkin.com)
|
|
|
|
|
// + improved by: Bayron Guevara
|
|
|
|
|
// + improved by: Thunder.m
|
|
|
|
|
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
|
|
|
|
// + bugfixed by: Pellentesque Malesuada
|
|
|
|
|
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
|
|
|
|
// - depends on: this.utf8_encode
|
|
|
|
|
// * example 1: this.base64_encode('Kevin van Zonneveld');
|
|
|
|
|
// * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
|
|
|
|
|
// mozilla has this native
|
|
|
|
|
// - but breaks in 2.0.0.12!
|
|
|
|
|
//if (typeof this.window['atob'] == 'function') {
|
|
|
|
|
// return atob(data);
|
|
|
|
|
//}
|
|
|
|
|
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
|
|
|
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
|
|
|
|
|
ac = 0,
|
|
|
|
|
enc = '',
|
|
|
|
|
tmp_arr = [];
|
|
|
|
|
|
|
|
|
|
if (!data) {
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data = this.utf8_encode(data + '');
|
|
|
|
|
|
|
|
|
|
do { // pack three octets into four hexets
|
|
|
|
|
o1 = data.charCodeAt(i++);
|
|
|
|
|
o2 = data.charCodeAt(i++);
|
|
|
|
|
o3 = data.charCodeAt(i++);
|
|
|
|
|
|
|
|
|
|
bits = o1 << 16 | o2 << 8 | o3;
|
|
|
|
|
|
|
|
|
|
h1 = bits >> 18 & 0x3f;
|
|
|
|
|
h2 = bits >> 12 & 0x3f;
|
|
|
|
|
h3 = bits >> 6 & 0x3f;
|
|
|
|
|
h4 = bits & 0x3f;
|
|
|
|
|
|
|
|
|
|
// use hexets to index into b64, and append result to encoded string
|
|
|
|
|
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
|
|
|
|
|
} while (i < data.length);
|
|
|
|
|
|
|
|
|
|
enc = tmp_arr.join('');
|
|
|
|
|
|
|
|
|
|
switch (data.length % 3) {
|
|
|
|
|
case 1:
|
|
|
|
|
enc = enc.slice(0, -2) + '==';
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
enc = enc.slice(0, -1) + '=';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return enc;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* encode string in url by base64
|
|
|
|
|
* @param {String} string in url
|
|
|
|
|
* @return {String} encoded string
|
|
|
|
|
*/
|
|
|
|
|
this.URLSafeBase64Encode = function(v) {
|
|
|
|
|
v = this.base64_encode(v);
|
|
|
|
|
return v.replace(/\//g, '_').replace(/\+/g, '-');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// TODO: use mOxie
|
|
|
|
|
/**
|
|
|
|
|
* craete object used to AJAX
|
|
|
|
|
* @return {Object}
|
|
|
|
|
*/
|
|
|
|
|
this.createAjax = function(argument) {
|
|
|
|
|
var xmlhttp = {};
|
|
|
|
|
if (window.XMLHttpRequest) {
|
|
|
|
|
xmlhttp = new XMLHttpRequest();
|
|
|
|
|
} else {
|
|
|
|
|
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
|
|
|
|
|
}
|
|
|
|
|
return xmlhttp;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// TODO: enhance IE compatibility
|
|
|
|
|
/**
|
|
|
|
|
* parse json string to javascript object
|
|
|
|
|
* @param {String} json string
|
|
|
|
|
* @return {Object} object
|
|
|
|
|
*/
|
|
|
|
|
this.parseJSON = function(data) {
|
|
|
|
|
// Attempt to parse using the native JSON parser first
|
|
|
|
|
if (window.JSON && window.JSON.parse) {
|
|
|
|
|
return window.JSON.parse(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//var rx_one = /^[\],:{}\s]*$/,
|
|
|
|
|
// rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
|
|
|
|
|
// rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
|
|
|
|
|
// rx_four = /(?:^|:|,)(?:\s*\[)+/g,
|
|
|
|
|
var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
|
|
|
|
|
|
|
|
|
|
//var json;
|
|
|
|
|
|
|
|
|
|
var text = String(data);
|
|
|
|
|
rx_dangerous.lastIndex = 0;
|
|
|
|
|
if(rx_dangerous.test(text)){
|
|
|
|
|
text = text.replace(rx_dangerous, function(a){
|
|
|
|
|
return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// todo 使用一下判断,增加安全性
|
|
|
|
|
//if (
|
|
|
|
|
// rx_one.test(
|
|
|
|
|
// text
|
|
|
|
|
// .replace(rx_two, '@')
|
|
|
|
|
// .replace(rx_three, ']')
|
|
|
|
|
// .replace(rx_four, '')
|
|
|
|
|
// )
|
|
|
|
|
//) {
|
|
|
|
|
// return eval('(' + text + ')');
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
return eval('('+text+')');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* parse javascript object to json string
|
|
|
|
|
* @param {Object} object
|
|
|
|
|
* @return {String} json string
|
|
|
|
|
*/
|
|
|
|
|
this.stringifyJSON = function(obj) {
|
|
|
|
|
// Attempt to parse using the native JSON parser first
|
|
|
|
|
if (window.JSON && window.JSON.stringify) {
|
|
|
|
|
return window.JSON.stringify(obj);
|
|
|
|
|
}
|
|
|
|
|
switch (typeof (obj)) {
|
|
|
|
|
case 'string':
|
|
|
|
|
return '"' + obj.replace(/(["\\])/g, '\\$1') + '"';
|
|
|
|
|
case 'array':
|
|
|
|
|
return '[' + obj.map(that.stringifyJSON).join(',') + ']';
|
|
|
|
|
case 'object':
|
|
|
|
|
if (obj instanceof Array) {
|
|
|
|
|
var strArr = [];
|
|
|
|
|
var len = obj.length;
|
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
|
|
|
strArr.push(that.stringifyJSON(obj[i]));
|
|
|
|
|
}
|
|
|
|
|
return '[' + strArr.join(',') + ']';
|
|
|
|
|
} else if (obj === null) {
|
|
|
|
|
return 'null';
|
|
|
|
|
} else {
|
|
|
|
|
var string = [];
|
|
|
|
|
for (var property in obj) {
|
|
|
|
|
if (obj.hasOwnProperty(property)) {
|
|
|
|
|
string.push(that.stringifyJSON(property) + ':' + that.stringifyJSON(obj[property]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return '{' + string.join(',') + '}';
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'number':
|
|
|
|
|
return obj;
|
|
|
|
|
case false:
|
|
|
|
|
return obj;
|
|
|
|
|
case 'boolean':
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* trim space beside text
|
|
|
|
|
* @param {String} untrimed string
|
|
|
|
|
* @return {String} trimed string
|
|
|
|
|
*/
|
|
|
|
|
this.trim = function(text) {
|
|
|
|
|
return text === null ? "" : text.replace(/^\s+|\s+$/g, '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* create a uploader by QiniuJsSDK
|
|
|
|
|
* @param {object} options to create a new uploader
|
|
|
|
|
* @return {object} uploader
|
|
|
|
|
*/
|
|
|
|
|
this.uploader = function(op) {
|
|
|
|
|
|
|
|
|
|
/********** inner function define start **********/
|
|
|
|
|
|
|
|
|
|
// according the different condition to reset chunk size
|
|
|
|
|
// and the upload strategy according with the chunk size
|
|
|
|
|
// when chunk size is zero will cause to direct upload
|
|
|
|
|
// see the statement binded on 'BeforeUpload' event
|
|
|
|
|
var reset_chunk_size = function() {
|
|
|
|
|
var ie = that.detectIEVersion();
|
|
|
|
|
var BLOCK_BITS, MAX_CHUNK_SIZE, chunk_size;
|
|
|
|
|
// case Safari 5、Windows 7、iOS 7 set isSpecialSafari to true
|
|
|
|
|
var isSpecialSafari = (mOxie.Env.browser === "Safari" && mOxie.Env.version <= 5 && mOxie.Env.os === "Windows" && mOxie.Env.osVersion === "7") || (mOxie.Env.browser === "Safari" && mOxie.Env.os === "iOS" && mOxie.Env.osVersion === "7");
|
|
|
|
|
// case IE 9-,chunk_size is not empty and flash is included in runtimes
|
|
|
|
|
// set op.chunk_size to zero
|
|
|
|
|
//if (ie && ie < 9 && op.chunk_size && op.runtimes.indexOf('flash') >= 0) {
|
|
|
|
|
if (ie && ie < 9 && op.chunk_size && op.runtimes.indexOf('flash') >= 0) {
|
|
|
|
|
// link: http://www.plupload.com/docs/Frequently-Asked-Questions#when-to-use-chunking-and-when-not
|
|
|
|
|
// when plupload chunk_size setting is't null ,it cause bug in ie8/9 which runs flash runtimes (not support html5) .
|
|
|
|
|
op.chunk_size = 0;
|
|
|
|
|
} else if (isSpecialSafari) {
|
|
|
|
|
// win7 safari / iOS7 safari have bug when in chunk upload mode
|
|
|
|
|
// reset chunk_size to 0
|
|
|
|
|
// disable chunk in special version safari
|
|
|
|
|
op.chunk_size = 0;
|
|
|
|
|
} else {
|
|
|
|
|
BLOCK_BITS = 20;
|
|
|
|
|
MAX_CHUNK_SIZE = 4 << BLOCK_BITS; //4M
|
|
|
|
|
|
|
|
|
|
chunk_size = plupload.parseSize(op.chunk_size);
|
|
|
|
|
if (chunk_size > MAX_CHUNK_SIZE) {
|
|
|
|
|
op.chunk_size = MAX_CHUNK_SIZE;
|
|
|
|
|
}
|
|
|
|
|
// qiniu service max_chunk_size is 4m
|
|
|
|
|
// reset chunk_size to max_chunk_size(4m) when chunk_size > 4m
|
|
|
|
|
}
|
|
|
|
|
// if op.chunk_size set 0 will be cause to direct upload
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// getUptoken maybe called at Init Event or BeforeUpload Event
|
|
|
|
|
// case Init Event, the file param of getUptken will be set a null value
|
|
|
|
|
// if op.uptoken has value, set uptoken with op.uptoken
|
|
|
|
|
// else if op.uptoken_url has value, set uptoken from op.uptoken_url
|
|
|
|
|
// else if op.uptoken_func has value, set uptoken by result of op.uptoken_func
|
|
|
|
|
var getUpToken = function(file) {
|
|
|
|
|
if (op.uptoken) {
|
|
|
|
|
that.token = op.uptoken;
|
|
|
|
|
return;
|
|
|
|
|
} else if (op.uptoken_url) {
|
|
|
|
|
logger.debug("get uptoken from: ", that.uptoken_url);
|
|
|
|
|
// TODO: use mOxie
|
|
|
|
|
var ajax = that.createAjax();
|
|
|
|
|
ajax.open('GET', that.uptoken_url, false);
|
|
|
|
|
ajax.setRequestHeader("If-Modified-Since", "0");
|
|
|
|
|
// ajax.onreadystatechange = function() {
|
|
|
|
|
// if (ajax.readyState === 4 && ajax.status === 200) {
|
|
|
|
|
// var res = that.parseJSON(ajax.responseText);
|
|
|
|
|
// that.token = res.uptoken;
|
|
|
|
|
// }
|
|
|
|
|
// };
|
|
|
|
|
ajax.send();
|
|
|
|
|
if (ajax.status === 200) {
|
|
|
|
|
var res = that.parseJSON(ajax.responseText);
|
|
|
|
|
that.token = res.uptoken;
|
|
|
|
|
logger.debug("get new uptoken: ", res.uptoken);
|
|
|
|
|
} else {
|
|
|
|
|
logger.error("get uptoken error: ", ajax.responseText);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
} else if (op.uptoken_func) {
|
|
|
|
|
logger.debug("get uptoken from uptoken_func");
|
|
|
|
|
that.token = op.uptoken_func(file);
|
|
|
|
|
logger.debug("get new uptoken: ", that.token);
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
logger.error("one of [uptoken, uptoken_url, uptoken_func] settings in options is required!");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// get file key according with the user passed options
|
|
|
|
|
var getFileKey = function(up, file, func) {
|
|
|
|
|
// TODO: save_key can read from scope of token
|
|
|
|
|
var key = '',
|
|
|
|
|
unique_names = false;
|
|
|
|
|
if (!op.save_key) {
|
|
|
|
|
unique_names = up.getOption && up.getOption('unique_names');
|
|
|
|
|
unique_names = unique_names || (up.settings && up.settings.unique_names);
|
|
|
|
|
if (unique_names) {
|
|
|
|
|
var ext = that.getFileExtension(file.name);
|
|
|
|
|
key = ext ? file.id + '.' + ext : file.id;
|
|
|
|
|
} else if (typeof func === 'function') {
|
|
|
|
|
key = func(up, file);
|
|
|
|
|
} else {
|
|
|
|
|
key = file.name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return key;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/********** inner function define end **********/
|
|
|
|
|
|
|
|
|
|
if (op.log_level) {
|
|
|
|
|
logger.level = op.log_level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!op.domain) {
|
|
|
|
|
throw 'domain setting in options is required!';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!op.browse_button) {
|
|
|
|
|
throw 'browse_button setting in options is required!';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!op.uptoken && !op.uptoken_url && !op.uptoken_func) {
|
|
|
|
|
throw 'one of [uptoken, uptoken_url, uptoken_func] settings in options is required!';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.debug("init uploader start");
|
|
|
|
|
|
|
|
|
|
logger.debug("environment: ", mOxie.Env);
|
|
|
|
|
|
|
|
|
|
logger.debug("userAgent: ", navigator.userAgent);
|
|
|
|
|
|
|
|
|
|
var option = {};
|
|
|
|
|
|
|
|
|
|
// hold the handler from user passed options
|
|
|
|
|
var _Error_Handler = op.init && op.init.Error;
|
|
|
|
|
var _FileUploaded_Handler = op.init && op.init.FileUploaded;
|
|
|
|
|
|
|
|
|
|
// replace the handler for intercept
|
|
|
|
|
op.init.Error = function() {};
|
|
|
|
|
op.init.FileUploaded = function() {};
|
|
|
|
|
|
|
|
|
|
that.uptoken_url = op.uptoken_url;
|
|
|
|
|
that.token = '';
|
|
|
|
|
that.key_handler = typeof op.init.Key === 'function' ? op.init.Key : '';
|
|
|
|
|
this.domain = op.domain;
|
|
|
|
|
// TODO: ctx is global in scope of a uploader instance
|
|
|
|
|
// this maybe cause error
|
|
|
|
|
var ctx = '';
|
|
|
|
|
var speedCalInfo = {
|
|
|
|
|
isResumeUpload: false,
|
|
|
|
|
resumeFilesize: 0,
|
|
|
|
|
startTime: '',
|
|
|
|
|
currentTime: ''
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
reset_chunk_size();
|
|
|
|
|
logger.debug("invoke reset_chunk_size()");
|
|
|
|
|
logger.debug("op.chunk_size: ", op.chunk_size);
|
|
|
|
|
|
|
|
|
|
// compose options with user passed options and default setting
|
|
|
|
|
plupload.extend(option, op, {
|
|
|
|
|
url: qiniuUploadUrl,
|
|
|
|
|
multipart_params: {
|
|
|
|
|
token: ''
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.debug("option: ", option);
|
|
|
|
|
|
|
|
|
|
// create a new uploader with composed options
|
|
|
|
|
var uploader = new plupload.Uploader(option);
|
|
|
|
|
|
|
|
|
|
logger.debug("new plupload.Uploader(option)");
|
|
|
|
|
|
|
|
|
|
// bind getUpToken to 'Init' event
|
|
|
|
|
uploader.bind('Init', function(up, params) {
|
|
|
|
|
logger.debug("Init event activated");
|
|
|
|
|
// if op.get_new_uptoken is not true
|
|
|
|
|
// invoke getUptoken when uploader init
|
|
|
|
|
// else
|
|
|
|
|
// getUptoken everytime before a new file upload
|
|
|
|
|
if(!op.get_new_uptoken){
|
|
|
|
|
getUpToken(null);
|
|
|
|
|
}
|
|
|
|
|
//getUpToken(null);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.debug("bind Init event");
|
|
|
|
|
|
|
|
|
|
// bind 'FilesAdded' event
|
|
|
|
|
// when file be added and auto_start has set value
|
|
|
|
|
// uploader will auto start upload the file
|
|
|
|
|
uploader.bind('FilesAdded', function(up, files) {
|
|
|
|
|
logger.debug("FilesAdded event activated");
|
|
|
|
|
var auto_start = up.getOption && up.getOption('auto_start');
|
|
|
|
|
auto_start = auto_start || (up.settings && up.settings.auto_start);
|
|
|
|
|
logger.debug("auto_start: ", auto_start);
|
|
|
|
|
logger.debug("files: ", files);
|
|
|
|
|
|
|
|
|
|
// detect is iOS
|
|
|
|
|
var is_ios = function (){
|
|
|
|
|
if(mOxie.Env.OS.toLowerCase()==="ios") {
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// if current env os is iOS change file name to [time].[ext]
|
|
|
|
|
if (is_ios()) {
|
|
|
|
|
for (var i = 0; i < files.length; i++) {
|
|
|
|
|
var file = files[i];
|
|
|
|
|
var ext = that.getFileExtension(file.name);
|
|
|
|
|
file.name = file.id + "." + ext;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (auto_start) {
|
|
|
|
|
setTimeout(function(){
|
|
|
|
|
up.start();
|
|
|
|
|
logger.debug("invoke up.start()");
|
|
|
|
|
}, 0);
|
|
|
|
|
// up.start();
|
|
|
|
|
// plupload.each(files, function(i, file) {
|
|
|
|
|
// up.start();
|
|
|
|
|
// logger.debug("invoke up.start()")
|
|
|
|
|
// logger.debug("file: ", file);
|
|
|
|
|
// });
|
|
|
|
|
}
|
|
|
|
|
up.refresh(); // Reposition Flash/Silverlight
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.debug("bind FilesAdded event");
|
|
|
|
|
|
|
|
|
|
// bind 'BeforeUpload' event
|
|
|
|
|
// intercept the process of upload
|
|
|
|
|
// - prepare uptoken
|
|
|
|
|
// - according the chunk size to make differnt upload strategy
|
|
|
|
|
// - resume upload with the last breakpoint of file
|
|
|
|
|
uploader.bind('BeforeUpload', function(up, file) {
|
|
|
|
|
logger.debug("BeforeUpload event activated");
|
|
|
|
|
// add a key named speed for file object
|
|
|
|
|
file.speed = file.speed || 0;
|
|
|
|
|
ctx = '';
|
|
|
|
|
|
|
|
|
|
if(op.get_new_uptoken){
|
|
|
|
|
getUpToken(file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var directUpload = function(up, file, func) {
|
|
|
|
|
speedCalInfo.startTime = new Date().getTime();
|
|
|
|
|
var multipart_params_obj;
|
|
|
|
|
if (op.save_key) {
|
|
|
|
|
multipart_params_obj = {
|
|
|
|
|
'token': that.token
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
multipart_params_obj = {
|
|
|
|
|
'key': getFileKey(up, file, func),
|
|
|
|
|
'token': that.token
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.debug("directUpload multipart_params_obj: ", multipart_params_obj);
|
|
|
|
|
|
|
|
|
|
var x_vars = op.x_vars;
|
|
|
|
|
if (x_vars !== undefined && typeof x_vars === 'object') {
|
|
|
|
|
for (var x_key in x_vars) {
|
|
|
|
|
if (x_vars.hasOwnProperty(x_key)) {
|
|
|
|
|
if (typeof x_vars[x_key] === 'function') {
|
|
|
|
|
multipart_params_obj['x:' + x_key] = x_vars[x_key](up, file);
|
|
|
|
|
} else if (typeof x_vars[x_key] !== 'object') {
|
|
|
|
|
multipart_params_obj['x:' + x_key] = x_vars[x_key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
up.setOption({
|
|
|
|
|
'url': qiniuUploadUrl,
|
|
|
|
|
'multipart': true,
|
|
|
|
|
'chunk_size': is_android_weixin_or_qq() ? op.max_file_size : undefined,
|
|
|
|
|
'multipart_params': multipart_params_obj
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// detect is weixin or qq inner browser
|
|
|
|
|
var is_android_weixin_or_qq = function (){
|
|
|
|
|
var ua = navigator.userAgent.toLowerCase();
|
|
|
|
|
if((ua.match(/MicroMessenger/i) || mOxie.Env.browser === "QQBrowser" || ua.match(/V1_AND_SQ/i)) && mOxie.Env.OS.toLowerCase()==="android") {
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var chunk_size = up.getOption && up.getOption('chunk_size');
|
|
|
|
|
chunk_size = chunk_size || (up.settings && up.settings.chunk_size);
|
|
|
|
|
|
|
|
|
|
logger.debug("uploader.runtime: ",uploader.runtime);
|
|
|
|
|
logger.debug("chunk_size: ",chunk_size);
|
|
|
|
|
|
|
|
|
|
// TODO: flash support chunk upload
|
|
|
|
|
if ((uploader.runtime === 'html5' || uploader.runtime === 'flash') && chunk_size) {
|
|
|
|
|
if (file.size < chunk_size || is_android_weixin_or_qq()) {
|
|
|
|
|
logger.debug("directUpload because file.size < chunk_size || is_android_weixin_or_qq()");
|
|
|
|
|
// direct upload if file size is less then the chunk size
|
|
|
|
|
directUpload(up, file, that.key_handler);
|
|
|
|
|
} else {
|
|
|
|
|
// TODO: need a polifill to make it work in IE 9-
|
|
|
|
|
// ISSUE: if file.name is existed in localStorage
|
|
|
|
|
// but not the same file maybe cause error
|
|
|
|
|
var localFileInfo = localStorage.getItem(file.name);
|
|
|
|
|
var blockSize = chunk_size;
|
|
|
|
|
if (localFileInfo) {
|
|
|
|
|
// TODO: although only the html5 runtime will enter this statement
|
|
|
|
|
// but need uniform way to make convertion between string and json
|
|
|
|
|
localFileInfo = that.parseJSON(localFileInfo);
|
|
|
|
|
var now = (new Date()).getTime();
|
|
|
|
|
var before = localFileInfo.time || 0;
|
|
|
|
|
var aDay = 24 * 60 * 60 * 1000; // milliseconds of one day
|
|
|
|
|
// if the last upload time is within one day
|
|
|
|
|
// will upload continuously follow the last breakpoint
|
|
|
|
|
// else
|
|
|
|
|
// will reupload entire file
|
|
|
|
|
if (now - before < aDay) {
|
|
|
|
|
|
|
|
|
|
if (localFileInfo.percent !== 100) {
|
|
|
|
|
if (file.size === localFileInfo.total) {
|
|
|
|
|
// TODO: if file.name and file.size is the same
|
|
|
|
|
// but not the same file will cause error
|
|
|
|
|
file.percent = localFileInfo.percent;
|
|
|
|
|
file.loaded = localFileInfo.offset;
|
|
|
|
|
ctx = localFileInfo.ctx;
|
|
|
|
|
|
|
|
|
|
// set speed info
|
|
|
|
|
speedCalInfo.isResumeUpload = true;
|
|
|
|
|
speedCalInfo.resumeFilesize = localFileInfo.offset;
|
|
|
|
|
|
|
|
|
|
// set block size
|
|
|
|
|
if (localFileInfo.offset + blockSize > file.size) {
|
|
|
|
|
blockSize = file.size - localFileInfo.offset;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// remove file info when file.size is conflict with file info
|
|
|
|
|
localStorage.removeItem(file.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// remove file info when upload percent is 100%
|
|
|
|
|
// avoid 499 bug
|
|
|
|
|
localStorage.removeItem(file.name);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// remove file info when last upload time is over one day
|
|
|
|
|
localStorage.removeItem(file.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
speedCalInfo.startTime = new Date().getTime();
|
|
|
|
|
// TODO: to support bput
|
|
|
|
|
// http://developer.qiniu.com/docs/v6/api/reference/up/bput.html
|
|
|
|
|
up.setOption({
|
|
|
|
|
'url': qiniuUploadUrl + '/mkblk/' + blockSize,
|
|
|
|
|
'multipart': false,
|
|
|
|
|
'chunk_size': chunk_size,
|
|
|
|
|
'required_features': "chunks",
|
|
|
|
|
'headers': {
|
|
|
|
|
'Authorization': 'UpToken ' + that.token
|
|
|
|
|
},
|
|
|
|
|
'multipart_params': {}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
logger.debug("directUpload because uploader.runtime !== 'html5' || uploader.runtime !== 'flash' || !chunk_size");
|
|
|
|
|
// direct upload if runtime is not html5
|
|
|
|
|
directUpload(up, file, that.key_handler);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.debug("bind BeforeUpload event");
|
|
|
|
|
|
|
|
|
|
// bind 'UploadProgress' event
|
|
|
|
|
// calculate upload speed
|
|
|
|
|
uploader.bind('UploadProgress', function(up, file) {
|
|
|
|
|
logger.trace("UploadProgress event activated");
|
|
|
|
|
speedCalInfo.currentTime = new Date().getTime();
|
|
|
|
|
var timeUsed = speedCalInfo.currentTime - speedCalInfo.startTime; // ms
|
|
|
|
|
var fileUploaded = file.loaded || 0;
|
|
|
|
|
if (speedCalInfo.isResumeUpload) {
|
|
|
|
|
fileUploaded = file.loaded - speedCalInfo.resumeFilesize;
|
|
|
|
|
}
|
|
|
|
|
file.speed = (fileUploaded / timeUsed * 1000).toFixed(0) || 0; // unit: byte/s
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.debug("bind UploadProgress event");
|
|
|
|
|
|
|
|
|
|
// bind 'ChunkUploaded' event
|
|
|
|
|
// store the chunk upload info and set next chunk upload url
|
|
|
|
|
uploader.bind('ChunkUploaded', function(up, file, info) {
|
|
|
|
|
logger.debug("ChunkUploaded event activated");
|
|
|
|
|
logger.debug("file: ", file);
|
|
|
|
|
logger.debug("info: ", info);
|
|
|
|
|
var res = that.parseJSON(info.response);
|
|
|
|
|
logger.debug("res: ", res);
|
|
|
|
|
// ctx should look like '[chunk01_ctx],[chunk02_ctx],[chunk03_ctx],...'
|
|
|
|
|
ctx = ctx ? ctx + ',' + res.ctx : res.ctx;
|
|
|
|
|
var leftSize = info.total - info.offset;
|
|
|
|
|
var chunk_size = up.getOption && up.getOption('chunk_size');
|
|
|
|
|
chunk_size = chunk_size || (up.settings && up.settings.chunk_size);
|
|
|
|
|
if (leftSize < chunk_size) {
|
|
|
|
|
up.setOption({
|
|
|
|
|
'url': qiniuUploadUrl + '/mkblk/' + leftSize
|
|
|
|
|
});
|
|
|
|
|
logger.debug("up.setOption url: ", qiniuUploadUrl + '/mkblk/' + leftSize);
|
|
|
|
|
}
|
|
|
|
|
localStorage.setItem(file.name, that.stringifyJSON({
|
|
|
|
|
ctx: ctx,
|
|
|
|
|
percent: file.percent,
|
|
|
|
|
total: info.total,
|
|
|
|
|
offset: info.offset,
|
|
|
|
|
time: (new Date()).getTime()
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.debug("bind ChunkUploaded event");
|
|
|
|
|
|
|
|
|
|
var retries = qiniuUploadUrls.length;
|
|
|
|
|
|
|
|
|
|
// if error is unkown switch upload url and retry
|
|
|
|
|
var unknow_error_retry = function(file){
|
|
|
|
|
if (retries-- > 0) {
|
|
|
|
|
setTimeout(function(){
|
|
|
|
|
that.resetUploadUrl();
|
|
|
|
|
file.status = plupload.QUEUED;
|
|
|
|
|
uploader.stop();
|
|
|
|
|
uploader.start();
|
|
|
|
|
}, 0);
|
|
|
|
|
return true;
|
|
|
|
|
}else{
|
|
|
|
|
retries = qiniuUploadUrls.length;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// bind 'Error' event
|
|
|
|
|
// check the err.code and return the errTip
|
|
|
|
|
uploader.bind('Error', (function(_Error_Handler) {
|
|
|
|
|
return function(up, err) {
|
|
|
|
|
logger.error("Error event activated");
|
|
|
|
|
logger.error("err: ", err);
|
|
|
|
|
var errTip = '';
|
|
|
|
|
var file = err.file;
|
|
|
|
|
if (file) {
|
|
|
|
|
switch (err.code) {
|
|
|
|
|
case plupload.FAILED:
|
|
|
|
|
errTip = '上传失败。请稍后再试。';
|
|
|
|
|
break;
|
|
|
|
|
case plupload.FILE_SIZE_ERROR:
|
|
|
|
|
var max_file_size = up.getOption && up.getOption('max_file_size');
|
|
|
|
|
max_file_size = max_file_size || (up.settings && up.settings.max_file_size);
|
|
|
|
|
errTip = '浏览器最大可上传' + max_file_size + '。更大文件请使用命令行工具。';
|
|
|
|
|
break;
|
|
|
|
|
case plupload.FILE_EXTENSION_ERROR:
|
|
|
|
|
errTip = '文件验证失败。请稍后重试。';
|
|
|
|
|
break;
|
|
|
|
|
case plupload.HTTP_ERROR:
|
|
|
|
|
if (err.response === '') {
|
|
|
|
|
// Fix parseJSON error ,when http error is like net::ERR_ADDRESS_UNREACHABLE
|
|
|
|
|
errTip = err.message || '未知网络错误。';
|
|
|
|
|
if (!unknow_error_retry(file)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
var errorObj = that.parseJSON(err.response);
|
|
|
|
|
var errorText = errorObj.error;
|
|
|
|
|
switch (err.status) {
|
|
|
|
|
case 400:
|
|
|
|
|
errTip = "请求报文格式错误。";
|
|
|
|
|
break;
|
|
|
|
|
case 401:
|
|
|
|
|
errTip = "客户端认证授权失败。请重试或提交反馈。";
|
|
|
|
|
break;
|
|
|
|
|
case 405:
|
|
|
|
|
errTip = "客户端请求错误。请重试或提交反馈。";
|
|
|
|
|
break;
|
|
|
|
|
case 579:
|
|
|
|
|
errTip = "资源上传成功,但回调失败。";
|
|
|
|
|
break;
|
|
|
|
|
case 599:
|
|
|
|
|
errTip = "网络连接异常。请重试或提交反馈。";
|
|
|
|
|
if (!unknow_error_retry(file)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 614:
|
|
|
|
|
errTip = "文件已存在。";
|
|
|
|
|
try {
|
|
|
|
|
errorObj = that.parseJSON(errorObj.error);
|
|
|
|
|
errorText = errorObj.error || 'file exists';
|
|
|
|
|
} catch (e) {
|
|
|
|
|
errorText = errorObj.error || 'file exists';
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 631:
|
|
|
|
|
errTip = "指定空间不存在。";
|
|
|
|
|
break;
|
|
|
|
|
case 701:
|
|
|
|
|
errTip = "上传数据块校验出错。请重试或提交反馈。";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
errTip = "未知错误。";
|
|
|
|
|
if (!unknow_error_retry(file)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
errTip = errTip + '(' + err.status + ':' + errorText + ')';
|
|
|
|
|
break;
|
|
|
|
|
case plupload.SECURITY_ERROR:
|
|
|
|
|
errTip = '安全配置错误。请联系网站管理员。';
|
|
|
|
|
break;
|
|
|
|
|
case plupload.GENERIC_ERROR:
|
|
|
|
|
errTip = '上传失败。请稍后再试。';
|
|
|
|
|
break;
|
|
|
|
|
case plupload.IO_ERROR:
|
|
|
|
|
errTip = '上传失败。请稍后再试。';
|
|
|
|
|
break;
|
|
|
|
|
case plupload.INIT_ERROR:
|
|
|
|
|
errTip = '网站配置错误。请联系网站管理员。';
|
|
|
|
|
uploader.destroy();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
errTip = err.message + err.details;
|
|
|
|
|
if (!unknow_error_retry(file)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (_Error_Handler) {
|
|
|
|
|
_Error_Handler(up, err, errTip);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
up.refresh(); // Reposition Flash/Silverlight
|
|
|
|
|
};
|
|
|
|
|
})(_Error_Handler));
|
|
|
|
|
|
|
|
|
|
logger.debug("bind Error event");
|
|
|
|
|
|
|
|
|
|
// bind 'FileUploaded' event
|
|
|
|
|
// intercept the complete of upload
|
|
|
|
|
// - get downtoken from downtoken_url if bucket is private
|
|
|
|
|
// - invoke mkfile api to compose chunks if upload strategy is chunk upload
|
|
|
|
|
uploader.bind('FileUploaded', (function(_FileUploaded_Handler) {
|
|
|
|
|
return function(up, file, info) {
|
|
|
|
|
logger.debug("FileUploaded event activated");
|
|
|
|
|
logger.debug("file: ", file);
|
|
|
|
|
logger.debug("info: ", info);
|
|
|
|
|
var last_step = function(up, file, info) {
|
|
|
|
|
if (op.downtoken_url) {
|
|
|
|
|
// if op.dowontoken_url is not empty
|
|
|
|
|
// need get downtoken before invoke the _FileUploaded_Handler
|
|
|
|
|
var ajax_downtoken = that.createAjax();
|
|
|
|
|
ajax_downtoken.open('POST', op.downtoken_url, true);
|
|
|
|
|
ajax_downtoken.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
|
|
|
ajax_downtoken.onreadystatechange = function() {
|
|
|
|
|
if (ajax_downtoken.readyState === 4) {
|
|
|
|
|
if (ajax_downtoken.status === 200) {
|
|
|
|
|
var res_downtoken;
|
|
|
|
|
try {
|
|
|
|
|
res_downtoken = that.parseJSON(ajax_downtoken.responseText);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw ('invalid json format');
|
|
|
|
|
}
|
|
|
|
|
var info_extended = {};
|
|
|
|
|
plupload.extend(info_extended, that.parseJSON(info), res_downtoken);
|
|
|
|
|
if (_FileUploaded_Handler) {
|
|
|
|
|
_FileUploaded_Handler(up, file, that.stringifyJSON(info_extended));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
uploader.trigger('Error', {
|
|
|
|
|
status: ajax_downtoken.status,
|
|
|
|
|
response: ajax_downtoken.responseText,
|
|
|
|
|
file: file,
|
|
|
|
|
code: plupload.HTTP_ERROR
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
ajax_downtoken.send('key=' + that.parseJSON(info).key + '&domain=' + op.domain);
|
|
|
|
|
} else if (_FileUploaded_Handler) {
|
|
|
|
|
_FileUploaded_Handler(up, file, info);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var res = that.parseJSON(info.response);
|
|
|
|
|
ctx = ctx ? ctx : res.ctx;
|
|
|
|
|
// if ctx is not empty
|
|
|
|
|
// that means the upload strategy is chunk upload
|
|
|
|
|
// befroe the invoke the last_step
|
|
|
|
|
// we need request the mkfile to compose all uploaded chunks
|
|
|
|
|
// else
|
|
|
|
|
// invalke the last_step
|
|
|
|
|
logger.debug("ctx: ", ctx);
|
|
|
|
|
if (ctx) {
|
|
|
|
|
var key = '';
|
|
|
|
|
logger.debug("save_key: ", op.save_key);
|
|
|
|
|
if (!op.save_key) {
|
|
|
|
|
key = getFileKey(up, file, that.key_handler);
|
|
|
|
|
key = key ? '/key/' + that.URLSafeBase64Encode(key) : '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var fname = '/fname/' + that.URLSafeBase64Encode(file.name);
|
|
|
|
|
|
|
|
|
|
logger.debug("op.x_vars: ", op.x_vars);
|
|
|
|
|
var x_vars = op.x_vars,
|
|
|
|
|
x_val = '',
|
|
|
|
|
x_vars_url = '';
|
|
|
|
|
if (x_vars !== undefined && typeof x_vars === 'object') {
|
|
|
|
|
for (var x_key in x_vars) {
|
|
|
|
|
if (x_vars.hasOwnProperty(x_key)) {
|
|
|
|
|
if (typeof x_vars[x_key] === 'function') {
|
|
|
|
|
x_val = that.URLSafeBase64Encode(x_vars[x_key](up, file));
|
|
|
|
|
} else if (typeof x_vars[x_key] !== 'object') {
|
|
|
|
|
x_val = that.URLSafeBase64Encode(x_vars[x_key]);
|
|
|
|
|
}
|
|
|
|
|
x_vars_url += '/x:' + x_key + '/' + x_val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var url = qiniuUploadUrl + '/mkfile/' + file.size + key + fname + x_vars_url;
|
|
|
|
|
|
|
|
|
|
var ie = that.detectIEVersion();
|
|
|
|
|
var ajax;
|
|
|
|
|
if (ie && ie <= 9) {
|
|
|
|
|
ajax = new mOxie.XMLHttpRequest();
|
|
|
|
|
mOxie.Env.swf_url = op.flash_swf_url;
|
|
|
|
|
}else{
|
|
|
|
|
ajax = that.createAjax();
|
|
|
|
|
}
|
|
|
|
|
ajax.open('POST', url, true);
|
|
|
|
|
ajax.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
|
|
|
|
|
ajax.setRequestHeader('Authorization', 'UpToken ' + that.token);
|
|
|
|
|
var onreadystatechange = function(){
|
|
|
|
|
logger.debug("ajax.readyState: ", ajax.readyState);
|
|
|
|
|
if (ajax.readyState === 4) {
|
|
|
|
|
localStorage.removeItem(file.name);
|
|
|
|
|
var info;
|
|
|
|
|
if (ajax.status === 200) {
|
|
|
|
|
info = ajax.responseText;
|
|
|
|
|
logger.debug("mkfile is success: ", info);
|
|
|
|
|
last_step(up, file, info);
|
|
|
|
|
} else {
|
|
|
|
|
info = {
|
|
|
|
|
status: ajax.status,
|
|
|
|
|
response: ajax.responseText,
|
|
|
|
|
file: file,
|
|
|
|
|
code: -200
|
|
|
|
|
};
|
|
|
|
|
logger.debug("mkfile is error: ", info);
|
|
|
|
|
uploader.trigger('Error', info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (ie && ie <= 9) {
|
|
|
|
|
ajax.bind('readystatechange', onreadystatechange);
|
|
|
|
|
}else{
|
|
|
|
|
ajax.onreadystatechange = onreadystatechange;
|
|
|
|
|
}
|
|
|
|
|
ajax.send(ctx);
|
|
|
|
|
logger.debug("mkfile: ", url);
|
|
|
|
|
} else {
|
|
|
|
|
last_step(up, file, info.response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
})(_FileUploaded_Handler));
|
|
|
|
|
|
|
|
|
|
logger.debug("bind FileUploaded event");
|
|
|
|
|
|
|
|
|
|
// init uploader
|
|
|
|
|
uploader.init();
|
|
|
|
|
|
|
|
|
|
logger.debug("invoke uploader.init()");
|
|
|
|
|
|
|
|
|
|
logger.debug("init uploader end");
|
|
|
|
|
|
|
|
|
|
return uploader;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* get url by key
|
|
|
|
|
* @param {String} key of file
|
|
|
|
|
* @return {String} url of file
|
|
|
|
|
*/
|
|
|
|
|
this.getUrl = function(key) {
|
|
|
|
|
if (!key) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
key = encodeURI(key);
|
|
|
|
|
var domain = this.domain;
|
|
|
|
|
if (domain.slice(domain.length - 1) !== '/') {
|
|
|
|
|
domain = domain + '/';
|
|
|
|
|
}
|
|
|
|
|
return domain + key;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* invoke the imageView2 api of Qiniu
|
|
|
|
|
* @param {Object} api params
|
|
|
|
|
* @param {String} key of file
|
|
|
|
|
* @return {String} url of processed image
|
|
|
|
|
*/
|
|
|
|
|
this.imageView2 = function(op, key) {
|
|
|
|
|
var mode = op.mode || '',
|
|
|
|
|
w = op.w || '',
|
|
|
|
|
h = op.h || '',
|
|
|
|
|
q = op.q || '',
|
|
|
|
|
format = op.format || '';
|
|
|
|
|
if (!mode) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!w && !h) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var imageUrl = 'imageView2/' + mode;
|
|
|
|
|
imageUrl += w ? '/w/' + w : '';
|
|
|
|
|
imageUrl += h ? '/h/' + h : '';
|
|
|
|
|
imageUrl += q ? '/q/' + q : '';
|
|
|
|
|
imageUrl += format ? '/format/' + format : '';
|
|
|
|
|
if (key) {
|
|
|
|
|
imageUrl = this.getUrl(key) + '?' + imageUrl;
|
|
|
|
|
}
|
|
|
|
|
return imageUrl;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* invoke the imageMogr2 api of Qiniu
|
|
|
|
|
* @param {Object} api params
|
|
|
|
|
* @param {String} key of file
|
|
|
|
|
* @return {String} url of processed image
|
|
|
|
|
*/
|
|
|
|
|
this.imageMogr2 = function(op, key) {
|
|
|
|
|
var auto_orient = op['auto-orient'] || '',
|
|
|
|
|
thumbnail = op.thumbnail || '',
|
|
|
|
|
strip = op.strip || '',
|
|
|
|
|
gravity = op.gravity || '',
|
|
|
|
|
crop = op.crop || '',
|
|
|
|
|
quality = op.quality || '',
|
|
|
|
|
rotate = op.rotate || '',
|
|
|
|
|
format = op.format || '',
|
|
|
|
|
blur = op.blur || '';
|
|
|
|
|
//Todo check option
|
|
|
|
|
|
|
|
|
|
var imageUrl = 'imageMogr2';
|
|
|
|
|
|
|
|
|
|
imageUrl += auto_orient ? '/auto-orient' : '';
|
|
|
|
|
imageUrl += thumbnail ? '/thumbnail/' + thumbnail : '';
|
|
|
|
|
imageUrl += strip ? '/strip' : '';
|
|
|
|
|
imageUrl += gravity ? '/gravity/' + gravity : '';
|
|
|
|
|
imageUrl += quality ? '/quality/' + quality : '';
|
|
|
|
|
imageUrl += crop ? '/crop/' + crop : '';
|
|
|
|
|
imageUrl += rotate ? '/rotate/' + rotate : '';
|
|
|
|
|
imageUrl += format ? '/format/' + format : '';
|
|
|
|
|
imageUrl += blur ? '/blur/' + blur : '';
|
|
|
|
|
|
|
|
|
|
if (key) {
|
|
|
|
|
imageUrl = this.getUrl(key) + '?' + imageUrl;
|
|
|
|
|
}
|
|
|
|
|
return imageUrl;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* invoke the watermark api of Qiniu
|
|
|
|
|
* @param {Object} api params
|
|
|
|
|
* @param {String} key of file
|
|
|
|
|
* @return {String} url of processed image
|
|
|
|
|
*/
|
|
|
|
|
this.watermark = function(op, key) {
|
|
|
|
|
var mode = op.mode;
|
|
|
|
|
if (!mode) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var imageUrl = 'watermark/' + mode;
|
|
|
|
|
|
|
|
|
|
if (mode === 1) {
|
|
|
|
|
var image = op.image || '';
|
|
|
|
|
if (!image) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
imageUrl += image ? '/image/' + this.URLSafeBase64Encode(image) : '';
|
|
|
|
|
} else if (mode === 2) {
|
|
|
|
|
var text = op.text ? op.text : '',
|
|
|
|
|
font = op.font ? op.font : '',
|
|
|
|
|
fontsize = op.fontsize ? op.fontsize : '',
|
|
|
|
|
fill = op.fill ? op.fill : '';
|
|
|
|
|
if (!text) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
imageUrl += text ? '/text/' + this.URLSafeBase64Encode(text) : '';
|
|
|
|
|
imageUrl += font ? '/font/' + this.URLSafeBase64Encode(font) : '';
|
|
|
|
|
imageUrl += fontsize ? '/fontsize/' + fontsize : '';
|
|
|
|
|
imageUrl += fill ? '/fill/' + this.URLSafeBase64Encode(fill) : '';
|
|
|
|
|
} else {
|
|
|
|
|
// Todo mode3
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dissolve = op.dissolve || '',
|
|
|
|
|
gravity = op.gravity || '',
|
|
|
|
|
dx = op.dx || '',
|
|
|
|
|
dy = op.dy || '';
|
|
|
|
|
|
|
|
|
|
imageUrl += dissolve ? '/dissolve/' + dissolve : '';
|
|
|
|
|
imageUrl += gravity ? '/gravity/' + gravity : '';
|
|
|
|
|
imageUrl += dx ? '/dx/' + dx : '';
|
|
|
|
|
imageUrl += dy ? '/dy/' + dy : '';
|
|
|
|
|
|
|
|
|
|
if (key) {
|
|
|
|
|
imageUrl = this.getUrl(key) + '?' + imageUrl;
|
|
|
|
|
}
|
|
|
|
|
return imageUrl;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* invoke the imageInfo api of Qiniu
|
|
|
|
|
* @param {String} key of file
|
|
|
|
|
* @return {Object} image info
|
|
|
|
|
*/
|
|
|
|
|
this.imageInfo = function(key) {
|
|
|
|
|
if (!key) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
var url = this.getUrl(key) + '?imageInfo';
|
|
|
|
|
var xhr = this.createAjax();
|
|
|
|
|
var info;
|
|
|
|
|
var that = this;
|
|
|
|
|
xhr.open('GET', url, false);
|
|
|
|
|
xhr.onreadystatechange = function() {
|
|
|
|
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
|
|
|
info = that.parseJSON(xhr.responseText);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
xhr.send();
|
|
|
|
|
return info;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* invoke the exif api of Qiniu
|
|
|
|
|
* @param {String} key of file
|
|
|
|
|
* @return {Object} image exif
|
|
|
|
|
*/
|
|
|
|
|
this.exif = function(key) {
|
|
|
|
|
if (!key) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
var url = this.getUrl(key) + '?exif';
|
|
|
|
|
var xhr = this.createAjax();
|
|
|
|
|
var info;
|
|
|
|
|
var that = this;
|
|
|
|
|
xhr.open('GET', url, false);
|
|
|
|
|
xhr.onreadystatechange = function() {
|
|
|
|
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
|
|
|
info = that.parseJSON(xhr.responseText);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
xhr.send();
|
|
|
|
|
return info;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* invoke the exif or imageInfo api of Qiniu
|
|
|
|
|
* according with type param
|
|
|
|
|
* @param {String} ['exif'|'imageInfo']type of info
|
|
|
|
|
* @param {String} key of file
|
|
|
|
|
* @return {Object} image exif or info
|
|
|
|
|
*/
|
|
|
|
|
this.get = function(type, key) {
|
|
|
|
|
if (!key || !type) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (type === 'exif') {
|
|
|
|
|
return this.exif(key);
|
|
|
|
|
} else if (type === 'imageInfo') {
|
|
|
|
|
return this.imageInfo(key);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* invoke api of Qiniu like a pipeline
|
|
|
|
|
* @param {Array of Object} params of a series api call
|
|
|
|
|
* each object in array is options of api which name is set as 'fop' property
|
|
|
|
|
* each api's output will be next api's input
|
|
|
|
|
* @param {String} key of file
|
|
|
|
|
* @return {String|Boolean} url of processed image
|
|
|
|
|
*/
|
|
|
|
|
this.pipeline = function(arr, key) {
|
|
|
|
|
var isArray = Object.prototype.toString.call(arr) === '[object Array]';
|
|
|
|
|
var option, errOp, imageUrl = '';
|
|
|
|
|
if (isArray) {
|
|
|
|
|
for (var i = 0, len = arr.length; i < len; i++) {
|
|
|
|
|
option = arr[i];
|
|
|
|
|
if (!option.fop) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
switch (option.fop) {
|
|
|
|
|
case 'watermark':
|
|
|
|
|
imageUrl += this.watermark(option) + '|';
|
|
|
|
|
break;
|
|
|
|
|
case 'imageView2':
|
|
|
|
|
imageUrl += this.imageView2(option) + '|';
|
|
|
|
|
break;
|
|
|
|
|
case 'imageMogr2':
|
|
|
|
|
imageUrl += this.imageMogr2(option) + '|';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
errOp = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (errOp) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (key) {
|
|
|
|
|
imageUrl = this.getUrl(key) + '?' + imageUrl;
|
|
|
|
|
var length = imageUrl.length;
|
|
|
|
|
if (imageUrl.slice(length - 1) === '|') {
|
|
|
|
|
imageUrl = imageUrl.slice(0, length - 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return imageUrl;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var Qiniu = new QiniuJsSDK();
|
|
|
|
|
|
|
|
|
|
global.Qiniu = Qiniu;
|
|
|
|
|
|
|
|
|
|
global.QiniuJsSDK = QiniuJsSDK;
|
|
|
|
|
|
|
|
|
|
})( window );
|