From 56b92af787dab61cf2192d6818c4295c4b5733c0 Mon Sep 17 00:00:00 2001 From: Schoorens Date: Fri, 9 Feb 2024 14:28:05 +0100 Subject: [PATCH] Declare jsu for typescript import compatibility refs #34417 (#9) Declare jsu for typescript import compatibility refs #34417 #9 --- .gitignore | 2 +- dist/jsu.min.js | 2 +- gulpfile.js | 11 +- karma.conf.js => karma.conf.ts | 24 +- package.json | 4 +- src/jsu.js | 501 ++++++++++++++------------------- src/lib/chunked-upload.js | 22 +- test-main.js | 25 -- tests/test_jsu.spec.js | 9 +- tests/test_upload.spec.js | 11 +- tests/test_xhr.spec.js | 4 +- 11 files changed, 269 insertions(+), 346 deletions(-) rename karma.conf.js => karma.conf.ts (78%) delete mode 100644 test-main.js diff --git a/.gitignore b/.gitignore index 0e5e230..53cc2b0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ __pycache__ pip-log.txt # Unit test / coverage reports -.coverage +coverage/ .tox nosetests.xml diff --git a/dist/jsu.min.js b/dist/jsu.min.js index b64a5b4..11ae8b9 100644 --- a/dist/jsu.min.js +++ b/dist/jsu.min.js @@ -1 +1 @@ -window.console||(window.console={}),window.console.log||(window.console.log=function(){}),window.console.error||(window.console.error=window.console.log),window.console.debug||(window.console.debug=window.console.log),window.console.info||(window.console.info=window.console.log),window.console.warn||(window.console.warn=window.console.log);const VERSION=8,jsu=window.jsu?window.jsu:{version:8};window.jsu=jsu;const shouldBeDefined=function(e){return 8>jsu.version||!(e in jsu)};if(8!=jsu.version&&(console.warn('Another version of jsu.js was already imported, all attributes will be set by most recent version. Version: "8", other version: "'+jsu.version+'".'),8>jsu.version&&(jsu.version=8)),shouldBeDefined("getCookie")&&(jsu.getCookie=function(e,t){if(document.cookie.length>0){let t=document.cookie.indexOf(e+"=");if(-1!=t){t=t+e.length+1;let n=document.cookie.indexOf(";",t);return-1==n&&(n=document.cookie.length),window.unescape(document.cookie.substring(t,n))}}return void 0!==t?t:""},jsu.setCookie=function(e,t,n){const s=new Date;s.setDate(s.getDate()+(n||360));const o=0===window.location.href.indexOf("https://")?"; secure; samesite=none":"";document.cookie=e+"="+window.escape(t)+"; expires="+s.toUTCString()+"; path=/"+o}),shouldBeDefined("strip")&&(jsu.strip=function(e,t){if(!e)return e;const n=void 0!==t?t:" \n\r\t ";let s=0;for(;s=0&&-1!=n.indexOf(e[o]);)o--;return e.substring(s,o+1)}),shouldBeDefined("slugify")&&(jsu.slugify=function(e){return e.toString().toLowerCase().replace(/\s+/g,"-").replace(/[^-\w]+/g,"").replace(/-+/g,"-").replace(/^-+/,"").replace(/-+$/,"")}),shouldBeDefined("stripHTML")&&(jsu.stripHTML=function(e){if(!e)return e;const t=document.createElement("div");return t.innerHTML=e,t.textContent}),shouldBeDefined("decodeHTML")&&(jsu.decodeHTML=function(e){if(!e)return"";const t=document.createElement("div");return t.innerHTML=e,0===t.childNodes.length?"":t.childNodes[0].nodeValue}),shouldBeDefined("escapeHTML")&&(jsu.escapeHTML=function(e){if(!e)return e;let t=e.toString();return t=(t=(t=(t=t.replace(/(&)/g,"&")).replace(/(<)/g,"<")).replace(/(>)/g,">")).replace(/(\n)/g,"
")}),shouldBeDefined("escapeAttribute")&&(jsu.escapeAttribute=function(e){if(!e)return e;let t=e.toString();return t=(t=(t=t.replace(/(")/g,""")).replace(/(')/g,"'")).replace(/(\n)/g," ")}),shouldBeDefined("getClickPosition")&&(jsu.getClickPosition=function(e,t){let n=t,s=0,o=0;for(;null!=n;)s+=n.offsetLeft,o+=n.offsetTop,n=n.offsetParent;return{x:e.pageX-s,y:e.pageY-o}}),shouldBeDefined("onDOMLoad")&&(jsu.onDOMLoad=function(e){"complete"===document.readyState||"interactive"===document.readyState?setTimeout(e,1):document.addEventListener("DOMContentLoaded",e)}),shouldBeDefined("httpRequest")&&(jsu.httpRequest=function(e){const t=e.params?e.params:{};e.cache||(t._=(new Date).getTime());const n=e.method?e.method.toUpperCase():"GET";let s=e.url?e.url:"";const o=e.headers?e.headers:{};if(!/^(GET|HEAD|OPTIONS|TRACE)$/.test(n)){const e=jsu.getCookie("csrftoken");e&&(o["X-CSRFToken"]=e)}const r=[];for(const e in t)if(t[e]instanceof Array)for(const n of t[e])r.push(encodeURIComponent(e)+"="+encodeURIComponent(n));else r.push(encodeURIComponent(e)+"="+encodeURIComponent(t[e]));let i;if(r.length>0&&(s+=(-1===s.indexOf("?")?"?":"&")+r.join("&")),e.jsonData)o["Content-Type"]="application/json; charset=UTF-8",i=e.data;else if(e.data instanceof FormData)i=e.data;else if(e.data){i=new FormData;for(const t in e.data)if(e.data[t]instanceof Array)for(const n of e.data[t])i.append(t+"[]",n);else i.append(t,e.data[t])}else i=null;const a=new XMLHttpRequest;e.progress&&a.upload&&a.upload.addEventListener("progress",e.progress,!1),e.callback&&(a.addEventListener("readystatechange",function(){if(this.readyState!==XMLHttpRequest.DONE)return;if(a._callbackCalled)return;let t;if(a._callbackCalled=!0,e.json)if(""===this.responseText)t={error:"No response.",empty:!0,raw:this.responseText};else try{t=JSON.parse(this.responseText)}catch(e){t={error:"Failed to parse json response: "+e,raw:this.responseText}}else t=this.responseText;e.callback(this,t)}),a.addEventListener("error",function(t){if(a._callbackCalled)return;a._callbackCalled=!0;const n=t.error||t.message||(t.detail?t.detail.error||t.detail.message:"Unknown error");e.callback(this,{error:n})})),a.open(n,s,!e.synchronous);for(const e in o)if(o[e]instanceof Array)for(const t of o[e])a.setRequestHeader(e,t);else a.setRequestHeader(e,o[e]);return a.send(i),a}),shouldBeDefined("compareVersions")&&(jsu.compareVersions=function(e,t,n){t="="==t?"==":t;const s=e.split("."),o=n.split("."),r=Math.max(s.length,o.length);for(let e=0;en)return-1}return 0}),shouldBeDefined("setObjectAttributes")&&(jsu.setObjectAttributes=function(e,t,n){if(t){"translations"in t&&(jsu.addTranslations(t.translations),delete t.translations);for(const s in t)n&&-1==n.indexOf(s)||(e[s]=t[s])}}),shouldBeDefined("getWebglContext")&&(jsu.getWebglContext=function(e,t,n){if(window.WebGLRenderingContext)try{let s;return(s="safari"===n?e.getContext("webgl",t)||e.getContext("experimental-webgl",t):e.getContext("webgl2",t)||e.getContext("webgl",t)||e.getContext("experimental-webgl",t))||(console.log("Failed to initialize WebGL context. Your browser does not support Webgl context."),null)}catch(e){return console.log("WebGL context is supported but may be disable, please check your browser configuration."),null}return console.log("Your browser does not support Webgl context"),null}),shouldBeDefined("isInIframe")&&(jsu.isInIframe=function(){return!(!window.frameElement||"IFRAME"!=window.frameElement.nodeName)}),shouldBeDefined("ignoreUntilFocusChanges")&&(jsu.ignoreUntilFocusChanges=!1,jsu.attemptFocus=function(e){if(!this.isFocusable(e))return!1;jsu.ignoreUntilFocusChanges=!0;try{e.focus()}catch(t){console.log("Failed to focus element.",e,t)}return jsu.ignoreUntilFocusChanges=!1,document.activeElement===e},jsu.isFocusable=function(e){if(e.tabIndex>0||0===e.tabIndex&&null!==e.getAttribute("tabIndex"))return!0;if(e.disabled)return!1;switch(e.nodeName){case"A":return!!e.href&&"ignore"!=e.rel;case"INPUT":return"hidden"!=e.type&&"file"!=e.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}},jsu.focusFirstDescendant=function(e){for(let t=0;t=0;t--){const n=e.childNodes[t];if(jsu.attemptFocus(n)||jsu.focusLastDescendant(n))return!0}return!1}),shouldBeDefined("userAgent")&&(jsu.userAgent=window.navigator&&window.navigator.userAgent?window.navigator.userAgent.toLowerCase():"unknown",jsu.userAgentData=null,jsu._getOSInfo=function(){let e,t;if(!e&&window.navigator&&window.navigator.platform){const n=window.navigator.platform.toLowerCase();-1==n.indexOf("ipad")&&-1==n.indexOf("iphone")&&-1==n.indexOf("ipod")||(e="ios",t=parseFloat((""+(/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))||!1)}if(!e&&window.navigator&&window.navigator.appVersion){const t=window.navigator.appVersion.toLowerCase();-1!=t.indexOf("win")?e="windows":-1!=t.indexOf("mac")?e="macos":-1==t.indexOf("x11")&&-1==t.indexOf("linux")||(e="linux")}jsu.osName=e||"unknown",jsu.osVersion=t||0},jsu._getOSInfo(),jsu._getBrowserInfo=function(){let e,t=0;if(window.navigator&&window.navigator.userAgentData&&window.navigator.userAgentData.brands)for(let n=0;n=45,t=("chrome"===jsu.browserName||"chromium"===jsu.browserName)&&jsu.browserVersion>=57,n="safari"===jsu.browserName&&jsu.browserVersion>=16,s="edge"===jsu.browserName&&jsu.browserVersion>=79;return e||n||t||s}),shouldBeDefined("isLivestreamingAvailable")&&(jsu.isLivestreamingAvailable=function(){const e=("chrome"===jsu.browserName||"chromium"===jsu.browserName)&&jsu.browserVersion>=57,t="edge"===jsu.browserName&&jsu.browserVersion>=79,n="safari"===jsu.browserName&&jsu.browserVersion>=16;return e||t||n}),shouldBeDefined("translate")&&(jsu._translations={en:{}},jsu._currentLang="en",jsu._currentCatalog=jsu._translations.en,jsu.useLang=function(e){jsu._currentLang=e,jsu._translations[e]||(jsu._translations[e]={}),jsu._currentCatalog=jsu._translations[e]},jsu.getCurrentLang=function(){return jsu._currentLang},jsu.getCurrentCatalog=function(){return jsu._currentCatalog},jsu.addTranslations=function(e,t){let n;t?(jsu._translations[t]||(jsu._translations[t]={}),n=jsu._translations[t]):n=jsu._currentCatalog;for(const t of Object.keys(e))e[t]&&(n[t]=e[t])},jsu.translate=function(e,t){const n=(t?t+"":"")+e;return n in jsu._currentCatalog?jsu._currentCatalog[n]:"en"!=jsu._currentLang&&n in jsu._translations.en?jsu._translations.en[n]:e},jsu.translateHTML=function(e,t){const n=jsu.translate(e,t);return jsu.escapeHTML(n)},jsu.translateAttribute=function(e,t){const n=jsu.translate(e,t);return jsu.escapeAttribute(n)},jsu.getDateDisplay=function(e){if(!e)return"";const t=/^(\d+)-(\d+)-(\d+)(?: |T)(\d+):(\d+):(\d+)$/.exec(e);if(!t)return e;const n=t[1];let s=null;switch(t[2]){case"01":s=jsu.translate("January");break;case"02":s=jsu.translate("February");break;case"03":s=jsu.translate("March");break;case"04":s=jsu.translate("April");break;case"05":s=jsu.translate("May");break;case"06":s=jsu.translate("June");break;case"07":s=jsu.translate("July");break;case"08":s=jsu.translate("August");break;case"09":s=jsu.translate("September");break;case"10":s=jsu.translate("October");break;case"11":s=jsu.translate("November");break;case"12":s=jsu.translate("December")}const o=t[3];let r,i=parseInt(t[4],10),a=parseInt(t[5],10);if(!s||isNaN(i)||isNaN(a))return e;if(a<10&&(a="0"+a),"en"!==jsu._currentLang)i<10&&(i="0"+i),r=i+":"+a;else{let e;i<12?(e="AM",i||(i=12)):(e="PM",i>12&&(i-=12)),r=i+":"+a+" "+e}return o+" "+s+" "+n+" "+jsu.translate("at")+" "+r},jsu.getSizeDisplay=function(e){if(!e||isNaN(e))return"0 "+jsu.translate("B");let t="";return e>1e3&&(t="k",(e/=1e3)>1e3&&(t="M",(e/=1e3)>1e3&&(t="G",(e/=1e3)>1e3&&(e/=1e3,t="T")))),e.toFixed(1)+" "+t+jsu.translate("B")}),shouldBeDefined("getHashFromRequest")&&(jsu.getHashFromRequest=function(e,t,n,s){let o=e+t;if(o&&o.includes("_=")&&((o=o.replace(/_=[0-9]+&?/g,"")).endsWith("?")||o.endsWith("&"))&&(o=o.substring(0,o.length-1)),n instanceof FormData||n instanceof URLSearchParams)o+=JSON.stringify(Object.fromEntries(n));else if(n instanceof Blob)o+="blob-"+n.size;else if(n instanceof ArrayBuffer)o+="arraybuffer-"+n.byteLength;else if(n)try{o+=JSON.stringify(n)}catch(e){o+=JSON.stringify(new Date)}return s&&(o+=JSON.stringify(s)),o}),shouldBeDefined("xhrOverride")){jsu.xhrOverride=!0;const e=[];XMLHttpRequest.noIntercept=!1;const t=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(e,n){return this._method=e,this._url=n,t.apply(this,arguments)};const n=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.setRequestHeader=function(e,t){const s=n.apply(this,arguments);return this._headers||(this._headers={}),this._headers[e]||(this._headers[e]=[]),this._headers[e].push(t),s};const s=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.send=function(t){const n=jsu.getHashFromRequest(this._method,this._url,t,this._headers);if(e.includes(n)){const e="Duplicated request aborted";return Object.defineProperty(this,"statusText",{value:e,writable:!1}),this.dispatchEvent(new CustomEvent("error",{detail:{error:e,message:e}}))}return e.push(n),this.noIntercept||this.addEventListener&&this.addEventListener("readystatechange",function(){this.readyState===XMLHttpRequest.DONE&&e.splice(e.indexOf(n),1)},!1),s.apply(this,arguments)}}class ChunkedUpload{constructor(e){const t=["file","uploadURL","completeURL"],n={debugMode:null,extraHeaders:{},extraData:{},maxRetry:30,retryDelay:1e4,chunkSize:2e7,fileNameSuffix:"",progressCallback:null,retryCallback:null,successCallback:null,failureCallback:null};for(const n of t){if(!e[n])throw new Error('A mandatory argument is missing: "'+n+'".');this[n]=e[n]}for(const t in n)this[t]=void 0!==e[t]?e[t]:n[t];null===this.debugMode&&-1!==window.location.hash.indexOf("debug")&&(this.debugMode=!0),this.sendFile()}log(){this.debugMode&&console.log.apply(null,arguments)}onProgress(e){this.progressCallback&&this.progressCallback(e)}onRetry(e,t){let n;if(this.retryCallback&&(n=this.retryCallback(e)),void 0===n){const e=this;n=new Promise(function(t){e.log("Retrying in "+e.retryDelay+" ms..."),setTimeout(t,e.retryDelay)})}n.then(t)}onSuccess(){this.successCallback&&this.successCallback(this.uploadId)}onFailure(e){this.failureCallback&&this.failureCallback(e)}sendFile(){if(this.onProgress(0),this.uploadId=null,this.fileName=this.file.name,this.fileNameSuffix){const e=this.fileName.lastIndexOf(".");this.fileName=e>0?this.fileName.substring(0,e)+this.fileNameSuffix+this.fileName.substring(e):"file"+this.fileNameSuffix+".tmp"}this.log("Number of chunk to send:",Math.ceil(this.file.size/this.chunkSize),this.chunkSize,this.file.size),this.sendNextChunk(0,0)}sendNextChunk(e,t){this.log("Sending chunk:","start:",e,"total size:",this.file.size,"retries:",t);const n=Math.min(e+this.chunkSize,this.file.size),s=new FormData;s.append("file",this.file.slice(e,n),this.fileName),s.append("retries",t),this.uploadId&&s.append("upload_id",this.uploadId);for(const e in this.extraData)s.append(e,this.extraData[e]);const o=(n-e)/this.file.size,r=Object.assign(this.extraHeaders,{"Content-Range":"bytes "+e+"-"+(n-1)+"/"+this.file.size});this.log("Content-Range",r["Content-Range"]);const i=this;jsu.httpRequest({method:"POST",url:this.uploadURL,headers:r,data:s,json:!0,progress:function(t){if(t.lengthComputable){let n=e/i.file.size;t.total&&(n+=o*(t.loaded/t.total)),n=Math.floor(95*n),i.log("Progress:",n,o,t.loaded,t.total),i.onProgress(n)}},callback:function(s,o){if(200==s.status&&o.upload_id){i.log("Chunk sent",o),i.uploadId=o.upload_id;const e=n;e>=i.file.size?i.completeUpload(0):i.sendNextChunk(e,0)}else console.error("Failed to send chunk:",o),t0){let t=document.cookie.indexOf(e+"=");if(-1!=t){t=t+e.length+1;let s=document.cookie.indexOf(";",t);return-1==s&&(s=document.cookie.length),window.decodeURIComponent(document.cookie.substring(t,s))}}return t}setCookie(e,t,s=360){const n=new Date;n.setDate(n.getDate()+s);const i=0===window.location.href.indexOf("https://")?"; secure; samesite=none":"";document.cookie=e+"="+window.decodeURIComponent(t)+"; expires="+n.toUTCString()+"; path=/"+i}strip(e,t=""){if(!e)return e;const s=""!==t?t:" \n\r\t ";let n=0;for(;n=0&&-1!=s.indexOf(e[i]);)i--;return e.substring(n,i+1)}slugify(e){return e.toString().toLowerCase().replace(/\s+/g,"-").replace(/[^-\w]+/g,"").replace(/-+/g,"-").replace(/^-+/,"").replace(/-+$/,"")}stripHTML(e){if(!e)return e;const t=document.createElement("div");return t.innerHTML=e,t.textContent}decodeHTML(e){if(!e)return"";const t=document.createElement("div");return t.innerHTML=e,0===t.childNodes.length?"":t.childNodes[0].nodeValue}escapeHTML(e){if(!e)return e;let t=e.toString();return t=(t=(t=(t=t.replace(/(&)/g,"&")).replace(/(<)/g,"<")).replace(/(>)/g,">")).replace(/(\n)/g,"
")}escapeAttribute(e){if(!e)return e;let t=e.toString();return t=(t=(t=t.replace(/(")/g,""")).replace(/(')/g,"'")).replace(/(\n)/g," ")}getClickPosition(e,t){let s=t,n=0,i=0;for(;null!=s;)n+=s.offsetLeft,i+=s.offsetTop,s=s.offsetParent;return{x:e.pageX-n,y:e.pageY-i}}onDOMLoad(e){"complete"===document.readyState||"interactive"===document.readyState?setTimeout(e,1):document.addEventListener("DOMContentLoaded",e)}httpRequest(e){const t=e.params?e.params:{};e.cache||(t._=(new Date).getTime());const s=e.method?e.method.toUpperCase():"GET";let n=e.url?e.url:"";const i=e.headers?e.headers:{};if(!/^(GET|HEAD|OPTIONS|TRACE)$/.test(s)){const e=this.getCookie("csrftoken");e&&(i["X-CSRFToken"]=e)}const r=[];for(const e in t)if(t[e]instanceof Array)for(const s of t[e])r.push(encodeURIComponent(e)+"="+encodeURIComponent(s));else r.push(encodeURIComponent(e)+"="+encodeURIComponent(t[e]));let o;if(r.length>0&&(n+=(-1===n.indexOf("?")?"?":"&")+r.join("&")),e.jsonData)i["Content-Type"]="application/json; charset=UTF-8",o=e.data;else if(e.data instanceof FormData)o=e.data;else if(e.data){o=new FormData;for(const t in e.data)if(e.data[t]instanceof Array)for(const s of e.data[t])o.append(t+"[]",s);else o.append(t,e.data[t])}else o=null;const a=new XMLHttpRequest;e.progress&&a.upload&&a.upload.addEventListener("progress",e.progress,!1),e.callback&&(a.addEventListener("readystatechange",function(){if(this.readyState!==XMLHttpRequest.DONE)return;if(a._callbackCalled)return;let t;if(a._callbackCalled=!0,e.json)if(""===this.responseText)t={error:"No response.",empty:!0,raw:this.responseText};else try{t=JSON.parse(this.responseText)}catch(e){t={error:"Failed to parse json response: "+e,raw:this.responseText}}else t=this.responseText;e.callback(this,t)}),a.addEventListener("error",function(t){if(a._callbackCalled)return;a._callbackCalled=!0;const s=t.error||t.message||(t.detail?t.detail.error||t.detail.message:"Unknown error");e.callback(this,{error:s})})),a.open(s,n,!e.synchronous);for(const e in i)if(i[e]instanceof Array)for(const t of i[e])a.setRequestHeader(e,t);else a.setRequestHeader(e,i[e]);return a.send(o),a}compareVersions(e,t,s){t="="==t?"==":t;const n=e.split("."),i=s.split("."),r=Math.max(n.length,i.length);for(let e=0;es)return-1}return 0}setObjectAttributes(e,t,s=null){if(t){"translations"in t&&(this.addTranslations(t.translations),delete t.translations);for(const n in t)s&&-1==s.indexOf(n)||(e[n]=t[n])}}getWebglContext(e,t={},s=""){if(window.WebGLRenderingContext)try{let n;return(n="safari"===s?e.getContext("webgl",t)||e.getContext("experimental-webgl",t):e.getContext("webgl2",t)||e.getContext("webgl",t)||e.getContext("experimental-webgl",t))||(console.log("Failed to initialize WebGL context. Your browser does not support Webgl context."),null)}catch(e){return console.log("WebGL context is supported but may be disable, please check your browser configuration."),null}return console.log("Your browser does not support Webgl context"),null}isInIframe(){return!(!window.frameElement||"IFRAME"!=window.frameElement.nodeName)}attemptFocus(e){if(!this.isFocusable(e))return!1;this.ignoreUntilFocusChanges=!0;try{e.focus()}catch(t){console.log("Failed to focus element.",e,t)}return this.ignoreUntilFocusChanges=!1,document.activeElement===e}isFocusable(e){if(e.tabIndex>0||0===e.tabIndex&&null!==e.getAttribute("tabIndex"))return!0;if(e.disabled)return!1;switch(e.nodeName){case"A":return!!e.href&&"ignore"!=e.rel;case"INPUT":return"hidden"!=e.type&&"file"!=e.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}}focusFirstDescendant(e){for(let t=0;t=0;t--){const s=e.childNodes[t];if(this.attemptFocus(s)||this.focusLastDescendant(s))return!0}return!1}_getOSInfo(){let e,t;if(!e&&window.navigator&&window.navigator.platform){const s=window.navigator.platform.toLowerCase();-1==s.indexOf("ipad")&&-1==s.indexOf("iphone")&&-1==s.indexOf("ipod")||(e="ios",t=parseFloat((""+(/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))||!1)}if(!e&&window.navigator&&window.navigator.appVersion){const t=window.navigator.appVersion.toLowerCase();-1!=t.indexOf("win")?e="windows":-1!=t.indexOf("mac")?e="macos":-1==t.indexOf("x11")&&-1==t.indexOf("linux")||(e="linux")}this.osName=e||"unknown",this.osVersion=t||0}_getBrowserInfo(){let e,t=0;if(window.navigator&&window.navigator.userAgentData&&window.navigator.userAgentData.brands)for(let s=0;s=45,t=("chrome"===this.browserName||"chromium"===this.browserName)&&this.browserVersion>=57,s="safari"===this.browserName&&this.browserVersion>=16,n="edge"===this.browserName&&this.browserVersion>=79;return e||t||n||s}isLivestreamingAvailable(){const e=("chrome"===this.browserName||"chromium"===this.browserName)&&this.browserVersion>=57,t="edge"===this.browserName&&this.browserVersion>=79,s="safari"===this.browserName&&this.browserVersion>=16;return e||t||s}useLang(e){this._currentLang=e,this._translations[e]||(this._translations[e]={}),this._currentCatalog=this._translations[e]}getCurrentLang(){return this._currentLang}getCurrentCatalog(){return this._currentCatalog}addTranslations(e,t=""){let s;t?(this._translations[t]||(this._translations[t]={}),s=this._translations[t]):s=this._currentCatalog;for(const t of Object.keys(e))e[t]&&(s[t]=e[t])}translate(e,t=""){const s=(t?t+"":"")+e;return s in this._currentCatalog?this._currentCatalog[s]:"en"!=this._currentLang&&s in this._translations.en?this._translations.en[s]:e}translateHTML(e,t=""){const s=this.translate(e,t);return this.escapeHTML(s)}translateAttribute(e,t=""){const s=this.translate(e,t);return this.escapeAttribute(s)}getDateDisplay(e){if(!e)return"";const t=/^(\d+)-(\d+)-(\d+)(?: |T)(\d+):(\d+):(\d+)$/.exec(e);if(!t)return e;const s=t[1];let n=null;switch(t[2]){case"01":n=this.translate("January");break;case"02":n=this.translate("February");break;case"03":n=this.translate("March");break;case"04":n=this.translate("April");break;case"05":n=this.translate("May");break;case"06":n=this.translate("June");break;case"07":n=this.translate("July");break;case"08":n=this.translate("August");break;case"09":n=this.translate("September");break;case"10":n=this.translate("October");break;case"11":n=this.translate("November");break;case"12":n=this.translate("December")}const i=t[3];let r,o=parseInt(t[4],10),a=parseInt(t[5],10);if(!n||isNaN(o)||isNaN(a))return e;if(a<10&&(a="0"+a),"en"!==this._currentLang)o<10&&(o="0"+o),r=o+":"+a;else{let e;o<12?(e="AM",o||(o=12)):(e="PM",o>12&&(o-=12)),r=o+":"+a+" "+e}return i+" "+n+" "+s+" "+this.translate("at")+" "+r}getSizeDisplay(e){if(!e||isNaN(e))return"0 "+this.translate("B");let t="";return e>1e3&&(t="k",(e/=1e3)>1e3&&(t="M",(e/=1e3)>1e3&&(t="G",(e/=1e3)>1e3&&(e/=1e3,t="T")))),e.toFixed(1)+" "+t+this.translate("B")}getHashFromRequest(e,t,s,n={}){let i=e+t;if(i&&i.includes("_=")&&((i=i.replace(/_=[0-9]+&?/g,"")).endsWith("?")||i.endsWith("&"))&&(i=i.substring(0,i.length-1)),s instanceof FormData||s instanceof URLSearchParams)i+=JSON.stringify(Object.fromEntries(s));else if(s instanceof Blob)i+="blob-"+s.size;else if(s instanceof ArrayBuffer)i+="arraybuffer-"+s.byteLength;else if(s)try{i+=JSON.stringify(s)}catch(e){i+=JSON.stringify(new Date)}return Object.keys(n).length&&(i+=JSON.stringify(n)),i}_overrideHttpRequest(){window.xhrOverride=!0;const e=[];XMLHttpRequest.noIntercept=!1;const t=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(e,s){return this._method=e,this._url=s,t.apply(this,arguments)};const s=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.setRequestHeader=function(e,t){const n=s.apply(this,arguments);return this._headers||(this._headers={}),this._headers[e]||(this._headers[e]=[]),this._headers[e].push(t),n};const n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.send=function(t){const s=window.jsu.getHashFromRequest(this._method,this._url,t,this._headers);if(e.includes(s)){const e="Duplicated request aborted";return Object.defineProperty(this,"statusText",{value:e,writable:!1}),this.dispatchEvent(new CustomEvent("error",{detail:{error:e,message:e}}))}return e.push(s),this.noIntercept||this.addEventListener&&this.addEventListener("readystatechange",function(){this.readyState===XMLHttpRequest.DONE&&e.splice(e.indexOf(s),1)},!1),n.apply(this,arguments)}}}new JavaScriptUtilities;export class ChunkedUpload{constructor(e){const t=["file","uploadURL","completeURL"],s={debugMode:null,extraHeaders:{},extraData:{},maxRetry:30,retryDelay:1e4,chunkSize:2e7,fileNameSuffix:"",progressCallback:null,retryCallback:null,successCallback:null,failureCallback:null,inTest:!1};for(const s of t){if(!e[s])throw new Error('A mandatory argument is missing: "'+s+'".');this[s]=e[s]}for(const t in s)this[t]=void 0!==e[t]?e[t]:s[t];null===this.debugMode&&-1!==window.location.hash.indexOf("debug")&&(this.debugMode=!0),this.sendFile()}log(){this.debugMode&&!this.inTest&&console.log.apply(null,arguments)}onProgress(e){this.progressCallback&&this.progressCallback(e)}onRetry(e,t){let s;if(this.retryCallback&&(s=this.retryCallback(e)),void 0===s){const e=this;s=new Promise(function(t){e.log("Retrying in "+e.retryDelay+" ms..."),setTimeout(t,e.retryDelay)})}s.then(t)}onSuccess(){this.successCallback&&this.successCallback(this.uploadId)}onFailure(e){this.failureCallback&&this.failureCallback(e)}sendFile(){if(this.onProgress(0),this.uploadId=null,this.fileName=this.file.name,this.fileNameSuffix){const e=this.fileName.lastIndexOf(".");this.fileName=e>0?this.fileName.substring(0,e)+this.fileNameSuffix+this.fileName.substring(e):"file"+this.fileNameSuffix+".tmp"}this.log("Number of chunk to send:",Math.ceil(this.file.size/this.chunkSize),this.chunkSize,this.file.size),this.sendNextChunk(0,0)}sendNextChunk(e,t){this.log("Sending chunk:","start:",e,"total size:",this.file.size,"retries:",t);const s=Math.min(e+this.chunkSize,this.file.size),n=new FormData;n.append("file",this.file.slice(e,s),this.fileName),n.append("retries",t),this.uploadId&&n.append("upload_id",this.uploadId);for(const e in this.extraData)n.append(e,this.extraData[e]);const i=(s-e)/this.file.size,r=Object.assign(this.extraHeaders,{"Content-Range":"bytes "+e+"-"+(s-1)+"/"+this.file.size});this.log("Content-Range",r["Content-Range"]);const o=this;jsu.httpRequest({method:"POST",url:this.uploadURL,headers:r,data:n,json:!0,progress:function(t){if(t.lengthComputable){let s=e/o.file.size;t.total&&(s+=i*(t.loaded/t.total)),s=Math.floor(95*s),o.log("Progress:",s,i,t.loaded,t.total),o.onProgress(s)}},callback:function(n,i){if(200==n.status&&i.upload_id){o.log("Chunk sent",i),o.uploadId=i.upload_id;const e=s;e>=o.file.size?o.completeUpload(0):o.sendNextChunk(e,0)}else o.inTest||console.log(o.inTest),t { config.set({ - // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', @@ -30,8 +29,8 @@ module.exports = function (config) { // preprocess matching files before serving them to the browser // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor preprocessors: { - 'src/**/*.js': ['coverage'], - 'tests/*.spec.js': ['browserify'] + 'src/**/*.js': ['browserify', 'coverage'], + 'tests/*.spec.js': ['browserify'], }, @@ -60,7 +59,13 @@ module.exports = function (config) { // start these browsers // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless'], + browsers: ['ChromeHeadlessNoSandbox'], + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: 'ChromeHeadless', + flags: ['--no-sandbox'] + } + }, // Continuous Integration mode @@ -72,11 +77,14 @@ module.exports = function (config) { concurrency: 1, browserify: { - debug: true // display file line error + debug: true, + plugin: ['esmify'] }, coverageReporter: { - type: 'text', - dir: '.coverage' + reporters:[ + {type: 'lcov', dir:'coverage/', includeAllSources: true}, + {type: 'text', includeAllSources: true} + ] } }); }; diff --git a/package.json b/package.json index 5fd1b76..b8baded 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,9 @@ "version": "8", "description": "", "main": "gulpfile.js", + "type": "module", "scripts": { - "test": "karma start karma.conf.js", + "test": "karma start", "build": "gulp build", "lint": "eslint ." }, @@ -12,6 +13,7 @@ "license": "LGPLv3", "devDependencies": { "eslint": "latest", + "esmify": "latest", "gulp": "latest", "gulp-concat": "latest", "gulp-minify": "latest", diff --git a/src/jsu.js b/src/jsu.js index 19767ff..7488138 100644 --- a/src/jsu.js +++ b/src/jsu.js @@ -1,47 +1,39 @@ /******************************************* * jsu: JavaScript Utilities * *******************************************/ - -/* ---- Polyfill definitions ---- */ - -// Add console functions for old browsers -if (!window.console) { - window.console = {}; -} -if (!window.console.log) { - window.console.log = function () {}; -} -if (!window.console.error) { - window.console.error = window.console.log; -} -if (!window.console.debug) { - window.console.debug = window.console.log; -} -if (!window.console.info) { - window.console.info = window.console.log; -} -if (!window.console.warn) { - window.console.warn = window.console.log; -} - -/* ---- jsu object definition ---- */ -const VERSION = 8; -const jsu = window.jsu ? window.jsu : {version: VERSION}; -window.jsu = jsu; -const shouldBeDefined = function (attribute) { - // Function to handle other versions of jsu objects - const allow = VERSION > jsu.version || !(attribute in jsu); - return allow; -}; -if (VERSION != jsu.version) { - console.warn('Another version of jsu.js was already imported, all attributes will be set by most recent version. Version: "' + VERSION + '", other version: "' + jsu.version + '".'); - if (VERSION > jsu.version) { - jsu.version = VERSION; +const VERSION = 9; + +export default class JavaScriptUtilities { + + constructor () { + this.version = VERSION; + this.ignoreUntilFocusChanges = false; + this.userAgent = window.navigator && window.navigator.userAgent ? window.navigator.userAgent.toLowerCase() : 'unknown'; + this.userAgentData = null; + this.osName = ''; + this.osVersion = ''; + this.browserName = ''; + this.browserVersion = ''; + this.isTactile = false; + this.isMobile = false; + this._translations = { en: {} }; + this._currentLang = 'en'; + this._currentCatalog = this._translations.en; + this._getOSInfo(); + this._getBrowserInfo(); + this._overrideHttpRequest(); + if (!window.jsu) { + window.jsu = this; + } else { + if (VERSION != window.jsu.version) { + console.warn('Another version of jsu.js was already imported, jsu will be replaced by most recent version. Version: "' + VERSION + '", other version: "' + window.jsu.version + '".'); + } + if (window.jsu.version < VERSION) { + window.jsu = this; + } + } } -} - -if (shouldBeDefined('getCookie')) { - jsu.getCookie = function (name, defaultValue) { + getCookie (name, defaultValue = '') { if (document.cookie.length > 0) { let cStart = document.cookie.indexOf(name + '='); if (cStart != -1) { @@ -50,25 +42,22 @@ if (shouldBeDefined('getCookie')) { if (cEnd == -1) { cEnd = document.cookie.length; } - return window.unescape(document.cookie.substring(cStart, cEnd)); + return window.decodeURIComponent(document.cookie.substring(cStart, cEnd)); } } - return defaultValue !== undefined ? defaultValue : ''; - }; - jsu.setCookie = function (name, value, expireDays) { + return defaultValue; + } + setCookie (name, value, expireDays = 360) { const exDate = new Date(); - exDate.setDate(exDate.getDate() + (expireDays ? expireDays : 360)); + exDate.setDate(exDate.getDate() + expireDays); const secure = window.location.href.indexOf('https://') === 0 ? '; secure; samesite=none' : ''; - document.cookie = name + '=' + window.escape(value) + '; expires=' + exDate.toUTCString() + '; path=/' + secure; - }; -} - -if (shouldBeDefined('strip')) { - jsu.strip = function (str, characters) { + document.cookie = name + '=' + window.decodeURIComponent(value) + '; expires=' + exDate.toUTCString() + '; path=/' + secure; + } + strip (str, characters = '') { if (!str) { return str; } - const crs = characters !== undefined ? characters : ' \n\r\t '; // the last space is a non secable space + const crs = characters !== '' ? characters : ' \n\r\t '; // the last space is a non secable space let start = 0; while (start < str.length && crs.indexOf(str[start]) != -1) { start++; @@ -78,32 +67,24 @@ if (shouldBeDefined('strip')) { end--; } return str.substring(start, end + 1); - }; -} - -if (shouldBeDefined('slugify')) { - jsu.slugify = function (text) { + } + slugify (text) { return text.toString().toLowerCase() .replace(/\s+/g, '-') // Replace spaces with - .replace(/[^-\w]+/g, '') // Remove all non-word chars .replace(/-+/g, '-') // Replace multiple - with single - .replace(/^-+/, '') // Trim - from start of text .replace(/-+$/, ''); // Trim - from end of text - }; -} - -if (shouldBeDefined('stripHTML')) { - jsu.stripHTML = function (html) { + } + stripHTML (html) { if (!html) { return html; } const div = document.createElement('div'); div.innerHTML = html; return div.textContent; - }; -} -if (shouldBeDefined('decodeHTML')) { - jsu.decodeHTML = function (html) { + } + decodeHTML (html) { if (!html) { return ''; } @@ -111,10 +92,8 @@ if (shouldBeDefined('decodeHTML')) { div.innerHTML = html; // handle case of empty input return div.childNodes.length === 0 ? '' : div.childNodes[0].nodeValue; - }; -} -if (shouldBeDefined('escapeHTML')) { - jsu.escapeHTML = function (text) { + } + escapeHTML (text) { if (!text) { return text; } @@ -124,10 +103,8 @@ if (shouldBeDefined('escapeHTML')) { result = result.replace(/(>)/g, '>'); result = result.replace(/(\n)/g, '
'); return result; - }; -} -if (shouldBeDefined('escapeAttribute')) { - jsu.escapeAttribute = function (attr) { + } + escapeAttribute (attr) { if (!attr) { return attr; } @@ -136,11 +113,8 @@ if (shouldBeDefined('escapeAttribute')) { result = result.replace(/(')/g, '''); result = result.replace(/(\n)/g, ' '); return result; - }; -} - -if (shouldBeDefined('getClickPosition')) { - jsu.getClickPosition = function (evt, dom) { + } + getClickPosition (evt, dom) { let element = dom, xOffset = 0, yOffset = 0; // get canvas offset while (element !== null && element !== undefined) { @@ -149,11 +123,8 @@ if (shouldBeDefined('getClickPosition')) { element = element.offsetParent; } return { x: evt.pageX - xOffset, y: evt.pageY - yOffset }; - }; -} - -if (shouldBeDefined('onDOMLoad')) { - jsu.onDOMLoad = function (callback) { + } + onDOMLoad (callback) { // see if DOM is already available if (document.readyState === 'complete' || document.readyState === 'interactive') { // call on next available tick @@ -161,11 +132,8 @@ if (shouldBeDefined('onDOMLoad')) { } else { document.addEventListener('DOMContentLoaded', callback); } - }; -} - -if (shouldBeDefined('httpRequest')) { - jsu.httpRequest = function (args) { + } + httpRequest (args) { /* args = { method: 'GET', url: '', @@ -188,7 +156,7 @@ if (shouldBeDefined('httpRequest')) { const headers = args.headers ? args.headers : {}; const noCSRF = (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); if (!noCSRF) { - const csrftoken = jsu.getCookie('csrftoken'); + const csrftoken = this.getCookie('csrftoken'); if (csrftoken) { headers['X-CSRFToken'] = csrftoken; } @@ -287,11 +255,8 @@ if (shouldBeDefined('httpRequest')) { } xhr.send(formData); return xhr; - }; -} - -if (shouldBeDefined('compareVersions')) { - jsu.compareVersions = function (v1, comparator, v2) { + } + compareVersions (v1, comparator, v2) { // Function to compare versions like "4.5.6" comparator = comparator == '=' ? '==' : comparator; const v1parts = v1.split('.'), v2parts = v2.split('.'); @@ -306,17 +271,14 @@ if (shouldBeDefined('compareVersions')) { } } return 0; - }; -} - -if (shouldBeDefined('setObjectAttributes')) { - jsu.setObjectAttributes = function (obj, data, allowedAttributes) { + } + setObjectAttributes (obj, data, allowedAttributes = null) { if (!data) { return; } if ('translations' in data) { // Update translations - jsu.addTranslations(data.translations); + this.addTranslations(data.translations); delete data.translations; } // Override fields @@ -325,11 +287,8 @@ if (shouldBeDefined('setObjectAttributes')) { obj[attr] = data[attr]; } } - }; -} - -if (shouldBeDefined('getWebglContext')) { - jsu.getWebglContext = function (canvas, options, browserName) { + } + getWebglContext (canvas, options = {}, browserName = '') { if (window.WebGLRenderingContext) { try { let webglContext; @@ -350,36 +309,27 @@ if (shouldBeDefined('getWebglContext')) { } console.log('Your browser does not support Webgl context'); return null; - }; -} - -if (shouldBeDefined('isInIframe')) { - jsu.isInIframe = function () { + } + isInIframe () { if (window.frameElement && window.frameElement.nodeName == 'IFRAME') { return true; } return false; - }; -} - - -/* Focus related functions */ -if (shouldBeDefined('ignoreUntilFocusChanges')) { - jsu.ignoreUntilFocusChanges = false; - jsu.attemptFocus = function (element) { + } + attemptFocus (element) { if (!this.isFocusable(element)) { return false; } - jsu.ignoreUntilFocusChanges = true; + this.ignoreUntilFocusChanges = true; try { element.focus(); } catch (e) { console.log('Failed to focus element.', element, e); } - jsu.ignoreUntilFocusChanges = false; + this.ignoreUntilFocusChanges = false; return (document.activeElement === element); - }; - jsu.isFocusable = function (element) { + } + isFocusable (element) { if (element.tabIndex > 0 || (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)) { return true; } @@ -400,35 +350,27 @@ if (shouldBeDefined('ignoreUntilFocusChanges')) { default: return false; } - }; - jsu.focusFirstDescendant = function (element) { + } + focusFirstDescendant (element) { for (let i = 0; i < element.childNodes.length; i++) { const child = element.childNodes[i]; - if (jsu.attemptFocus(child) || - jsu.focusFirstDescendant(child)) { + if (this.attemptFocus(child) || + this.focusFirstDescendant(child)) { return true; } } return false; - }; - jsu.focusLastDescendant = function (element) { + } + focusLastDescendant (element) { for (let i = element.childNodes.length - 1; i >= 0; i--) { const child = element.childNodes[i]; - if (jsu.attemptFocus(child) || jsu.focusLastDescendant(child)) { + if (this.attemptFocus(child) || this.focusLastDescendant(child)) { return true; } } return false; - }; -} - - -/* User agent and platform related functions */ -if (shouldBeDefined('userAgent')) { - jsu.userAgent = window.navigator && window.navigator.userAgent ? window.navigator.userAgent.toLowerCase() : 'unknown'; - jsu.userAgentData = null; - - jsu._getOSInfo = function () { + } + _getOSInfo () { let name; let version; /* TODO: Find a synchronous way to get platform. @@ -439,7 +381,7 @@ if (shouldBeDefined('userAgent')) { if (data.platform == 'Mac OS X') { name = 'macos'; } - jsu.userAgentPlatform = name; + this.userAgentPlatform = name; } }); } @@ -461,12 +403,10 @@ if (shouldBeDefined('userAgent')) { name = 'linux'; } } - jsu.osName = name ? name : 'unknown'; - jsu.osVersion = version ? version : 0; - }; - jsu._getOSInfo(); - - jsu._getBrowserInfo = function () { + this.osName = name ? name : 'unknown'; + this.osVersion = version ? version : 0; + } + _getBrowserInfo () { // get browser name and version let name; let version = 0.0; @@ -484,12 +424,12 @@ if (shouldBeDefined('userAgent')) { } else if (name == 'microsoft edge') { name = 'edge'; } - jsu.userAgentData = window.navigator.userAgentData; + this.userAgentData = window.navigator.userAgentData; break; } } } - if (!name && jsu.userAgent) { + if (!name && this.userAgent) { const extractVersion = function (ua, re) { const matches = ua.match(re); if (matches && !isNaN(parseInt(matches[1], 10))) { @@ -506,7 +446,7 @@ if (shouldBeDefined('userAgent')) { } return 0.0; }; - const ua = jsu.userAgent; + const ua = this.userAgent; if (ua.indexOf('firefox') != -1) { name = 'firefox'; version = extractVersion(ua, /firefox\/(\d+)\.(\d+)/); @@ -547,66 +487,54 @@ if (shouldBeDefined('userAgent')) { } // detect type of device if (window.navigator && window.navigator.userAgentData) { - jsu.isMobile = Boolean(window.navigator.userAgentData.mobile); + this.isMobile = Boolean(window.navigator.userAgentData.mobile); } else { - const ua = jsu.userAgent; - jsu.isMobile = ua.indexOf('iphone') != -1 || ua.indexOf('ipod') != -1 || ua.indexOf('android') != -1 || ua.indexOf('iemobile') != -1 || ua.indexOf('opera mobi') != -1 || ua.indexOf('opera mini') != -1 || ua.indexOf('windows ce') != -1 || ua.indexOf('fennec') != -1 || ua.indexOf('series60') != -1 || ua.indexOf('symbian') != -1 || ua.indexOf('blackberry') != -1 || window.orientation !== undefined || (window.navigator && window.navigator.platform == 'iPad'); + const ua = this.userAgent; + this.isMobile = ua.indexOf('iphone') != -1 || ua.indexOf('ipod') != -1 || ua.indexOf('android') != -1 || ua.indexOf('iemobile') != -1 || ua.indexOf('opera mobi') != -1 || ua.indexOf('opera mini') != -1 || ua.indexOf('windows ce') != -1 || ua.indexOf('fennec') != -1 || ua.indexOf('series60') != -1 || ua.indexOf('symbian') != -1 || ua.indexOf('blackberry') != -1 || window.orientation !== undefined || (window.navigator && window.navigator.platform == 'iPad'); } - jsu.isTactile = document.documentElement && 'ontouchstart' in document.documentElement; - - jsu.browserName = name ? name : 'unknown'; - jsu.browserVersion = version; - }; - jsu._getBrowserInfo(); -} + this.isTactile = document.documentElement && 'ontouchstart' in document.documentElement; -if (shouldBeDefined('isRecordingAvailable')) { - jsu.isRecordingAvailable = function () { - const isFirefoxCompat = jsu.browserName === 'firefox' && jsu.browserVersion >= 45; - const isChromeCompat = (jsu.browserName === 'chrome' || jsu.browserName === 'chromium') && jsu.browserVersion >= 57; - const isSafariCompat = jsu.browserName === 'safari' && jsu.browserVersion >= 16; - const isEdgeCompat = jsu.browserName === 'edge' && jsu.browserVersion >= 79; - return isFirefoxCompat || isSafariCompat || isChromeCompat || isEdgeCompat; - }; -} -if (shouldBeDefined('isLivestreamingAvailable')) { - jsu.isLivestreamingAvailable = function () { - const isChromeCompat = (jsu.browserName === 'chrome' || jsu.browserName === 'chromium') && jsu.browserVersion >= 57; - const isEdgeCompat = jsu.browserName === 'edge' && jsu.browserVersion >= 79; - const isSafariCompat = jsu.browserName === 'safari' && jsu.browserVersion >= 16; + this.browserName = name ? name : 'unknown'; + this.browserVersion = version; + } + isRecordingAvailable () { + const isFirefoxCompat = this.browserName === 'firefox' && this.browserVersion >= 45; + const isChromeCompat = (this.browserName === 'chrome' || this.browserName === 'chromium') && this.browserVersion >= 57; + const isSafariCompat = this.browserName === 'safari' && this.browserVersion >= 16; + const isEdgeCompat = this.browserName === 'edge' && this.browserVersion >= 79; + return isFirefoxCompat || isChromeCompat || isEdgeCompat || isSafariCompat; + } + isLivestreamingAvailable () { + const isChromeCompat = (this.browserName === 'chrome' || this.browserName === 'chromium') && this.browserVersion >= 57; + const isEdgeCompat = this.browserName === 'edge' && this.browserVersion >= 79; + const isSafariCompat = this.browserName === 'safari' && this.browserVersion >= 16; return isChromeCompat || isEdgeCompat || isSafariCompat; - }; -} -/* Translations related functions */ -if (shouldBeDefined('translate')) { - jsu._translations = { en: {} }; - jsu._currentLang = 'en'; - jsu._currentCatalog = jsu._translations.en; - jsu.useLang = function (lang) { - jsu._currentLang = lang; - if (!jsu._translations[lang]) { - jsu._translations[lang] = {}; - } - jsu._currentCatalog = jsu._translations[lang]; - }; - jsu.getCurrentLang = function () { - return jsu._currentLang; - }; - jsu.getCurrentCatalog = function () { - return jsu._currentCatalog; - }; - jsu.addTranslations = function (translations, lang) { + } + useLang (lang) { + this._currentLang = lang; + if (!this._translations[lang]) { + this._translations[lang] = {}; + } + this._currentCatalog = this._translations[lang]; + } + getCurrentLang () { + return this._currentLang; + } + getCurrentCatalog () { + return this._currentCatalog; + } + addTranslations (translations, lang = '') { // translations keys must be text or context + '\u0004' + text // example for translations: // {'text source 1': 'translated text 1', 'context\u0004text source 2': 'translated text 2'} let catalog; if (lang) { - if (!jsu._translations[lang]) { - jsu._translations[lang] = {}; + if (!this._translations[lang]) { + this._translations[lang] = {}; } - catalog = jsu._translations[lang]; + catalog = this._translations[lang]; } else { - catalog = jsu._currentCatalog; + catalog = this._currentCatalog; } for (const text of Object.keys(translations)) { if (translations[text]) { @@ -614,27 +542,27 @@ if (shouldBeDefined('translate')) { catalog[text] = translations[text]; } } - }; - jsu.translate = function (text, context) { + } + translate (text, context = '') { const key = (context ? context + '\u0004' : '') + text; - if (key in jsu._currentCatalog) { - return jsu._currentCatalog[key]; - } else if (jsu._currentLang != 'en' && key in jsu._translations.en) { - return jsu._translations.en[key]; + if (key in this._currentCatalog) { + return this._currentCatalog[key]; + } else if (this._currentLang != 'en' && key in this._translations.en) { + return this._translations.en[key]; } return text; - }; - jsu.translateHTML = function (text, context) { + } + translateHTML (text, context = '') { // translate and escape text for HTML usage - const trans = jsu.translate(text, context); - return jsu.escapeHTML(trans); - }; - jsu.translateAttribute = function (text, context) { + const trans = this.translate(text, context); + return this.escapeHTML(trans); + } + translateAttribute (text, context = '') { // translate and escape text for HTML attribute usage - const trans = jsu.translate(text, context); - return jsu.escapeAttribute(trans); - }; - jsu.getDateDisplay = function (date) { + const trans = this.translate(text, context); + return this.escapeAttribute(trans); + } + getDateDisplay (date) { // date formats: "YYYY-MM-DDTHH:MM:SS" or "YYYY-MM-DD HH:MM:SS" if (!date) { return ''; @@ -648,18 +576,18 @@ if (shouldBeDefined('translate')) { // month let month = null; switch (arr[2]) { - case '01': month = jsu.translate('January'); break; - case '02': month = jsu.translate('February'); break; - case '03': month = jsu.translate('March'); break; - case '04': month = jsu.translate('April'); break; - case '05': month = jsu.translate('May'); break; - case '06': month = jsu.translate('June'); break; - case '07': month = jsu.translate('July'); break; - case '08': month = jsu.translate('August'); break; - case '09': month = jsu.translate('September'); break; - case '10': month = jsu.translate('October'); break; - case '11': month = jsu.translate('November'); break; - case '12': month = jsu.translate('December'); break; + case '01': month = this.translate('January'); break; + case '02': month = this.translate('February'); break; + case '03': month = this.translate('March'); break; + case '04': month = this.translate('April'); break; + case '05': month = this.translate('May'); break; + case '06': month = this.translate('June'); break; + case '07': month = this.translate('July'); break; + case '08': month = this.translate('August'); break; + case '09': month = this.translate('September'); break; + case '10': month = this.translate('October'); break; + case '11': month = this.translate('November'); break; + case '12': month = this.translate('December'); break; } // day const day = arr[3]; @@ -676,7 +604,7 @@ if (shouldBeDefined('translate')) { minute = '0' + minute; } let time; - if (jsu._currentLang !== 'en') { + if (this._currentLang !== 'en') { // 24 hours time format if (hour < 10) { hour = '0' + hour; @@ -698,11 +626,11 @@ if (shouldBeDefined('translate')) { } time = hour + ':' + minute + ' ' + moment; } - return day + ' ' + month + ' ' + year + ' ' + jsu.translate('at') + ' ' + time; - }; - jsu.getSizeDisplay = function (value) { + return day + ' ' + month + ' ' + year + ' ' + this.translate('at') + ' ' + time; + } + getSizeDisplay (value) { if (!value || isNaN(value)) { - return '0 ' + jsu.translate('B'); + return '0 ' + this.translate('B'); } let unit = ''; if (value > 1000) { @@ -721,11 +649,9 @@ if (shouldBeDefined('translate')) { } } } - return value.toFixed(1) + ' ' + unit + jsu.translate('B'); - }; -} -if (shouldBeDefined('getHashFromRequest')) { - jsu.getHashFromRequest = function (method, url, data, headers) { + return value.toFixed(1) + ' ' + unit + this.translate('B'); + } + getHashFromRequest (method, url, data, headers = {}) { let hash = method + url; if (hash && hash.includes('_=')) { hash = hash.replace(/_=[0-9]+&?/g, ''); @@ -746,66 +672,67 @@ if (shouldBeDefined('getHashFromRequest')) { hash += JSON.stringify(new Date()); } } - if (headers) { + if (Object.keys(headers).length) { hash += JSON.stringify(headers); } return hash; - }; -} -if (shouldBeDefined('xhrOverride')) { - jsu.xhrOverride = true; - // Avoid same ajax call if server doesn't respond yet - - const lastsXHRCalls = []; - XMLHttpRequest.noIntercept = false; + } + _overrideHttpRequest () { + window.xhrOverride = true; + // Avoid same ajax call if server doesn't respond yet - const open = XMLHttpRequest.prototype.open; + const lastsXHRCalls = []; + XMLHttpRequest.noIntercept = false; - XMLHttpRequest.prototype.open = function (method, url) { - this._method = method; - this._url = url; - return open.apply(this, arguments); - }; + const open = XMLHttpRequest.prototype.open; - const setRequestHeader = XMLHttpRequest.prototype.setRequestHeader; + XMLHttpRequest.prototype.open = function (method, url) { + this._method = method; + this._url = url; + return open.apply(this, arguments); + }; - XMLHttpRequest.prototype.setRequestHeader = function (header, value) { - const returnData = setRequestHeader.apply(this, arguments); + const setRequestHeader = XMLHttpRequest.prototype.setRequestHeader; - if (!this._headers) { - this._headers = {}; - } + XMLHttpRequest.prototype.setRequestHeader = function (header, value) { + const returnData = setRequestHeader.apply(this, arguments); - if (!this._headers[header]) { - this._headers[header] = []; - } - this._headers[header].push(value); - return returnData; - }; - const send = XMLHttpRequest.prototype.send; + if (!this._headers) { + this._headers = {}; + } - XMLHttpRequest.prototype.send = function (data) { - const hash = jsu.getHashFromRequest(this._method, this._url, data, this._headers); - if (lastsXHRCalls.includes(hash)) { - const message = 'Duplicated request aborted'; - Object.defineProperty(this, 'statusText', { - value: message, - writable: false - }); - return this.dispatchEvent(new CustomEvent('error', {'detail': {'error': message, 'message': message}})); - } else { - lastsXHRCalls.push(hash); - } - function onReadyStateChange () { - if (this.readyState === XMLHttpRequest.DONE) { - lastsXHRCalls.splice(lastsXHRCalls.indexOf(hash), 1); + if (!this._headers[header]) { + this._headers[header] = []; } - } - if (!this.noIntercept) { - if (this.addEventListener) { - this.addEventListener('readystatechange', onReadyStateChange, false); + this._headers[header].push(value); + return returnData; + }; + const send = XMLHttpRequest.prototype.send; + + XMLHttpRequest.prototype.send = function (data) { + const hash = window.jsu.getHashFromRequest(this._method, this._url, data, this._headers); + if (lastsXHRCalls.includes(hash)) { + const message = 'Duplicated request aborted'; + Object.defineProperty(this, 'statusText', { + value: message, + writable: false + }); + return this.dispatchEvent(new CustomEvent('error', {'detail': {'error': message, 'message': message}})); + } else { + lastsXHRCalls.push(hash); } - } - return send.apply(this, arguments); - }; + function onReadyStateChange () { + if (this.readyState === XMLHttpRequest.DONE) { + lastsXHRCalls.splice(lastsXHRCalls.indexOf(hash), 1); + } + } + if (!this.noIntercept) { + if (this.addEventListener) { + this.addEventListener('readystatechange', onReadyStateChange, false); + } + } + return send.apply(this, arguments); + }; + } } +new JavaScriptUtilities(); diff --git a/src/lib/chunked-upload.js b/src/lib/chunked-upload.js index 67a7568..e65b86c 100644 --- a/src/lib/chunked-upload.js +++ b/src/lib/chunked-upload.js @@ -4,7 +4,7 @@ Module to upload a file by chunks. /* global jsu */ // eslint-disable-next-line no-unused-vars -class ChunkedUpload { +export class ChunkedUpload { constructor (options) { // Get and check options const mandatoryArgs = [ @@ -51,7 +51,10 @@ class ChunkedUpload { 'successCallback': null, // Function. The function to call if the upload fails. Arguments: . - 'failureCallback': null + 'failureCallback': null, + + // Avoid console logs while in test + 'inTest': false }; for (const arg of mandatoryArgs) { if (!options[arg]) { @@ -70,7 +73,7 @@ class ChunkedUpload { } log () { - if (this.debugMode) { + if (this.debugMode && !this.inTest) { console.log.apply(null, arguments); } } @@ -176,10 +179,15 @@ class ChunkedUpload { self.sendNextChunk(nextStart, 0); } } else { - console.error('Failed to send chunk:', response); + if (!self.inTest) { + console.log(self.inTest); + //console.error('Failed to send chunk:', response); + } if (retries < self.maxRetry) { if (response.offset !== undefined && start !== response.offset) { - console.warn('Jumping from offset ' + start + ' to ' + response.offset); + if (!self.inTest) { + console.warn('Jumping from offset ' + start + ' to ' + response.offset); + } start = response.offset; } self.onRetry(xhr, self.sendNextChunk.bind(self, start, retries + 1)); @@ -214,7 +222,9 @@ class ChunkedUpload { self.onProgress(100); self.onSuccess(); } else { - console.error('Failed to call complete:', response); + if (!self.inTest) { + console.error('Failed to call complete:', response); + } if (retries < self.maxRetry) { self.onRetry(xhr, self.completeUpload.bind(self, retries + 1)); } else { diff --git a/test-main.js b/test-main.js deleted file mode 100644 index 9f68d75..0000000 --- a/test-main.js +++ /dev/null @@ -1,25 +0,0 @@ -/* globals require */ -const allTestFiles = []; -const TEST_REGEXP = /(spec|test)\.js$/i; - -// Get a list of all the test files to include -Object.keys(window.__karma__.files).forEach(function (file) { - if (TEST_REGEXP.test(file)) { - // Normalize paths to RequireJS module names. - // If you require sub-dependencies of test files to be loaded as-is (requiring file extension) - // then do not normalize the paths - const normalizedTestModule = file.replace(/^\/base\/|\.js$/g, ''); - allTestFiles.push(normalizedTestModule); - } -}); - -require.config({ - // Karma serves files under /base, which is the basePath from your config file - baseUrl: '/base', - - // dynamically load all test files - deps: allTestFiles, - - // we have to kickoff jasmine, as it is asynchronous - callback: window.__karma__.start -}); diff --git a/tests/test_jsu.spec.js b/tests/test_jsu.spec.js index c2fb8e4..a78377b 100644 --- a/tests/test_jsu.spec.js +++ b/tests/test_jsu.spec.js @@ -1,12 +1,12 @@ -/* globals require, describe, it */ +/* globals require, describe, it, process */ const assert = require('assert'); require('./common.js'); -require('../src/jsu.js'); -const jsu = window.jsu; +import JavaScriptUtilities from '../src/jsu.js'; +const jsu = new JavaScriptUtilities(); describe('JSU', () => { it('should return correct version', () => { - assert(jsu.version === 8); + assert(jsu.version === 9); }); it('should set/get cookies', () => { jsu.setCookie('a', '1'); @@ -103,6 +103,7 @@ describe('JSU', () => { assert(obj.b); }); it('should getWebglContext', () => { + console.error(process.env); const testDatas = [ {'options': {}, 'browserName': 'chrome'}, {'options': {}, 'browserName': 'safari'} diff --git a/tests/test_upload.spec.js b/tests/test_upload.spec.js index dc3848a..6ee7bb2 100644 --- a/tests/test_upload.spec.js +++ b/tests/test_upload.spec.js @@ -1,10 +1,9 @@ -/* globals require, describe, it, ChunkedUpload */ +/* globals require, describe, it */ const assert = require('assert'); require('./common.js'); require('../src/jsu.js'); -require('../src/lib/chunked-upload.js'); - +import { ChunkedUpload } from '../src/lib/chunked-upload.js'; describe('Upload', () => { it('chunkedUpload success', async () => { @@ -17,7 +16,7 @@ describe('Upload', () => { let success = null; let msg = null; new ChunkedUpload({ - debugMode: true, + inTest: true, file: blob, uploadURL: 'http://localhost:9876/base/tests/mocking/upload-chunk-ok.json', completeURL: 'http://localhost:9876/base/tests/mocking/upload-complete-ok.json', @@ -56,7 +55,7 @@ describe('Upload', () => { let success = null; let msg = null; new ChunkedUpload({ - debugMode: true, + inTest: true, file: blob, uploadURL: 'http://localhost:9876?upload', completeURL: 'http://localhost:9876?complete', @@ -109,6 +108,7 @@ describe('Upload', () => { let success = null; let msg = null; new ChunkedUpload({ + inTest: true, file: blob, uploadURL: 'http://localhost:9876?upload', completeURL: 'http://localhost:9876?complete', @@ -148,6 +148,7 @@ describe('Upload', () => { let success = null; let msg = null; new ChunkedUpload({ + inTest: true, file: blob, uploadURL: 'http://localhost:9876/base/tests/mocking/upload-chunk-ok.json', completeURL: 'http://localhost:9876?complete', diff --git a/tests/test_xhr.spec.js b/tests/test_xhr.spec.js index a793c06..0364c97 100644 --- a/tests/test_xhr.spec.js +++ b/tests/test_xhr.spec.js @@ -2,8 +2,8 @@ const assert = require('assert'); require('./common.js'); -require('../src/jsu.js'); -const jsu = window.jsu; +import JavaScriptUtilities from '../src/jsu.js'; +const jsu = new JavaScriptUtilities(); describe('XHR', () => {