diff --git a/examples/index.html b/examples/index.html index 1b3a9ae..5816bd1 100644 --- a/examples/index.html +++ b/examples/index.html @@ -9,6 +9,8 @@

Example


+ Request hooks +
@@ -17,7 +19,7 @@

- + + + + + diff --git a/karma.conf.js b/karma.conf.js index 9adda23..bea3550 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -11,7 +11,7 @@ module.exports = function(config) { // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha', 'chai'], + frameworks: ['jasmine'], // list of files / patterns to load in the browser diff --git a/package-lock.json b/package-lock.json index 0b66e62..7ffe47b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -965,12 +965,6 @@ "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -1091,12 +1085,6 @@ "fill-range": "^7.0.1" } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", @@ -1155,20 +1143,6 @@ "integrity": "sha512-AHpONWuGFWO8yY9igdXH94tikM6ERS84286r0cAMAXYFtJBk76lhiMhtCxBJNBZsD6hzlvpWZ2AtbVFEkf4JQA==", "dev": true }, - "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", - "dev": true, - "requires": { - "assertion-error": "^1.0.1", - "check-error": "^1.0.1", - "deep-eql": "^3.0.0", - "get-func-name": "^2.0.0", - "pathval": "^1.0.0", - "type-detect": "^4.0.0" - } - }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -1186,12 +1160,6 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, "chokidar": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", @@ -1244,12 +1212,6 @@ "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", "dev": true }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -1382,15 +1344,6 @@ "ms": "2.0.0" } }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -1418,12 +1371,6 @@ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2096,12 +2043,6 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -2137,12 +2078,6 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -2187,16 +2122,10 @@ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "http-errors": { @@ -2443,6 +2372,12 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "jasmine-core": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.7.1.tgz", + "integrity": "sha512-DH3oYDS/AUvvr22+xUBW62m1Xoy7tUlY1tsxKEJvl5JeJ7q8zd1K5bUwiOxdH+erj6l2vAMM3hV25Xs9/WrmuQ==", + "dev": true + }, "js-levenshtein": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.3.tgz", @@ -2557,21 +2492,13 @@ "which": "^1.2.1" } }, - "karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha1-7qrH/8DiAetjxGdEDStpx883eL8=", + "karma-jasmine": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.1.tgz", + "integrity": "sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw==", "dev": true, "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } + "jasmine-core": "^3.6.0" } }, "levn": { @@ -2607,9 +2534,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "log4js": { @@ -2726,36 +2653,6 @@ } } }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3036,12 +2933,6 @@ "pify": "^2.0.0" } }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true - }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -3708,12 +3599,6 @@ "prelude-ls": "~1.1.2" } }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index 4e1494d..2f082c5 100644 --- a/package.json +++ b/package.json @@ -34,18 +34,17 @@ "devDependencies": { "@babel/core": "^7.1.0", "@babel/preset-env": "^7.1.0", - "chai": "^4.1.2", "eslint": "^5.16.0", "eslint-config-airbnb-base": "^13.1.0", "eslint-plugin-import": "^2.16.0", "karma": "^4.2.0", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^2.2.0", - "karma-mocha": "^1.3.0", - "mocha": "^5.2.0", + "karma-jasmine": "^4.0.1", "prettier": "^1.16.4", "puppeteer": "^1.18.1", "rollup": "^0.63.2", "rollup-plugin-babel": "^4.0.3" - } + }, + "dependencies": {} } diff --git a/src/api.js b/src/api.js index 106ed0c..8942ce1 100644 --- a/src/api.js +++ b/src/api.js @@ -8,6 +8,20 @@ function isEmptyObject(obj) { return Object.keys(obj).length === 0 && obj.constructor === Object; } +function areValidRequestHooks(requestHooks) { + const isValid = Array.isArray(requestHooks) && requestHooks.every(requestHook => + typeof requestHook === 'function' + && requestHook.length === 2 + && requestHook(new XMLHttpRequest()) instanceof XMLHttpRequest + ); + + if (!isValid) { + console.warn('Request hooks should have the following signature: function requestHook(request, metadata) { return request; }'); + } + + return isValid; +} + const getFirstResult = result => result[0]; const getFirstResultIfLengthGtOne = result => { if (result.length > 1) { @@ -26,6 +40,15 @@ const MEDIATYPES = { PNG: "image/png" }; +/** + * A callback with the request instance and metadata information + * of the currently request being executed that should necessarily + * return the given request optionally modified. + * @typedef {function} RequestHook + * @param {XMLHttpRequest} request - The original XMLHttpRequest instance. + * @param {object} metadata - The metadata used by the request. + */ + /** * Class for interacting with DICOMweb RESTful services. */ @@ -40,6 +63,8 @@ class DICOMwebClient { * @param {String} options.username - Username * @param {String} options.password - Password * @param {Object} options.headers - HTTP headers + * @param {Array.} options.requestHooks - Request hooks. + * @param {Object} options.verbose - print to console request warnings and errors, default true */ constructor(options) { this.baseURL = options.url; @@ -78,11 +103,36 @@ class DICOMwebClient { this.stowURL = this.baseURL; } + if ("requestHooks" in options) { + this.requestHooks = options.requestHooks; + } + // Headers to pass to requests. this.headers = options.headers || {}; // Optional error interceptor callback to handle any failed request. this.errorInterceptor = options.errorInterceptor || function() {}; + + // Verbose - print to console request warnings and errors, default true + this.verbose = options.verbose === false ? false : true; + } + + /** + * Sets verbose flag. + * + * @param {Boolean} verbose + */ + setVerbose(verbose) { + this.verbose = verbose + } + + /** + * Gets verbose flag. + * + * @return {Boolean} verbose + */ + getVerbose() { + return this.verbose; } static _parseQueryParameters(params = {}) { @@ -103,15 +153,16 @@ class DICOMwebClient { * @param {String} method * @param {Object} headers * @param {Object} options + * @param {Array.} options.requestHooks - Request hooks. * @return {*} * @private */ _httpRequest(url, method, headers, options = {}) { - const {errorInterceptor} = this; + const { errorInterceptor, requestHooks } = this; return new Promise((resolve, reject) => { - const request = new XMLHttpRequest(); + let request = new XMLHttpRequest(); request.open(method, url, true); if ("responseType" in options) { request.responseType = options.responseType; @@ -141,24 +192,30 @@ class DICOMwebClient { }; // Handle response message - request.onreadystatechange = function onreadystatechange() { + request.onreadystatechange = () => { if (request.readyState === 4) { if (request.status === 200) { resolve(request.response); } else if (request.status === 202) { - console.warn("some resources already existed: ", request); + if (this.verbose) { + console.warn("some resources already existed: ", request); + } resolve(request.response); } else if (request.status === 204) { - console.warn("empty response for request: ", request); + if (this.verbose) { + console.warn("empty response for request: ", request); + } resolve([]); } else { - console.error("request failed: ", request); const error = new Error("request failed"); error.request = request; error.response = request.response; error.status = request.status; - console.error(error); - console.error(error.response); + if (this.verbose) { + console.error("request failed: ", request); + console.error(error); + console.error(error.response); + } errorInterceptor(error); @@ -174,6 +231,20 @@ class DICOMwebClient { } } + if (requestHooks && areValidRequestHooks(requestHooks)) { + const metadata = { method, url }; + const pipeRequestHooks = functions => (args) => functions.reduce((args, fn) => fn(args, metadata), args); + const pipedRequest = pipeRequestHooks(requestHooks); + request = pipedRequest(request); + } + + // Add withCredentials to request if needed + if ("withCredentials" in options) { + if (options.withCredentials) { + request.withCredentials = true; + } + } + if ("data" in options) { request.send(options.data); } else { @@ -192,10 +263,11 @@ class DICOMwebClient { * @return {*} * @private */ - _httpGet(url, headers, responseType, progressCallback) { + _httpGet(url, headers, responseType, progressCallback, withCredentials) { return this._httpRequest(url, "get", headers, { responseType, - progressCallback + progressCallback, + withCredentials }); } @@ -209,7 +281,7 @@ class DICOMwebClient { * @return {*} * @private */ - _httpGetApplicationJson(url, params = {}, progressCallback) { + _httpGetApplicationJson(url, params = {}, progressCallback, withCredentials) { let urlWithQueryParams = url; if (typeof params === "object") { @@ -223,7 +295,8 @@ class DICOMwebClient { urlWithQueryParams, headers, responseType, - progressCallback + progressCallback, + withCredentials ); } @@ -237,7 +310,7 @@ class DICOMwebClient { * @return {*} * @private */ - _httpGetApplicationPdf(url, params = {}, progressCallback) { + _httpGetApplicationPdf(url, params = {}, progressCallback, withCredentials) { let urlWithQueryParams = url; if (typeof params === "object") { @@ -251,7 +324,8 @@ class DICOMwebClient { urlWithQueryParams, headers, responseType, - progressCallback + progressCallback, + withCredentials ); } @@ -266,7 +340,7 @@ class DICOMwebClient { * @return {*} * @private */ - _httpGetImage(url, mediaTypes, params = {}, progressCallback) { + _httpGetImage(url, mediaTypes, params = {}, progressCallback, withCredentials) { let urlWithQueryParams = url; if (typeof params === "object") { @@ -294,7 +368,8 @@ class DICOMwebClient { urlWithQueryParams, headers, responseType, - progressCallback + progressCallback, + withCredentials ); } @@ -309,7 +384,7 @@ class DICOMwebClient { * @return {*} * @private */ - _httpGetText(url, mediaTypes, params = {}, progressCallback) { + _httpGetText(url, mediaTypes, params = {}, progressCallback, withCredentials) { let urlWithQueryParams = url; if (typeof params === "object") { @@ -337,7 +412,8 @@ class DICOMwebClient { urlWithQueryParams, headers, responseType, - progressCallback + progressCallback, + withCredentials ); } @@ -352,7 +428,7 @@ class DICOMwebClient { * @return {*} * @private */ - _httpGetVideo(url, mediaTypes, params = {}, progressCallback) { + _httpGetVideo(url, mediaTypes, params = {}, progressCallback, withCredentials) { let urlWithQueryParams = url; if (typeof params === "object") { @@ -379,7 +455,8 @@ class DICOMwebClient { urlWithQueryParams, headers, responseType, - progressCallback + progressCallback, + withCredentials ); } @@ -428,7 +505,8 @@ class DICOMwebClient { byteRange, params, rendered = false, - progressCallback + progressCallback, + withCredentials ) { const headers = {}; let supportedMediaTypes; @@ -464,7 +542,7 @@ class DICOMwebClient { supportedMediaTypes ); - return this._httpGet(url, headers, "arraybuffer", progressCallback).then( + return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then( multipartDecode ); } @@ -488,7 +566,8 @@ class DICOMwebClient { byteRange, params, rendered = false, - progressCallback + progressCallback, + withCredentials ) { const headers = {}; let supportedMediaTypes; @@ -521,7 +600,7 @@ class DICOMwebClient { supportedMediaTypes ); - return this._httpGet(url, headers, "arraybuffer", progressCallback).then( + return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then( multipartDecode ); } @@ -537,7 +616,7 @@ class DICOMwebClient { * @private * @returns {Promise} Content of HTTP message body parts */ - _httpGetMultipartApplicationDicom(url, mediaTypes, params, progressCallback) { + _httpGetMultipartApplicationDicom(url, mediaTypes, params, progressCallback, withCredentials) { const headers = {}; const defaultMediaType = "application/dicom"; const supportedMediaTypes = { @@ -572,7 +651,7 @@ class DICOMwebClient { supportedMediaTypes ); - return this._httpGet(url, headers, "arraybuffer", progressCallback).then( + return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then( multipartDecode ); } @@ -594,7 +673,8 @@ class DICOMwebClient { mediaTypes, byteRange, params, - progressCallback + progressCallback, + withCredentials ) { const headers = {}; const defaultMediaType = "application/octet-stream"; @@ -616,7 +696,7 @@ class DICOMwebClient { supportedMediaTypes ); - return this._httpGet(url, headers, "arraybuffer", progressCallback).then( + return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then( multipartDecode ); } @@ -631,10 +711,11 @@ class DICOMwebClient { * @private * @returns {Promise} Response */ - _httpPost(url, headers, data, progressCallback) { + _httpPost(url, headers, data, progressCallback, withCredentials) { return this._httpRequest(url, "post", headers, { data, - progressCallback + progressCallback, + withCredentials }); } @@ -648,9 +729,9 @@ class DICOMwebClient { * @private * @returns {Promise} Response */ - _httpPostApplicationJson(url, data, progressCallback) { + _httpPostApplicationJson(url, data, progressCallback, withCredentials) { const headers = { "Content-Type": MEDIATYPES.DICOM_JSON }; - return this._httpPost(url, headers, data, progressCallback); + return this._httpPost(url, headers, data, progressCallback, withCredentials); } /** @@ -849,11 +930,17 @@ class DICOMwebClient { */ searchForStudies(options = {}) { console.log("search for studies"); + let withCredentials = false; let url = `${this.qidoURL}/studies`; if ("queryParams" in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } - return this._httpGetApplicationJson(url); + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } + return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** @@ -872,7 +959,13 @@ class DICOMwebClient { } console.log(`retrieve metadata of study ${options.studyInstanceUID}`); const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/metadata`; - return this._httpGetApplicationJson(url); + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } + return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** @@ -893,7 +986,13 @@ class DICOMwebClient { if ("queryParams" in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } - return this._httpGetApplicationJson(url); + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } + return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** @@ -921,7 +1020,13 @@ class DICOMwebClient { const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ options.seriesInstanceUID }/metadata`; - return this._httpGetApplicationJson(url); + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } + return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** @@ -935,6 +1040,7 @@ class DICOMwebClient { */ searchForInstances(options = {}) { let url = this.qidoURL; + let withCredentials = false; if ("studyInstanceUID" in options) { url += `/studies/${options.studyInstanceUID}`; if ("seriesInstanceUID" in options) { @@ -954,7 +1060,12 @@ class DICOMwebClient { if ("queryParams" in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } - return this._httpGetApplicationJson(url); + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } + return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** Returns a WADO-URI URL for an instance @@ -1021,8 +1132,13 @@ class DICOMwebClient { const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ options.seriesInstanceUID }/instances/${options.sopInstanceUID}/metadata`; - - return this._httpGetApplicationJson(url); + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } + return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** @@ -1067,19 +1183,25 @@ class DICOMwebClient { }/frames/${options.frameNumbers.toString()}`; const { mediaTypes } = options; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } + if (!mediaTypes) { - return this._httpGetMultipartApplicationOctetStream(url); + return this._httpGetMultipartApplicationOctetStream(url, false, false, false, false, withCredentials); } const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType === MEDIATYPES.OCTET_STREAM) { - return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); + return this._httpGetMultipartApplicationOctetStream(url, mediaTypes, false, false, false, withCredentials); } else if (commonMediaType.startsWith("image")) { - return this._httpGetMultipartImage(url, mediaTypes); + return this._httpGetMultipartImage(url, mediaTypes, false, false, false, false, withCredentials); } else if (commonMediaType.startsWith("video")) { - return this._httpGetMultipartVideo(url, mediaTypes); + return this._httpGetMultipartVideo(url, mediaTypes, false, false, false, false, withCredentials); } throw new Error( @@ -1121,24 +1243,29 @@ class DICOMwebClient { const { mediaTypes, queryParams } = options; const headers = {}; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } if (!mediaTypes) { const responseType = "arraybuffer"; if (queryParams) { url += DICOMwebClient._parseQueryParameters(queryParams); } - return this._httpGet(url, headers, responseType); + return this._httpGet(url, headers, responseType, false, withCredentials); } const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType.startsWith("image")) { - return this._httpGetImage(url, mediaTypes, queryParams); + return this._httpGetImage(url, mediaTypes, queryParams, false, withCredentials); } else if (commonMediaType.startsWith("video")) { - return this._httpGetVideo(url, mediaTypes, queryParams); + return this._httpGetVideo(url, mediaTypes, queryParams, false, withCredentials); } else if (commonMediaType.startsWith("text")) { - return this._httpGetText(url, mediaTypes, queryParams); + return this._httpGetText(url, mediaTypes, queryParams, false, withCredentials); } else if (commonMediaType === MEDIATYPES.PDF) { - return this._httpGetApplicationPdf(url, queryParams); + return this._httpGetApplicationPdf(url, queryParams, false, withCredentials); } throw new Error( @@ -1180,18 +1307,23 @@ class DICOMwebClient { const { mediaTypes, queryParams } = options; const headers = {}; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } if (!mediaTypes) { const responseType = "arraybuffer"; if (queryParams) { url += DICOMwebClient._parseQueryParameters(queryParams); } - return this._httpGet(url, headers, responseType); + return this._httpGet(url, headers, responseType, false, withCredentials); } const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType.startsWith("image")) { - return this._httpGetImage(url, mediaTypes, queryParams); + return this._httpGetImage(url, mediaTypes, queryParams, false, withCredentials); } throw new Error( @@ -1246,20 +1378,25 @@ class DICOMwebClient { const { mediaTypes, queryParams } = options; const headers = {}; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } if (!mediaTypes) { const responseType = "arraybuffer"; if (queryParams) { url += DICOMwebClient._parseQueryParameters(queryParams); } - return this._httpGet(url, headers, responseType); + return this._httpGet(url, headers, responseType, false, withCredentials); } const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType.startsWith("image")) { - return this._httpGetImage(url, mediaTypes, queryParams); + return this._httpGetImage(url, mediaTypes, queryParams, false, withCredentials); } else if (commonMediaType.startsWith("video")) { - return this._httpGetVideo(url, mediaTypes, queryParams); + return this._httpGetVideo(url, mediaTypes, queryParams, false, withCredentials); } throw new Error( @@ -1313,18 +1450,23 @@ class DICOMwebClient { const { mediaTypes, queryParams } = options; const headers = {}; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } if (!mediaTypes) { const responseType = "arraybuffer"; if (queryParams) { url += DICOMwebClient._parseQueryParameters(queryParams); } - return this._httpGet(url, headers, responseType); + return this._httpGet(url, headers, responseType, false, withCredentials); } const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType.startsWith("image")) { - return this._httpGetImage(url, mediaTypes, queryParams); + return this._httpGetImage(url, mediaTypes, queryParams, false, withCredentials); } throw new Error( @@ -1356,14 +1498,19 @@ class DICOMwebClient { }/instances/${options.sopInstanceUID}`; const { mediaTypes } = options; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } if (!mediaTypes) { - return this._httpGetMultipartApplicationDicom(url).then(getFirstResult); + return this._httpGetMultipartApplicationDicom(url, false, false, false, withCredentials).then(getFirstResult); } const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType === MEDIATYPES.DICOM) { - return this._httpGetMultipartApplicationDicom(url, mediaTypes).then( + return this._httpGetMultipartApplicationDicom(url, mediaTypes, false, false, withCredentials).then( getFirstResult ); } @@ -1394,14 +1541,19 @@ class DICOMwebClient { }`; const { mediaTypes } = options; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } if (!mediaTypes) { - return this._httpGetMultipartApplicationDicom(url); + return this._httpGetMultipartApplicationDicom(url, false, false, false, withCredentials); } const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType === MEDIATYPES.DICOM) { - return this._httpGetMultipartApplicationDicom(url, mediaTypes); + return this._httpGetMultipartApplicationDicom(url, mediaTypes, false, false, withCredentials); } throw new Error( @@ -1424,14 +1576,19 @@ class DICOMwebClient { const url = `${this.wadoURL}/studies/${options.studyInstanceUID}`; const { mediaTypes } = options; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } if (!mediaTypes) { - return this._httpGetMultipartApplicationDicom(url); + return this._httpGetMultipartApplicationDicom(url, false, false, false, withCredentials); } const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType === MEDIATYPES.DICOM) { - return this._httpGetMultipartApplicationDicom(url, mediaTypes); + return this._httpGetMultipartApplicationDicom(url, mediaTypes, false, false, withCredentials); } throw new Error( @@ -1457,12 +1614,18 @@ class DICOMwebClient { const url = options.BulkDataURI; const { mediaTypes, byteRange } = options; - + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } if (!mediaTypes) { return this._httpGetMultipartApplicationOctetStream( url, mediaTypes, - byteRange + byteRange, + false, false, withCredentials ); } @@ -1472,10 +1635,11 @@ class DICOMwebClient { return this._httpGetMultipartApplicationOctetStream( url, mediaTypes, - byteRange + byteRange, + false, false, withCredentials ); } else if (commonMediaType.startsWith("image")) { - return this._httpGetMultipartImage(url, mediaTypes, byteRange); + return this._httpGetMultipartImage(url, mediaTypes, byteRange, false, false, false, withCredentials); } throw new Error( @@ -1505,8 +1669,13 @@ class DICOMwebClient { const headers = { "Content-Type": `multipart/related; type="application/dicom"; boundary="${boundary}"` }; - - return this._httpPost(url, headers, data, options.progressCallback); + let withCredentials = false; + if ("withCredentials" in options) { + if(options.withCredentials) { + withCredentials = options.withCredentials; + } + } + return this._httpPost(url, headers, data, options.progressCallback, withCredentials); } } diff --git a/test/test.js b/test/test.js index 0a8f909..2792b2e 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,4 @@ -const { expect } = chai; +const { createSpy } = jasmine; function getTestDataInstance(url) { return new Promise((resolve, reject) => { @@ -6,7 +6,7 @@ function getTestDataInstance(url) { xhr.open("GET", url, true); xhr.responseType = "arraybuffer"; - xhr.onload = function() { + xhr.onload = function() { const arrayBuffer = this.response; if (arrayBuffer) { resolve(arrayBuffer); @@ -19,25 +19,22 @@ function getTestDataInstance(url) { }); } -describe('dicomweb.api.DICOMwebClient', function () { +describe('dicomweb.api.DICOMwebClient', function() { const dwc = new DICOMwebClient.api.DICOMwebClient({ url: 'http://localhost:8008/dcm4chee-arc/aets/DCM4CHEE/rs', retrieveRendered: false }); - it('should have correct constructor name', function() { - expect(dwc.constructor.name).to.equal('DICOMwebClient'); + it('should have correct constructor name', function() { + expect(dwc.constructor.name).toEqual('DICOMwebClient'); }); - it('should find zero studies', async function() { - const studies = await dwc.searchForStudies(); - - expect(studies).to.have.length(0); + it('should find zero studies', async function() { + const studies = await dwc.searchForStudies({ queryParams: { PatientID: 11235813 } }); + expect(studies.length).toBe(0); }); - it('should store one instance', async function() { - this.timeout(5000); - + it('should store one instance', async function() { // This is the HTTP server run by the Karma test // runner const url = 'http://localhost:9876/base/testData/sample.dcm'; @@ -48,16 +45,14 @@ describe('dicomweb.api.DICOMwebClient', function () { }; await dwc.storeInstances(options); - }); + }, 5000); - it('should find one study', async function() { + it('should find one study', async function() { const studies = await dwc.searchForStudies(); - expect(studies).to.have.length(1); + expect(studies.length).toBe(4); }); - it('should store two instances', async function() { - this.timeout(10000); - + it('should store two instances', async function() { // This is the HTTP server run by the Karma test // runner const url1 = 'http://localhost:9876/base/testData/sample2.dcm'; @@ -77,15 +72,15 @@ describe('dicomweb.api.DICOMwebClient', function () { }; await dwc.storeInstances(options); - }); + }, 10000); - it('should find four studes', async function() { + it('should find four studes', async function() { const studies = await dwc.searchForStudies(); - expect(studies).to.have.length(4); + expect(studies.length).toBe(4); }); - it('should retrieve a single frame of an instance', async function() { + it('should retrieve a single frame of an instance', async function() { // from sample.dcm const options = { studyInstanceUID: '1.3.6.1.4.1.14519.5.2.1.2744.7002.271803936741289691489150315969', @@ -97,7 +92,7 @@ describe('dicomweb.api.DICOMwebClient', function () { const frames = dwc.retrieveInstance(options); }); - it('should retrieve a single instance', async function() { + it('should retrieve a single instance', async function() { // from sample.dcm const options = { studyInstanceUID: '1.3.6.1.4.1.14519.5.2.1.2744.7002.271803936741289691489150315969', @@ -107,10 +102,10 @@ describe('dicomweb.api.DICOMwebClient', function () { const instance = await dwc.retrieveInstance(options); - expect(instance).to.be.an('arraybuffer'); + expect(instance instanceof ArrayBuffer).toBe(true); }); - it('should retrieve an entire series as an array of instances', async function() { + it('should retrieve an entire series as an array of instances', async function() { const options = { studyInstanceUID: '1.3.6.1.4.1.14519.5.2.1.2744.7002.271803936741289691489150315969', seriesInstanceUID: '1.3.6.1.4.1.14519.5.2.1.2744.7002.117357550898198415937979788256', @@ -118,21 +113,20 @@ describe('dicomweb.api.DICOMwebClient', function () { const instances = await dwc.retrieveSeries(options); - expect(instances).to.have.length(1); + expect(instances.length).toBe(1); }); - it('should retrieve an entire study as an array of instances', async function() { + it('should retrieve an entire study as an array of instances', async function() { const options = { studyInstanceUID: '1.3.6.1.4.1.14519.5.2.1.2744.7002.271803936741289691489150315969', }; const instances = await dwc.retrieveStudy(options); - expect(instances).to.have.length(1); + expect(instances.length).toBe(1); }); - it('should retrieve bulk data', async function() { - this.timeout(15000) + it('should retrieve bulk data', async function() { const options = { studyInstanceUID: '999.999.3859744', seriesInstanceUID: '999.999.94827453', @@ -148,8 +142,54 @@ describe('dicomweb.api.DICOMwebClient', function () { const bulkData = await dwc.retrieveBulkData(bulkDataOptions); - expect(bulkData).to.be.an('array'); - expect(bulkData).to.to.have.length(1); - expect(bulkData[0]).to.be.an('arraybuffer'); + expect(bulkData instanceof Array).toBe(true); + expect(bulkData.length).toBe(1); + expect(bulkData[0] instanceof ArrayBuffer).toBe(true); + }, 15000); + + describe('Request hooks', function() { + let requestHook1Spy, requestHook2Spy, url, metadataUrl, request; + + beforeEach(function() { + request = new XMLHttpRequest(); + url = 'http://localhost:8008/dcm4chee-arc/aets/DCM4CHEE/rs'; + metadataUrl = 'http://localhost:8008/dcm4chee-arc/aets/DCM4CHEE/rs/studies/999.999.3859744/series/999.999.94827453/instances/999.999.133.1996.1.1800.1.6.25/metadata'; + requestHook1Spy = createSpy('requestHook1Spy', function (request, metadata) { return request }).and.callFake((request, metadata) => request); + requestHook2Spy = createSpy('requestHook2Spy', function (request, metadata) { return request }).and.callFake((request, metadata) => request); + }); + + it('invalid request hooks should be notified and ignored', async function() { + /** Spy with invalid request hook signature */ + requestHook2Spy = createSpy('requestHook2Spy', function (request) { return request }).and.callFake((request, metadata) => request); + const dwc = new DICOMwebClient.api.DICOMwebClient({ + url, + requestHooks: [requestHook1Spy, requestHook2Spy] + }); + const metadata = { url: metadataUrl, method: 'get' }; + request.open('GET', metadata.url); + await dwc.retrieveInstanceMetadata({ + studyInstanceUID: '999.999.3859744', + seriesInstanceUID: '999.999.94827453', + sopInstanceUID: '999.999.133.1996.1.1800.1.6.25', + }); + expect(requestHook1Spy).not.toHaveBeenCalledWith(request, metadata); + expect(requestHook2Spy).not.toHaveBeenCalledWith(request, metadata); + }) + + it('valid request hooks should be called', async function() { + const dwc = new DICOMwebClient.api.DICOMwebClient({ + url, + requestHooks: [requestHook1Spy, requestHook2Spy] + }); + const metadata = { url: metadataUrl, method: 'get' }; + request.open('GET', metadata.url); + await dwc.retrieveInstanceMetadata({ + studyInstanceUID: '999.999.3859744', + seriesInstanceUID: '999.999.94827453', + sopInstanceUID: '999.999.133.1996.1.1800.1.6.25', + }); + expect(requestHook1Spy).toHaveBeenCalledWith(request, metadata); + expect(requestHook2Spy).toHaveBeenCalledWith(request, metadata); + }); }); });