You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1418 lines
53 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*!
* 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 );