yourls-api
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -0,1 +1,5 @@ | ||
## Version 2.1.0, 2017.03.24 | ||
* Support JSON requests (GET & POST) [#6](https://github.com/neocotic/yourls-api/issues/6) | ||
## Version 2.0.0, 2016.11.23 | ||
@@ -2,0 +6,0 @@ |
1281
dist/yourls.js
@@ -1,22 +0,1 @@ | ||
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in all | ||
* copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
* SOFTWARE. | ||
*/ | ||
(function (global, factory) { | ||
@@ -29,3 +8,3 @@ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2016 Alasdair Mercer, Skelp | ||
* | ||
@@ -52,35 +31,81 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
/** | ||
* The singleton {@link API} instance which is privatized to prevent leaking credentials. | ||
* A bare-bones constructor for surrogate prototype swapping. | ||
* | ||
* @private | ||
* @type {API} | ||
* @constructor | ||
*/ | ||
var instance = null; | ||
var Constructor = function() {}; | ||
/** | ||
* A reference to <code>Object.prototype.hasOwnProperty</code>. | ||
* | ||
* @private | ||
* @type {Function} | ||
*/ | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
/** | ||
* A reference to <code>Array.prototype.slice</code>. | ||
* | ||
* @private | ||
* @type {Function} | ||
*/ | ||
var slice = Array.prototype.slice; | ||
/** | ||
* Sanitizes the specified <code>credentials</code> by ensuring that only valid properties are present and only when | ||
* appropriate. | ||
* Extends the specified <code>target</code> object with the properties in each of the <code>sources</code> provided. | ||
* | ||
* This function does not modify <code>credentials</code> and instead creates a new object with the sanitized | ||
* properties. | ||
* Nothing happens if <code>target</code> is <code>null</code> and if any source is <code>null</code> it will be | ||
* ignored. | ||
* | ||
* @param {API~Credentials} credentials - the credentials to be sanitized (may be <code>null</code>) | ||
* @return {API~Credentials} A sanitized version of <code>credentials</code> or <code>null</code> if | ||
* <code>credentials</code> is <code>null</code>. | ||
* @param {boolean} own - <code>true</code> to only copy <b>own</b> properties from <code>sources</code> onto | ||
* <code>target</code>; otherwise <code>false</code> | ||
* @param {Object} [target] - the target object which should be extended | ||
* @param {...Object} [sources] - the source objects whose properties are to be copied onto <code>target</code> | ||
* @return {void} | ||
* @private | ||
*/ | ||
function sanitizeCredentials(credentials) { | ||
if (!credentials) { | ||
return null | ||
function extend(own, target, sources) { | ||
if (target == null) { | ||
return | ||
} | ||
var result = {}; | ||
if (credentials.signature) { | ||
result.signature = credentials.signature; | ||
result.timestamp = credentials.timestamp; | ||
sources = slice.call(arguments, 2); | ||
var property; | ||
var source; | ||
for (var i = 0, length = sources.length; i < length; i++) { | ||
source = sources[i]; | ||
for (property in source) { | ||
if (!own || hasOwnProperty.call(source, property)) { | ||
target[property] = source[property]; | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* Creates an object which inherits the given <code>prototype</code>. | ||
* | ||
* Optionally, the created object can be extended further with the specified <code>properties</code>. | ||
* | ||
* @param {Object} prototype - the prototype to be inherited by the created object | ||
* @param {Object} [properties] - the optional properties to be extended by the created object | ||
* @return {Object} The newly created object. | ||
* @private | ||
*/ | ||
function create(prototype, properties) { | ||
var result; | ||
if (typeof Object.create === 'function') { | ||
result = Object.create(prototype); | ||
} else { | ||
result.password = credentials.password; | ||
result.username = credentials.username; | ||
Constructor.prototype = prototype; | ||
result = new Constructor(); | ||
Constructor.prototype = null; | ||
} | ||
if (properties) { | ||
extend(true, result, properties); | ||
} | ||
return result | ||
@@ -90,2 +115,119 @@ } | ||
/** | ||
* The base constructor from which all others should extend. | ||
* | ||
* @public | ||
* @constructor | ||
*/ | ||
function Oopsy() {} | ||
/** | ||
* Extends the constructor to which this method is associated with the <code>prototype</code> and/or | ||
* <code>statics</code> provided. | ||
* | ||
* If <code>constructor</code> is provided, it will be used as the constructor for the child, otherwise a simple | ||
* constructor which only calls the super constructor will be used instead. | ||
* | ||
* The super constructor can be accessed via a special <code>super_</code> property on the child constructor. | ||
* | ||
* @param {Function} [constructor] - the constructor for the child | ||
* @param {Object} [prototype] - the prototype properties to be defined for the child | ||
* @param {Object} [statics] - the static properties to be defined for the child | ||
* @return {Function} The child <code>constructor</code> provided or the one created if none was given. | ||
* @public | ||
* @static | ||
*/ | ||
Oopsy.extend = function(constructor, prototype, statics) { | ||
var superConstructor = this; | ||
if (typeof constructor !== 'function') { | ||
statics = prototype; | ||
prototype = constructor; | ||
constructor = function() { | ||
return superConstructor.apply(this, arguments) | ||
}; | ||
} | ||
extend(false, constructor, superConstructor, statics); | ||
constructor.prototype = create(superConstructor.prototype, prototype); | ||
constructor.prototype.constructor = constructor; | ||
constructor.super_ = superConstructor; | ||
return constructor | ||
}; | ||
/* | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in all | ||
* copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
* SOFTWARE. | ||
*/ | ||
/** | ||
* Extends the specified <code>target</code> object with the properties in each of the <code>sources</code> provided. | ||
* | ||
* Any of the <code>sources</code> that are <code>null</code> will simply be ignored. | ||
* | ||
* @param {Object} target - the target object which should be extended | ||
* @param {...Object} [sources] - the source objects whose properties are to be copied onto <code>target</code> | ||
* @return {Object} A reference to <code>target</code>. | ||
* @protected | ||
*/ | ||
function extend$1(target, sources) { | ||
sources = Array.prototype.slice.call(arguments, 1); | ||
for (var i = 0, length = sources.length, property, source; i < length; i++) { | ||
source = sources[i]; | ||
if (source) { | ||
for (property in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, property)) { | ||
target[property] = source[property]; | ||
} | ||
} | ||
} | ||
} | ||
return target | ||
} | ||
/* | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in all | ||
* copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
* SOFTWARE. | ||
*/ | ||
/** | ||
* Contains information on how to connect to and authenticate with a YOURLS server. | ||
@@ -96,6 +238,8 @@ * | ||
* <code>null</code>) | ||
* @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be | ||
* <code>null</code>) | ||
* @protected | ||
* @constructor | ||
*/ | ||
function API(url, credentials) { | ||
var API = Oopsy.extend(function(url, credentials, options) { | ||
/** | ||
@@ -116,39 +260,94 @@ * The URL of the YOURLS server. | ||
*/ | ||
this.credentials = sanitizeCredentials(credentials); | ||
} | ||
this.credentials = API._sanitizeCredentials(credentials); | ||
/** | ||
* The options to be used to send requests to the YOURLS server. | ||
* | ||
* @public | ||
* @type {API~Options} | ||
*/ | ||
this.options = API._sanitizeOptions(options); | ||
}, null, { | ||
/** | ||
* Destroys the singleton instance of {@link API}. | ||
* | ||
* @return {void} | ||
* @public | ||
* @static | ||
*/ | ||
API.clear = function() { | ||
instance = null; | ||
}; | ||
/** | ||
* The default options to be used. | ||
* | ||
* @protected | ||
* @static | ||
* @type {API~Options} | ||
*/ | ||
defaultOptions: { | ||
format: 'jsonp', | ||
method: 'GET' | ||
}, | ||
/** | ||
* Retrieves the singleton instance of {@link API}. | ||
* | ||
* This function will return <code>null</code> unless an instance is currently stored. | ||
* | ||
* @return {API} The connected {@link API} or <code>null</code> if none exists. | ||
* @public | ||
* @static | ||
*/ | ||
API.fetch = function() { | ||
return instance | ||
}; | ||
/** | ||
* The singleton {@link API} instance which is privatized to prevent leaking credentials. | ||
* | ||
* @public | ||
* @static | ||
* @type {API} | ||
*/ | ||
instance: null, | ||
/** | ||
* Stores this {@link API} as the singleton, potentially replacing the existing instance. | ||
* | ||
* @return {void} | ||
* @public | ||
*/ | ||
API.prototype.store = function() { | ||
instance = this; | ||
}; | ||
/** | ||
* Sanitizes the specified <code>credentials</code> by ensuring that only valid properties are present and only when | ||
* appropriate. | ||
* | ||
* This method does not modify <code>credentials</code> and instead creates a new object with the sanitized | ||
* properties. | ||
* | ||
* @param {API~Credentials} credentials - the credentials to be sanitized (may be <code>null</code>) | ||
* @return {API~Credentials} A sanitized version of <code>credentials</code> or <code>null</code> if | ||
* <code>credentials</code> is <code>null</code>. | ||
* @private | ||
* @static | ||
*/ | ||
_sanitizeCredentials: function(credentials) { | ||
if (!credentials) { | ||
return null | ||
} | ||
var result = {}; | ||
if (credentials.signature) { | ||
result.signature = credentials.signature; | ||
result.timestamp = credentials.timestamp; | ||
} else { | ||
result.password = credentials.password; | ||
result.username = credentials.username; | ||
} | ||
return result | ||
}, | ||
/** | ||
* Sanitizes the specified <code>options</code> by ensuring that only valid properties are present and in the correct | ||
* format. | ||
* | ||
* This method does not modify <code>options</code> and instead creates a new object with the sanitized properties and | ||
* default values will be used for missing options. | ||
* | ||
* @param {API~Options} options - the options to be sanitized (may be <code>null</code>) | ||
* @return {API~Options} A sanitized version of <code>options</code> which will contain only default values if | ||
* <code>options</code> is <code>null</code>. | ||
* @private | ||
* @static | ||
*/ | ||
_sanitizeOptions: function(options) { | ||
var result = extend$1({}, API.defaultOptions); | ||
if (!options) { | ||
return result | ||
} | ||
if (options.format) { | ||
result.format = options.format.toLowerCase(); | ||
} | ||
if (options.method) { | ||
result.method = options.method.toUpperCase(); | ||
} | ||
return result | ||
} | ||
}); | ||
/** | ||
@@ -172,4 +371,16 @@ * The credentials to be used to authenticate with a private YOURLS API. | ||
/** | ||
* The options that determine how requests are sent to the YOURLS server. | ||
* | ||
* If the request <code>format</code> does not support the HTTP <code>method</code>, requests will not be sent and an | ||
* error will be thrown when such attempts occur. | ||
* | ||
* @typedef {Object} API~Options | ||
* @property {string} [format="jsonp"] - The format in which requests are sent (either <code>"json"</code> or | ||
* <code>"jsonp"</code>). | ||
* @property {string} [method="GET"] - The HTTP method to be used for requests. | ||
*/ | ||
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -198,3 +409,3 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* | ||
* This function will use the native <code>Array.isArray</code>, if available. | ||
* This method will use the native <code>Array.isArray</code>, if available. | ||
* | ||
@@ -210,3 +421,3 @@ * @param {*} obj - the object to be checked (may be <code>null</code>) | ||
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -233,28 +444,194 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
/** | ||
* Creates a serialized representation of the specified <code>params</code> into a URL query string. | ||
* Contains logic to connect with a YOURLS server and send data to its API. | ||
* | ||
* @param {Object} [params] - the hash of parameter key/value pairs to be serialized | ||
* @return {string} A URL query string representing <code>params</code>. | ||
* Due to the nature of HTTP, requests sent using a "GET" HTTP method will include all information in the URL of the | ||
* request. This includes the data that is sent as well as <b>any credentials</b> used to authenticate with the API. You | ||
* have been warned. | ||
* | ||
* @constructor | ||
* @protected | ||
*/ | ||
function paramify(params) { | ||
if (!params) { | ||
return '' | ||
var Request = Oopsy.extend({ | ||
/** | ||
* Builds the body for this {@link Request} based on the <code>api</code> and <code>data</code> provided. | ||
* | ||
* @param {API} api - the {@link API} to which the request is being sent | ||
* @param {Object} [data] - the data being sent in this request | ||
* @return {Object} The request body. | ||
* @protected | ||
*/ | ||
buildBody: function(api, data) { | ||
return extend$1({ format: api.options.format }, api.credentials, data) | ||
}, | ||
/** | ||
* Returns the list of the HTTP methods that are supported by this {@link Request}. | ||
* | ||
* By default, this method returns <code>null</code>, so implementations <b>must</b> implement this method to ensure | ||
* that {@link Request#isMethodSupported} works correctly. | ||
* | ||
* @return {string[]} The supported HTTP methods. | ||
* @protected | ||
*/ | ||
getSupportedHttpMethods: function() { | ||
return null | ||
}, | ||
/** | ||
* Determines whether this {@link Request} supports the specified HTTP <code>method</code>. | ||
* | ||
* @param {string} method - the HTTP method to be checked | ||
* @return {boolean} <code>true</code> if <code>method</code> is supported; otherwise <code>false</code>. | ||
* @public | ||
*/ | ||
isMethodSupported: function(method) { | ||
var supportedMethods = this.getSupportedHttpMethods(); | ||
return supportedMethods && supportedMethods.indexOf(method) !== -1 | ||
}, | ||
/** | ||
* Determines whether the data that is to be sent to the YOURLS server in this {@link Request} must be serialized as | ||
* query string parameters. | ||
* | ||
* @param {string} method - the HTTP method to be used | ||
* @return {boolean} <code>true</code> if the data needs to be sent as query string parameters; otherwise | ||
* <code>false</code>. | ||
* @protected | ||
*/ | ||
isQueryStringRequired: function(method) { | ||
return method === 'GET' | ||
}, | ||
/** | ||
* Processes this {@link Request} by sending it to the specified target <code>url</code> containing the | ||
* <code>body</code> provided. | ||
* | ||
* <code>callback</code> should be called with the response regardless of whether the it was a success or failure. | ||
* | ||
* This method is called internally by {@link Request#send} and does all of the actual work involved to send the | ||
* request and parse the response. All implementations <b>must</b> implement this method. | ||
* | ||
* @param {string} method - the request HTTP method | ||
* @param {string} url - the request URL | ||
* @param {Object} body - the request body (may be <code>null</code>) | ||
* @param {Function} callback - the function to be called with the response | ||
* @return {void} | ||
* @protected | ||
* @abstract | ||
*/ | ||
process: function(method, url, body, callback) { | ||
// Must be implemented | ||
}, | ||
/** | ||
* Sends the request to the connected YOURLS API with the <code>data</code> provided which should, in turn, call the | ||
* specified <code>callback</code> with the result. | ||
* | ||
* If the request is successful, <code>callback</code> will be passed the value of the named properties from the | ||
* response. If <code>resultNames</code> is a string or only contains a single string, only the value for that named | ||
* property will be passed as the first argument. Otherwise, an object containing the key/value pairs for each named | ||
* property will be passed as the first argument. The actual response will always be passed as the second argument. | ||
* | ||
* @param {Object} data - the data to be sent | ||
* @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to | ||
* <code>callback</code> as the first argument | ||
* @param {Function} callback - the function to be called with the result | ||
* @return {void} | ||
* @public | ||
*/ | ||
send: function(data, resultNames, callback) { | ||
var api = API.instance; | ||
var body = Request._serializeParameters(this.buildBody(api, data)); | ||
var method = api.options.method; | ||
var url = api.url; | ||
if (this.isQueryStringRequired(method)) { | ||
url += '?' + body; | ||
body = null; | ||
} | ||
this.process(method, url, body, function(response) { | ||
callback(Request._extractResult(resultNames, response), response); | ||
}); | ||
} | ||
var results = []; | ||
}, { | ||
for (var key in params) { | ||
if (Object.prototype.hasOwnProperty.call(params, key)) { | ||
if (params[key] != null) { | ||
results.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key])); | ||
/** | ||
* Extracts the values of the properties with the specified <code>names</code> from the <code>response</code> provided | ||
* and returns them in a single result. | ||
* | ||
* If <code>names</code> is a string or only contains a single string, only the value for that named property will be | ||
* returned. Otherwise, an object containing the key/value pairs for each named property will be returned. | ||
* | ||
* If <code>response</code> is <code>null</code> this method will return <code>null</code>. | ||
* | ||
* @param {string|string[]} names - the names of the <code>response</code> properties whose values are to be returned | ||
* as the result | ||
* @param {Object} response - the YOURLS API response | ||
* @return {*} The result extracted from <code>response</code>. | ||
* @private | ||
* @static | ||
*/ | ||
_extractResult: function(names, response) { | ||
names = isArray(names) ? names : [ names ]; | ||
var i; | ||
var name; | ||
var result = null; | ||
if (!response) { | ||
return result | ||
} | ||
if (names.length === 1) { | ||
result = response[names[0]]; | ||
} else { | ||
result = {}; | ||
for (i = 0; i < names.length; i++) { | ||
name = names[i]; | ||
if (typeof response[name] !== 'undefined') { | ||
result[name] = response[name]; | ||
} | ||
} | ||
} | ||
return result | ||
}, | ||
/** | ||
* Creates a serialized representation of the specified parameters. | ||
* | ||
* All of the parameter names and values are URL-encoded so that they can be safely included in the query string or | ||
* request body. | ||
* | ||
* @param {Object} [params] - the hash of parameter name/value pairs to be serialized | ||
* @return {string} A URL-encoded representing <code>obj</code> or an empty string if <code>obj</code> is | ||
* <code>null</code>. | ||
* @private | ||
* @static | ||
*/ | ||
_serializeParameters: function(params) { | ||
if (!params) { | ||
return '' | ||
} | ||
var results = []; | ||
for (var name in params) { | ||
if (Object.prototype.hasOwnProperty.call(params, name) && params[name] != null) { | ||
results.push(encodeURIComponent(name) + '=' + encodeURIComponent(params[name])); | ||
} | ||
} | ||
return results.join('&') | ||
} | ||
return results.join('&') | ||
} | ||
}); | ||
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -281,125 +658,272 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
/** | ||
* The key of the callback function holder within the global namespace. | ||
* An implementation of {@link Request} that provides support for JSON requests to the YOURLS API. | ||
* | ||
* @private | ||
* @type {string} | ||
* JSON requests can only be sent using the "GET" or "POST" HTTP methods. | ||
* | ||
* @constructor | ||
* @extends Request | ||
* @protected | ||
*/ | ||
var callbackHolderKey = '__yourls' + Date.now() + '_jsonp'; | ||
/** | ||
* Contains the callback functions for active JSONP requests. | ||
var JSONRequest = Request.extend({ | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
getSupportedHttpMethods: function() { | ||
return [ 'GET', 'POST' ] | ||
}, | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
process: function(method, url, body, callback) { | ||
var xhr = new XMLHttpRequest(); | ||
xhr.open(method, url, true); | ||
xhr.onreadystatechange = function() { | ||
var response; | ||
if (xhr.readyState === 4) { | ||
try { | ||
response = JSON.parse(xhr.responseText); | ||
callback(response); | ||
} catch (e) { | ||
throw new Error('Unable to parse response: ' + e) | ||
} | ||
} | ||
}; | ||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); | ||
if (body != null) { | ||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
} | ||
xhr.send(body); | ||
} | ||
}); | ||
/* | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
* Callback references should be removed immediately once they have been called. | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* Due to the nature of JSON, a reference to this object <b>must</b> be publicly available (i.e. global). | ||
* The above copyright notice and this permission notice shall be included in all | ||
* copies or substantial portions of the Software. | ||
* | ||
* @private | ||
* @type {Object<number, Function>} | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
* SOFTWARE. | ||
*/ | ||
var callbackHolder = window[callbackHolderKey] = {}; | ||
/** | ||
* Generates a quick and dirty unique ID for a callback. | ||
* The seed to be used to generate IDs. | ||
* | ||
* @return {number} The generated callback ID. | ||
* @private | ||
* @type {number} | ||
*/ | ||
function generateCallbackId() { | ||
var id = Date.now(); | ||
while (callbackHolder[id]) { | ||
id++; | ||
} | ||
var seed = new Date().getTime(); | ||
return id | ||
} | ||
/** | ||
* Extracts the values of the properties with the specified <code>names</code> from the <code>response</code> provided | ||
* and returns them in a single result. | ||
* An implementation of {@link Request} that provides support for JSONP requests to the YOURLS API. | ||
* | ||
* If <code>names</code> is a string or only contains a single string, only the value for that named property will be | ||
* returned. Otherwise, an object containing the key/value pairs for each named property will be returned. | ||
* JSONP requests can only be sent using the "GET" HTTP method. | ||
* | ||
* If <code>response</code> is <code>null</code> this function will return <code>null</code>. | ||
* | ||
* @param {string|string[]} names - the names of the <code>response</code> properties whose values are to be returned as | ||
* the result | ||
* @param {Object} response - the YOURLS API response | ||
* @return {*} The result extracted from <code>response</code>. | ||
* @private | ||
* @constructor | ||
* @extends Request | ||
* @protected | ||
*/ | ||
function getResult(names, response) { | ||
names = isArray(names) ? names : [ names ]; | ||
var JSONPRequest = Request.extend(function() { | ||
JSONPRequest.super_.call(this); | ||
var i; | ||
var name; | ||
var result = null; | ||
if (!window[JSONPRequest._callbackHolderKey]) { | ||
window[JSONPRequest._callbackHolderKey] = JSONPRequest._callbackHolder; | ||
} | ||
if (!response) { | ||
return result | ||
/** | ||
* The generated ID which is used to store a reference to the callback function within the holder so that the JSONP | ||
* payload can find it in the global namespace. | ||
* | ||
* @private | ||
* @type {number} | ||
*/ | ||
this._id = JSONPRequest._generateId(); | ||
}, { | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
getSupportedHttpMethods: function() { | ||
return [ 'GET' ] | ||
}, | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
buildBody: function(api, data) { | ||
var body = JSONPRequest.super_.prototype.buildBody.call(this, api, data); | ||
body.callback = JSONPRequest._callbackHolderKey + '[' + this._id + ']'; | ||
return body | ||
}, | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
process: function(method, url, body, callback) { | ||
var script = document.createElement('script'); | ||
var self = this; | ||
JSONPRequest._callbackHolder[this._id] = function(response) { | ||
delete JSONPRequest._callbackHolder[self._id]; | ||
script.parentNode.removeChild(script); | ||
callback(response); | ||
}; | ||
script.setAttribute('src', url); | ||
document.getElementsByTagName('head')[0].appendChild(script); | ||
} | ||
if (names.length === 1) { | ||
result = response[names[0]]; | ||
} else { | ||
result = {}; | ||
}, { | ||
for (i = 0; i < names.length; i++) { | ||
name = names[i]; | ||
/** | ||
* The key of the callback function holder within the global namespace. | ||
* | ||
* @private | ||
* @static | ||
* @type {string} | ||
*/ | ||
_callbackHolderKey: '__yourls' + seed + '_jsonp', | ||
if (typeof response[name] !== 'undefined') { | ||
result[name] = response[name]; | ||
} | ||
} | ||
/** | ||
* Contains the callback functions for active JSONP requests. | ||
* | ||
* Callback references should be removed immediately once they have been called. | ||
* | ||
* Due to the nature of JSON, a reference to this object <b>must</b> be publicly available (i.e. global). | ||
* | ||
* @private | ||
* @static | ||
* @type {Object<number, Function>} | ||
*/ | ||
_callbackHolder: {}, | ||
/** | ||
* Generates an ID to be used when storing a reference to a callback function. | ||
* | ||
* @return {number} The generated ID. | ||
* @private | ||
*/ | ||
_generateId: function() { | ||
do { | ||
seed++; | ||
} while (JSONPRequest._callbackHolder[seed]) | ||
return seed | ||
} | ||
return result | ||
} | ||
}); | ||
/** | ||
* Sends a JSONP request to the connected YOURLS API with the <code>data</code> provided which should, in turn, call the | ||
* specified <code>callback</code> with the result. | ||
/* | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
* If the request is successful, <code>callback</code> will be passed the value of the named properties from the | ||
* response. If <code>resultNames</code> is a string or only contains a single string, only the value for that named | ||
* property will be passed as the first argument. Otherwise, an object containing the key/value pairs for each named | ||
* property will be passed as the first argument. The actual response will always be passed as the second argument. | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* Due to the nature of JSONP, all information will be included in the URL of the request. This includes | ||
* <code>data</code> as well as <b>any credentials</b> used to authenticate with the API. You have been warned. | ||
* The above copyright notice and this permission notice shall be included in all | ||
* copies or substantial portions of the Software. | ||
* | ||
* @param {Object} data - the data to be sent | ||
* @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to | ||
* <code>callback</code> as the first argument | ||
* @param {Function} callback - the function to be called with the result | ||
* @return {void} | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
* SOFTWARE. | ||
*/ | ||
/** | ||
* Can make requests to the connected YOURLS API. | ||
* | ||
* @constructor | ||
* @protected | ||
*/ | ||
function jsonp(data, resultNames, callback) { | ||
var api = API.fetch(); | ||
var id = generateCallbackId(); | ||
var script = document.createElement('script'); | ||
var Requestor = Oopsy.extend({ | ||
callbackHolder[id] = function(response) { | ||
var result = getResult(resultNames, response); | ||
/** | ||
* Sends the request to the connected YOURLS API with the <code>data</code> provided which should, in turn, call the | ||
* specified <code>callback</code> with the result. | ||
* | ||
* This method is primarily a proxy to {@link Request#send} but does validate the state of the connection information | ||
* to ensure that is is valid before making the request. | ||
* | ||
* @param {Object} data - the data to be sent | ||
* @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to | ||
* <code>callback</code> as the first argument | ||
* @param {Function} callback - the function to be called with the result | ||
* @return {void} | ||
* @throws {Error} - If either no connection is present, the request format is not supported, or the configured HTTP | ||
* method is not supported by the {@link Request}. | ||
* @protected | ||
*/ | ||
sendRequest: function(data, resultNames, callback) { | ||
var api = API.instance; | ||
delete callbackHolder[id]; | ||
script.parentNode.removeChild(script); | ||
if (!api) { | ||
throw new Error('No connection has been made') | ||
} | ||
callback(result, response); | ||
}; | ||
var format = api.options.format; | ||
var method = api.options.method; | ||
var Request = Requestor._requestFormatMap[format]; | ||
var target = api.url + '?' + paramify({ callback: callbackHolderKey + '[' + id + ']', format: 'jsonp' }); | ||
if (api.credentials) { | ||
target += '&' + paramify(api.credentials); | ||
if (!Request) { | ||
throw new Error('Request format not supported: ' + format) | ||
} | ||
var request = new Request(); | ||
if (!request.isMethodSupported(method)) { | ||
throw new Error('HTTP method not supported: ' + method) | ||
} | ||
request.send(data, resultNames, callback); | ||
} | ||
if (data) { | ||
target += '&' + paramify(data); | ||
}, { | ||
/** | ||
* The mapping of supported request formats to {@link Request} constructors. | ||
* | ||
* @private | ||
* @static | ||
* @type {Object<string, Function>} | ||
*/ | ||
_requestFormatMap: { | ||
json: JSONRequest, | ||
jsonp: JSONPRequest | ||
} | ||
script.setAttribute('src', target); | ||
document.getElementsByTagName('head')[0].appendChild(script); | ||
} | ||
}); | ||
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -429,25 +953,26 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* @constructor | ||
* @extends Requestor | ||
* @protected | ||
*/ | ||
function DB() { | ||
// Do nothing | ||
} | ||
var DB = Requestor.extend({ | ||
/** | ||
* Retrieves the statistics for this {@link DB}. | ||
* | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {DB} A reference to this {@link DB} for chaining purposes. | ||
* @public | ||
*/ | ||
DB.prototype.stats = function(callback) { | ||
var data = { action: 'db-stats' }; | ||
/** | ||
* Retrieves the statistics for this {@link DB}. | ||
* | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {DB} A reference to this {@link DB} for chaining purposes. | ||
* @public | ||
*/ | ||
stats: function(callback) { | ||
var data = { action: 'db-stats' }; | ||
jsonp(data, 'db-stats', callback); | ||
this.sendRequest(data, 'db-stats', callback); | ||
return this | ||
}; | ||
return this | ||
} | ||
}); | ||
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -478,5 +1003,8 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* @constructor | ||
* @extends Requestor | ||
* @protected | ||
*/ | ||
function URL(url) { | ||
var URL = Requestor.extend(function(url) { | ||
URL.super_.call(this); | ||
/** | ||
@@ -489,3 +1017,3 @@ * Either the shortened URL or its keyword for this {@link URL}. | ||
this.url = url; | ||
} | ||
}); | ||
@@ -505,3 +1033,3 @@ /** | ||
jsonp(data, [ 'keyword', 'longurl', 'shorturl' ], callback); | ||
this.sendRequest(data, [ 'keyword', 'longurl', 'shorturl' ], callback); | ||
@@ -524,3 +1052,3 @@ return this | ||
jsonp(data, 'link', callback); | ||
this.sendRequest(data, 'link', callback); | ||
@@ -531,3 +1059,3 @@ return this | ||
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -554,34 +1082,2 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
/** | ||
* Sanitizes the result of {@link YOURLS#stats} so that it's more usable in application code by transforming the links | ||
* from an object mapping into an array. | ||
* | ||
* This function simply returns <code>result</code> if it is <code>null</code>, has no links property or one that is | ||
* already an array (future-proofing). | ||
* | ||
* @param {Object} result - the result to be sanitized (may be <code>null</code>) | ||
* @param {Object} [result.links] - the links to be transformed into an array (may be <code>null</code>) | ||
* @return {Object} The modified <code>result</code> or <code>null</code> if <code>result</code> is <code>null</code>. | ||
* @private | ||
*/ | ||
function sanitizeStatsResult(result) { | ||
// Future-proofing by sanitizing links *only* when not already an array | ||
if (!result || !result.links || isArray(result.links)) { | ||
return result | ||
} | ||
var index = 1; | ||
var link; | ||
var links = []; | ||
while ((link = result.links['link_' + index]) != null) { | ||
links.push(link); | ||
index++; | ||
} | ||
result.links = links; | ||
return result | ||
} | ||
/** | ||
* Provides the ability to connect to YOURLS servers and perform read/write operations via the API that they expose. | ||
@@ -593,5 +1089,8 @@ * | ||
* @constructor | ||
* @extends Requestor | ||
* @protected | ||
*/ | ||
var YOURLS = function() { | ||
var YOURLS = Requestor.extend(function() { | ||
YOURLS.super_.call(this); | ||
/** | ||
@@ -608,4 +1107,4 @@ * Provides information on the YOURLS {@link DB}. | ||
* | ||
* This is <b>not</b> the same as the version of YOURLS that is being connected to. The {@link YOURLS#version} | ||
* function should be used to provide that information. | ||
* This is <b>not</b> the same as the version of YOURLS that is being connected to. The {@link YOURLS#version} method | ||
* should be used to provide that information. | ||
* | ||
@@ -615,164 +1114,202 @@ * @public | ||
*/ | ||
this.VERSION = '2.0.0'; | ||
}; | ||
this.VERSION = '2.1.0'; | ||
}, { | ||
/** | ||
* Stores the specified information to be used later to connect to and authenticate with a YOURLS server. | ||
* | ||
* @param {string} [url=''] - the URL for the YOURLS server | ||
* @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be | ||
* <code>null</code>) | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.connect = function(url, credentials) { | ||
var api = new API(url, credentials); | ||
api.store(); | ||
/** | ||
* Stores the specified information to be used later to connect to and authenticate with a YOURLS server. | ||
* | ||
* @param {string} [url=''] - the URL for the YOURLS server | ||
* @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be | ||
* <code>null</code>) | ||
* @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be | ||
* <code>null</code>) | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
connect: function(url, credentials, options) { | ||
API.instance = new API(url, credentials, options); | ||
return this | ||
}; | ||
return this | ||
}, | ||
/** | ||
* Clears any information that may have been previously stored for connecting to and authenticating with a YOURLS | ||
* server. | ||
* | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.disconnect = function() { | ||
API.clear(); | ||
/** | ||
* Clears any information that may have been previously stored for connecting to and authenticating with a YOURLS | ||
* server. | ||
* | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
disconnect: function() { | ||
API.instance = null; | ||
return this | ||
}; | ||
return this | ||
}, | ||
/** | ||
* Creates a short URL for the specified long <code>url</code>. | ||
* | ||
* Optionally, a <code>descriptor</code> can be provided to specify a keyword and/or title for the short URL that is to | ||
* be created. If a keyword is specified, it must be available and, if not, the YOURLS server will generate a unique | ||
* keyword. If <code>descriptor</code> is a string, it will be treated as the keyword. | ||
* | ||
* @param {string} url - the long URL to be shortened | ||
* @param {YOURLS~UrlDescriptor|string} [descriptor] - the optional descriptor (or keyword, if it's a string) to be used | ||
* for the short URL | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.shorten = function(url, descriptor, callback) { | ||
var data = { | ||
action: 'shorturl', | ||
url: url | ||
}; | ||
/** | ||
* Creates a short URL for the specified long <code>url</code>. | ||
* | ||
* Optionally, a <code>descriptor</code> can be provided to specify a keyword and/or title for the short URL that is | ||
* to be created. If a keyword is specified, it must be available and, if not, the YOURLS server will generate a | ||
* unique keyword. If <code>descriptor</code> is a string, it will be treated as the keyword. | ||
* | ||
* @param {string} url - the long URL to be shortened | ||
* @param {YOURLS~UrlDescriptor|string} [descriptor] - the optional descriptor (or keyword, if it's a string) to be | ||
* used for the short URL | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
shorten: function(url, descriptor, callback) { | ||
var data = { | ||
action: 'shorturl', | ||
url: url | ||
}; | ||
switch (typeof descriptor) { | ||
case 'function': | ||
callback = descriptor; | ||
descriptor = null; | ||
break | ||
case 'string': | ||
descriptor = { keyword: descriptor }; | ||
break | ||
default: | ||
// Do nothing | ||
} | ||
switch (typeof descriptor) { | ||
case 'function': | ||
callback = descriptor; | ||
descriptor = null; | ||
break | ||
case 'string': | ||
descriptor = { keyword: descriptor }; | ||
break | ||
default: | ||
// Do nothing | ||
} | ||
if (descriptor) { | ||
data.keyword = descriptor.keyword; | ||
data.title = descriptor.title; | ||
} | ||
if (descriptor) { | ||
data.keyword = descriptor.keyword; | ||
data.title = descriptor.title; | ||
} | ||
jsonp(data, [ 'shorturl', 'title', 'url' ], callback); | ||
this.sendRequest(data, [ 'shorturl', 'title', 'url' ], callback); | ||
return this | ||
}; | ||
return this | ||
}, | ||
/** | ||
* Retrieves the statistics for all shortened URLs. | ||
* | ||
* Optionally, <code>criteria</code> can be provided to also include a refined set of links in the result. This includes | ||
* filter, which provides limited control over the sorting, as well as limit and start, which allow for pagination. If | ||
* <code>criteria</code> is a number, it will be treated as the limit. | ||
* | ||
* No links will be included in the result unless a limit is specified that is greater than zero. In that case, this | ||
* method would effectively be doing the same as {@link DB#stats}. | ||
* | ||
* @param {YOURLS~SearchCriteria|number} [criteria] - the optional criteria (or limit, if it's a number) to be used to | ||
* search for links to be included in the result | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.stats = function(criteria, callback) { | ||
var data = { action: 'stats' }; | ||
/** | ||
* Retrieves the statistics for all shortened URLs. | ||
* | ||
* Optionally, <code>criteria</code> can be provided to also include a refined set of links in the result. This | ||
* includes filter, which provides limited control over the sorting, as well as limit and start, which allow for | ||
* pagination. If <code>criteria</code> is a number, it will be treated as the limit. | ||
* | ||
* No links will be included in the result unless a limit is specified that is greater than zero. In that case, this | ||
* method would effectively be doing the same as {@link DB#stats}. | ||
* | ||
* @param {YOURLS~SearchCriteria|number} [criteria] - the optional criteria (or limit, if it's a number) to be used to | ||
* search for links to be included in the result | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
stats: function(criteria, callback) { | ||
var data = { action: 'stats' }; | ||
switch (typeof criteria) { | ||
case 'function': | ||
callback = criteria; | ||
criteria = null; | ||
break | ||
case 'number': | ||
criteria = { limit: criteria }; | ||
break | ||
default: | ||
// Do nothing | ||
} | ||
switch (typeof criteria) { | ||
case 'function': | ||
callback = criteria; | ||
criteria = null; | ||
break | ||
case 'number': | ||
criteria = { limit: criteria }; | ||
break | ||
default: | ||
// Do nothing | ||
} | ||
if (criteria) { | ||
data.filter = criteria.filter; | ||
data.limit = criteria.limit; | ||
data.start = criteria.start; | ||
} | ||
if (criteria) { | ||
data.filter = criteria.filter; | ||
data.limit = criteria.limit; | ||
data.start = criteria.start; | ||
} | ||
jsonp(data, [ 'links', 'stats' ], function(result, response) { | ||
callback(sanitizeStatsResult(result), response); | ||
}); | ||
this.sendRequest(data, [ 'links', 'stats' ], function(result, response) { | ||
callback(YOURLS._sanitizeStatsResult(result), response); | ||
}); | ||
return this | ||
}; | ||
return this | ||
}, | ||
/** | ||
* Creates an instance of {@link URL} for the specified shortened <code>url</code> which can be used to lookup more | ||
* detailed information relating to it. | ||
* | ||
* No data is fetched just by calling this function; one of the functions on the returned instance need to be called for | ||
* that to happen. | ||
* | ||
* @param {string} url - the shortened URL (or its keyword) | ||
* @return {URL} The {@link URL} created for the shortened <code>url</code> or <code>null</code> if <code>url</code> is | ||
* <code>null</code>. | ||
* @public | ||
*/ | ||
YOURLS.prototype.url = function(url) { | ||
return url ? new URL(url) : null | ||
}; | ||
/** | ||
* Creates an instance of {@link URL} for the specified shortened <code>url</code> which can be used to lookup more | ||
* detailed information relating to it. | ||
* | ||
* No data is fetched just by calling this method; one of the methods on the returned instance need to be called for | ||
* that to happen. | ||
* | ||
* @param {string} url - the shortened URL (or its keyword) | ||
* @return {URL} The {@link URL} created for the shortened <code>url</code> or <code>null</code> if <code>url</code> | ||
* is <code>null</code>. | ||
* @public | ||
*/ | ||
url: function(url) { | ||
return url ? new URL(url) : null | ||
}, | ||
/** | ||
* Retrieves the version of the connected YOURLS API. | ||
* | ||
* Optionally, <code>db</code> can be passed to indicate that the YOURLS database version should also be included in the | ||
* result. | ||
* | ||
* @param {boolean} [db] - <code>true</code> to include the database version; otherwise <code>false</code> | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.version = function(db, callback) { | ||
var data = { action: 'version' }; | ||
/** | ||
* Retrieves the version of the connected YOURLS API. | ||
* | ||
* Optionally, <code>db</code> can be passed to indicate that the YOURLS database version should also be included in | ||
* the result. | ||
* | ||
* @param {boolean} [db] - <code>true</code> to include the database version; otherwise <code>false</code> | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
version: function(db, callback) { | ||
var data = { action: 'version' }; | ||
if (typeof db === 'function') { | ||
callback = db; | ||
db = null; | ||
if (typeof db === 'function') { | ||
callback = db; | ||
db = null; | ||
} | ||
if (db != null) { | ||
data.db = Number(db); | ||
} | ||
this.sendRequest(data, [ 'db_version', 'version' ], callback); | ||
return this | ||
} | ||
if (db != null) { | ||
data.db = Number(db); | ||
}, { | ||
/** | ||
* Sanitizes the result of {@link YOURLS#stats} so that it's more usable in application code by transforming the links | ||
* from an object mapping into an array. | ||
* | ||
* This method simply returns <code>result</code> if it is <code>null</code>, has no links property or one that is | ||
* already an array (future-proofing). | ||
* | ||
* @param {Object} result - the result to be sanitized (may be <code>null</code>) | ||
* @param {Object} [result.links] - the links to be transformed into an array (may be <code>null</code>) | ||
* @return {Object} The modified <code>result</code> or <code>null</code> if <code>result</code> is <code>null</code>. | ||
* @private | ||
* @static | ||
*/ | ||
_sanitizeStatsResult: function(result) { | ||
// Future-proofing by sanitizing links *only* when not already an array | ||
if (!result || !result.links || isArray(result.links)) { | ||
return result | ||
} | ||
var index = 1; | ||
var link; | ||
var links = []; | ||
while ((link = result.links['link_' + index]) != null) { | ||
links.push(link); | ||
index++; | ||
} | ||
result.links = links; | ||
return result | ||
} | ||
jsonp(data, [ 'db_version', 'version' ], callback); | ||
}); | ||
return this | ||
}; | ||
/** | ||
@@ -779,0 +1316,0 @@ * The singleton instance of {@link YOURLS}. |
@@ -1,4 +0,4 @@ | ||
/*! YOURLS API v2.0.0 | (C) 2016 Alasdair Mercer | MIT License */ | ||
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define("yourls-api",n):t.yourls=n()}(this,function(){"use strict";function t(t){if(!t)return null;var n={};return t.signature?(n.signature=t.signature,n.timestamp=t.timestamp):(n.password=t.password,n.username=t.username),n}function n(n,r){this.url=n?n.replace(/\/$/,""):"",this.credentials=t(r)}function r(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(t)}function e(t){if(!t)return"";var n=[];for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&null!=t[r]&&n.push(encodeURIComponent(r)+"="+encodeURIComponent(t[r]));return n.join("&")}function o(){for(var t=Date.now();p[t];)t++;return t}function i(t,n){t=r(t)?t:[t];var e,o,i=null;if(!n)return i;if(1===t.length)i=n[t[0]];else for(i={},e=0;e<t.length;e++)o=t[e],"undefined"!=typeof n[o]&&(i[o]=n[o]);return i}function u(t,r,u){var s=n.fetch(),a=o(),l=document.createElement("script");p[a]=function(t){var n=i(r,t);delete p[a],l.parentNode.removeChild(l),u(n,t)};var c=s.url+"?"+e({callback:f+"["+a+"]",format:"jsonp"});s.credentials&&(c+="&"+e(s.credentials)),t&&(c+="&"+e(t)),l.setAttribute("src",c),document.getElementsByTagName("head")[0].appendChild(l)}function s(){}function a(t){this.url=t}function l(t){if(!t||!t.links||r(t.links))return t;for(var n,e=1,o=[];null!=(n=t.links["link_"+e]);)o.push(n),e++;return t.links=o,t}var c=null;n.clear=function(){c=null},n.fetch=function(){return c},n.prototype.store=function(){c=this};var f="__yourls"+Date.now()+"_jsonp",p=window[f]={};s.prototype.stats=function(t){var n={action:"db-stats"};return u(n,"db-stats",t),this},a.prototype.expand=function(t){var n={action:"expand",shorturl:this.url};return u(n,["keyword","longurl","shorturl"],t),this},a.prototype.stats=function(t){var n={action:"url-stats",shorturl:this.url};return u(n,"link",t),this};var d=function(){this.db=new s,this.VERSION="2.0.0"};d.prototype.connect=function(t,r){var e=new n(t,r);return e.store(),this},d.prototype.disconnect=function(){return n.clear(),this},d.prototype.shorten=function(t,n,r){var e={action:"shorturl",url:t};switch(typeof n){case"function":r=n,n=null;break;case"string":n={keyword:n}}return n&&(e.keyword=n.keyword,e.title=n.title),u(e,["shorturl","title","url"],r),this},d.prototype.stats=function(t,n){var r={action:"stats"};switch(typeof t){case"function":n=t,t=null;break;case"number":t={limit:t}}return t&&(r.filter=t.filter,r.limit=t.limit,r.start=t.start),u(r,["links","stats"],function(t,r){n(l(t),r)}),this},d.prototype.url=function(t){return t?new a(t):null},d.prototype.version=function(t,n){var r={action:"version"};return"function"==typeof t&&(n=t,t=null),null!=t&&(r.db=Number(t)),u(r,["db_version","version"],n),this};var h=new d;return h}); | ||
/*! YOURLS API v2.1.0 | (C) 2017 Alasdair Mercer | MIT License */ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define("yourls-api",e):t.yourls=e()}(this,function(){"use strict";function t(t,e,n){if(null!=e){n=u.call(arguments,2);for(var r,o,i=0,a=n.length;i<a;i++){o=n[i];for(r in o)t&&!s.call(o,r)||(e[r]=o[r])}}}function e(e,n){var r;return"function"==typeof Object.create?r=Object.create(e):(i.prototype=e,r=new i,i.prototype=null),n&&t(!0,r,n),r}function n(){}function r(t,e){e=Array.prototype.slice.call(arguments,1);for(var n,r,o=0,i=e.length;o<i;o++)if(r=e[o])for(n in r)Object.prototype.hasOwnProperty.call(r,n)&&(t[n]=r[n]);return t}function o(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(t)}var i=function(){},s=Object.prototype.hasOwnProperty,u=Array.prototype.slice;n.extend=function(n,r,o){var i=this;return"function"!=typeof n&&(o=r,r=n,n=function(){return i.apply(this,arguments)}),t(!1,n,i,o),n.prototype=e(i.prototype,r),n.prototype.constructor=n,n.super_=i,n};var a=n.extend(function(t,e,n){this.url=t?t.replace(/\/$/,""):"",this.credentials=a._sanitizeCredentials(e),this.options=a._sanitizeOptions(n)},null,{defaultOptions:{format:"jsonp",method:"GET"},instance:null,_sanitizeCredentials:function(t){if(!t)return null;var e={};return t.signature?(e.signature=t.signature,e.timestamp=t.timestamp):(e.password=t.password,e.username=t.username),e},_sanitizeOptions:function(t){var e=r({},a.defaultOptions);return t?(t.format&&(e.format=t.format.toLowerCase()),t.method&&(e.method=t.method.toUpperCase()),e):e}}),l=n.extend({buildBody:function(t,e){return r({format:t.options.format},t.credentials,e)},getSupportedHttpMethods:function(){return null},isMethodSupported:function(t){var e=this.getSupportedHttpMethods();return e&&e.indexOf(t)!==-1},isQueryStringRequired:function(t){return"GET"===t},process:function(t,e,n,r){},send:function(t,e,n){var r=a.instance,o=l._serializeParameters(this.buildBody(r,t)),i=r.options.method,s=r.url;this.isQueryStringRequired(i)&&(s+="?"+o,o=null),this.process(i,s,o,function(t){n(l._extractResult(e,t),t)})}},{_extractResult:function(t,e){t=o(t)?t:[t];var n,r,i=null;if(!e)return i;if(1===t.length)i=e[t[0]];else for(i={},n=0;n<t.length;n++)r=t[n],void 0!==e[r]&&(i[r]=e[r]);return i},_serializeParameters:function(t){if(!t)return"";var e=[];for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&null!=t[n]&&e.push(encodeURIComponent(n)+"="+encodeURIComponent(t[n]));return e.join("&")}}),c=l.extend({getSupportedHttpMethods:function(){return["GET","POST"]},process:function(t,e,n,r){var o=new XMLHttpRequest;o.open(t,e,!0),o.onreadystatechange=function(){var t;if(4===o.readyState)try{t=JSON.parse(o.responseText),r(t)}catch(t){throw new Error("Unable to parse response: "+t)}},o.setRequestHeader("X-Requested-With","XMLHttpRequest"),null!=n&&o.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),o.send(n)}}),d=(new Date).getTime(),p=l.extend(function(){p.super_.call(this),window[p._callbackHolderKey]||(window[p._callbackHolderKey]=p._callbackHolder),this._id=p._generateId()},{getSupportedHttpMethods:function(){return["GET"]},buildBody:function(t,e){var n=p.super_.prototype.buildBody.call(this,t,e);return n.callback=p._callbackHolderKey+"["+this._id+"]",n},process:function(t,e,n,r){var o=document.createElement("script"),i=this;p._callbackHolder[this._id]=function(t){delete p._callbackHolder[i._id],o.parentNode.removeChild(o),r(t)},o.setAttribute("src",e),document.getElementsByTagName("head")[0].appendChild(o)}},{_callbackHolderKey:"__yourls"+d+"_jsonp",_callbackHolder:{},_generateId:function(){do{d++}while(p._callbackHolder[d]);return d}}),f=n.extend({sendRequest:function(t,e,n){var r=a.instance;if(!r)throw new Error("No connection has been made");var o=r.options.format,i=r.options.method,s=f._requestFormatMap[o];if(!s)throw new Error("Request format not supported: "+o);var u=new s;if(!u.isMethodSupported(i))throw new Error("HTTP method not supported: "+i);u.send(t,e,n)}},{_requestFormatMap:{json:c,jsonp:p}}),h=f.extend({stats:function(t){var e={action:"db-stats"};return this.sendRequest(e,"db-stats",t),this}}),y=f.extend(function(t){y.super_.call(this),this.url=t});y.prototype.expand=function(t){var e={action:"expand",shorturl:this.url};return this.sendRequest(e,["keyword","longurl","shorturl"],t),this},y.prototype.stats=function(t){var e={action:"url-stats",shorturl:this.url};return this.sendRequest(e,"link",t),this};var m=f.extend(function(){m.super_.call(this),this.db=new h,this.VERSION="2.1.0"},{connect:function(t,e,n){return a.instance=new a(t,e,n),this},disconnect:function(){return a.instance=null,this},shorten:function(t,e,n){var r={action:"shorturl",url:t};switch(typeof e){case"function":n=e,e=null;break;case"string":e={keyword:e}}return e&&(r.keyword=e.keyword,r.title=e.title),this.sendRequest(r,["shorturl","title","url"],n),this},stats:function(t,e){var n={action:"stats"};switch(typeof t){case"function":e=t,t=null;break;case"number":t={limit:t}}return t&&(n.filter=t.filter,n.limit=t.limit,n.start=t.start),this.sendRequest(n,["links","stats"],function(t,n){e(m._sanitizeStatsResult(t),n)}),this},url:function(t){return t?new y(t):null},version:function(t,e){var n={action:"version"};return"function"==typeof t&&(e=t,t=null),null!=t&&(n.db=Number(t)),this.sendRequest(n,["db_version","version"],e),this}},{_sanitizeStatsResult:function(t){if(!t||!t.links||o(t.links))return t;for(var e,n=1,r=[];null!=(e=t.links["link_"+n]);)r.push(e),n++;return t.links=r,t}});return new m}); | ||
//# sourceMappingURL=yourls.min.js.map |
@@ -1,2 +0,2 @@ | ||
Copyright (C) 2016 Alasdair Mercer | ||
Copyright (C) 2017 Alasdair Mercer | ||
@@ -3,0 +3,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
{ | ||
"name": "yourls-api", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "Bindings for the YOURLS API", | ||
@@ -20,2 +20,3 @@ "homepage": "https://github.com/neocotic/yourls-api", | ||
"api", | ||
"json", | ||
"jsonp" | ||
@@ -35,13 +36,15 @@ ], | ||
"grunt-rollup": "^1.0.1", | ||
"rollup-plugin-commonjs": "^5.0.5", | ||
"load-grunt-tasks": "^3.5.2", | ||
"oopsy": "^0.2.0", | ||
"rollup-plugin-node-resolve": "^2.0.0", | ||
"rollup-plugin-uglify": "^1.0.1", | ||
"semver": "^5.3.0" | ||
"rollup-plugin-uglify": "^1.0.1" | ||
}, | ||
"main": "dist/yourls.js", | ||
"browser": "dist/yourls.js", | ||
"jsnext:main": "src/yourls.js", | ||
"scripts": { | ||
"build": "grunt build", | ||
"ci": "grunt ci", | ||
"test": "grunt test" | ||
} | ||
} |
@@ -18,12 +18,10 @@ Y88b d88P .d88888b. 888 888 8888888b. 888 .d8888b. | ||
[YOURLS API](https://github.com/neocotic/yourls-api) is a JavaScript library that provides bindings for | ||
[YOURLS](https://yourls.org) URL shortener servers. | ||
> This library is only compatible with YOURLS servers running version **1.5.1** or newer as it requires JSONP support. | ||
[![Build Status](https://img.shields.io/travis/neocotic/yourls-api/develop.svg?style=flat-square)](https://travis-ci.org/neocotic/yourls-api) | ||
[![Dev Dependency Status](https://img.shields.io/david/dev/neocotic/yourls-api.svg?style=flat-square)](https://david-dm.org/neocotic/yourls-api#info=devDependencies) | ||
[![Dev Dependency Status](https://img.shields.io/david/dev/neocotic/yourls-api.svg?style=flat-square)](https://david-dm.org/neocotic/yourls-api?type=dev) | ||
[![License](https://img.shields.io/npm/l/yourls-api.svg?style=flat-square)](https://github.com/neocotic/yourls-api/blob/master/LICENSE.md) | ||
[![Release](https://img.shields.io/npm/v/yourls-api.svg?style=flat-square)](https://www.npmjs.com/package/yourls-api) | ||
[YOURLS API](https://github.com/neocotic/yourls-api) is a JavaScript library that provides bindings for | ||
[YOURLS](https://yourls.org) URL shortener servers. | ||
* [Install](#install) | ||
@@ -52,4 +50,4 @@ * [API](#api) | ||
* [Development Version](https://github.com/neocotic/yourls-api/blob/master/dist/yourls.js) | ||
* [Production Version](https://github.com/neocotic/yourls-api/blob/master/dist/yourls.min.js) | ||
* [Development Version](https://cdn.rawgit.com/neocotic/yourls-api/master/dist/yourls.js) (TODO - [Source Map](https://cdn.rawgit.com/neocotic/yourls-api/master/dist/yourls.js.map)) | ||
* [Production Version](https://cdn.rawgit.com/neocotic/yourls-api/master/dist/yourls.min.js) (TODO - [Source Map](https://cdn.rawgit.com/neocotic/yourls-api/master/dist/yourls.min.js.map)) | ||
@@ -74,3 +72,3 @@ ## API | ||
``` javascript | ||
yourls.connect(url[, credentials]) | ||
yourls.connect(url[, credentials][, options]) | ||
``` | ||
@@ -126,8 +124,44 @@ | ||
> **IMPORTANT:** When sending `GET` requests, by design, all information will be included in the URL of the request | ||
> This includes data as well as *any credentials* used to authenticate with the API. You have been warned. | ||
> This is unavoidable when sending requests in the JSONP format but, when using the JSON format, you can send `POST` | ||
> requests, which means that your data is sent inside the body of the request. Combine this with HTTPS and your data and | ||
> credentials cannot be sniffed over the network. | ||
As you may have noticed; this method also accepts the following entirely optional `options`: | ||
Option | Description | Default | ||
------ | ----------------------------------- | --------- | ||
format | Format in which requests are sent | `"jsonp"` | ||
method | HTTP method to be used for requests | `"GET"` | ||
``` javascript | ||
// Does the same as specifying no options (i.e. using defaults) | ||
yourls.connect('https://example.com/yourls-api.php', null, { | ||
format: 'jsonp', | ||
method: 'GET' | ||
}) | ||
// Best practice if you want to secure the data you're transmitting and you've setup CORS, if needed | ||
yourls.connect('https://example.com/yourls-api.php', { | ||
signature: '3002a61584' | ||
}, { | ||
format: 'json', | ||
method: 'POST' | ||
}) | ||
``` | ||
The following formats are supported with the corresponding HTTP methods: | ||
Format | HTTP Methods | ||
------ | ------------ | ||
json | GET, POST | ||
jsonp | GET | ||
> **IMPORTANT:** The YOURLS server must be running version **1.5.1** or newer in order to send requests in the JSONP | ||
> format. | ||
Despite the name of this method, no connection or authentication is carried out at this point and this initial method | ||
simply stores these values to prevent you from having to specify them with every API call. | ||
> When using JSONP to send requests, by design, all information will be included in the URL of the request. This | ||
> includes data as well as **any credentials** used to authenticate with the API. You have been warned. | ||
### Disconnecting | ||
@@ -278,2 +312,3 @@ | ||
``` javascript | ||
// Get more details for link | ||
yourls.url('https://example.com/yourls').expand(function(result, response) { | ||
@@ -298,2 +333,3 @@ console.log(result.keyword) | ||
``` javascript | ||
// Get statistics only for this link | ||
yourls.url('https://example.com/yourls').stats(function(result, response) { | ||
@@ -318,2 +354,3 @@ console.log(result.clicks) | ||
``` javascript | ||
// Get YOURLS version | ||
yourls.version(function(result, response) { | ||
@@ -328,2 +365,3 @@ console.log(result.version) | ||
``` javascript | ||
// Get YOURLS database version as well | ||
yourls.version(true, function(result, response) { | ||
@@ -340,4 +378,5 @@ console.log(result.version) | ||
``` javascript | ||
// Get version of this library | ||
console.log(yourls.VERSION) | ||
//=> "2.0.0" | ||
//=> "2.1.0" | ||
``` | ||
@@ -376,4 +415,4 @@ | ||
Copyright © 2016 Alasdair Mercer | ||
Copyright © 2017 Alasdair Mercer | ||
See [LICENSE.md](https://github.com/neocotic/yourls-api/blob/master/LICENSE.md) for more information on our MIT license. |
176
src/api.js
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -23,39 +23,6 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
/** | ||
* The singleton {@link API} instance which is privatized to prevent leaking credentials. | ||
* | ||
* @private | ||
* @type {API} | ||
*/ | ||
var instance = null | ||
import Oopsy from 'oopsy' | ||
/** | ||
* Sanitizes the specified <code>credentials</code> by ensuring that only valid properties are present and only when | ||
* appropriate. | ||
* | ||
* This function does not modify <code>credentials</code> and instead creates a new object with the sanitized | ||
* properties. | ||
* | ||
* @param {API~Credentials} credentials - the credentials to be sanitized (may be <code>null</code>) | ||
* @return {API~Credentials} A sanitized version of <code>credentials</code> or <code>null</code> if | ||
* <code>credentials</code> is <code>null</code>. | ||
* @private | ||
*/ | ||
function sanitizeCredentials(credentials) { | ||
if (!credentials) { | ||
return null | ||
} | ||
import { extend } from './util/extend' | ||
var result = {} | ||
if (credentials.signature) { | ||
result.signature = credentials.signature | ||
result.timestamp = credentials.timestamp | ||
} else { | ||
result.password = credentials.password | ||
result.username = credentials.username | ||
} | ||
return result | ||
} | ||
/** | ||
@@ -67,6 +34,8 @@ * Contains information on how to connect to and authenticate with a YOURLS server. | ||
* <code>null</code>) | ||
* @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be | ||
* <code>null</code>) | ||
* @protected | ||
* @constructor | ||
*/ | ||
export function API(url, credentials) { | ||
export var API = Oopsy.extend(function(url, credentials, options) { | ||
/** | ||
@@ -87,39 +56,94 @@ * The URL of the YOURLS server. | ||
*/ | ||
this.credentials = sanitizeCredentials(credentials) | ||
} | ||
this.credentials = API._sanitizeCredentials(credentials) | ||
/** | ||
* The options to be used to send requests to the YOURLS server. | ||
* | ||
* @public | ||
* @type {API~Options} | ||
*/ | ||
this.options = API._sanitizeOptions(options) | ||
}, null, { | ||
/** | ||
* Destroys the singleton instance of {@link API}. | ||
* | ||
* @return {void} | ||
* @public | ||
* @static | ||
*/ | ||
API.clear = function() { | ||
instance = null | ||
} | ||
/** | ||
* The default options to be used. | ||
* | ||
* @protected | ||
* @static | ||
* @type {API~Options} | ||
*/ | ||
defaultOptions: { | ||
format: 'jsonp', | ||
method: 'GET' | ||
}, | ||
/** | ||
* Retrieves the singleton instance of {@link API}. | ||
* | ||
* This function will return <code>null</code> unless an instance is currently stored. | ||
* | ||
* @return {API} The connected {@link API} or <code>null</code> if none exists. | ||
* @public | ||
* @static | ||
*/ | ||
API.fetch = function() { | ||
return instance | ||
} | ||
/** | ||
* The singleton {@link API} instance which is privatized to prevent leaking credentials. | ||
* | ||
* @public | ||
* @static | ||
* @type {API} | ||
*/ | ||
instance: null, | ||
/** | ||
* Stores this {@link API} as the singleton, potentially replacing the existing instance. | ||
* | ||
* @return {void} | ||
* @public | ||
*/ | ||
API.prototype.store = function() { | ||
instance = this | ||
} | ||
/** | ||
* Sanitizes the specified <code>credentials</code> by ensuring that only valid properties are present and only when | ||
* appropriate. | ||
* | ||
* This method does not modify <code>credentials</code> and instead creates a new object with the sanitized | ||
* properties. | ||
* | ||
* @param {API~Credentials} credentials - the credentials to be sanitized (may be <code>null</code>) | ||
* @return {API~Credentials} A sanitized version of <code>credentials</code> or <code>null</code> if | ||
* <code>credentials</code> is <code>null</code>. | ||
* @private | ||
* @static | ||
*/ | ||
_sanitizeCredentials: function(credentials) { | ||
if (!credentials) { | ||
return null | ||
} | ||
var result = {} | ||
if (credentials.signature) { | ||
result.signature = credentials.signature | ||
result.timestamp = credentials.timestamp | ||
} else { | ||
result.password = credentials.password | ||
result.username = credentials.username | ||
} | ||
return result | ||
}, | ||
/** | ||
* Sanitizes the specified <code>options</code> by ensuring that only valid properties are present and in the correct | ||
* format. | ||
* | ||
* This method does not modify <code>options</code> and instead creates a new object with the sanitized properties and | ||
* default values will be used for missing options. | ||
* | ||
* @param {API~Options} options - the options to be sanitized (may be <code>null</code>) | ||
* @return {API~Options} A sanitized version of <code>options</code> which will contain only default values if | ||
* <code>options</code> is <code>null</code>. | ||
* @private | ||
* @static | ||
*/ | ||
_sanitizeOptions: function(options) { | ||
var result = extend({}, API.defaultOptions) | ||
if (!options) { | ||
return result | ||
} | ||
if (options.format) { | ||
result.format = options.format.toLowerCase() | ||
} | ||
if (options.method) { | ||
result.method = options.method.toUpperCase() | ||
} | ||
return result | ||
} | ||
}) | ||
/** | ||
@@ -142,1 +166,13 @@ * The credentials to be used to authenticate with a private YOURLS API. | ||
*/ | ||
/** | ||
* The options that determine how requests are sent to the YOURLS server. | ||
* | ||
* If the request <code>format</code> does not support the HTTP <code>method</code>, requests will not be sent and an | ||
* error will be thrown when such attempts occur. | ||
* | ||
* @typedef {Object} API~Options | ||
* @property {string} [format="jsonp"] - The format in which requests are sent (either <code>"json"</code> or | ||
* <code>"jsonp"</code>). | ||
* @property {string} [method="GET"] - The HTTP method to be used for requests. | ||
*/ |
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -23,126 +23,114 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
import { API } from '../api' | ||
import { isArray } from '../util/array' | ||
import { paramify } from './paramify' | ||
import { Request } from './request' | ||
/** | ||
* The key of the callback function holder within the global namespace. | ||
* The seed to be used to generate IDs. | ||
* | ||
* @private | ||
* @type {string} | ||
* @type {number} | ||
*/ | ||
var callbackHolderKey = '__yourls' + Date.now() + '_jsonp' | ||
var seed = new Date().getTime() | ||
/** | ||
* Contains the callback functions for active JSONP requests. | ||
* An implementation of {@link Request} that provides support for JSONP requests to the YOURLS API. | ||
* | ||
* Callback references should be removed immediately once they have been called. | ||
* JSONP requests can only be sent using the "GET" HTTP method. | ||
* | ||
* Due to the nature of JSON, a reference to this object <b>must</b> be publicly available (i.e. global). | ||
* | ||
* @private | ||
* @type {Object<number, Function>} | ||
* @constructor | ||
* @extends Request | ||
* @protected | ||
*/ | ||
var callbackHolder = window[callbackHolderKey] = {} | ||
export var JSONPRequest = Request.extend(function() { | ||
JSONPRequest.super_.call(this) | ||
/** | ||
* Generates a quick and dirty unique ID for a callback. | ||
* | ||
* @return {number} The generated callback ID. | ||
* @private | ||
*/ | ||
function generateCallbackId() { | ||
var id = Date.now() | ||
while (callbackHolder[id]) { | ||
id++ | ||
if (!window[JSONPRequest._callbackHolderKey]) { | ||
window[JSONPRequest._callbackHolderKey] = JSONPRequest._callbackHolder | ||
} | ||
return id | ||
} | ||
/** | ||
* The generated ID which is used to store a reference to the callback function within the holder so that the JSONP | ||
* payload can find it in the global namespace. | ||
* | ||
* @private | ||
* @type {number} | ||
*/ | ||
this._id = JSONPRequest._generateId() | ||
}, { | ||
/** | ||
* Extracts the values of the properties with the specified <code>names</code> from the <code>response</code> provided | ||
* and returns them in a single result. | ||
* | ||
* If <code>names</code> is a string or only contains a single string, only the value for that named property will be | ||
* returned. Otherwise, an object containing the key/value pairs for each named property will be returned. | ||
* | ||
* If <code>response</code> is <code>null</code> this function will return <code>null</code>. | ||
* | ||
* @param {string|string[]} names - the names of the <code>response</code> properties whose values are to be returned as | ||
* the result | ||
* @param {Object} response - the YOURLS API response | ||
* @return {*} The result extracted from <code>response</code>. | ||
* @private | ||
*/ | ||
function getResult(names, response) { | ||
names = isArray(names) ? names : [ names ] | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
getSupportedHttpMethods: function() { | ||
return [ 'GET' ] | ||
}, | ||
var i | ||
var name | ||
var result = null | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
buildBody: function(api, data) { | ||
var body = JSONPRequest.super_.prototype.buildBody.call(this, api, data) | ||
body.callback = JSONPRequest._callbackHolderKey + '[' + this._id + ']' | ||
if (!response) { | ||
return result | ||
} | ||
return body | ||
}, | ||
if (names.length === 1) { | ||
result = response[names[0]] | ||
} else { | ||
result = {} | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
process: function(method, url, body, callback) { | ||
var script = document.createElement('script') | ||
for (i = 0; i < names.length; i++) { | ||
name = names[i] | ||
var self = this | ||
JSONPRequest._callbackHolder[this._id] = function(response) { | ||
delete JSONPRequest._callbackHolder[self._id] | ||
script.parentNode.removeChild(script) | ||
if (typeof response[name] !== 'undefined') { | ||
result[name] = response[name] | ||
} | ||
callback(response) | ||
} | ||
script.setAttribute('src', url) | ||
document.getElementsByTagName('head')[0].appendChild(script) | ||
} | ||
return result | ||
} | ||
}, { | ||
/** | ||
* Sends a JSONP request to the connected YOURLS API with the <code>data</code> provided which should, in turn, call the | ||
* specified <code>callback</code> with the result. | ||
* | ||
* If the request is successful, <code>callback</code> will be passed the value of the named properties from the | ||
* response. If <code>resultNames</code> is a string or only contains a single string, only the value for that named | ||
* property will be passed as the first argument. Otherwise, an object containing the key/value pairs for each named | ||
* property will be passed as the first argument. The actual response will always be passed as the second argument. | ||
* | ||
* Due to the nature of JSONP, all information will be included in the URL of the request. This includes | ||
* <code>data</code> as well as <b>any credentials</b> used to authenticate with the API. You have been warned. | ||
* | ||
* @param {Object} data - the data to be sent | ||
* @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to | ||
* <code>callback</code> as the first argument | ||
* @param {Function} callback - the function to be called with the result | ||
* @return {void} | ||
* @protected | ||
*/ | ||
export function jsonp(data, resultNames, callback) { | ||
var api = API.fetch() | ||
var id = generateCallbackId() | ||
var script = document.createElement('script') | ||
/** | ||
* The key of the callback function holder within the global namespace. | ||
* | ||
* @private | ||
* @static | ||
* @type {string} | ||
*/ | ||
_callbackHolderKey: '__yourls' + seed + '_jsonp', | ||
callbackHolder[id] = function(response) { | ||
var result = getResult(resultNames, response) | ||
/** | ||
* Contains the callback functions for active JSONP requests. | ||
* | ||
* Callback references should be removed immediately once they have been called. | ||
* | ||
* Due to the nature of JSON, a reference to this object <b>must</b> be publicly available (i.e. global). | ||
* | ||
* @private | ||
* @static | ||
* @type {Object<number, Function>} | ||
*/ | ||
_callbackHolder: {}, | ||
delete callbackHolder[id] | ||
script.parentNode.removeChild(script) | ||
/** | ||
* Generates an ID to be used when storing a reference to a callback function. | ||
* | ||
* @return {number} The generated ID. | ||
* @private | ||
*/ | ||
_generateId: function() { | ||
do { | ||
seed++ | ||
} while (JSONPRequest._callbackHolder[seed]) | ||
callback(result, response) | ||
return seed | ||
} | ||
var target = api.url + '?' + paramify({ callback: callbackHolderKey + '[' + id + ']', format: 'jsonp' }) | ||
if (api.credentials) { | ||
target += '&' + paramify(api.credentials) | ||
} | ||
if (data) { | ||
target += '&' + paramify(data) | ||
} | ||
script.setAttribute('src', target) | ||
document.getElementsByTagName('head')[0].appendChild(script) | ||
} | ||
}) |
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -26,3 +26,3 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* | ||
* This function will use the native <code>Array.isArray</code>, if available. | ||
* This method will use the native <code>Array.isArray</code>, if available. | ||
* | ||
@@ -29,0 +29,0 @@ * @param {*} obj - the object to be checked (may be <code>null</code>) |
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -23,3 +23,3 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
import { jsonp } from './request/jsonp' | ||
import { Requestor } from './request/requestor' | ||
@@ -30,21 +30,22 @@ /** | ||
* @constructor | ||
* @extends Requestor | ||
* @protected | ||
*/ | ||
export function DB() { | ||
// Do nothing | ||
} | ||
export var DB = Requestor.extend({ | ||
/** | ||
* Retrieves the statistics for this {@link DB}. | ||
* | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {DB} A reference to this {@link DB} for chaining purposes. | ||
* @public | ||
*/ | ||
DB.prototype.stats = function(callback) { | ||
var data = { action: 'db-stats' } | ||
/** | ||
* Retrieves the statistics for this {@link DB}. | ||
* | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {DB} A reference to this {@link DB} for chaining purposes. | ||
* @public | ||
*/ | ||
stats: function(callback) { | ||
var data = { action: 'db-stats' } | ||
jsonp(data, 'db-stats', callback) | ||
this.sendRequest(data, 'db-stats', callback) | ||
return this | ||
} | ||
return this | ||
} | ||
}) |
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -23,3 +23,3 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
import { jsonp } from './request/jsonp' | ||
import { Requestor } from './request/requestor' | ||
@@ -31,5 +31,8 @@ /** | ||
* @constructor | ||
* @extends Requestor | ||
* @protected | ||
*/ | ||
export function URL(url) { | ||
export var URL = Requestor.extend(function(url) { | ||
URL.super_.call(this) | ||
/** | ||
@@ -42,3 +45,3 @@ * Either the shortened URL or its keyword for this {@link URL}. | ||
this.url = url | ||
} | ||
}) | ||
@@ -58,3 +61,3 @@ /** | ||
jsonp(data, [ 'keyword', 'longurl', 'shorturl' ], callback) | ||
this.sendRequest(data, [ 'keyword', 'longurl', 'shorturl' ], callback) | ||
@@ -77,5 +80,5 @@ return this | ||
jsonp(data, 'link', callback) | ||
this.sendRequest(data, 'link', callback) | ||
return this | ||
} |
/* | ||
* Copyright (C) 2016 Alasdair Mercer | ||
* Copyright (C) 2017 Alasdair Mercer | ||
* | ||
@@ -26,38 +26,6 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
import { isArray } from './util/array' | ||
import { jsonp } from './request/jsonp' | ||
import { Requestor } from './request/requestor' | ||
import { URL } from './yourls-url' | ||
/** | ||
* Sanitizes the result of {@link YOURLS#stats} so that it's more usable in application code by transforming the links | ||
* from an object mapping into an array. | ||
* | ||
* This function simply returns <code>result</code> if it is <code>null</code>, has no links property or one that is | ||
* already an array (future-proofing). | ||
* | ||
* @param {Object} result - the result to be sanitized (may be <code>null</code>) | ||
* @param {Object} [result.links] - the links to be transformed into an array (may be <code>null</code>) | ||
* @return {Object} The modified <code>result</code> or <code>null</code> if <code>result</code> is <code>null</code>. | ||
* @private | ||
*/ | ||
function sanitizeStatsResult(result) { | ||
// Future-proofing by sanitizing links *only* when not already an array | ||
if (!result || !result.links || isArray(result.links)) { | ||
return result | ||
} | ||
var index = 1 | ||
var link | ||
var links = [] | ||
while ((link = result.links['link_' + index]) != null) { | ||
links.push(link) | ||
index++ | ||
} | ||
result.links = links | ||
return result | ||
} | ||
/** | ||
* Provides the ability to connect to YOURLS servers and perform read/write operations via the API that they expose. | ||
@@ -69,5 +37,8 @@ * | ||
* @constructor | ||
* @extends Requestor | ||
* @protected | ||
*/ | ||
var YOURLS = function() { | ||
var YOURLS = Requestor.extend(function() { | ||
YOURLS.super_.call(this) | ||
/** | ||
@@ -84,4 +55,4 @@ * Provides information on the YOURLS {@link DB}. | ||
* | ||
* This is <b>not</b> the same as the version of YOURLS that is being connected to. The {@link YOURLS#version} | ||
* function should be used to provide that information. | ||
* This is <b>not</b> the same as the version of YOURLS that is being connected to. The {@link YOURLS#version} method | ||
* should be used to provide that information. | ||
* | ||
@@ -91,164 +62,202 @@ * @public | ||
*/ | ||
this.VERSION = '2.0.0' | ||
} | ||
this.VERSION = '2.1.0' | ||
}, { | ||
/** | ||
* Stores the specified information to be used later to connect to and authenticate with a YOURLS server. | ||
* | ||
* @param {string} [url=''] - the URL for the YOURLS server | ||
* @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be | ||
* <code>null</code>) | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.connect = function(url, credentials) { | ||
var api = new API(url, credentials) | ||
api.store() | ||
/** | ||
* Stores the specified information to be used later to connect to and authenticate with a YOURLS server. | ||
* | ||
* @param {string} [url=''] - the URL for the YOURLS server | ||
* @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be | ||
* <code>null</code>) | ||
* @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be | ||
* <code>null</code>) | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
connect: function(url, credentials, options) { | ||
API.instance = new API(url, credentials, options) | ||
return this | ||
} | ||
return this | ||
}, | ||
/** | ||
* Clears any information that may have been previously stored for connecting to and authenticating with a YOURLS | ||
* server. | ||
* | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.disconnect = function() { | ||
API.clear() | ||
/** | ||
* Clears any information that may have been previously stored for connecting to and authenticating with a YOURLS | ||
* server. | ||
* | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
disconnect: function() { | ||
API.instance = null | ||
return this | ||
} | ||
return this | ||
}, | ||
/** | ||
* Creates a short URL for the specified long <code>url</code>. | ||
* | ||
* Optionally, a <code>descriptor</code> can be provided to specify a keyword and/or title for the short URL that is to | ||
* be created. If a keyword is specified, it must be available and, if not, the YOURLS server will generate a unique | ||
* keyword. If <code>descriptor</code> is a string, it will be treated as the keyword. | ||
* | ||
* @param {string} url - the long URL to be shortened | ||
* @param {YOURLS~UrlDescriptor|string} [descriptor] - the optional descriptor (or keyword, if it's a string) to be used | ||
* for the short URL | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.shorten = function(url, descriptor, callback) { | ||
var data = { | ||
action: 'shorturl', | ||
url: url | ||
} | ||
/** | ||
* Creates a short URL for the specified long <code>url</code>. | ||
* | ||
* Optionally, a <code>descriptor</code> can be provided to specify a keyword and/or title for the short URL that is | ||
* to be created. If a keyword is specified, it must be available and, if not, the YOURLS server will generate a | ||
* unique keyword. If <code>descriptor</code> is a string, it will be treated as the keyword. | ||
* | ||
* @param {string} url - the long URL to be shortened | ||
* @param {YOURLS~UrlDescriptor|string} [descriptor] - the optional descriptor (or keyword, if it's a string) to be | ||
* used for the short URL | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
shorten: function(url, descriptor, callback) { | ||
var data = { | ||
action: 'shorturl', | ||
url: url | ||
} | ||
switch (typeof descriptor) { | ||
case 'function': | ||
callback = descriptor | ||
descriptor = null | ||
break | ||
case 'string': | ||
descriptor = { keyword: descriptor } | ||
break | ||
default: | ||
// Do nothing | ||
} | ||
switch (typeof descriptor) { | ||
case 'function': | ||
callback = descriptor | ||
descriptor = null | ||
break | ||
case 'string': | ||
descriptor = { keyword: descriptor } | ||
break | ||
default: | ||
// Do nothing | ||
} | ||
if (descriptor) { | ||
data.keyword = descriptor.keyword | ||
data.title = descriptor.title | ||
} | ||
if (descriptor) { | ||
data.keyword = descriptor.keyword | ||
data.title = descriptor.title | ||
} | ||
jsonp(data, [ 'shorturl', 'title', 'url' ], callback) | ||
this.sendRequest(data, [ 'shorturl', 'title', 'url' ], callback) | ||
return this | ||
} | ||
return this | ||
}, | ||
/** | ||
* Retrieves the statistics for all shortened URLs. | ||
* | ||
* Optionally, <code>criteria</code> can be provided to also include a refined set of links in the result. This includes | ||
* filter, which provides limited control over the sorting, as well as limit and start, which allow for pagination. If | ||
* <code>criteria</code> is a number, it will be treated as the limit. | ||
* | ||
* No links will be included in the result unless a limit is specified that is greater than zero. In that case, this | ||
* method would effectively be doing the same as {@link DB#stats}. | ||
* | ||
* @param {YOURLS~SearchCriteria|number} [criteria] - the optional criteria (or limit, if it's a number) to be used to | ||
* search for links to be included in the result | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.stats = function(criteria, callback) { | ||
var data = { action: 'stats' } | ||
/** | ||
* Retrieves the statistics for all shortened URLs. | ||
* | ||
* Optionally, <code>criteria</code> can be provided to also include a refined set of links in the result. This | ||
* includes filter, which provides limited control over the sorting, as well as limit and start, which allow for | ||
* pagination. If <code>criteria</code> is a number, it will be treated as the limit. | ||
* | ||
* No links will be included in the result unless a limit is specified that is greater than zero. In that case, this | ||
* method would effectively be doing the same as {@link DB#stats}. | ||
* | ||
* @param {YOURLS~SearchCriteria|number} [criteria] - the optional criteria (or limit, if it's a number) to be used to | ||
* search for links to be included in the result | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
stats: function(criteria, callback) { | ||
var data = { action: 'stats' } | ||
switch (typeof criteria) { | ||
case 'function': | ||
callback = criteria | ||
criteria = null | ||
break | ||
case 'number': | ||
criteria = { limit: criteria } | ||
break | ||
default: | ||
// Do nothing | ||
} | ||
switch (typeof criteria) { | ||
case 'function': | ||
callback = criteria | ||
criteria = null | ||
break | ||
case 'number': | ||
criteria = { limit: criteria } | ||
break | ||
default: | ||
// Do nothing | ||
} | ||
if (criteria) { | ||
data.filter = criteria.filter | ||
data.limit = criteria.limit | ||
data.start = criteria.start | ||
} | ||
if (criteria) { | ||
data.filter = criteria.filter | ||
data.limit = criteria.limit | ||
data.start = criteria.start | ||
} | ||
jsonp(data, [ 'links', 'stats' ], function(result, response) { | ||
callback(sanitizeStatsResult(result), response) | ||
}) | ||
this.sendRequest(data, [ 'links', 'stats' ], function(result, response) { | ||
callback(YOURLS._sanitizeStatsResult(result), response) | ||
}) | ||
return this | ||
} | ||
return this | ||
}, | ||
/** | ||
* Creates an instance of {@link URL} for the specified shortened <code>url</code> which can be used to lookup more | ||
* detailed information relating to it. | ||
* | ||
* No data is fetched just by calling this function; one of the functions on the returned instance need to be called for | ||
* that to happen. | ||
* | ||
* @param {string} url - the shortened URL (or its keyword) | ||
* @return {URL} The {@link URL} created for the shortened <code>url</code> or <code>null</code> if <code>url</code> is | ||
* <code>null</code>. | ||
* @public | ||
*/ | ||
YOURLS.prototype.url = function(url) { | ||
return url ? new URL(url) : null | ||
} | ||
/** | ||
* Creates an instance of {@link URL} for the specified shortened <code>url</code> which can be used to lookup more | ||
* detailed information relating to it. | ||
* | ||
* No data is fetched just by calling this method; one of the methods on the returned instance need to be called for | ||
* that to happen. | ||
* | ||
* @param {string} url - the shortened URL (or its keyword) | ||
* @return {URL} The {@link URL} created for the shortened <code>url</code> or <code>null</code> if <code>url</code> | ||
* is <code>null</code>. | ||
* @public | ||
*/ | ||
url: function(url) { | ||
return url ? new URL(url) : null | ||
}, | ||
/** | ||
* Retrieves the version of the connected YOURLS API. | ||
* | ||
* Optionally, <code>db</code> can be passed to indicate that the YOURLS database version should also be included in the | ||
* result. | ||
* | ||
* @param {boolean} [db] - <code>true</code> to include the database version; otherwise <code>false</code> | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
YOURLS.prototype.version = function(db, callback) { | ||
var data = { action: 'version' } | ||
/** | ||
* Retrieves the version of the connected YOURLS API. | ||
* | ||
* Optionally, <code>db</code> can be passed to indicate that the YOURLS database version should also be included in | ||
* the result. | ||
* | ||
* @param {boolean} [db] - <code>true</code> to include the database version; otherwise <code>false</code> | ||
* @param {Function} callback - the callback function to be called with the result | ||
* @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. | ||
* @public | ||
*/ | ||
version: function(db, callback) { | ||
var data = { action: 'version' } | ||
if (typeof db === 'function') { | ||
callback = db | ||
db = null | ||
if (typeof db === 'function') { | ||
callback = db | ||
db = null | ||
} | ||
if (db != null) { | ||
data.db = Number(db) | ||
} | ||
this.sendRequest(data, [ 'db_version', 'version' ], callback) | ||
return this | ||
} | ||
if (db != null) { | ||
data.db = Number(db) | ||
}, { | ||
/** | ||
* Sanitizes the result of {@link YOURLS#stats} so that it's more usable in application code by transforming the links | ||
* from an object mapping into an array. | ||
* | ||
* This method simply returns <code>result</code> if it is <code>null</code>, has no links property or one that is | ||
* already an array (future-proofing). | ||
* | ||
* @param {Object} result - the result to be sanitized (may be <code>null</code>) | ||
* @param {Object} [result.links] - the links to be transformed into an array (may be <code>null</code>) | ||
* @return {Object} The modified <code>result</code> or <code>null</code> if <code>result</code> is <code>null</code>. | ||
* @private | ||
* @static | ||
*/ | ||
_sanitizeStatsResult: function(result) { | ||
// Future-proofing by sanitizing links *only* when not already an array | ||
if (!result || !result.links || isArray(result.links)) { | ||
return result | ||
} | ||
var index = 1 | ||
var link | ||
var links = [] | ||
while ((link = result.links['link_' + index]) != null) { | ||
links.push(link) | ||
index++ | ||
} | ||
result.links = links | ||
return result | ||
} | ||
jsonp(data, [ 'db_version', 'version' ], callback) | ||
}) | ||
return this | ||
} | ||
/** | ||
@@ -255,0 +264,0 @@ * The singleton instance of {@link YOURLS}. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
237293
18
2260
409