UNPKG

swish-http

Version:

A Swish implementation that tunnels over HTTP

354 lines (342 loc) 13.3 kB
(function (global, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof module === 'object' && module.exports) { module.exports = factory(); } else { global.SwishHttp = factory(); } })(this, function () { // Hopefully good enough var upDirRegex = /\/[^/]+\/\.\.\//g; function basicResolveUrl(base, other) { if (/\:\/\//.test(other)) return other; if (other.substring(0, 2) === '//') { base = base.replace(/[#?].*/, ''); if (!/\:\/\//.test(base)) return other; return base.replace(/:\/\/.*/, ':') + other; } else if (other[0] === '/') { base = base.replace(/[#?].*/, ''); if (/(\:|^)\/\//.test(base)) { return base.replace(/[^/]\/([^/]|$).*/, function (r) { return r[0]; }) + other; } else { return other; } } else if (other[0] === '?') { return base.replace(/[#?].*/, '') + other; } else if (other[1] === '#') { return base.replace(/#.*/, '') + other; } else { base = base.replace(/[#?].*/, ''); base = base.replace(/[^/]+$/, ''); // Strip out final component var result = base + other; while (upDirRegex.test(result)) { result = result.replace(upDirRegex, '/'); } return result; } } var queryKeywords = { '_schema_json': function (result, value) { result.schema = JSON.parse(value); }, '_options_json': function (result, value) { result.options = JSON.parse(value); } }; function parseQuery(query) { var result = { schema: {}, options: {} }; for (var key in query) { if (queryKeywords[key]) { try { queryKeywords[key](result, query[key]); } catch (e) { return {error: 'Error decoding query', key: key, message: e.message}; } } else if (key[0] === '/') { // JSON Pointer var parts = key.substring(1).split('/'); for (var j = 0; j < parts.length; j++) { parts[j] = stringToBasicValue(parts[j].replace(/~1/g, '/').replace(/~0/g, '~')); } result = setValueWithPath(result, parts, stringToBasicValue(query[key])); } } return result; } function createQuery(schema, options) { var query = {}; var combined = {}; for (var key in schema) { combined.schema = schema; break; } for (var key in options) { combined.options = options; break; } var paths = objectToPaths(combined); for (var i = 0; i < paths.length; i++) { var pair = paths[i], path = pair.path; // basic value and JSON Pointer for (var j = 0; j < path.length; j++) { path[j] = '/' + basicValueToString(path[j]).replace(/~/g, '~0').replace(/\//g, '~1'); } path = path.join(''); query[path] = basicValueToString(pair.value); } return query; } function setValueWithPath(root, path, value) { if (!path.length) return value; var key = path[0], remainder = path.slice(1); if (typeof key === 'number') { if (!Array.isArray(root)) root = []; } else if (!root || typeof root !== 'object') { root = {}; } root[key] = setValueWithPath(root[key], remainder, value); return root; } function basicValueToString(value) { if (typeof value === 'string') { // Prefix '_' to any strings that would otherwise decode incorrectly if (stringToBasicValue(value) !== value) { return '_' + value; } } if (Array.isArray(value)) return '[]'; if (value && typeof value === 'object') return '{}'; return value + ""; } function stringToBasicValue(string) { if (string[0] === '_') return string.substring(1); if (string === 'true') return true; if (string === 'false') return false; if (string === 'null') return null; if (string === '{}') return {}; if (string === '[]') return []; if (/\-?^[0-9]+(\.[0-9]+)?$/.test(string) && !isNaN(parseFloat(string))) return parseFloat(string); return string; } function objectToPaths(value, paths, prefix) { paths = paths || []; prefix = prefix || []; if (Array.isArray(value)) { if (!value.length) paths.push({path: prefix, value: []}); for (var i = 0; i < value.length; i++) { objectToPaths(value[i], paths, prefix.concat(i)); } } else if (value && typeof value === 'object') { var set = false; for (var key in value) { objectToPaths(value[key], paths, prefix.concat(key)); set = true; } if (!set) paths.push({path: prefix, value: {}}); } else if (typeof value !== 'undefined') { paths.push({path: prefix, value: value}); } return paths; } // copied from core swish module function exampleToSchema(obj) { var schema = {}; if (Array.isArray(obj)) { var optionEnums = []; var optionSchemas = []; for (var i = 0; i < obj.length; i++) { var subSchema = exampleToSchema(obj[i]); optionSchemas.push(subSchema); if (optionEnums && subSchema['enum']) { optionEnums = optionEnums.concat(subSchema['enum']); for (var key in subSchema) { if (key !== 'enum') { optionEnums = null; break; } } } else { optionEnums = null; } } if (optionEnums) return {enum: optionEnums}; return {anyOf: optionSchemas}; } else if (obj && typeof obj === 'object') { schema.type = 'object'; schema.properties = {}; schema.required = []; for (var key in obj) { schema.properties[key] = exampleToSchema(obj[key]); schema.required.push(key); } } else { schema['enum'] = [obj]; } return schema; } // copied from core swish module function patchIsRemove(patch) { for (var i = patch.length - 1; i >= 0; i--) { var change = patch[i]; if (change.path !== "") continue; if (change.op === "remove") { return true; } if (change.op === "replace" || change.op === 'add') { return typeof change.value === 'undefined'; } } return false; } function SwishClient(url) { if (!(this instanceof SwishClient)) return new SwishClient(url); this._url = url; } SwishClient.prototype = { _req: function(method, schema, options, data, contentType, callback) { var url = this._url; if (options._overrideUrl) { url = options._overrideUrl; } else { var query = createQuery(schema, options); for (var key in query) { url += (url.indexOf('?') >= 0) ? '&' : '?'; url += encodeURIComponent(key) + '=' + encodeURIComponent(query[key]); } } var r = new XMLHttpRequest(); r.open(method, url, true); r.onreadystatechange = function () { if (r.readyState != 4) return; var error = null, data = r.responseText; try { data = JSON.parse(data); } catch (e) { error = e; } if (r.status < 200 || r.status >= 300) { error = new Error(r.status + ' ' + r.statusText + ' (' + url + ')'); } callback(error, data, r, url); } if (typeof data !== 'undefined') { r.setRequestHeader('Content-Type', contentType || 'application/json'); r.send(JSON.stringify(data)); } else { r.send(); } }, create: function (object, options, callback) { if (typeof options === 'function') { callback = options; options = null; } options = options || {}; this.createMultiple([object], options, function (error, results) { callback(error, results && results[0]); }); }, createMultiple: function (entries, options, callback) { var thisStore = this; if (typeof options === 'function') { callback = options; options = null; } options = options || {}; if (entries.length === 1 && !Array.isArray(entries)) { this._req('POST', null, options, entries[0], null, callback); } else { this._req('POST', null, options, entries, null, callback); } }, searchByExample: function (example, options, callback) { this.search(exampleToSchema(example), options, callback); }, search: function (schema, options, callback) { if (typeof options === 'function') { callback = options; options = null; } options = options || {}; this._req('GET', schema, options, undefined, null, function (error, result, xhr, requestUrl) { if (error) return callback(error); var continueOptions = null; var linkHeader = xhr.getResponseHeader('Link'); if (linkHeader) { var links = linkHeader.match(/([^,"']|"([^"\\]|\\.)*")+/g); for (var i = 0; i < links.length; i++) { var parts = links[i].match(/([^;"']|"([^"\\]|\\.)*")+/g); var url = parts.shift().replace(/^\s*</, '').replace(/>\s*$/, ''); var props = {}; for (var j = 0; j < parts.length; j++) { var part = parts[j].replace(/^\s+/, '').replace(/\s+$/, ''); var key = part.split('=', 1)[0]; var value = part.substring(key.length + 1); if (value[0] === '"' && value[value.length - 1] === '"') { // TODO: do we need to de-escape the value? value = value.substring(1, value.length - 1); } props[key] = value; } if (props.rel === 'next') { continueOptions = {_overrideUrl: basicResolveUrl(requestUrl, url)}; } } } callback(null, result, continueOptions); }); }, replaceByExample: function (example, replacement, options, callback) { this.replace(exampleToSchema(example), replacement, options, callback); }, replace: function (schema, replacement, options, callback) { if (typeof options === 'function') { callback = options; options = null; } options = options || {}; this._req('PUT', schema, options, replacement, null, callback); }, updateByExample: function (example, merge, options, callback) { this.update(exampleToSchema(example), merge, options, callback); }, update: function (schema, merge, options, callback) { if (typeof options === 'function') { callback = options; options = null; } options = options || {}; this._req('PATCH', schema, options, merge, 'application/merge-patch+json', callback); }, patchByExample: function (example, patch, options, callback) { this.patch(exampleToSchema(example), patch, options, callback); }, patch: function (schema, patch, options, callback) { if (typeof options === 'function') { callback = options; options = null; } options = options || {}; if (patchIsRemove(patch)) { this._req('DELETE', schema, options, undefined, null, callback); } else { this._req('PATCH', schema, options, patch, 'application/json-patch+json', callback); } }, removeByExample: function (example, options, callback) { this.remove(exampleToSchema(example), options, callback); }, remove: function (schema, options, callback) { // Removal patch this.patch(schema, [{op: 'remove', path: ''}], options, callback); }, }; SwishClient.parseQuery = parseQuery; SwishClient.createQuery = createQuery; return SwishClient; });