/*! * 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 = '', 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 += '
'+msg+'
'; } } 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 );