├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── AUTHORS.md ├── CHANGES.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── bower.json ├── dist ├── yourls.js ├── yourls.js.map ├── yourls.min.js └── yourls.min.js.map ├── package.json └── src ├── api.js ├── request ├── json.js ├── jsonp.js ├── request.js └── requestor.js ├── util ├── array.js └── extend.js ├── yourls-db.js ├── yourls-url.js └── yourls.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | Gruntfile.js 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "skelp/v3/es5", 3 | "env": { 4 | "browser": true 5 | }, 6 | "parserOptions": { 7 | "sourceType": "module" 8 | }, 9 | "rules": { 10 | "callback-return": "off", 11 | "max-params": [ 12 | "error", 13 | 4 14 | ], 15 | "no-invalid-this": "off", 16 | "no-unused-vars": [ 17 | "warn", 18 | { 19 | "args": "none", 20 | "vars": "all" 21 | } 22 | ], 23 | "sort-keys": "off" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | AUTHORS.md 3 | bower.json 4 | CHANGES.md 5 | CONTRIBUTING.md 6 | Gruntfile.js 7 | README.md 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "4" 5 | - "6" 6 | - "7" 7 | script: 8 | - npm run ci 9 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors ordered by first contribution 2 | 3 | * Alasdair Mercer 4 | * Josh Panter 5 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Version 2.1.0, 2017.03.24 2 | 3 | * Support JSON requests (GET & POST) [#6](https://github.com/neocotic/yourls-api/issues/6) 4 | 5 | ## Version 2.0.0, 2016.11.23 6 | 7 | * Support time-limited signature token for [passwordless API](https://github.com/YOURLS/YOURLS/wiki/PasswordlessAPI) (Josh Panter) [#1](https://github.com/neocotic/yourls-api/issues/1) 8 | * Rewrite library to be easier to maintain 9 | * Refactor API methods to be easier to use (incl. breaking changes) 10 | * Add `yourls.disconnect` API method to clear previously stored connection information 11 | * Add `yourls.db.stats` API method to fetch statistics for all links 12 | * Add `yourls.version` API method to fetch YOURLS version information 13 | * Fix bug where query string parameter name and/or value are not always URL-encoded 14 | * Rewrite documentation to be more informative 15 | * Remove `docs` generated by [Docco](https://jashkenas.github.io/docco/) 16 | * Add [EditorConfig](http://editorconfig.org) file 17 | * Add [ESLint](http://eslint.org) 18 | * Bundle library using [Rollup](http://rollupjs.org) 19 | * Build library using [Grunt](http://gruntjs.com) 20 | * Add continuous integration using [Travis](http://travis-ci.org) 21 | * Add contributor guidelines 22 | * Publish to [Bower](http://bower.io) 23 | * Publish to [npm](https://www.npmjs.org) 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you have any questions about [YOURLS API](https://github.com/neocotic/yourls-api) please feel free to 4 | [raise an issue](https://github.com/neocotic/yourls-api/issues/new). 5 | 6 | Please [search existing issues](https://github.com/neocotic/yourls-api/issues) for the same feature and/or issue before 7 | raising a new issue. Commenting on an existing issue is usually preferred over raising duplicate issues. 8 | 9 | Please ensure that all files conform to the coding standards, using the same coding style as the rest of the code base. 10 | This can easily be checked via command-line: 11 | 12 | ``` bash 13 | # install/update package dependencies 14 | $ npm install 15 | # run test suite 16 | $ npm test 17 | ``` 18 | 19 | You must have at least [Node.js](https://nodejs.org) version 4 or newer installed. 20 | 21 | All pull requests should be made to the `develop` branch. 22 | 23 | Don't forget to add your details to the list of 24 | [AUTHORS.md](https://github.com/neocotic/yourls-api/blob/master/AUTHORS.md) if you want your contribution to be 25 | recognized by others. 26 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | module.exports = function(grunt) { 24 | var nodeResolve = require('rollup-plugin-node-resolve') 25 | var uglify = require('rollup-plugin-uglify') 26 | 27 | grunt.initConfig({ 28 | pkg: grunt.file.readJSON('package.json'), 29 | 30 | clean: { 31 | build: [ 'dist/**' ] 32 | }, 33 | 34 | eslint: { 35 | target: [ 'src/**/*.js' ] 36 | }, 37 | 38 | rollup: { 39 | umdDevelopment: { 40 | options: { 41 | format: 'umd', 42 | moduleId: 'yourls-api', 43 | moduleName: 'yourls', 44 | sourceMap: true, 45 | sourceMapRelativePaths: true, 46 | plugins: function() { 47 | return [ 48 | nodeResolve({ 49 | browser: true, 50 | jsnext: true, 51 | main: true 52 | }) 53 | ] 54 | } 55 | }, 56 | files: { 57 | 'dist/yourls.js': 'src/yourls.js' 58 | } 59 | }, 60 | umdProduction: { 61 | options: { 62 | format: 'umd', 63 | moduleId: 'yourls-api', 64 | moduleName: 'yourls', 65 | sourceMap: true, 66 | sourceMapRelativePaths: true, 67 | banner: '/*! YOURLS API v<%= pkg.version %> | (C) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> | <%= pkg.license %> License */', 68 | plugins: function() { 69 | return [ 70 | nodeResolve({ 71 | browser: true, 72 | jsnext: true, 73 | main: true 74 | }), 75 | uglify({ 76 | output: { 77 | comments: function(node, comment) { 78 | return comment.type === 'comment2' && /^\!/.test(comment.value) 79 | } 80 | } 81 | }) 82 | ] 83 | } 84 | }, 85 | files: { 86 | 'dist/yourls.min.js': 'src/yourls.js' 87 | } 88 | } 89 | }, 90 | 91 | watch: { 92 | all: { 93 | files: [ 'src/**/*.js' ], 94 | tasks: [ 'eslint' ] 95 | } 96 | } 97 | }) 98 | 99 | require('load-grunt-tasks')(grunt) 100 | 101 | grunt.registerTask('default', [ 'ci' ]) 102 | grunt.registerTask('build', [ 'eslint', 'clean:build', 'rollup' ]) 103 | grunt.registerTask('ci', [ 'eslint', 'clean', 'rollup' ]) 104 | grunt.registerTask('test', [ 'eslint' ]) 105 | } 106 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017 Alasdair Mercer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Y88b d88P .d88888b. 888 888 8888888b. 888 .d8888b. 2 | Y88b d88P d88P" "Y88b 888 888 888 Y88b 888 d88P Y88b 3 | Y88o88P 888 888 888 888 888 888 888 Y88b. 4 | Y888P 888 888 888 888 888 d88P 888 "Y888b. 5 | 888 888 888 888 888 8888888P" 888 "Y88b. 6 | 888 888 888 888 888 888 T88b 888 "888 7 | 888 Y88b. .d88P Y88b. .d88P 888 T88b 888 Y88b d88P 8 | 888 "Y88888P" "Y88888P" 888 T88b 88888888 "Y8888P" 9 | d8888 8888888b. 8888888 10 | d88888 888 Y88b 888 11 | d88P888 888 888 888 12 | d88P 888 888 d88P 888 13 | d88P 888 8888888P" 888 14 | d88P 888 888 888 15 | d8888888888 888 888 16 | d88P 888 888 8888888 17 | 18 | [![Build Status](https://img.shields.io/travis/neocotic/yourls-api/develop.svg?style=flat-square)](https://travis-ci.org/neocotic/yourls-api) 19 | [![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) 20 | [![License](https://img.shields.io/npm/l/yourls-api.svg?style=flat-square)](https://github.com/neocotic/yourls-api/blob/master/LICENSE.md) 21 | [![Release](https://img.shields.io/npm/v/yourls-api.svg?style=flat-square)](https://www.npmjs.com/package/yourls-api) 22 | 23 | [YOURLS API](https://github.com/neocotic/yourls-api) is a JavaScript library that provides bindings for 24 | [YOURLS](https://yourls.org) URL shortener servers. 25 | 26 | * [Install](#install) 27 | * [API](#api) 28 | * [Migrating from v1](#migrating-from-v1) 29 | * [Bugs](#bugs) 30 | * [Contributors](#contributors) 31 | * [License](#license) 32 | 33 | ## Install 34 | 35 | Install using the package manager for your desired environment(s): 36 | 37 | ``` bash 38 | $ npm install --save yourls-api 39 | # OR: 40 | $ bower install --save yourls-api 41 | ``` 42 | 43 | You'll need to have at least [Node.js](https://nodejs.org) installed and you'll only need [Bower](https://bower.io) if 44 | you want to install that way instead of using `npm`. And, although this library can be installed via `npm` it is only 45 | intended for use in the browser. `npm` installations are supported for the many web bundlers out there now. 46 | 47 | If you want to simply download the file to be used in the browser you can find them below: 48 | 49 | * [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)) 50 | * [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)) 51 | 52 | ## API 53 | 54 | The API has been designed to be as simple and human-friendly as possible and attempts to hide the majority of work when 55 | dealing with the YOURLS API from you. 56 | 57 | All methods of the API return a reference to the API itself to enable a clean chaining of method calls, if desired. 58 | 59 | All requests that are sent to YOURLS servers are asynchronous and callback methods are used to track these. Callback 60 | methods are passed the most is deemed (by this library) to be the most important information from the response as the 61 | first argument and the entire response as the second argument. 62 | 63 | The following documentation contains some examples of the results that can be expected from YOURLS, however, it doesn't 64 | cover everything. It's recommended that you play around with making requests and inspecting/logging results and 65 | responses to see all of the data that is available. 66 | 67 | ### Connecting 68 | 69 | ``` javascript 70 | yourls.connect(url[, credentials][, options]) 71 | ``` 72 | 73 | This is the first step and is where you'll provide the `url` of the YOURLS API that you wish to connect to. It **must** 74 | point to the `yourls-api.php` file or its equivalent (e.g. if it's been renamed or using URL rewrites). You can only 75 | connect to a single YOURLS server at a time. 76 | 77 | ``` javascript 78 | // Simple connection to public server 79 | yourls.connect('https://example.com/yourls-api.php') 80 | ``` 81 | 82 | If you're going to be connecting to a private YOURLS server, you'll also need to provide `credentials` that can be used 83 | to authenticate with it. The recommended method is to specify the `signature` token and use the 84 | [passwordless API requests](http://code.google.com/p/yourls/wiki/PasswordlessAPI) as the signature token can be reset 85 | easily. 86 | 87 | ``` javascript 88 | // Passwordless connection to private server 89 | yourls.connect('https://example.com/yourls-api.php', { 90 | signature: '3002a61584' 91 | }) 92 | ``` 93 | 94 | However, it's even better if you use a time-limited signature token as it's somewhat more secure. That said; this 95 | library leaves it up to you to provide the signature token as an md5 sum (which should be made from a concatenation of 96 | the `timestamp` passed in and the signature token, and in that order). It's also worth noting that the timestamp should 97 | be in seconds, not milliseconds, since Unix Epoch. 98 | 99 | Although it is possible to create md5 sums in the browser with the use of a third-party JavaScript library, at that 100 | point you signature token has probably already been exposed. The best bet is for your server to calculate the md5 sum 101 | and then pass it and the timestamp on which it was based to your frontend to be consumed by this library. 102 | 103 | ``` javascript 104 | // Time-limited passwordless connection to private server 105 | yourls.connect('https://example.com/yourls-api.php', { 106 | signature: md5(1477947900 + '3002a61584'), 107 | timestamp: 1477947900 108 | }) 109 | ``` 110 | 111 | This library does also support the more traditional `username`/`password` combination as well. 112 | 113 | ``` javascript 114 | // Basic authentication connection to private server 115 | yourls.connect('https://example.com/yourls-api.php', { 116 | username: 'admin', 117 | password: 'qwerty' 118 | }) 119 | ``` 120 | 121 | > **IMPORTANT:** When sending `GET` requests, by design, all information will be included in the URL of the request 122 | > This includes data as well as *any credentials* used to authenticate with the API. You have been warned. 123 | > This is unavoidable when sending requests in the JSONP format but, when using the JSON format, you can send `POST` 124 | > requests, which means that your data is sent inside the body of the request. Combine this with HTTPS and your data and 125 | > credentials cannot be sniffed over the network. 126 | 127 | As you may have noticed; this method also accepts the following entirely optional `options`: 128 | 129 | Option | Description | Default 130 | ------ | ----------------------------------- | --------- 131 | format | Format in which requests are sent | `"jsonp"` 132 | method | HTTP method to be used for requests | `"GET"` 133 | 134 | ``` javascript 135 | // Does the same as specifying no options (i.e. using defaults) 136 | yourls.connect('https://example.com/yourls-api.php', null, { 137 | format: 'jsonp', 138 | method: 'GET' 139 | }) 140 | 141 | // Best practice if you want to secure the data you're transmitting and you've setup CORS, if needed 142 | yourls.connect('https://example.com/yourls-api.php', { 143 | signature: '3002a61584' 144 | }, { 145 | format: 'json', 146 | method: 'POST' 147 | }) 148 | ``` 149 | 150 | The following formats are supported with the corresponding HTTP methods: 151 | 152 | Format | HTTP Methods 153 | ------ | ------------ 154 | json | GET, POST 155 | jsonp | GET 156 | 157 | > **IMPORTANT:** The YOURLS server must be running version **1.5.1** or newer in order to send requests in the JSONP 158 | > format. 159 | 160 | Despite the name of this method, no connection or authentication is carried out at this point and this initial method 161 | simply stores these values to prevent you from having to specify them with every API call. 162 | 163 | ### Disconnecting 164 | 165 | ``` javascript 166 | yourls.disconnect() 167 | ``` 168 | 169 | Calling this method simply clears any previously stored connection information and, despite the name of this method, no 170 | live connections are actually terminated. 171 | 172 | ### Shortening 173 | 174 | ``` javascript 175 | yourls.shorten(url[, descriptor], callback(result, response)) 176 | ``` 177 | 178 | This method shortens the `url` provided with a keyword/hash that is generated by the YOURLS server. 179 | 180 | ``` javascript 181 | // Shorten a URL with a random keyword 182 | yourls.shorten('https://github.com/neocotic/yourls-api', function(result, response) { 183 | console.log(result.shorturl) 184 | //=> "https://example.com/abc123" 185 | console.log(result.title) 186 | //=> "https://github.com/neocotic/yourls-api" 187 | console.log(result.url.keyword) 188 | //=> "abc123" 189 | }) 190 | ``` 191 | 192 | Optionally, this method can take a `descriptor` containing additional information including the `keyword` to be used for 193 | the short URL that is created and a `title` that is to be associated with it. As a shortcut, the keyword can be passed 194 | as the `descriptor` itself. 195 | 196 | ``` javascript 197 | // Shorten a URL with a predefined keyword 198 | yourls.shorten('https://github.com/neocotic/yourls-api', 'yourls', function(result, response) { 199 | console.log(result.shorturl) 200 | //=> "https://example.com/yourls" 201 | console.log(result.title) 202 | //=> "https://github.com/neocotic/yourls-api" 203 | console.log(result.url.keyword) 204 | //=> "yourls" 205 | }) 206 | 207 | // Shorten a URL with a predefined keyword and title 208 | yourls.shorten('https://github.com/neocotic/yourls-api', { keyword: 'yourls', title: 'YOURLS API' }, function(result, response) { 209 | console.log(result.shorturl) 210 | //=> "https://example.com/yourls" 211 | console.log(result.title) 212 | //=> "YOURLS API" 213 | console.log(result.url.keyword) 214 | //=> "yourls" 215 | }) 216 | ``` 217 | 218 | ### Statistics 219 | 220 | ``` javascript 221 | yourls.stats([criteria, ]callback(result, response)) 222 | ``` 223 | 224 | This method fetches the statistics for all links. 225 | 226 | ``` javascript 227 | // Get link statistics 228 | yourls.stats(function(result, response) { 229 | console.log(result.stats.total_clicks) 230 | //=> "98765" 231 | console.log(result.stats.total_links) 232 | //=> "123" 233 | }) 234 | ``` 235 | 236 | Optionally, this method can take a `criteria` containing search criteria for a sub-set of links which can be included in 237 | the result. This includes the `filter` (either `"top"`, `"bottom"`, `"rand"`, or `"last"`), which can be used to control 238 | sorting, as well as `limit` and `start`, which can be used for pagination lookups. As a shortcut, the limit can be 239 | passed as the `criteria` itself. The minimum required in order to do this is for a `limit` to be specified. 240 | 241 | ``` javascript 242 | // Get top 10 links 243 | yourls.stats(10, function(result, response) { 244 | console.log(result.links.length) 245 | //=> 10 246 | console.log(result.links[0].shorturl) 247 | //=> "https://example.com/yourls" 248 | console.log(result.stats.total_links) 249 | //=> "123" 250 | }) 251 | 252 | // Get second page of 5 newest links 253 | yourls.stats({ filter: 'last', limit: 5, start: 5 }, function(result, response) { 254 | console.log(result.links.length) 255 | //=> 5 256 | console.log(result.links[0].shorturl) 257 | //=> "https://example.com/abc123" 258 | console.log(result.stats.total_links) 259 | //=> "123" 260 | }) 261 | ``` 262 | 263 | --- 264 | 265 | ``` javascript 266 | yourls.db.stats(callback(result, response)) 267 | ``` 268 | 269 | This method does exactly the same as `yourls.stats` except that it *only* returns the statistics for all links. 270 | 271 | ``` javascript 272 | // Get link statistics 273 | yourls.db.stats(function(result, response) { 274 | console.log(result.total_clicks) 275 | //=> "98765" 276 | console.log(result.total_links) 277 | //=> "123" 278 | }) 279 | ``` 280 | 281 | ### URL Information 282 | 283 | ``` javascript 284 | yourls.url(url) 285 | ``` 286 | 287 | Unlike other API calls, this method returns a wrapper for making API calls that specific to a given shortened `url`, 288 | which can be the keyword instead, if desired. 289 | 290 | ``` javascript 291 | // Both do the same thing: 292 | var yourls = yourls.url('https://example.com/yourls') 293 | var yourls = yourls.url('yourls') 294 | ``` 295 | 296 | Just like the top-level API methods, all of the URL-specific methods return a reference to the URL wrapper to enable a 297 | clean chaining of method calls, if desired. 298 | 299 | #### Expanding 300 | 301 | ``` javascript 302 | yourls.url(url).expand(callback(result, response)) 303 | ``` 304 | 305 | This method expands the shortened `url` into the original (long) URL. 306 | 307 | ``` javascript 308 | // Get more details for link 309 | yourls.url('https://example.com/yourls').expand(function(result, response) { 310 | console.log(result.keyword) 311 | //=> "yourls" 312 | console.log(result.longurl) 313 | //=> "https://github.com/neocotic/yourls-api" 314 | console.log(result.shorturl) 315 | //=> "https://example.com/yourls" 316 | }) 317 | ``` 318 | 319 | #### Statistics 320 | 321 | ``` javascript 322 | yourls.url(url).stats(callback(result, response)) 323 | ``` 324 | 325 | This method fetches the statistics for the shortened `url`. 326 | 327 | ``` javascript 328 | // Get statistics only for this link 329 | yourls.url('https://example.com/yourls').stats(function(result, response) { 330 | console.log(result.clicks) 331 | //=> "123" 332 | console.log(result.title) 333 | //=> "neocotic/yourls-api: JavaScript bindings for the YOURLS API" 334 | console.log(result.url) 335 | //=> "https://github.com/neocotic/yourls-api" 336 | }) 337 | ``` 338 | 339 | ### Versions 340 | 341 | ``` javascript 342 | yourls.version([db, ]callback(result, response)) 343 | ``` 344 | 345 | This methods fetches the version of YOURLS running on the connected server. 346 | 347 | ``` javascript 348 | // Get YOURLS version 349 | yourls.version(function(result, response) { 350 | console.log(result.version) 351 | //=> "1.7" 352 | }) 353 | ``` 354 | 355 | Optionally, a `db` flag can be enabled for the YOURLS database version to also be included in the result. 356 | 357 | ``` javascript 358 | // Get YOURLS database version as well 359 | yourls.version(true, function(result, response) { 360 | console.log(result.version) 361 | //=> "1.7" 362 | console.log(result.db_version) 363 | //=> "482" 364 | }) 365 | ``` 366 | 367 | --- 368 | 369 | ``` javascript 370 | // Get version of this library 371 | console.log(yourls.VERSION) 372 | //=> "2.1.0" 373 | ``` 374 | 375 | The current version of this library. 376 | 377 | ## Migrating from v1 378 | 379 | If you've been using v1 then you can find details about what's changed and a guide on how to migrate to v2 below: 380 | 381 | https://github.com/neocotic/yourls-api/wiki/Migrating-from-v1 382 | 383 | You can also find the code and documentation for the v1 below: 384 | 385 | https://github.com/neocotic/yourls-api/tree/1.0.0 386 | 387 | ## Bugs 388 | 389 | If you have any problems with this library or would like to see changes currently in development you can do so 390 | [here](https://github.com/neocotic/yourls-api/issues). 391 | 392 | However, if you believe that your issue is with YOURLS itself, please take a look a 393 | [their issues](https://github.com/YOURLS/YOURLS/issues) instead. 394 | 395 | ## Contributors 396 | 397 | If you want to contribute, you're a legend! Information on how you can do so can be found in 398 | [CONTRIBUTING.md](https://github.com/neocotic/yourls-api/blob/master/CONTRIBUTING.md). We want your suggestions and pull 399 | requests! 400 | 401 | A list of YOURLS API contributors can be found in 402 | [AUTHORS.md](https://github.com/neocotic/yourls-api/blob/master/AUTHORS.md). 403 | 404 | ## License 405 | 406 | Copyright © 2017 Alasdair Mercer 407 | 408 | See [LICENSE.md](https://github.com/neocotic/yourls-api/blob/master/LICENSE.md) for more information on our MIT license. 409 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yourls-api", 3 | "version": "2.1.0", 4 | "description": "Bindings for the YOURLS API", 5 | "homepage": "https://github.com/neocotic/yourls-api", 6 | "authors": [ 7 | { 8 | "name": "Alasdair Mercer", 9 | "email": "mercer.alasdair@gmail.com", 10 | "homepage": "https://neocotic.com" 11 | } 12 | ], 13 | "license": "MIT", 14 | "keywords": [ 15 | "yourls", 16 | "url", 17 | "shortener", 18 | "api", 19 | "json", 20 | "jsonp" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/neocotic/yourls-api.git" 25 | }, 26 | "main": "dist/yourls.js", 27 | "moduleType": [ 28 | "amd", 29 | "globals", 30 | "node" 31 | ], 32 | "ignore": [ 33 | ".*", 34 | "src/", 35 | "AUTHORS.md", 36 | "CHANGES.md", 37 | "CONTRIBUTING.md", 38 | "Gruntfile.js", 39 | "package.json", 40 | "README.md" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /dist/yourls.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define('yourls-api', factory) : 4 | (global.yourls = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | /* 8 | * Copyright (C) 2016 Alasdair Mercer, Skelp 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | /** 30 | * A bare-bones constructor for surrogate prototype swapping. 31 | * 32 | * @private 33 | * @constructor 34 | */ 35 | var Constructor = function() {}; 36 | /** 37 | * A reference to Object.prototype.hasOwnProperty. 38 | * 39 | * @private 40 | * @type {Function} 41 | */ 42 | var hasOwnProperty = Object.prototype.hasOwnProperty; 43 | /** 44 | * A reference to Array.prototype.slice. 45 | * 46 | * @private 47 | * @type {Function} 48 | */ 49 | var slice = Array.prototype.slice; 50 | 51 | /** 52 | * Extends the specified target object with the properties in each of the sources provided. 53 | * 54 | * Nothing happens if target is null and if any source is null it will be 55 | * ignored. 56 | * 57 | * @param {boolean} own - true to only copy own properties from sources onto 58 | * target; otherwise false 59 | * @param {Object} [target] - the target object which should be extended 60 | * @param {...Object} [sources] - the source objects whose properties are to be copied onto target 61 | * @return {void} 62 | * @private 63 | */ 64 | function extend(own, target, sources) { 65 | if (target == null) { 66 | return 67 | } 68 | 69 | sources = slice.call(arguments, 2); 70 | 71 | var property; 72 | var source; 73 | 74 | for (var i = 0, length = sources.length; i < length; i++) { 75 | source = sources[i]; 76 | 77 | for (property in source) { 78 | if (!own || hasOwnProperty.call(source, property)) { 79 | target[property] = source[property]; 80 | } 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Creates an object which inherits the given prototype. 87 | * 88 | * Optionally, the created object can be extended further with the specified properties. 89 | * 90 | * @param {Object} prototype - the prototype to be inherited by the created object 91 | * @param {Object} [properties] - the optional properties to be extended by the created object 92 | * @return {Object} The newly created object. 93 | * @private 94 | */ 95 | function create(prototype, properties) { 96 | var result; 97 | if (typeof Object.create === 'function') { 98 | result = Object.create(prototype); 99 | } else { 100 | Constructor.prototype = prototype; 101 | result = new Constructor(); 102 | Constructor.prototype = null; 103 | } 104 | 105 | if (properties) { 106 | extend(true, result, properties); 107 | } 108 | 109 | return result 110 | } 111 | 112 | /** 113 | * The base constructor from which all others should extend. 114 | * 115 | * @public 116 | * @constructor 117 | */ 118 | function Oopsy() {} 119 | 120 | /** 121 | * Extends the constructor to which this method is associated with the prototype and/or 122 | * statics provided. 123 | * 124 | * If constructor is provided, it will be used as the constructor for the child, otherwise a simple 125 | * constructor which only calls the super constructor will be used instead. 126 | * 127 | * The super constructor can be accessed via a special super_ property on the child constructor. 128 | * 129 | * @param {Function} [constructor] - the constructor for the child 130 | * @param {Object} [prototype] - the prototype properties to be defined for the child 131 | * @param {Object} [statics] - the static properties to be defined for the child 132 | * @return {Function} The child constructor provided or the one created if none was given. 133 | * @public 134 | * @static 135 | */ 136 | Oopsy.extend = function(constructor, prototype, statics) { 137 | var superConstructor = this; 138 | 139 | if (typeof constructor !== 'function') { 140 | statics = prototype; 141 | prototype = constructor; 142 | constructor = function() { 143 | return superConstructor.apply(this, arguments) 144 | }; 145 | } 146 | 147 | extend(false, constructor, superConstructor, statics); 148 | 149 | constructor.prototype = create(superConstructor.prototype, prototype); 150 | constructor.prototype.constructor = constructor; 151 | 152 | constructor.super_ = superConstructor; 153 | 154 | return constructor 155 | }; 156 | 157 | /* 158 | * Copyright (C) 2017 Alasdair Mercer 159 | * 160 | * Permission is hereby granted, free of charge, to any person obtaining a copy 161 | * of this software and associated documentation files (the "Software"), to deal 162 | * in the Software without restriction, including without limitation the rights 163 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 164 | * copies of the Software, and to permit persons to whom the Software is 165 | * furnished to do so, subject to the following conditions: 166 | * 167 | * The above copyright notice and this permission notice shall be included in all 168 | * copies or substantial portions of the Software. 169 | * 170 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 171 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 172 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 173 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 174 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 175 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 176 | * SOFTWARE. 177 | */ 178 | 179 | /** 180 | * Extends the specified target object with the properties in each of the sources provided. 181 | * 182 | * Any of the sources that are null will simply be ignored. 183 | * 184 | * @param {Object} target - the target object which should be extended 185 | * @param {...Object} [sources] - the source objects whose properties are to be copied onto target 186 | * @return {Object} A reference to target. 187 | * @protected 188 | */ 189 | function extend$1(target, sources) { 190 | sources = Array.prototype.slice.call(arguments, 1); 191 | 192 | for (var i = 0, length = sources.length, property, source; i < length; i++) { 193 | source = sources[i]; 194 | 195 | if (source) { 196 | for (property in source) { 197 | if (Object.prototype.hasOwnProperty.call(source, property)) { 198 | target[property] = source[property]; 199 | } 200 | } 201 | } 202 | } 203 | 204 | return target 205 | } 206 | 207 | /* 208 | * Copyright (C) 2017 Alasdair Mercer 209 | * 210 | * Permission is hereby granted, free of charge, to any person obtaining a copy 211 | * of this software and associated documentation files (the "Software"), to deal 212 | * in the Software without restriction, including without limitation the rights 213 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 214 | * copies of the Software, and to permit persons to whom the Software is 215 | * furnished to do so, subject to the following conditions: 216 | * 217 | * The above copyright notice and this permission notice shall be included in all 218 | * copies or substantial portions of the Software. 219 | * 220 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 221 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 222 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 223 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 224 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 225 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 226 | * SOFTWARE. 227 | */ 228 | 229 | /** 230 | * Contains information on how to connect to and authenticate with a YOURLS server. 231 | * 232 | * @param {string} [url=''] - the URL for the YOURLS server 233 | * @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be 234 | * null) 235 | * @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be 236 | * null) 237 | * @protected 238 | * @constructor 239 | */ 240 | var API = Oopsy.extend(function(url, credentials, options) { 241 | /** 242 | * The URL of the YOURLS server. 243 | * 244 | * @public 245 | * @type {string} 246 | */ 247 | this.url = url ? url.replace(/\/$/, '') : ''; 248 | /** 249 | * The credentials to be used to authenticate with the YOURLS API. 250 | * 251 | * This may be null if the YOURLS API is public. 252 | * 253 | * @public 254 | * @type {API~Credentials} 255 | */ 256 | this.credentials = API._sanitizeCredentials(credentials); 257 | /** 258 | * The options to be used to send requests to the YOURLS server. 259 | * 260 | * @public 261 | * @type {API~Options} 262 | */ 263 | this.options = API._sanitizeOptions(options); 264 | }, null, { 265 | 266 | /** 267 | * The default options to be used. 268 | * 269 | * @protected 270 | * @static 271 | * @type {API~Options} 272 | */ 273 | defaultOptions: { 274 | format: 'jsonp', 275 | method: 'GET' 276 | }, 277 | 278 | /** 279 | * The singleton {@link API} instance which is privatized to prevent leaking credentials. 280 | * 281 | * @public 282 | * @static 283 | * @type {API} 284 | */ 285 | instance: null, 286 | 287 | /** 288 | * Sanitizes the specified credentials by ensuring that only valid properties are present and only when 289 | * appropriate. 290 | * 291 | * This method does not modify credentials and instead creates a new object with the sanitized 292 | * properties. 293 | * 294 | * @param {API~Credentials} credentials - the credentials to be sanitized (may be null) 295 | * @return {API~Credentials} A sanitized version of credentials or null if 296 | * credentials is null. 297 | * @private 298 | * @static 299 | */ 300 | _sanitizeCredentials: function(credentials) { 301 | if (!credentials) { 302 | return null 303 | } 304 | 305 | var result = {}; 306 | if (credentials.signature) { 307 | result.signature = credentials.signature; 308 | result.timestamp = credentials.timestamp; 309 | } else { 310 | result.password = credentials.password; 311 | result.username = credentials.username; 312 | } 313 | 314 | return result 315 | }, 316 | 317 | /** 318 | * Sanitizes the specified options by ensuring that only valid properties are present and in the correct 319 | * format. 320 | * 321 | * This method does not modify options and instead creates a new object with the sanitized properties and 322 | * default values will be used for missing options. 323 | * 324 | * @param {API~Options} options - the options to be sanitized (may be null) 325 | * @return {API~Options} A sanitized version of options which will contain only default values if 326 | * options is null. 327 | * @private 328 | * @static 329 | */ 330 | _sanitizeOptions: function(options) { 331 | var result = extend$1({}, API.defaultOptions); 332 | if (!options) { 333 | return result 334 | } 335 | 336 | if (options.format) { 337 | result.format = options.format.toLowerCase(); 338 | } 339 | if (options.method) { 340 | result.method = options.method.toUpperCase(); 341 | } 342 | 343 | return result 344 | } 345 | 346 | }); 347 | 348 | /** 349 | * The credentials to be used to authenticate with a private YOURLS API. 350 | * 351 | * Authentication can be done with a traditional username and password combination or by using the secret 352 | * signature token (e.g. 1002a612b4)for the YOURLS API. The latter is not available for public YOURLS APIs 353 | * and can be found on the "Tools" page. 354 | * 355 | * Optionally, a timestamp can accompany the signature token to make it time-limited (depending on the server 356 | * configuration). When a timestamp is provided the signature token must be the md5 sum of the timestamp and 357 | * signature token concatenated, and in that order. 358 | * 359 | * @typedef {Object} API~Credentials 360 | * @property {string} [password] - The password of the user to be authenticated. 361 | * @property {string} [username] - The name of the user to be authenticated. 362 | * @property {string} [signature] - The signature token to be used for passwordless authentication with the YOURLS API. 363 | * @property {number|string} [timestamp] - The optional timestamp to limit the signature token. 364 | */ 365 | 366 | /** 367 | * The options that determine how requests are sent to the YOURLS server. 368 | * 369 | * If the request format does not support the HTTP method, requests will not be sent and an 370 | * error will be thrown when such attempts occur. 371 | * 372 | * @typedef {Object} API~Options 373 | * @property {string} [format="jsonp"] - The format in which requests are sent (either "json" or 374 | * "jsonp"). 375 | * @property {string} [method="GET"] - The HTTP method to be used for requests. 376 | */ 377 | 378 | /* 379 | * Copyright (C) 2017 Alasdair Mercer 380 | * 381 | * Permission is hereby granted, free of charge, to any person obtaining a copy 382 | * of this software and associated documentation files (the "Software"), to deal 383 | * in the Software without restriction, including without limitation the rights 384 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 385 | * copies of the Software, and to permit persons to whom the Software is 386 | * furnished to do so, subject to the following conditions: 387 | * 388 | * The above copyright notice and this permission notice shall be included in all 389 | * copies or substantial portions of the Software. 390 | * 391 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 392 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 393 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 394 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 395 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 396 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 397 | * SOFTWARE. 398 | */ 399 | 400 | /** 401 | * Returns whether the specified obj is an array. 402 | * 403 | * This method will use the native Array.isArray, if available. 404 | * 405 | * @param {*} obj - the object to be checked (may be null) 406 | * @return {boolean} true if obj is an array; otherwise false. 407 | * @protected 408 | */ 409 | function isArray(obj) { 410 | return Array.isArray ? Array.isArray(obj) : Object.prototype.toString.call(obj) === '[object Array]' 411 | } 412 | 413 | /* 414 | * Copyright (C) 2017 Alasdair Mercer 415 | * 416 | * Permission is hereby granted, free of charge, to any person obtaining a copy 417 | * of this software and associated documentation files (the "Software"), to deal 418 | * in the Software without restriction, including without limitation the rights 419 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 420 | * copies of the Software, and to permit persons to whom the Software is 421 | * furnished to do so, subject to the following conditions: 422 | * 423 | * The above copyright notice and this permission notice shall be included in all 424 | * copies or substantial portions of the Software. 425 | * 426 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 427 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 428 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 429 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 430 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 431 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 432 | * SOFTWARE. 433 | */ 434 | 435 | /** 436 | * Contains logic to connect with a YOURLS server and send data to its API. 437 | * 438 | * Due to the nature of HTTP, requests sent using a "GET" HTTP method will include all information in the URL of the 439 | * request. This includes the data that is sent as well as any credentials used to authenticate with the API. You 440 | * have been warned. 441 | * 442 | * @constructor 443 | * @protected 444 | */ 445 | var Request = Oopsy.extend({ 446 | 447 | /** 448 | * Builds the body for this {@link Request} based on the api and data provided. 449 | * 450 | * @param {API} api - the {@link API} to which the request is being sent 451 | * @param {Object} [data] - the data being sent in this request 452 | * @return {Object} The request body. 453 | * @protected 454 | */ 455 | buildBody: function(api, data) { 456 | return extend$1({ format: api.options.format }, api.credentials, data) 457 | }, 458 | 459 | /** 460 | * Returns the list of the HTTP methods that are supported by this {@link Request}. 461 | * 462 | * By default, this method returns null, so implementations must implement this method to ensure 463 | * that {@link Request#isMethodSupported} works correctly. 464 | * 465 | * @return {string[]} The supported HTTP methods. 466 | * @protected 467 | */ 468 | getSupportedHttpMethods: function() { 469 | return null 470 | }, 471 | 472 | /** 473 | * Determines whether this {@link Request} supports the specified HTTP method. 474 | * 475 | * @param {string} method - the HTTP method to be checked 476 | * @return {boolean} true if method is supported; otherwise false. 477 | * @public 478 | */ 479 | isMethodSupported: function(method) { 480 | var supportedMethods = this.getSupportedHttpMethods(); 481 | return supportedMethods && supportedMethods.indexOf(method) !== -1 482 | }, 483 | 484 | /** 485 | * Determines whether the data that is to be sent to the YOURLS server in this {@link Request} must be serialized as 486 | * query string parameters. 487 | * 488 | * @param {string} method - the HTTP method to be used 489 | * @return {boolean} true if the data needs to be sent as query string parameters; otherwise 490 | * false. 491 | * @protected 492 | */ 493 | isQueryStringRequired: function(method) { 494 | return method === 'GET' 495 | }, 496 | 497 | /** 498 | * Processes this {@link Request} by sending it to the specified target url containing the 499 | * body provided. 500 | * 501 | * callback should be called with the response regardless of whether the it was a success or failure. 502 | * 503 | * This method is called internally by {@link Request#send} and does all of the actual work involved to send the 504 | * request and parse the response. All implementations must implement this method. 505 | * 506 | * @param {string} method - the request HTTP method 507 | * @param {string} url - the request URL 508 | * @param {Object} body - the request body (may be null) 509 | * @param {Function} callback - the function to be called with the response 510 | * @return {void} 511 | * @protected 512 | * @abstract 513 | */ 514 | process: function(method, url, body, callback) { 515 | // Must be implemented 516 | }, 517 | 518 | /** 519 | * Sends the request to the connected YOURLS API with the data provided which should, in turn, call the 520 | * specified callback with the result. 521 | * 522 | * If the request is successful, callback will be passed the value of the named properties from the 523 | * response. If resultNames is a string or only contains a single string, only the value for that named 524 | * property will be passed as the first argument. Otherwise, an object containing the key/value pairs for each named 525 | * property will be passed as the first argument. The actual response will always be passed as the second argument. 526 | * 527 | * @param {Object} data - the data to be sent 528 | * @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to 529 | * callback as the first argument 530 | * @param {Function} callback - the function to be called with the result 531 | * @return {void} 532 | * @public 533 | */ 534 | send: function(data, resultNames, callback) { 535 | var api = API.instance; 536 | var body = Request._serializeParameters(this.buildBody(api, data)); 537 | var method = api.options.method; 538 | var url = api.url; 539 | 540 | if (this.isQueryStringRequired(method)) { 541 | url += '?' + body; 542 | body = null; 543 | } 544 | 545 | this.process(method, url, body, function(response) { 546 | callback(Request._extractResult(resultNames, response), response); 547 | }); 548 | } 549 | 550 | }, { 551 | 552 | /** 553 | * Extracts the values of the properties with the specified names from the response provided 554 | * and returns them in a single result. 555 | * 556 | * If names is a string or only contains a single string, only the value for that named property will be 557 | * returned. Otherwise, an object containing the key/value pairs for each named property will be returned. 558 | * 559 | * If response is null this method will return null. 560 | * 561 | * @param {string|string[]} names - the names of the response properties whose values are to be returned 562 | * as the result 563 | * @param {Object} response - the YOURLS API response 564 | * @return {*} The result extracted from response. 565 | * @private 566 | * @static 567 | */ 568 | _extractResult: function(names, response) { 569 | names = isArray(names) ? names : [ names ]; 570 | 571 | var i; 572 | var name; 573 | var result = null; 574 | 575 | if (!response) { 576 | return result 577 | } 578 | 579 | if (names.length === 1) { 580 | result = response[names[0]]; 581 | } else { 582 | result = {}; 583 | 584 | for (i = 0; i < names.length; i++) { 585 | name = names[i]; 586 | 587 | if (typeof response[name] !== 'undefined') { 588 | result[name] = response[name]; 589 | } 590 | } 591 | } 592 | 593 | return result 594 | }, 595 | 596 | /** 597 | * Creates a serialized representation of the specified parameters. 598 | * 599 | * All of the parameter names and values are URL-encoded so that they can be safely included in the query string or 600 | * request body. 601 | * 602 | * @param {Object} [params] - the hash of parameter name/value pairs to be serialized 603 | * @return {string} A URL-encoded representing obj or an empty string if obj is 604 | * null. 605 | * @private 606 | * @static 607 | */ 608 | _serializeParameters: function(params) { 609 | if (!params) { 610 | return '' 611 | } 612 | 613 | var results = []; 614 | 615 | for (var name in params) { 616 | if (Object.prototype.hasOwnProperty.call(params, name) && params[name] != null) { 617 | results.push(encodeURIComponent(name) + '=' + encodeURIComponent(params[name])); 618 | } 619 | } 620 | 621 | return results.join('&') 622 | } 623 | 624 | }); 625 | 626 | /* 627 | * Copyright (C) 2017 Alasdair Mercer 628 | * 629 | * Permission is hereby granted, free of charge, to any person obtaining a copy 630 | * of this software and associated documentation files (the "Software"), to deal 631 | * in the Software without restriction, including without limitation the rights 632 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 633 | * copies of the Software, and to permit persons to whom the Software is 634 | * furnished to do so, subject to the following conditions: 635 | * 636 | * The above copyright notice and this permission notice shall be included in all 637 | * copies or substantial portions of the Software. 638 | * 639 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 640 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 641 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 642 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 643 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 644 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 645 | * SOFTWARE. 646 | */ 647 | 648 | /** 649 | * An implementation of {@link Request} that provides support for JSON requests to the YOURLS API. 650 | * 651 | * JSON requests can only be sent using the "GET" or "POST" HTTP methods. 652 | * 653 | * @constructor 654 | * @extends Request 655 | * @protected 656 | */ 657 | var JSONRequest = Request.extend({ 658 | 659 | /** 660 | * @inheritDoc 661 | * @override 662 | */ 663 | getSupportedHttpMethods: function() { 664 | return [ 'GET', 'POST' ] 665 | }, 666 | 667 | /** 668 | * @inheritDoc 669 | * @override 670 | */ 671 | process: function(method, url, body, callback) { 672 | var xhr = new XMLHttpRequest(); 673 | xhr.open(method, url, true); 674 | xhr.onreadystatechange = function() { 675 | var response; 676 | 677 | if (xhr.readyState === 4) { 678 | try { 679 | response = JSON.parse(xhr.responseText); 680 | callback(response); 681 | } catch (e) { 682 | throw new Error('Unable to parse response: ' + e) 683 | } 684 | } 685 | }; 686 | 687 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 688 | if (body != null) { 689 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 690 | } 691 | 692 | xhr.send(body); 693 | } 694 | 695 | }); 696 | 697 | /* 698 | * Copyright (C) 2017 Alasdair Mercer 699 | * 700 | * Permission is hereby granted, free of charge, to any person obtaining a copy 701 | * of this software and associated documentation files (the "Software"), to deal 702 | * in the Software without restriction, including without limitation the rights 703 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 704 | * copies of the Software, and to permit persons to whom the Software is 705 | * furnished to do so, subject to the following conditions: 706 | * 707 | * The above copyright notice and this permission notice shall be included in all 708 | * copies or substantial portions of the Software. 709 | * 710 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 711 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 712 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 713 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 714 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 715 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 716 | * SOFTWARE. 717 | */ 718 | 719 | /** 720 | * The seed to be used to generate IDs. 721 | * 722 | * @private 723 | * @type {number} 724 | */ 725 | var seed = new Date().getTime(); 726 | 727 | /** 728 | * An implementation of {@link Request} that provides support for JSONP requests to the YOURLS API. 729 | * 730 | * JSONP requests can only be sent using the "GET" HTTP method. 731 | * 732 | * @constructor 733 | * @extends Request 734 | * @protected 735 | */ 736 | var JSONPRequest = Request.extend(function() { 737 | JSONPRequest.super_.call(this); 738 | 739 | if (!window[JSONPRequest._callbackHolderKey]) { 740 | window[JSONPRequest._callbackHolderKey] = JSONPRequest._callbackHolder; 741 | } 742 | 743 | /** 744 | * The generated ID which is used to store a reference to the callback function within the holder so that the JSONP 745 | * payload can find it in the global namespace. 746 | * 747 | * @private 748 | * @type {number} 749 | */ 750 | this._id = JSONPRequest._generateId(); 751 | }, { 752 | 753 | /** 754 | * @inheritDoc 755 | * @override 756 | */ 757 | getSupportedHttpMethods: function() { 758 | return [ 'GET' ] 759 | }, 760 | 761 | /** 762 | * @inheritDoc 763 | * @override 764 | */ 765 | buildBody: function(api, data) { 766 | var body = JSONPRequest.super_.prototype.buildBody.call(this, api, data); 767 | body.callback = JSONPRequest._callbackHolderKey + '[' + this._id + ']'; 768 | 769 | return body 770 | }, 771 | 772 | /** 773 | * @inheritDoc 774 | * @override 775 | */ 776 | process: function(method, url, body, callback) { 777 | var script = document.createElement('script'); 778 | 779 | var self = this; 780 | JSONPRequest._callbackHolder[this._id] = function(response) { 781 | delete JSONPRequest._callbackHolder[self._id]; 782 | script.parentNode.removeChild(script); 783 | 784 | callback(response); 785 | }; 786 | 787 | script.setAttribute('src', url); 788 | document.getElementsByTagName('head')[0].appendChild(script); 789 | } 790 | 791 | }, { 792 | 793 | /** 794 | * The key of the callback function holder within the global namespace. 795 | * 796 | * @private 797 | * @static 798 | * @type {string} 799 | */ 800 | _callbackHolderKey: '__yourls' + seed + '_jsonp', 801 | 802 | /** 803 | * Contains the callback functions for active JSONP requests. 804 | * 805 | * Callback references should be removed immediately once they have been called. 806 | * 807 | * Due to the nature of JSON, a reference to this object must be publicly available (i.e. global). 808 | * 809 | * @private 810 | * @static 811 | * @type {Object} 812 | */ 813 | _callbackHolder: {}, 814 | 815 | /** 816 | * Generates an ID to be used when storing a reference to a callback function. 817 | * 818 | * @return {number} The generated ID. 819 | * @private 820 | */ 821 | _generateId: function() { 822 | do { 823 | seed++; 824 | } while (JSONPRequest._callbackHolder[seed]) 825 | 826 | return seed 827 | } 828 | 829 | }); 830 | 831 | /* 832 | * Copyright (C) 2017 Alasdair Mercer 833 | * 834 | * Permission is hereby granted, free of charge, to any person obtaining a copy 835 | * of this software and associated documentation files (the "Software"), to deal 836 | * in the Software without restriction, including without limitation the rights 837 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 838 | * copies of the Software, and to permit persons to whom the Software is 839 | * furnished to do so, subject to the following conditions: 840 | * 841 | * The above copyright notice and this permission notice shall be included in all 842 | * copies or substantial portions of the Software. 843 | * 844 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 845 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 846 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 847 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 848 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 849 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 850 | * SOFTWARE. 851 | */ 852 | 853 | /** 854 | * Can make requests to the connected YOURLS API. 855 | * 856 | * @constructor 857 | * @protected 858 | */ 859 | var Requestor = Oopsy.extend({ 860 | 861 | /** 862 | * Sends the request to the connected YOURLS API with the data provided which should, in turn, call the 863 | * specified callback with the result. 864 | * 865 | * This method is primarily a proxy to {@link Request#send} but does validate the state of the connection information 866 | * to ensure that is is valid before making the request. 867 | * 868 | * @param {Object} data - the data to be sent 869 | * @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to 870 | * callback as the first argument 871 | * @param {Function} callback - the function to be called with the result 872 | * @return {void} 873 | * @throws {Error} - If either no connection is present, the request format is not supported, or the configured HTTP 874 | * method is not supported by the {@link Request}. 875 | * @protected 876 | */ 877 | sendRequest: function(data, resultNames, callback) { 878 | var api = API.instance; 879 | 880 | if (!api) { 881 | throw new Error('No connection has been made') 882 | } 883 | 884 | var format = api.options.format; 885 | var method = api.options.method; 886 | var Request = Requestor._requestFormatMap[format]; 887 | 888 | if (!Request) { 889 | throw new Error('Request format not supported: ' + format) 890 | } 891 | 892 | var request = new Request(); 893 | 894 | if (!request.isMethodSupported(method)) { 895 | throw new Error('HTTP method not supported: ' + method) 896 | } 897 | 898 | request.send(data, resultNames, callback); 899 | } 900 | 901 | }, { 902 | 903 | /** 904 | * The mapping of supported request formats to {@link Request} constructors. 905 | * 906 | * @private 907 | * @static 908 | * @type {Object} 909 | */ 910 | _requestFormatMap: { 911 | json: JSONRequest, 912 | jsonp: JSONPRequest 913 | } 914 | 915 | }); 916 | 917 | /* 918 | * Copyright (C) 2017 Alasdair Mercer 919 | * 920 | * Permission is hereby granted, free of charge, to any person obtaining a copy 921 | * of this software and associated documentation files (the "Software"), to deal 922 | * in the Software without restriction, including without limitation the rights 923 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 924 | * copies of the Software, and to permit persons to whom the Software is 925 | * furnished to do so, subject to the following conditions: 926 | * 927 | * The above copyright notice and this permission notice shall be included in all 928 | * copies or substantial portions of the Software. 929 | * 930 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 931 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 932 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 933 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 934 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 935 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 936 | * SOFTWARE. 937 | */ 938 | 939 | /** 940 | * Provides the ability to lookup information related to the YOURLS database. 941 | * 942 | * @constructor 943 | * @extends Requestor 944 | * @protected 945 | */ 946 | var DB = Requestor.extend({ 947 | 948 | /** 949 | * Retrieves the statistics for this {@link DB}. 950 | * 951 | * @param {Function} callback - the callback function to be called with the result 952 | * @return {DB} A reference to this {@link DB} for chaining purposes. 953 | * @public 954 | */ 955 | stats: function(callback) { 956 | var data = { action: 'db-stats' }; 957 | 958 | this.sendRequest(data, 'db-stats', callback); 959 | 960 | return this 961 | } 962 | 963 | }); 964 | 965 | /* 966 | * Copyright (C) 2017 Alasdair Mercer 967 | * 968 | * Permission is hereby granted, free of charge, to any person obtaining a copy 969 | * of this software and associated documentation files (the "Software"), to deal 970 | * in the Software without restriction, including without limitation the rights 971 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 972 | * copies of the Software, and to permit persons to whom the Software is 973 | * furnished to do so, subject to the following conditions: 974 | * 975 | * The above copyright notice and this permission notice shall be included in all 976 | * copies or substantial portions of the Software. 977 | * 978 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 979 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 980 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE0 981 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 982 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 983 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 984 | * SOFTWARE. 985 | */ 986 | 987 | /** 988 | * Provides the ability to lookup information related to the specified shortened url. 989 | * 990 | * @param {string} url - the shortened URL (or its keyword) to be used 991 | * @constructor 992 | * @extends Requestor 993 | * @protected 994 | */ 995 | var URL = Requestor.extend(function(url) { 996 | URL.super_.call(this); 997 | 998 | /** 999 | * Either the shortened URL or its keyword for this {@link URL}. 1000 | * 1001 | * @public 1002 | * @type {string} 1003 | */ 1004 | this.url = url; 1005 | }); 1006 | 1007 | /** 1008 | * Retrieves the original ("long") URL for this shortened {@link URL}. 1009 | * 1010 | * @param {Function} callback - the callback function to be called with the result 1011 | * @return {URL} A reference to this {@link URL} for chaining purposes. 1012 | * @public 1013 | */ 1014 | URL.prototype.expand = function(callback) { 1015 | var data = { 1016 | action: 'expand', 1017 | shorturl: this.url 1018 | }; 1019 | 1020 | this.sendRequest(data, [ 'keyword', 'longurl', 'shorturl' ], callback); 1021 | 1022 | return this 1023 | }; 1024 | 1025 | /** 1026 | * Retrieves the statistics for this shortened {@link URL}. 1027 | * 1028 | * @param {Function} callback - the callback function to be called with the result 1029 | * @return {URL} A reference to this {@link URL} for chaining purposes. 1030 | * @public 1031 | */ 1032 | URL.prototype.stats = function(callback) { 1033 | var data = { 1034 | action: 'url-stats', 1035 | shorturl: this.url 1036 | }; 1037 | 1038 | this.sendRequest(data, 'link', callback); 1039 | 1040 | return this 1041 | }; 1042 | 1043 | /* 1044 | * Copyright (C) 2017 Alasdair Mercer 1045 | * 1046 | * Permission is hereby granted, free of charge, to any person obtaining a copy 1047 | * of this software and associated documentation files (the "Software"), to deal 1048 | * in the Software without restriction, including without limitation the rights 1049 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 1050 | * copies of the Software, and to permit persons to whom the Software is 1051 | * furnished to do so, subject to the following conditions: 1052 | * 1053 | * The above copyright notice and this permission notice shall be included in all 1054 | * copies or substantial portions of the Software. 1055 | * 1056 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1057 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1058 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1059 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1060 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1061 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1062 | * SOFTWARE. 1063 | */ 1064 | 1065 | /** 1066 | * Provides the ability to connect to YOURLS servers and perform read/write operations via the API that they expose. 1067 | * 1068 | * Before attempting to interact with a YOURLS server, you must call {@link YOURLS#connect} first to configure 1069 | * the URL of the YOURLS server and any credentials required to authenticate with its API (only required when private). 1070 | * 1071 | * @constructor 1072 | * @extends Requestor 1073 | * @protected 1074 | */ 1075 | var YOURLS = Requestor.extend(function() { 1076 | YOURLS.super_.call(this); 1077 | 1078 | /** 1079 | * Provides information on the YOURLS {@link DB}. 1080 | * 1081 | * @public 1082 | * @type {DB} 1083 | */ 1084 | this.db = new DB(); 1085 | 1086 | /** 1087 | * The current version of yourls. 1088 | * 1089 | * This is not the same as the version of YOURLS that is being connected to. The {@link YOURLS#version} method 1090 | * should be used to provide that information. 1091 | * 1092 | * @public 1093 | * @type {string} 1094 | */ 1095 | this.VERSION = '2.1.0'; 1096 | }, { 1097 | 1098 | /** 1099 | * Stores the specified information to be used later to connect to and authenticate with a YOURLS server. 1100 | * 1101 | * @param {string} [url=''] - the URL for the YOURLS server 1102 | * @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be 1103 | * null) 1104 | * @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be 1105 | * null) 1106 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 1107 | * @public 1108 | */ 1109 | connect: function(url, credentials, options) { 1110 | API.instance = new API(url, credentials, options); 1111 | 1112 | return this 1113 | }, 1114 | 1115 | /** 1116 | * Clears any information that may have been previously stored for connecting to and authenticating with a YOURLS 1117 | * server. 1118 | * 1119 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 1120 | * @public 1121 | */ 1122 | disconnect: function() { 1123 | API.instance = null; 1124 | 1125 | return this 1126 | }, 1127 | 1128 | /** 1129 | * Creates a short URL for the specified long url. 1130 | * 1131 | * Optionally, a descriptor can be provided to specify a keyword and/or title for the short URL that is 1132 | * to be created. If a keyword is specified, it must be available and, if not, the YOURLS server will generate a 1133 | * unique keyword. If descriptor is a string, it will be treated as the keyword. 1134 | * 1135 | * @param {string} url - the long URL to be shortened 1136 | * @param {YOURLS~UrlDescriptor|string} [descriptor] - the optional descriptor (or keyword, if it's a string) to be 1137 | * used for the short URL 1138 | * @param {Function} callback - the callback function to be called with the result 1139 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 1140 | * @public 1141 | */ 1142 | shorten: function(url, descriptor, callback) { 1143 | var data = { 1144 | action: 'shorturl', 1145 | url: url 1146 | }; 1147 | 1148 | switch (typeof descriptor) { 1149 | case 'function': 1150 | callback = descriptor; 1151 | descriptor = null; 1152 | break 1153 | case 'string': 1154 | descriptor = { keyword: descriptor }; 1155 | break 1156 | default: 1157 | // Do nothing 1158 | } 1159 | 1160 | if (descriptor) { 1161 | data.keyword = descriptor.keyword; 1162 | data.title = descriptor.title; 1163 | } 1164 | 1165 | this.sendRequest(data, [ 'shorturl', 'title', 'url' ], callback); 1166 | 1167 | return this 1168 | }, 1169 | 1170 | /** 1171 | * Retrieves the statistics for all shortened URLs. 1172 | * 1173 | * Optionally, criteria can be provided to also include a refined set of links in the result. This 1174 | * includes filter, which provides limited control over the sorting, as well as limit and start, which allow for 1175 | * pagination. If criteria is a number, it will be treated as the limit. 1176 | * 1177 | * No links will be included in the result unless a limit is specified that is greater than zero. In that case, this 1178 | * method would effectively be doing the same as {@link DB#stats}. 1179 | * 1180 | * @param {YOURLS~SearchCriteria|number} [criteria] - the optional criteria (or limit, if it's a number) to be used to 1181 | * search for links to be included in the result 1182 | * @param {Function} callback - the callback function to be called with the result 1183 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 1184 | * @public 1185 | */ 1186 | stats: function(criteria, callback) { 1187 | var data = { action: 'stats' }; 1188 | 1189 | switch (typeof criteria) { 1190 | case 'function': 1191 | callback = criteria; 1192 | criteria = null; 1193 | break 1194 | case 'number': 1195 | criteria = { limit: criteria }; 1196 | break 1197 | default: 1198 | // Do nothing 1199 | } 1200 | 1201 | if (criteria) { 1202 | data.filter = criteria.filter; 1203 | data.limit = criteria.limit; 1204 | data.start = criteria.start; 1205 | } 1206 | 1207 | this.sendRequest(data, [ 'links', 'stats' ], function(result, response) { 1208 | callback(YOURLS._sanitizeStatsResult(result), response); 1209 | }); 1210 | 1211 | return this 1212 | }, 1213 | 1214 | /** 1215 | * Creates an instance of {@link URL} for the specified shortened url which can be used to lookup more 1216 | * detailed information relating to it. 1217 | * 1218 | * No data is fetched just by calling this method; one of the methods on the returned instance need to be called for 1219 | * that to happen. 1220 | * 1221 | * @param {string} url - the shortened URL (or its keyword) 1222 | * @return {URL} The {@link URL} created for the shortened url or null if url 1223 | * is null. 1224 | * @public 1225 | */ 1226 | url: function(url) { 1227 | return url ? new URL(url) : null 1228 | }, 1229 | 1230 | /** 1231 | * Retrieves the version of the connected YOURLS API. 1232 | * 1233 | * Optionally, db can be passed to indicate that the YOURLS database version should also be included in 1234 | * the result. 1235 | * 1236 | * @param {boolean} [db] - true to include the database version; otherwise false 1237 | * @param {Function} callback - the callback function to be called with the result 1238 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 1239 | * @public 1240 | */ 1241 | version: function(db, callback) { 1242 | var data = { action: 'version' }; 1243 | 1244 | if (typeof db === 'function') { 1245 | callback = db; 1246 | db = null; 1247 | } 1248 | 1249 | if (db != null) { 1250 | data.db = Number(db); 1251 | } 1252 | 1253 | this.sendRequest(data, [ 'db_version', 'version' ], callback); 1254 | 1255 | return this 1256 | } 1257 | 1258 | }, { 1259 | 1260 | /** 1261 | * Sanitizes the result of {@link YOURLS#stats} so that it's more usable in application code by transforming the links 1262 | * from an object mapping into an array. 1263 | * 1264 | * This method simply returns result if it is null, has no links property or one that is 1265 | * already an array (future-proofing). 1266 | * 1267 | * @param {Object} result - the result to be sanitized (may be null) 1268 | * @param {Object} [result.links] - the links to be transformed into an array (may be null) 1269 | * @return {Object} The modified result or null if result is null. 1270 | * @private 1271 | * @static 1272 | */ 1273 | _sanitizeStatsResult: function(result) { 1274 | // Future-proofing by sanitizing links *only* when not already an array 1275 | if (!result || !result.links || isArray(result.links)) { 1276 | return result 1277 | } 1278 | 1279 | var index = 1; 1280 | var link; 1281 | var links = []; 1282 | 1283 | while ((link = result.links['link_' + index]) != null) { 1284 | links.push(link); 1285 | index++; 1286 | } 1287 | 1288 | result.links = links; 1289 | 1290 | return result 1291 | } 1292 | 1293 | }); 1294 | 1295 | /** 1296 | * The singleton instance of {@link YOURLS}. 1297 | */ 1298 | var yourls = new YOURLS(); 1299 | 1300 | /** 1301 | * Contains criteria which can be used to search for a refined set of shortened URLs. 1302 | * 1303 | * Pagination can be achieved by using limit and start. 1304 | * 1305 | * No links will be returned unless limit is specified and has a value that is greater than zero. 1306 | * 1307 | * @typedef {Object} YOURLS~SearchCriteria 1308 | * @property {string} [filter] - The filter to be applied (either "top", "bottom", 1309 | * "rand", or "last"). 1310 | * @property {number} [limit] - The maximum number of links whose statistical information is to be counted. 1311 | * null or 0 will result in no links being included in the result. 1312 | * @property {number} [start] - The offset from where the search should begin. 1313 | */ 1314 | 1315 | /** 1316 | * Contains additional information which can be used when shortening a URL. 1317 | * 1318 | * If keyword is specified, it must be available and, if not, the YOURLS server will generate a unique 1319 | * keyword. 1320 | * 1321 | * @typedef {Object} YOURLS~UrlDescriptor 1322 | * @property {string} [keyword] - The optional keyword to be used for the shortened URL. 1323 | * @property {string} [title] - The optional title to be associated with the shortened URL. 1324 | */ 1325 | 1326 | return yourls; 1327 | 1328 | }))); 1329 | 1330 | //# sourceMappingURL=yourls.js.map -------------------------------------------------------------------------------- /dist/yourls.min.js: -------------------------------------------------------------------------------- 1 | /*! YOURLS API v2.1.0 | (C) 2017 Alasdair Mercer | MIT License */ 2 | !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;iObject.prototype.hasOwnProperty.\n *\n * @private\n * @type {Function}\n */\nvar hasOwnProperty = Object.prototype.hasOwnProperty\n/**\n * A reference to Array.prototype.slice.\n *\n * @private\n * @type {Function}\n */\nvar slice = Array.prototype.slice\n\n/**\n * Extends the specified target object with the properties in each of the sources provided.\n *\n * Nothing happens if target is null and if any source is null it will be\n * ignored.\n *\n * @param {boolean} own - true to only copy own properties from sources onto\n * target; otherwise false\n * @param {Object} [target] - the target object which should be extended\n * @param {...Object} [sources] - the source objects whose properties are to be copied onto target\n * @return {void}\n * @private\n */\nfunction extend(own, target, sources) {\n if (target == null) {\n return\n }\n\n sources = slice.call(arguments, 2)\n\n var property\n var source\n\n for (var i = 0, length = sources.length; i < length; i++) {\n source = sources[i]\n\n for (property in source) {\n if (!own || hasOwnProperty.call(source, property)) {\n target[property] = source[property]\n }\n }\n }\n}\n\n/**\n * Creates an object which inherits the given prototype.\n *\n * Optionally, the created object can be extended further with the specified properties.\n *\n * @param {Object} prototype - the prototype to be inherited by the created object\n * @param {Object} [properties] - the optional properties to be extended by the created object\n * @return {Object} The newly created object.\n * @private\n */\nfunction create(prototype, properties) {\n var result\n if (typeof Object.create === 'function') {\n result = Object.create(prototype)\n } else {\n Constructor.prototype = prototype\n result = new Constructor()\n Constructor.prototype = null\n }\n\n if (properties) {\n extend(true, result, properties)\n }\n\n return result\n}\n\n/**\n * The base constructor from which all others should extend.\n *\n * @public\n * @constructor\n */\nexport default function Oopsy() {}\n\n/**\n * Extends the constructor to which this method is associated with the prototype and/or\n * statics provided.\n *\n * If constructor is provided, it will be used as the constructor for the child, otherwise a simple\n * constructor which only calls the super constructor will be used instead.\n *\n * The super constructor can be accessed via a special super_ property on the child constructor.\n *\n * @param {Function} [constructor] - the constructor for the child\n * @param {Object} [prototype] - the prototype properties to be defined for the child\n * @param {Object} [statics] - the static properties to be defined for the child\n * @return {Function} The child constructor provided or the one created if none was given.\n * @public\n * @static\n */\nOopsy.extend = function(constructor, prototype, statics) {\n var superConstructor = this\n\n if (typeof constructor !== 'function') {\n statics = prototype\n prototype = constructor\n constructor = function() {\n return superConstructor.apply(this, arguments)\n }\n }\n\n extend(false, constructor, superConstructor, statics)\n\n constructor.prototype = create(superConstructor.prototype, prototype)\n constructor.prototype.constructor = constructor\n\n constructor.super_ = superConstructor\n\n return constructor\n}\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Extends the specified target object with the properties in each of the sources provided.\n *\n * Any of the sources that are null will simply be ignored.\n *\n * @param {Object} target - the target object which should be extended\n * @param {...Object} [sources] - the source objects whose properties are to be copied onto target\n * @return {Object} A reference to target.\n * @protected\n */\nexport function extend(target, sources) {\n sources = Array.prototype.slice.call(arguments, 1)\n\n for (var i = 0, length = sources.length, property, source; i < length; i++) {\n source = sources[i]\n\n if (source) {\n for (property in source) {\n if (Object.prototype.hasOwnProperty.call(source, property)) {\n target[property] = source[property]\n }\n }\n }\n }\n\n return target\n}\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Returns whether the specified obj is an array.\n *\n * This method will use the native Array.isArray, if available.\n *\n * @param {*} obj - the object to be checked (may be null)\n * @return {boolean} true if obj is an array; otherwise false.\n * @protected\n */\nexport function isArray(obj) {\n return Array.isArray ? Array.isArray(obj) : Object.prototype.toString.call(obj) === '[object Array]'\n}\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport Oopsy from 'oopsy'\n\nimport { extend } from './util/extend'\n\n/**\n * Contains information on how to connect to and authenticate with a YOURLS server.\n *\n * @param {string} [url=''] - the URL for the YOURLS server\n * @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be\n * null)\n * @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be\n * null)\n * @protected\n * @constructor\n */\nexport var API = Oopsy.extend(function(url, credentials, options) {\n /**\n * The URL of the YOURLS server.\n *\n * @public\n * @type {string}\n */\n this.url = url ? url.replace(/\\/$/, '') : ''\n /**\n * The credentials to be used to authenticate with the YOURLS API.\n *\n * This may be null if the YOURLS API is public.\n *\n * @public\n * @type {API~Credentials}\n */\n this.credentials = API._sanitizeCredentials(credentials)\n /**\n * The options to be used to send requests to the YOURLS server.\n *\n * @public\n * @type {API~Options}\n */\n this.options = API._sanitizeOptions(options)\n}, null, {\n\n /**\n * The default options to be used.\n *\n * @protected\n * @static\n * @type {API~Options}\n */\n defaultOptions: {\n format: 'jsonp',\n method: 'GET'\n },\n\n /**\n * The singleton {@link API} instance which is privatized to prevent leaking credentials.\n *\n * @public\n * @static\n * @type {API}\n */\n instance: null,\n\n /**\n * Sanitizes the specified credentials by ensuring that only valid properties are present and only when\n * appropriate.\n *\n * This method does not modify credentials and instead creates a new object with the sanitized\n * properties.\n *\n * @param {API~Credentials} credentials - the credentials to be sanitized (may be null)\n * @return {API~Credentials} A sanitized version of credentials or null if\n * credentials is null.\n * @private\n * @static\n */\n _sanitizeCredentials: function(credentials) {\n if (!credentials) {\n return null\n }\n\n var result = {}\n if (credentials.signature) {\n result.signature = credentials.signature\n result.timestamp = credentials.timestamp\n } else {\n result.password = credentials.password\n result.username = credentials.username\n }\n\n return result\n },\n\n /**\n * Sanitizes the specified options by ensuring that only valid properties are present and in the correct\n * format.\n *\n * This method does not modify options and instead creates a new object with the sanitized properties and\n * default values will be used for missing options.\n *\n * @param {API~Options} options - the options to be sanitized (may be null)\n * @return {API~Options} A sanitized version of options which will contain only default values if\n * options is null.\n * @private\n * @static\n */\n _sanitizeOptions: function(options) {\n var result = extend({}, API.defaultOptions)\n if (!options) {\n return result\n }\n\n if (options.format) {\n result.format = options.format.toLowerCase()\n }\n if (options.method) {\n result.method = options.method.toUpperCase()\n }\n\n return result\n }\n\n})\n\n/**\n * The credentials to be used to authenticate with a private YOURLS API.\n *\n * Authentication can be done with a traditional username and password combination or by using the secret\n * signature token (e.g. 1002a612b4)for the YOURLS API. The latter is not available for public YOURLS APIs\n * and can be found on the \"Tools\" page.\n *\n * Optionally, a timestamp can accompany the signature token to make it time-limited (depending on the server\n * configuration). When a timestamp is provided the signature token must be the md5 sum of the timestamp and\n * signature token concatenated, and in that order.\n *\n * @typedef {Object} API~Credentials\n * @property {string} [password] - The password of the user to be authenticated.\n * @property {string} [username] - The name of the user to be authenticated.\n * @property {string} [signature] - The signature token to be used for passwordless authentication with the YOURLS API.\n * @property {number|string} [timestamp] - The optional timestamp to limit the signature token.\n */\n\n/**\n * The options that determine how requests are sent to the YOURLS server.\n *\n * If the request format does not support the HTTP method, requests will not be sent and an\n * error will be thrown when such attempts occur.\n *\n * @typedef {Object} API~Options\n * @property {string} [format=\"jsonp\"] - The format in which requests are sent (either \"json\" or\n * \"jsonp\").\n * @property {string} [method=\"GET\"] - The HTTP method to be used for requests.\n */\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport Oopsy from 'oopsy'\n\nimport { API } from '../api'\nimport { extend } from '../util/extend'\nimport { isArray } from '../util/array'\n\n/**\n * Contains logic to connect with a YOURLS server and send data to its API.\n *\n * Due to the nature of HTTP, requests sent using a \"GET\" HTTP method will include all information in the URL of the\n * request. This includes the data that is sent as well as any credentials used to authenticate with the API. You\n * have been warned.\n *\n * @constructor\n * @protected\n */\nexport var Request = Oopsy.extend({\n\n /**\n * Builds the body for this {@link Request} based on the api and data provided.\n *\n * @param {API} api - the {@link API} to which the request is being sent\n * @param {Object} [data] - the data being sent in this request\n * @return {Object} The request body.\n * @protected\n */\n buildBody: function(api, data) {\n return extend({ format: api.options.format }, api.credentials, data)\n },\n\n /**\n * Returns the list of the HTTP methods that are supported by this {@link Request}.\n *\n * By default, this method returns null, so implementations must implement this method to ensure\n * that {@link Request#isMethodSupported} works correctly.\n *\n * @return {string[]} The supported HTTP methods.\n * @protected\n */\n getSupportedHttpMethods: function() {\n return null\n },\n\n /**\n * Determines whether this {@link Request} supports the specified HTTP method.\n *\n * @param {string} method - the HTTP method to be checked\n * @return {boolean} true if method is supported; otherwise false.\n * @public\n */\n isMethodSupported: function(method) {\n var supportedMethods = this.getSupportedHttpMethods()\n return supportedMethods && supportedMethods.indexOf(method) !== -1\n },\n\n /**\n * Determines whether the data that is to be sent to the YOURLS server in this {@link Request} must be serialized as\n * query string parameters.\n *\n * @param {string} method - the HTTP method to be used\n * @return {boolean} true if the data needs to be sent as query string parameters; otherwise\n * false.\n * @protected\n */\n isQueryStringRequired: function(method) {\n return method === 'GET'\n },\n\n /**\n * Processes this {@link Request} by sending it to the specified target url containing the\n * body provided.\n *\n * callback should be called with the response regardless of whether the it was a success or failure.\n *\n * This method is called internally by {@link Request#send} and does all of the actual work involved to send the\n * request and parse the response. All implementations must implement this method.\n *\n * @param {string} method - the request HTTP method\n * @param {string} url - the request URL\n * @param {Object} body - the request body (may be null)\n * @param {Function} callback - the function to be called with the response\n * @return {void}\n * @protected\n * @abstract\n */\n process: function(method, url, body, callback) {\n // Must be implemented\n },\n\n /**\n * Sends the request to the connected YOURLS API with the data provided which should, in turn, call the\n * specified callback with the result.\n *\n * If the request is successful, callback will be passed the value of the named properties from the\n * response. If resultNames is a string or only contains a single string, only the value for that named\n * property will be passed as the first argument. Otherwise, an object containing the key/value pairs for each named\n * property will be passed as the first argument. The actual response will always be passed as the second argument.\n *\n * @param {Object} data - the data to be sent\n * @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to\n * callback as the first argument\n * @param {Function} callback - the function to be called with the result\n * @return {void}\n * @public\n */\n send: function(data, resultNames, callback) {\n var api = API.instance\n var body = Request._serializeParameters(this.buildBody(api, data))\n var method = api.options.method\n var url = api.url\n\n if (this.isQueryStringRequired(method)) {\n url += '?' + body\n body = null\n }\n\n this.process(method, url, body, function(response) {\n callback(Request._extractResult(resultNames, response), response)\n })\n }\n\n}, {\n\n /**\n * Extracts the values of the properties with the specified names from the response provided\n * and returns them in a single result.\n *\n * If names is a string or only contains a single string, only the value for that named property will be\n * returned. Otherwise, an object containing the key/value pairs for each named property will be returned.\n *\n * If response is null this method will return null.\n *\n * @param {string|string[]} names - the names of the response properties whose values are to be returned\n * as the result\n * @param {Object} response - the YOURLS API response\n * @return {*} The result extracted from response.\n * @private\n * @static\n */\n _extractResult: function(names, response) {\n names = isArray(names) ? names : [ names ]\n\n var i\n var name\n var result = null\n\n if (!response) {\n return result\n }\n\n if (names.length === 1) {\n result = response[names[0]]\n } else {\n result = {}\n\n for (i = 0; i < names.length; i++) {\n name = names[i]\n\n if (typeof response[name] !== 'undefined') {\n result[name] = response[name]\n }\n }\n }\n\n return result\n },\n\n /**\n * Creates a serialized representation of the specified parameters.\n *\n * All of the parameter names and values are URL-encoded so that they can be safely included in the query string or\n * request body.\n *\n * @param {Object} [params] - the hash of parameter name/value pairs to be serialized\n * @return {string} A URL-encoded representing obj or an empty string if obj is\n * null.\n * @private\n * @static\n */\n _serializeParameters: function(params) {\n if (!params) {\n return ''\n }\n\n var results = []\n\n for (var name in params) {\n if (Object.prototype.hasOwnProperty.call(params, name) && params[name] != null) {\n results.push(encodeURIComponent(name) + '=' + encodeURIComponent(params[name]))\n }\n }\n\n return results.join('&')\n }\n\n})\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Request } from './request'\n\n/**\n * An implementation of {@link Request} that provides support for JSON requests to the YOURLS API.\n *\n * JSON requests can only be sent using the \"GET\" or \"POST\" HTTP methods.\n *\n * @constructor\n * @extends Request\n * @protected\n */\nexport var JSONRequest = Request.extend({\n\n /**\n * @inheritDoc\n * @override\n */\n getSupportedHttpMethods: function() {\n return [ 'GET', 'POST' ]\n },\n\n /**\n * @inheritDoc\n * @override\n */\n process: function(method, url, body, callback) {\n var xhr = new XMLHttpRequest()\n xhr.open(method, url, true)\n xhr.onreadystatechange = function() {\n var response\n\n if (xhr.readyState === 4) {\n try {\n response = JSON.parse(xhr.responseText)\n callback(response)\n } catch (e) {\n throw new Error('Unable to parse response: ' + e)\n }\n }\n }\n\n xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')\n if (body != null) {\n xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')\n }\n\n xhr.send(body)\n }\n\n})\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Request } from './request'\n\n/**\n * The seed to be used to generate IDs.\n *\n * @private\n * @type {number}\n */\nvar seed = new Date().getTime()\n\n/**\n * An implementation of {@link Request} that provides support for JSONP requests to the YOURLS API.\n *\n * JSONP requests can only be sent using the \"GET\" HTTP method.\n *\n * @constructor\n * @extends Request\n * @protected\n */\nexport var JSONPRequest = Request.extend(function() {\n JSONPRequest.super_.call(this)\n\n if (!window[JSONPRequest._callbackHolderKey]) {\n window[JSONPRequest._callbackHolderKey] = JSONPRequest._callbackHolder\n }\n\n /**\n * The generated ID which is used to store a reference to the callback function within the holder so that the JSONP\n * payload can find it in the global namespace.\n *\n * @private\n * @type {number}\n */\n this._id = JSONPRequest._generateId()\n}, {\n\n /**\n * @inheritDoc\n * @override\n */\n getSupportedHttpMethods: function() {\n return [ 'GET' ]\n },\n\n /**\n * @inheritDoc\n * @override\n */\n buildBody: function(api, data) {\n var body = JSONPRequest.super_.prototype.buildBody.call(this, api, data)\n body.callback = JSONPRequest._callbackHolderKey + '[' + this._id + ']'\n\n return body\n },\n\n /**\n * @inheritDoc\n * @override\n */\n process: function(method, url, body, callback) {\n var script = document.createElement('script')\n\n var self = this\n JSONPRequest._callbackHolder[this._id] = function(response) {\n delete JSONPRequest._callbackHolder[self._id]\n script.parentNode.removeChild(script)\n\n callback(response)\n }\n\n script.setAttribute('src', url)\n document.getElementsByTagName('head')[0].appendChild(script)\n }\n\n}, {\n\n /**\n * The key of the callback function holder within the global namespace.\n *\n * @private\n * @static\n * @type {string}\n */\n _callbackHolderKey: '__yourls' + seed + '_jsonp',\n\n /**\n * Contains the callback functions for active JSONP requests.\n *\n * Callback references should be removed immediately once they have been called.\n *\n * Due to the nature of JSON, a reference to this object must be publicly available (i.e. global).\n *\n * @private\n * @static\n * @type {Object}\n */\n _callbackHolder: {},\n\n /**\n * Generates an ID to be used when storing a reference to a callback function.\n *\n * @return {number} The generated ID.\n * @private\n */\n _generateId: function() {\n do {\n seed++\n } while (JSONPRequest._callbackHolder[seed])\n\n return seed\n }\n\n})\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport Oopsy from 'oopsy'\n\nimport { API } from '../api'\nimport { JSONRequest } from './json'\nimport { JSONPRequest } from './jsonp'\n\n/**\n * Can make requests to the connected YOURLS API.\n *\n * @constructor\n * @protected\n */\nexport var Requestor = Oopsy.extend({\n\n /**\n * Sends the request to the connected YOURLS API with the data provided which should, in turn, call the\n * specified callback with the result.\n *\n * This method is primarily a proxy to {@link Request#send} but does validate the state of the connection information\n * to ensure that is is valid before making the request.\n *\n * @param {Object} data - the data to be sent\n * @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to\n * callback as the first argument\n * @param {Function} callback - the function to be called with the result\n * @return {void}\n * @throws {Error} - If either no connection is present, the request format is not supported, or the configured HTTP\n * method is not supported by the {@link Request}.\n * @protected\n */\n sendRequest: function(data, resultNames, callback) {\n var api = API.instance\n\n if (!api) {\n throw new Error('No connection has been made')\n }\n\n var format = api.options.format\n var method = api.options.method\n var Request = Requestor._requestFormatMap[format]\n\n if (!Request) {\n throw new Error('Request format not supported: ' + format)\n }\n\n var request = new Request()\n\n if (!request.isMethodSupported(method)) {\n throw new Error('HTTP method not supported: ' + method)\n }\n\n request.send(data, resultNames, callback)\n }\n\n}, {\n\n /**\n * The mapping of supported request formats to {@link Request} constructors.\n *\n * @private\n * @static\n * @type {Object}\n */\n _requestFormatMap: {\n json: JSONRequest,\n jsonp: JSONPRequest\n }\n\n})\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Requestor } from './request/requestor'\n\n/**\n * Provides the ability to lookup information related to the YOURLS database.\n *\n * @constructor\n * @extends Requestor\n * @protected\n */\nexport var DB = Requestor.extend({\n\n /**\n * Retrieves the statistics for this {@link DB}.\n *\n * @param {Function} callback - the callback function to be called with the result\n * @return {DB} A reference to this {@link DB} for chaining purposes.\n * @public\n */\n stats: function(callback) {\n var data = { action: 'db-stats' }\n\n this.sendRequest(data, 'db-stats', callback)\n\n return this\n }\n\n})\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE0\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Requestor } from './request/requestor'\n\n/**\n * Provides the ability to lookup information related to the specified shortened url.\n *\n * @param {string} url - the shortened URL (or its keyword) to be used\n * @constructor\n * @extends Requestor\n * @protected\n */\nexport var URL = Requestor.extend(function(url) {\n URL.super_.call(this)\n\n /**\n * Either the shortened URL or its keyword for this {@link URL}.\n *\n * @public\n * @type {string}\n */\n this.url = url\n})\n\n/**\n * Retrieves the original (\"long\") URL for this shortened {@link URL}.\n *\n * @param {Function} callback - the callback function to be called with the result\n * @return {URL} A reference to this {@link URL} for chaining purposes.\n * @public\n */\nURL.prototype.expand = function(callback) {\n var data = {\n action: 'expand',\n shorturl: this.url\n }\n\n this.sendRequest(data, [ 'keyword', 'longurl', 'shorturl' ], callback)\n\n return this\n}\n\n/**\n * Retrieves the statistics for this shortened {@link URL}.\n *\n * @param {Function} callback - the callback function to be called with the result\n * @return {URL} A reference to this {@link URL} for chaining purposes.\n * @public\n */\nURL.prototype.stats = function(callback) {\n var data = {\n action: 'url-stats',\n shorturl: this.url\n }\n\n this.sendRequest(data, 'link', callback)\n\n return this\n}\n","/*\n * Copyright (C) 2017 Alasdair Mercer\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { API } from './api'\nimport { DB } from './yourls-db'\nimport { isArray } from './util/array'\nimport { Requestor } from './request/requestor'\nimport { URL } from './yourls-url'\n\n/**\n * Provides the ability to connect to YOURLS servers and perform read/write operations via the API that they expose.\n *\n * Before attempting to interact with a YOURLS server, you must call {@link YOURLS#connect} first to configure\n * the URL of the YOURLS server and any credentials required to authenticate with its API (only required when private).\n *\n * @constructor\n * @extends Requestor\n * @protected\n */\nvar YOURLS = Requestor.extend(function() {\n YOURLS.super_.call(this)\n\n /**\n * Provides information on the YOURLS {@link DB}.\n *\n * @public\n * @type {DB}\n */\n this.db = new DB()\n\n /**\n * The current version of yourls.\n *\n * This is not the same as the version of YOURLS that is being connected to. The {@link YOURLS#version} method\n * should be used to provide that information.\n *\n * @public\n * @type {string}\n */\n this.VERSION = '2.1.0'\n}, {\n\n /**\n * Stores the specified information to be used later to connect to and authenticate with a YOURLS server.\n *\n * @param {string} [url=''] - the URL for the YOURLS server\n * @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be\n * null)\n * @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be\n * null)\n * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes.\n * @public\n */\n connect: function(url, credentials, options) {\n API.instance = new API(url, credentials, options)\n\n return this\n },\n\n /**\n * Clears any information that may have been previously stored for connecting to and authenticating with a YOURLS\n * server.\n *\n * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes.\n * @public\n */\n disconnect: function() {\n API.instance = null\n\n return this\n },\n\n /**\n * Creates a short URL for the specified long url.\n *\n * Optionally, a descriptor can be provided to specify a keyword and/or title for the short URL that is\n * to be created. If a keyword is specified, it must be available and, if not, the YOURLS server will generate a\n * unique keyword. If descriptor is a string, it will be treated as the keyword.\n *\n * @param {string} url - the long URL to be shortened\n * @param {YOURLS~UrlDescriptor|string} [descriptor] - the optional descriptor (or keyword, if it's a string) to be\n * used for the short URL\n * @param {Function} callback - the callback function to be called with the result\n * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes.\n * @public\n */\n shorten: function(url, descriptor, callback) {\n var data = {\n action: 'shorturl',\n url: url\n }\n\n switch (typeof descriptor) {\n case 'function':\n callback = descriptor\n descriptor = null\n break\n case 'string':\n descriptor = { keyword: descriptor }\n break\n default:\n // Do nothing\n }\n\n if (descriptor) {\n data.keyword = descriptor.keyword\n data.title = descriptor.title\n }\n\n this.sendRequest(data, [ 'shorturl', 'title', 'url' ], callback)\n\n return this\n },\n\n /**\n * Retrieves the statistics for all shortened URLs.\n *\n * Optionally, criteria can be provided to also include a refined set of links in the result. This\n * includes filter, which provides limited control over the sorting, as well as limit and start, which allow for\n * pagination. If criteria is a number, it will be treated as the limit.\n *\n * No links will be included in the result unless a limit is specified that is greater than zero. In that case, this\n * method would effectively be doing the same as {@link DB#stats}.\n *\n * @param {YOURLS~SearchCriteria|number} [criteria] - the optional criteria (or limit, if it's a number) to be used to\n * search for links to be included in the result\n * @param {Function} callback - the callback function to be called with the result\n * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes.\n * @public\n */\n stats: function(criteria, callback) {\n var data = { action: 'stats' }\n\n switch (typeof criteria) {\n case 'function':\n callback = criteria\n criteria = null\n break\n case 'number':\n criteria = { limit: criteria }\n break\n default:\n // Do nothing\n }\n\n if (criteria) {\n data.filter = criteria.filter\n data.limit = criteria.limit\n data.start = criteria.start\n }\n\n this.sendRequest(data, [ 'links', 'stats' ], function(result, response) {\n callback(YOURLS._sanitizeStatsResult(result), response)\n })\n\n return this\n },\n\n /**\n * Creates an instance of {@link URL} for the specified shortened url which can be used to lookup more\n * detailed information relating to it.\n *\n * No data is fetched just by calling this method; one of the methods on the returned instance need to be called for\n * that to happen.\n *\n * @param {string} url - the shortened URL (or its keyword)\n * @return {URL} The {@link URL} created for the shortened url or null if url\n * is null.\n * @public\n */\n url: function(url) {\n return url ? new URL(url) : null\n },\n\n /**\n * Retrieves the version of the connected YOURLS API.\n *\n * Optionally, db can be passed to indicate that the YOURLS database version should also be included in\n * the result.\n *\n * @param {boolean} [db] - true to include the database version; otherwise false\n * @param {Function} callback - the callback function to be called with the result\n * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes.\n * @public\n */\n version: function(db, callback) {\n var data = { action: 'version' }\n\n if (typeof db === 'function') {\n callback = db\n db = null\n }\n\n if (db != null) {\n data.db = Number(db)\n }\n\n this.sendRequest(data, [ 'db_version', 'version' ], callback)\n\n return this\n }\n\n}, {\n\n /**\n * Sanitizes the result of {@link YOURLS#stats} so that it's more usable in application code by transforming the links\n * from an object mapping into an array.\n *\n * This method simply returns result if it is null, has no links property or one that is\n * already an array (future-proofing).\n *\n * @param {Object} result - the result to be sanitized (may be null)\n * @param {Object} [result.links] - the links to be transformed into an array (may be null)\n * @return {Object} The modified result or null if result is null.\n * @private\n * @static\n */\n _sanitizeStatsResult: function(result) {\n // Future-proofing by sanitizing links *only* when not already an array\n if (!result || !result.links || isArray(result.links)) {\n return result\n }\n\n var index = 1\n var link\n var links = []\n\n while ((link = result.links['link_' + index]) != null) {\n links.push(link)\n index++\n }\n\n result.links = links\n\n return result\n }\n\n})\n\n/**\n * The singleton instance of {@link YOURLS}.\n */\nexport default new YOURLS()\n\n/**\n * Contains criteria which can be used to search for a refined set of shortened URLs.\n *\n * Pagination can be achieved by using limit and start.\n *\n * No links will be returned unless limit is specified and has a value that is greater than zero.\n *\n * @typedef {Object} YOURLS~SearchCriteria\n * @property {string} [filter] - The filter to be applied (either \"top\", \"bottom\",\n * \"rand\", or \"last\").\n * @property {number} [limit] - The maximum number of links whose statistical information is to be counted.\n * null or 0 will result in no links being included in the result.\n * @property {number} [start] - The offset from where the search should begin.\n */\n\n/**\n * Contains additional information which can be used when shortening a URL.\n *\n * If keyword is specified, it must be available and, if not, the YOURLS server will generate a unique\n * keyword.\n *\n * @typedef {Object} YOURLS~UrlDescriptor\n * @property {string} [keyword] - The optional keyword to be used for the shortened URL.\n * @property {string} [title] - The optional title to be associated with the shortened URL.\n */\n"],"names":["extend","own","target","sources","slice","call","arguments","property","source","i","length","hasOwnProperty","create","prototype","properties","result","Object","Constructor","Oopsy","Array","isArray","obj","toString","constructor","statics","superConstructor","this","apply","super_","API","url","credentials","options","replace","_sanitizeCredentials","_sanitizeOptions","defaultOptions","format","method","instance","signature","timestamp","password","username","toLowerCase","toUpperCase","Request","buildBody","api","data","getSupportedHttpMethods","isMethodSupported","supportedMethods","indexOf","isQueryStringRequired","process","body","callback","send","resultNames","_serializeParameters","response","_extractResult","names","name","params","results","push","encodeURIComponent","join","JSONRequest","xhr","XMLHttpRequest","open","onreadystatechange","readyState","JSON","parse","responseText","e","Error","setRequestHeader","seed","Date","getTime","JSONPRequest","window","_callbackHolderKey","_callbackHolder","_id","_generateId","script","document","createElement","self","parentNode","removeChild","setAttribute","getElementsByTagName","appendChild","Requestor","sendRequest","_requestFormatMap","request","json","jsonp","DB","stats","action","URL","expand","shorturl","YOURLS","db","VERSION","connect","disconnect","shorten","descriptor","keyword","title","criteria","limit","filter","start","_sanitizeStatsResult","version","Number","links","link","index"],"mappings":";8LAyDA,SAASA,GAAOC,EAAKC,EAAQC,GAC3B,GAAc,MAAVD,EAAJ,CAIAC,EAAUC,EAAMC,KAAKC,UAAW,EAKhC,KAAK,GAHDC,GACAC,EAEKC,EAAI,EAAGC,EAASP,EAAQO,OAAQD,EAAIC,EAAQD,IAAK,CACxDD,EAASL,EAAQM,EAEjB,KAAKF,IAAYC,GACVP,IAAOU,EAAeN,KAAKG,EAAQD,KACtCL,EAAOK,GAAYC,EAAOD,MAgBlC,QAASK,GAAOC,EAAWC,GACzB,GAAIC,EAaJ,OAZ6B,kBAAlBC,QAAOJ,OAChBG,EAASC,OAAOJ,OAAOC,IAEvBI,EAAYJ,UAAYA,EACxBE,EAAS,GAAIE,GACbA,EAAYJ,UAAY,MAGtBC,GACFd,GAAO,EAAMe,EAAQD,GAGhBC,EASM,QAASG,MC/EjB,QAASlB,GAAOE,EAAQC,GAC7BA,EAAUgB,MAAMN,UAAUT,MAAMC,KAAKC,UAAW,EAEhD,KAAK,GAAoCC,GAAUC,EAA1CC,EAAI,EAAGC,EAASP,EAAQO,OAA0BD,EAAIC,EAAQD,IAGrE,GAFAD,EAASL,EAAQM,GAGf,IAAKF,IAAYC,GACXQ,OAAOH,UAAUF,eAAeN,KAAKG,EAAQD,KAC/CL,EAAOK,GAAYC,EAAOD,GAMlC,OAAOL,GChBF,QAASkB,GAAQC,GACtB,MAAOF,OAAMC,QAAUD,MAAMC,QAAQC,GAA+C,mBAAxCL,OAAOH,UAAUS,SAASjB,KAAKgB,MFJzEJ,GAAc,aAOdN,EAAiBK,OAAOH,UAAUF,eAOlCP,EAAQe,MAAMN,UAAUT,KAuF5Bc,GAAMlB,OAAS,SAASuB,EAAaV,EAAWW,GAC9C,GAAIC,GAAmBC,IAiBvB,OAf2B,kBAAhBH,KACTC,EAAUX,EACVA,EAAYU,EACZA,EAAc,WACZ,MAAOE,GAAiBE,MAAMD,KAAMpB,aAIxCN,GAAO,EAAOuB,EAAaE,EAAkBD,GAE7CD,EAAYV,UAAYD,EAAOa,EAAiBZ,UAAWA,GAC3DU,EAAYV,UAAUU,YAAcA,EAEpCA,EAAYK,OAASH,EAEdF,MG9GEM,GAAMX,EAAMlB,OAAO,SAAS8B,EAAKC,EAAaC,GAOvDN,KAAKI,IAAMA,EAAMA,EAAIG,QAAQ,MAAO,IAAM,GAS1CP,KAAKK,YAAcF,EAAIK,qBAAqBH,GAO5CL,KAAKM,QAAUH,EAAIM,iBAAiBH,IACnC,MASDI,gBACEC,OAAQ,QACRC,OAAQ,OAUVC,SAAU,KAeVL,qBAAsB,SAASH,GAC7B,IAAKA,EACH,MAAO,KAGT,IAAIhB,KASJ,OARIgB,GAAYS,WACdzB,EAAOyB,UAAYT,EAAYS,UAC/BzB,EAAO0B,UAAYV,EAAYU,YAE/B1B,EAAO2B,SAAWX,EAAYW,SAC9B3B,EAAO4B,SAAWZ,EAAYY,UAGzB5B,GAgBToB,iBAAkB,SAASH,GACzB,GAAIjB,GAASf,KAAW6B,EAAIO,eAC5B,OAAKJ,IAIDA,EAAQK,SACVtB,EAAOsB,OAASL,EAAQK,OAAOO,eAE7BZ,EAAQM,SACVvB,EAAOuB,OAASN,EAAQM,OAAOO,eAG1B9B,GAVEA,KC5FF+B,EAAU5B,EAAMlB,QAUzB+C,UAAW,SAASC,EAAKC,GACvB,MAAOjD,IAASqC,OAAQW,EAAIhB,QAAQK,QAAUW,EAAIjB,YAAakB,IAYjEC,wBAAyB,WACvB,MAAO,OAUTC,kBAAmB,SAASb,GAC1B,GAAIc,GAAmB1B,KAAKwB,yBAC5B,OAAOE,IAAoBA,EAAiBC,QAAQf,MAAa,GAYnEgB,sBAAuB,SAAShB,GAC9B,MAAkB,QAAXA,GAoBTiB,QAAS,SAASjB,EAAQR,EAAK0B,EAAMC,KAoBrCC,KAAM,SAAST,EAAMU,EAAaF,GAChC,GAAIT,GAAMnB,EAAIU,SACViB,EAAOV,EAAQc,qBAAqBlC,KAAKqB,UAAUC,EAAKC,IACxDX,EAASU,EAAIhB,QAAQM,OACrBR,EAAMkB,EAAIlB,GAEVJ,MAAK4B,sBAAsBhB,KAC7BR,GAAO,IAAM0B,EACbA,EAAO,MAGT9B,KAAK6B,QAAQjB,EAAQR,EAAK0B,EAAM,SAASK,GACvCJ,EAASX,EAAQgB,eAAeH,EAAaE,GAAWA,QAsB5DC,eAAgB,SAASC,EAAOF,GAC9BE,EAAQ3C,EAAQ2C,GAASA,GAAUA,EAEnC,IAAItD,GACAuD,EACAjD,EAAS,IAEb,KAAK8C,EACH,MAAO9C,EAGT,IAAqB,IAAjBgD,EAAMrD,OACRK,EAAS8C,EAASE,EAAM,QAIxB,KAFAhD,KAEKN,EAAI,EAAGA,EAAIsD,EAAMrD,OAAQD,IAC5BuD,EAAOD,EAAMtD,GAEiB,SAAnBoD,EAASG,KAClBjD,EAAOiD,GAAQH,EAASG,GAK9B,OAAOjD,IAeT6C,qBAAsB,SAASK,GAC7B,IAAKA,EACH,MAAO,EAGT,IAAIC,KAEJ,KAAK,GAAIF,KAAQC,GACXjD,OAAOH,UAAUF,eAAeN,KAAK4D,EAAQD,IAAyB,MAAhBC,EAAOD,IAC/DE,EAAQC,KAAKC,mBAAmBJ,GAAQ,IAAMI,mBAAmBH,EAAOD,IAI5E,OAAOE,GAAQG,KAAK,QCrLbC,EAAcxB,EAAQ9C,QAM/BkD,wBAAyB,WACvB,OAAS,MAAO,SAOlBK,QAAS,SAASjB,EAAQR,EAAK0B,EAAMC,GACnC,GAAIc,GAAM,GAAIC,eACdD,GAAIE,KAAKnC,EAAQR,GAAK,GACtByC,EAAIG,mBAAqB,WACvB,GAAIb,EAEJ,IAAuB,IAAnBU,EAAII,WACN,IACEd,EAAWe,KAAKC,MAAMN,EAAIO,cAC1BrB,EAASI,GACT,MAAOkB,GACP,KAAM,IAAIC,OAAM,6BAA+BD,KAKrDR,EAAIU,iBAAiB,mBAAoB,kBAC7B,MAARzB,GACFe,EAAIU,iBAAiB,eAAgB,qCAGvCV,EAAIb,KAAKF,MCtCT0B,GAAO,GAAIC,OAAOC,UAWXC,EAAevC,EAAQ9C,OAAO,WACvCqF,EAAazD,OAAOvB,KAAKqB,MAEpB4D,OAAOD,EAAaE,sBACvBD,OAAOD,EAAaE,oBAAsBF,EAAaG,iBAUzD9D,KAAK+D,IAAMJ,EAAaK,gBAOxBxC,wBAAyB,WACvB,OAAS,QAOXH,UAAW,SAASC,EAAKC,GACvB,GAAIO,GAAO6B,EAAazD,OAAOf,UAAUkC,UAAU1C,KAAKqB,KAAMsB,EAAKC,EAGnE,OAFAO,GAAKC,SAAW4B,EAAaE,mBAAqB,IAAM7D,KAAK+D,IAAM,IAE5DjC,GAOTD,QAAS,SAASjB,EAAQR,EAAK0B,EAAMC,GACnC,GAAIkC,GAASC,SAASC,cAAc,UAEhCC,EAAOpE,IACX2D,GAAaG,gBAAgB9D,KAAK+D,KAAO,SAAS5B,SACzCwB,GAAaG,gBAAgBM,EAAKL,KACzCE,EAAOI,WAAWC,YAAYL,GAE9BlC,EAASI,IAGX8B,EAAOM,aAAa,MAAOnE,GAC3B8D,SAASM,qBAAqB,QAAQ,GAAGC,YAAYR,MAYvDJ,mBAAoB,WAAaL,EAAO,SAaxCM,mBAQAE,YAAa,WACX,GACER,UACOG,EAAaG,gBAAgBN,GAEtC,OAAOA,MCjGAkB,EAAYlF,EAAMlB,QAkB3BqG,YAAa,SAASpD,EAAMU,EAAaF,GACvC,GAAIT,GAAMnB,EAAIU,QAEd,KAAKS,EACH,KAAM,IAAIgC,OAAM,8BAGlB,IAAI3C,GAASW,EAAIhB,QAAQK,OACrBC,EAASU,EAAIhB,QAAQM,OACrBQ,EAAUsD,EAAUE,kBAAkBjE,EAE1C,KAAKS,EACH,KAAM,IAAIkC,OAAM,iCAAmC3C,EAGrD,IAAIkE,GAAU,GAAIzD,EAElB,KAAKyD,EAAQpD,kBAAkBb,GAC7B,KAAM,IAAI0C,OAAM,8BAAgC1C,EAGlDiE,GAAQ7C,KAAKT,EAAMU,EAAaF,MAYlC6C,mBACEE,KAAMlC,EACNmC,MAAOpB,KCxDAqB,EAAKN,EAAUpG,QASxB2G,MAAO,SAASlD,GACd,GAAIR,IAAS2D,OAAQ,WAIrB,OAFAlF,MAAK2E,YAAYpD,EAAM,WAAYQ,GAE5B/B,QCbAmF,EAAMT,EAAUpG,OAAO,SAAS8B,GACzC+E,EAAIjF,OAAOvB,KAAKqB,MAQhBA,KAAKI,IAAMA,GAUb+E,GAAIhG,UAAUiG,OAAS,SAASrD,GAC9B,GAAIR,IACF2D,OAAQ,SACRG,SAAUrF,KAAKI,IAKjB,OAFAJ,MAAK2E,YAAYpD,GAAQ,UAAW,UAAW,YAAcQ,GAEtD/B,MAUTmF,EAAIhG,UAAU8F,MAAQ,SAASlD,GAC7B,GAAIR,IACF2D,OAAQ,YACRG,SAAUrF,KAAKI,IAKjB,OAFAJ,MAAK2E,YAAYpD,EAAM,OAAQQ,GAExB/B,SCvCLsF,GAASZ,EAAUpG,OAAO,WAC5BgH,EAAOpF,OAAOvB,KAAKqB,MAQnBA,KAAKuF,GAAK,GAAIP,GAWdhF,KAAKwF,QAAU,UAcfC,QAAS,SAASrF,EAAKC,EAAaC,GAGlC,MAFAH,GAAIU,SAAW,GAAIV,GAAIC,EAAKC,EAAaC,GAElCN,MAUT0F,WAAY,WAGV,MAFAvF,GAAIU,SAAW,KAERb,MAiBT2F,QAAS,SAASvF,EAAKwF,EAAY7D,GACjC,GAAIR,IACF2D,OAAQ,WACR9E,IAAKA,EAGP,cAAewF,IACf,IAAK,WACH7D,EAAW6D,EACXA,EAAa,IACb,MACF,KAAK,SACHA,GAAeC,QAASD,GAa1B,MAPIA,KACFrE,EAAKsE,QAAUD,EAAWC,QAC1BtE,EAAKuE,MAAQF,EAAWE,OAG1B9F,KAAK2E,YAAYpD,GAAQ,WAAY,QAAS,OAASQ,GAEhD/B,MAmBTiF,MAAO,SAASc,EAAUhE,GACxB,GAAIR,IAAS2D,OAAQ,QAErB,cAAea,IACf,IAAK,WACHhE,EAAWgE,EACXA,EAAW,IACX,MACF,KAAK,SACHA,GAAaC,MAAOD,GAgBtB,MAVIA,KACFxE,EAAK0E,OAASF,EAASE,OACvB1E,EAAKyE,MAAQD,EAASC,MACtBzE,EAAK2E,MAAQH,EAASG,OAGxBlG,KAAK2E,YAAYpD,GAAQ,QAAS,SAAW,SAASlC,EAAQ8C,GAC5DJ,EAASuD,EAAOa,qBAAqB9G,GAAS8C,KAGzCnC,MAeTI,IAAK,SAASA,GACZ,MAAOA,GAAM,GAAI+E,GAAI/E,GAAO,MAc9BgG,QAAS,SAASb,EAAIxD,GACpB,GAAIR,IAAS2D,OAAQ,UAarB,OAXkB,kBAAPK,KACTxD,EAAWwD,EACXA,EAAK,MAGG,MAANA,IACFhE,EAAKgE,GAAKc,OAAOd,IAGnBvF,KAAK2E,YAAYpD,GAAQ,aAAc,WAAaQ,GAE7C/B,QAkBTmG,qBAAsB,SAAS9G,GAE7B,IAAKA,IAAWA,EAAOiH,OAAS5G,EAAQL,EAAOiH,OAC7C,MAAOjH,EAOT,KAJA,GACIkH,GADAC,EAAQ,EAERF,KAE6C,OAAzCC,EAAOlH,EAAOiH,MAAM,QAAUE,KACpCF,EAAM7D,KAAK8D,GACXC,GAKF,OAFAnH,GAAOiH,MAAQA,EAERjH,WAQI,IAAIiG"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yourls-api", 3 | "version": "2.1.0", 4 | "description": "Bindings for the YOURLS API", 5 | "homepage": "https://github.com/neocotic/yourls-api", 6 | "bugs": { 7 | "url": "https://github.com/neocotic/yourls-api/issues" 8 | }, 9 | "author": { 10 | "name": "Alasdair Mercer", 11 | "email": "mercer.alasdair@gmail.com", 12 | "url": "https://neocotic.com" 13 | }, 14 | "license": "MIT", 15 | "keywords": [ 16 | "yourls", 17 | "url", 18 | "shortener", 19 | "api", 20 | "json", 21 | "jsonp" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/neocotic/yourls-api.git" 26 | }, 27 | "devDependencies": { 28 | "eslint-config-skelp": "^0.1.5", 29 | "grunt": "^1.0.1", 30 | "grunt-cli": "^1.2.0", 31 | "grunt-contrib-clean": "^1.0.0", 32 | "grunt-contrib-watch": "^1.0.0", 33 | "grunt-eslint": "^19.0.0", 34 | "grunt-rollup": "^1.0.1", 35 | "load-grunt-tasks": "^3.5.2", 36 | "oopsy": "^0.2.0", 37 | "rollup-plugin-node-resolve": "^2.0.0", 38 | "rollup-plugin-uglify": "^1.0.1" 39 | }, 40 | "main": "dist/yourls.js", 41 | "browser": "dist/yourls.js", 42 | "jsnext:main": "src/yourls.js", 43 | "scripts": { 44 | "build": "grunt build", 45 | "ci": "grunt ci", 46 | "test": "grunt test" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import Oopsy from 'oopsy' 24 | 25 | import { extend } from './util/extend' 26 | 27 | /** 28 | * Contains information on how to connect to and authenticate with a YOURLS server. 29 | * 30 | * @param {string} [url=''] - the URL for the YOURLS server 31 | * @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be 32 | * null) 33 | * @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be 34 | * null) 35 | * @protected 36 | * @constructor 37 | */ 38 | export var API = Oopsy.extend(function(url, credentials, options) { 39 | /** 40 | * The URL of the YOURLS server. 41 | * 42 | * @public 43 | * @type {string} 44 | */ 45 | this.url = url ? url.replace(/\/$/, '') : '' 46 | /** 47 | * The credentials to be used to authenticate with the YOURLS API. 48 | * 49 | * This may be null if the YOURLS API is public. 50 | * 51 | * @public 52 | * @type {API~Credentials} 53 | */ 54 | this.credentials = API._sanitizeCredentials(credentials) 55 | /** 56 | * The options to be used to send requests to the YOURLS server. 57 | * 58 | * @public 59 | * @type {API~Options} 60 | */ 61 | this.options = API._sanitizeOptions(options) 62 | }, null, { 63 | 64 | /** 65 | * The default options to be used. 66 | * 67 | * @protected 68 | * @static 69 | * @type {API~Options} 70 | */ 71 | defaultOptions: { 72 | format: 'jsonp', 73 | method: 'GET' 74 | }, 75 | 76 | /** 77 | * The singleton {@link API} instance which is privatized to prevent leaking credentials. 78 | * 79 | * @public 80 | * @static 81 | * @type {API} 82 | */ 83 | instance: null, 84 | 85 | /** 86 | * Sanitizes the specified credentials by ensuring that only valid properties are present and only when 87 | * appropriate. 88 | * 89 | * This method does not modify credentials and instead creates a new object with the sanitized 90 | * properties. 91 | * 92 | * @param {API~Credentials} credentials - the credentials to be sanitized (may be null) 93 | * @return {API~Credentials} A sanitized version of credentials or null if 94 | * credentials is null. 95 | * @private 96 | * @static 97 | */ 98 | _sanitizeCredentials: function(credentials) { 99 | if (!credentials) { 100 | return null 101 | } 102 | 103 | var result = {} 104 | if (credentials.signature) { 105 | result.signature = credentials.signature 106 | result.timestamp = credentials.timestamp 107 | } else { 108 | result.password = credentials.password 109 | result.username = credentials.username 110 | } 111 | 112 | return result 113 | }, 114 | 115 | /** 116 | * Sanitizes the specified options by ensuring that only valid properties are present and in the correct 117 | * format. 118 | * 119 | * This method does not modify options and instead creates a new object with the sanitized properties and 120 | * default values will be used for missing options. 121 | * 122 | * @param {API~Options} options - the options to be sanitized (may be null) 123 | * @return {API~Options} A sanitized version of options which will contain only default values if 124 | * options is null. 125 | * @private 126 | * @static 127 | */ 128 | _sanitizeOptions: function(options) { 129 | var result = extend({}, API.defaultOptions) 130 | if (!options) { 131 | return result 132 | } 133 | 134 | if (options.format) { 135 | result.format = options.format.toLowerCase() 136 | } 137 | if (options.method) { 138 | result.method = options.method.toUpperCase() 139 | } 140 | 141 | return result 142 | } 143 | 144 | }) 145 | 146 | /** 147 | * The credentials to be used to authenticate with a private YOURLS API. 148 | * 149 | * Authentication can be done with a traditional username and password combination or by using the secret 150 | * signature token (e.g. 1002a612b4)for the YOURLS API. The latter is not available for public YOURLS APIs 151 | * and can be found on the "Tools" page. 152 | * 153 | * Optionally, a timestamp can accompany the signature token to make it time-limited (depending on the server 154 | * configuration). When a timestamp is provided the signature token must be the md5 sum of the timestamp and 155 | * signature token concatenated, and in that order. 156 | * 157 | * @typedef {Object} API~Credentials 158 | * @property {string} [password] - The password of the user to be authenticated. 159 | * @property {string} [username] - The name of the user to be authenticated. 160 | * @property {string} [signature] - The signature token to be used for passwordless authentication with the YOURLS API. 161 | * @property {number|string} [timestamp] - The optional timestamp to limit the signature token. 162 | */ 163 | 164 | /** 165 | * The options that determine how requests are sent to the YOURLS server. 166 | * 167 | * If the request format does not support the HTTP method, requests will not be sent and an 168 | * error will be thrown when such attempts occur. 169 | * 170 | * @typedef {Object} API~Options 171 | * @property {string} [format="jsonp"] - The format in which requests are sent (either "json" or 172 | * "jsonp"). 173 | * @property {string} [method="GET"] - The HTTP method to be used for requests. 174 | */ 175 | -------------------------------------------------------------------------------- /src/request/json.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Request } from './request' 24 | 25 | /** 26 | * An implementation of {@link Request} that provides support for JSON requests to the YOURLS API. 27 | * 28 | * JSON requests can only be sent using the "GET" or "POST" HTTP methods. 29 | * 30 | * @constructor 31 | * @extends Request 32 | * @protected 33 | */ 34 | export var JSONRequest = Request.extend({ 35 | 36 | /** 37 | * @inheritDoc 38 | * @override 39 | */ 40 | getSupportedHttpMethods: function() { 41 | return [ 'GET', 'POST' ] 42 | }, 43 | 44 | /** 45 | * @inheritDoc 46 | * @override 47 | */ 48 | process: function(method, url, body, callback) { 49 | var xhr = new XMLHttpRequest() 50 | xhr.open(method, url, true) 51 | xhr.onreadystatechange = function() { 52 | var response 53 | 54 | if (xhr.readyState === 4) { 55 | try { 56 | response = JSON.parse(xhr.responseText) 57 | callback(response) 58 | } catch (e) { 59 | throw new Error('Unable to parse response: ' + e) 60 | } 61 | } 62 | } 63 | 64 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') 65 | if (body != null) { 66 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') 67 | } 68 | 69 | xhr.send(body) 70 | } 71 | 72 | }) 73 | -------------------------------------------------------------------------------- /src/request/jsonp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Request } from './request' 24 | 25 | /** 26 | * The seed to be used to generate IDs. 27 | * 28 | * @private 29 | * @type {number} 30 | */ 31 | var seed = new Date().getTime() 32 | 33 | /** 34 | * An implementation of {@link Request} that provides support for JSONP requests to the YOURLS API. 35 | * 36 | * JSONP requests can only be sent using the "GET" HTTP method. 37 | * 38 | * @constructor 39 | * @extends Request 40 | * @protected 41 | */ 42 | export var JSONPRequest = Request.extend(function() { 43 | JSONPRequest.super_.call(this) 44 | 45 | if (!window[JSONPRequest._callbackHolderKey]) { 46 | window[JSONPRequest._callbackHolderKey] = JSONPRequest._callbackHolder 47 | } 48 | 49 | /** 50 | * The generated ID which is used to store a reference to the callback function within the holder so that the JSONP 51 | * payload can find it in the global namespace. 52 | * 53 | * @private 54 | * @type {number} 55 | */ 56 | this._id = JSONPRequest._generateId() 57 | }, { 58 | 59 | /** 60 | * @inheritDoc 61 | * @override 62 | */ 63 | getSupportedHttpMethods: function() { 64 | return [ 'GET' ] 65 | }, 66 | 67 | /** 68 | * @inheritDoc 69 | * @override 70 | */ 71 | buildBody: function(api, data) { 72 | var body = JSONPRequest.super_.prototype.buildBody.call(this, api, data) 73 | body.callback = JSONPRequest._callbackHolderKey + '[' + this._id + ']' 74 | 75 | return body 76 | }, 77 | 78 | /** 79 | * @inheritDoc 80 | * @override 81 | */ 82 | process: function(method, url, body, callback) { 83 | var script = document.createElement('script') 84 | 85 | var self = this 86 | JSONPRequest._callbackHolder[this._id] = function(response) { 87 | delete JSONPRequest._callbackHolder[self._id] 88 | script.parentNode.removeChild(script) 89 | 90 | callback(response) 91 | } 92 | 93 | script.setAttribute('src', url) 94 | document.getElementsByTagName('head')[0].appendChild(script) 95 | } 96 | 97 | }, { 98 | 99 | /** 100 | * The key of the callback function holder within the global namespace. 101 | * 102 | * @private 103 | * @static 104 | * @type {string} 105 | */ 106 | _callbackHolderKey: '__yourls' + seed + '_jsonp', 107 | 108 | /** 109 | * Contains the callback functions for active JSONP requests. 110 | * 111 | * Callback references should be removed immediately once they have been called. 112 | * 113 | * Due to the nature of JSON, a reference to this object must be publicly available (i.e. global). 114 | * 115 | * @private 116 | * @static 117 | * @type {Object} 118 | */ 119 | _callbackHolder: {}, 120 | 121 | /** 122 | * Generates an ID to be used when storing a reference to a callback function. 123 | * 124 | * @return {number} The generated ID. 125 | * @private 126 | */ 127 | _generateId: function() { 128 | do { 129 | seed++ 130 | } while (JSONPRequest._callbackHolder[seed]) 131 | 132 | return seed 133 | } 134 | 135 | }) 136 | -------------------------------------------------------------------------------- /src/request/request.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import Oopsy from 'oopsy' 24 | 25 | import { API } from '../api' 26 | import { extend } from '../util/extend' 27 | import { isArray } from '../util/array' 28 | 29 | /** 30 | * Contains logic to connect with a YOURLS server and send data to its API. 31 | * 32 | * Due to the nature of HTTP, requests sent using a "GET" HTTP method will include all information in the URL of the 33 | * request. This includes the data that is sent as well as any credentials used to authenticate with the API. You 34 | * have been warned. 35 | * 36 | * @constructor 37 | * @protected 38 | */ 39 | export var Request = Oopsy.extend({ 40 | 41 | /** 42 | * Builds the body for this {@link Request} based on the api and data provided. 43 | * 44 | * @param {API} api - the {@link API} to which the request is being sent 45 | * @param {Object} [data] - the data being sent in this request 46 | * @return {Object} The request body. 47 | * @protected 48 | */ 49 | buildBody: function(api, data) { 50 | return extend({ format: api.options.format }, api.credentials, data) 51 | }, 52 | 53 | /** 54 | * Returns the list of the HTTP methods that are supported by this {@link Request}. 55 | * 56 | * By default, this method returns null, so implementations must implement this method to ensure 57 | * that {@link Request#isMethodSupported} works correctly. 58 | * 59 | * @return {string[]} The supported HTTP methods. 60 | * @protected 61 | */ 62 | getSupportedHttpMethods: function() { 63 | return null 64 | }, 65 | 66 | /** 67 | * Determines whether this {@link Request} supports the specified HTTP method. 68 | * 69 | * @param {string} method - the HTTP method to be checked 70 | * @return {boolean} true if method is supported; otherwise false. 71 | * @public 72 | */ 73 | isMethodSupported: function(method) { 74 | var supportedMethods = this.getSupportedHttpMethods() 75 | return supportedMethods && supportedMethods.indexOf(method) !== -1 76 | }, 77 | 78 | /** 79 | * Determines whether the data that is to be sent to the YOURLS server in this {@link Request} must be serialized as 80 | * query string parameters. 81 | * 82 | * @param {string} method - the HTTP method to be used 83 | * @return {boolean} true if the data needs to be sent as query string parameters; otherwise 84 | * false. 85 | * @protected 86 | */ 87 | isQueryStringRequired: function(method) { 88 | return method === 'GET' 89 | }, 90 | 91 | /** 92 | * Processes this {@link Request} by sending it to the specified target url containing the 93 | * body provided. 94 | * 95 | * callback should be called with the response regardless of whether the it was a success or failure. 96 | * 97 | * This method is called internally by {@link Request#send} and does all of the actual work involved to send the 98 | * request and parse the response. All implementations must implement this method. 99 | * 100 | * @param {string} method - the request HTTP method 101 | * @param {string} url - the request URL 102 | * @param {Object} body - the request body (may be null) 103 | * @param {Function} callback - the function to be called with the response 104 | * @return {void} 105 | * @protected 106 | * @abstract 107 | */ 108 | process: function(method, url, body, callback) { 109 | // Must be implemented 110 | }, 111 | 112 | /** 113 | * Sends the request to the connected YOURLS API with the data provided which should, in turn, call the 114 | * specified callback with the result. 115 | * 116 | * If the request is successful, callback will be passed the value of the named properties from the 117 | * response. If resultNames is a string or only contains a single string, only the value for that named 118 | * property will be passed as the first argument. Otherwise, an object containing the key/value pairs for each named 119 | * property will be passed as the first argument. The actual response will always be passed as the second argument. 120 | * 121 | * @param {Object} data - the data to be sent 122 | * @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to 123 | * callback as the first argument 124 | * @param {Function} callback - the function to be called with the result 125 | * @return {void} 126 | * @public 127 | */ 128 | send: function(data, resultNames, callback) { 129 | var api = API.instance 130 | var body = Request._serializeParameters(this.buildBody(api, data)) 131 | var method = api.options.method 132 | var url = api.url 133 | 134 | if (this.isQueryStringRequired(method)) { 135 | url += '?' + body 136 | body = null 137 | } 138 | 139 | this.process(method, url, body, function(response) { 140 | callback(Request._extractResult(resultNames, response), response) 141 | }) 142 | } 143 | 144 | }, { 145 | 146 | /** 147 | * Extracts the values of the properties with the specified names from the response provided 148 | * and returns them in a single result. 149 | * 150 | * If names is a string or only contains a single string, only the value for that named property will be 151 | * returned. Otherwise, an object containing the key/value pairs for each named property will be returned. 152 | * 153 | * If response is null this method will return null. 154 | * 155 | * @param {string|string[]} names - the names of the response properties whose values are to be returned 156 | * as the result 157 | * @param {Object} response - the YOURLS API response 158 | * @return {*} The result extracted from response. 159 | * @private 160 | * @static 161 | */ 162 | _extractResult: function(names, response) { 163 | names = isArray(names) ? names : [ names ] 164 | 165 | var i 166 | var name 167 | var result = null 168 | 169 | if (!response) { 170 | return result 171 | } 172 | 173 | if (names.length === 1) { 174 | result = response[names[0]] 175 | } else { 176 | result = {} 177 | 178 | for (i = 0; i < names.length; i++) { 179 | name = names[i] 180 | 181 | if (typeof response[name] !== 'undefined') { 182 | result[name] = response[name] 183 | } 184 | } 185 | } 186 | 187 | return result 188 | }, 189 | 190 | /** 191 | * Creates a serialized representation of the specified parameters. 192 | * 193 | * All of the parameter names and values are URL-encoded so that they can be safely included in the query string or 194 | * request body. 195 | * 196 | * @param {Object} [params] - the hash of parameter name/value pairs to be serialized 197 | * @return {string} A URL-encoded representing obj or an empty string if obj is 198 | * null. 199 | * @private 200 | * @static 201 | */ 202 | _serializeParameters: function(params) { 203 | if (!params) { 204 | return '' 205 | } 206 | 207 | var results = [] 208 | 209 | for (var name in params) { 210 | if (Object.prototype.hasOwnProperty.call(params, name) && params[name] != null) { 211 | results.push(encodeURIComponent(name) + '=' + encodeURIComponent(params[name])) 212 | } 213 | } 214 | 215 | return results.join('&') 216 | } 217 | 218 | }) 219 | -------------------------------------------------------------------------------- /src/request/requestor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import Oopsy from 'oopsy' 24 | 25 | import { API } from '../api' 26 | import { JSONRequest } from './json' 27 | import { JSONPRequest } from './jsonp' 28 | 29 | /** 30 | * Can make requests to the connected YOURLS API. 31 | * 32 | * @constructor 33 | * @protected 34 | */ 35 | export var Requestor = Oopsy.extend({ 36 | 37 | /** 38 | * Sends the request to the connected YOURLS API with the data provided which should, in turn, call the 39 | * specified callback with the result. 40 | * 41 | * This method is primarily a proxy to {@link Request#send} but does validate the state of the connection information 42 | * to ensure that is is valid before making the request. 43 | * 44 | * @param {Object} data - the data to be sent 45 | * @param {string|string[]} resultNames - the names of the response properties whose values are to be passed to 46 | * callback as the first argument 47 | * @param {Function} callback - the function to be called with the result 48 | * @return {void} 49 | * @throws {Error} - If either no connection is present, the request format is not supported, or the configured HTTP 50 | * method is not supported by the {@link Request}. 51 | * @protected 52 | */ 53 | sendRequest: function(data, resultNames, callback) { 54 | var api = API.instance 55 | 56 | if (!api) { 57 | throw new Error('No connection has been made') 58 | } 59 | 60 | var format = api.options.format 61 | var method = api.options.method 62 | var Request = Requestor._requestFormatMap[format] 63 | 64 | if (!Request) { 65 | throw new Error('Request format not supported: ' + format) 66 | } 67 | 68 | var request = new Request() 69 | 70 | if (!request.isMethodSupported(method)) { 71 | throw new Error('HTTP method not supported: ' + method) 72 | } 73 | 74 | request.send(data, resultNames, callback) 75 | } 76 | 77 | }, { 78 | 79 | /** 80 | * The mapping of supported request formats to {@link Request} constructors. 81 | * 82 | * @private 83 | * @static 84 | * @type {Object} 85 | */ 86 | _requestFormatMap: { 87 | json: JSONRequest, 88 | jsonp: JSONPRequest 89 | } 90 | 91 | }) 92 | -------------------------------------------------------------------------------- /src/util/array.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /** 24 | * Returns whether the specified obj is an array. 25 | * 26 | * This method will use the native Array.isArray, if available. 27 | * 28 | * @param {*} obj - the object to be checked (may be null) 29 | * @return {boolean} true if obj is an array; otherwise false. 30 | * @protected 31 | */ 32 | export function isArray(obj) { 33 | return Array.isArray ? Array.isArray(obj) : Object.prototype.toString.call(obj) === '[object Array]' 34 | } 35 | -------------------------------------------------------------------------------- /src/util/extend.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /** 24 | * Extends the specified target object with the properties in each of the sources provided. 25 | * 26 | * Any of the sources that are null will simply be ignored. 27 | * 28 | * @param {Object} target - the target object which should be extended 29 | * @param {...Object} [sources] - the source objects whose properties are to be copied onto target 30 | * @return {Object} A reference to target. 31 | * @protected 32 | */ 33 | export function extend(target, sources) { 34 | sources = Array.prototype.slice.call(arguments, 1) 35 | 36 | for (var i = 0, length = sources.length, property, source; i < length; i++) { 37 | source = sources[i] 38 | 39 | if (source) { 40 | for (property in source) { 41 | if (Object.prototype.hasOwnProperty.call(source, property)) { 42 | target[property] = source[property] 43 | } 44 | } 45 | } 46 | } 47 | 48 | return target 49 | } 50 | -------------------------------------------------------------------------------- /src/yourls-db.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Requestor } from './request/requestor' 24 | 25 | /** 26 | * Provides the ability to lookup information related to the YOURLS database. 27 | * 28 | * @constructor 29 | * @extends Requestor 30 | * @protected 31 | */ 32 | export var DB = Requestor.extend({ 33 | 34 | /** 35 | * Retrieves the statistics for this {@link DB}. 36 | * 37 | * @param {Function} callback - the callback function to be called with the result 38 | * @return {DB} A reference to this {@link DB} for chaining purposes. 39 | * @public 40 | */ 41 | stats: function(callback) { 42 | var data = { action: 'db-stats' } 43 | 44 | this.sendRequest(data, 'db-stats', callback) 45 | 46 | return this 47 | } 48 | 49 | }) 50 | -------------------------------------------------------------------------------- /src/yourls-url.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE0 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Requestor } from './request/requestor' 24 | 25 | /** 26 | * Provides the ability to lookup information related to the specified shortened url. 27 | * 28 | * @param {string} url - the shortened URL (or its keyword) to be used 29 | * @constructor 30 | * @extends Requestor 31 | * @protected 32 | */ 33 | export var URL = Requestor.extend(function(url) { 34 | URL.super_.call(this) 35 | 36 | /** 37 | * Either the shortened URL or its keyword for this {@link URL}. 38 | * 39 | * @public 40 | * @type {string} 41 | */ 42 | this.url = url 43 | }) 44 | 45 | /** 46 | * Retrieves the original ("long") URL for this shortened {@link URL}. 47 | * 48 | * @param {Function} callback - the callback function to be called with the result 49 | * @return {URL} A reference to this {@link URL} for chaining purposes. 50 | * @public 51 | */ 52 | URL.prototype.expand = function(callback) { 53 | var data = { 54 | action: 'expand', 55 | shorturl: this.url 56 | } 57 | 58 | this.sendRequest(data, [ 'keyword', 'longurl', 'shorturl' ], callback) 59 | 60 | return this 61 | } 62 | 63 | /** 64 | * Retrieves the statistics for this shortened {@link URL}. 65 | * 66 | * @param {Function} callback - the callback function to be called with the result 67 | * @return {URL} A reference to this {@link URL} for chaining purposes. 68 | * @public 69 | */ 70 | URL.prototype.stats = function(callback) { 71 | var data = { 72 | action: 'url-stats', 73 | shorturl: this.url 74 | } 75 | 76 | this.sendRequest(data, 'link', callback) 77 | 78 | return this 79 | } 80 | -------------------------------------------------------------------------------- /src/yourls.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Alasdair Mercer 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { API } from './api' 24 | import { DB } from './yourls-db' 25 | import { isArray } from './util/array' 26 | import { Requestor } from './request/requestor' 27 | import { URL } from './yourls-url' 28 | 29 | /** 30 | * Provides the ability to connect to YOURLS servers and perform read/write operations via the API that they expose. 31 | * 32 | * Before attempting to interact with a YOURLS server, you must call {@link YOURLS#connect} first to configure 33 | * the URL of the YOURLS server and any credentials required to authenticate with its API (only required when private). 34 | * 35 | * @constructor 36 | * @extends Requestor 37 | * @protected 38 | */ 39 | var YOURLS = Requestor.extend(function() { 40 | YOURLS.super_.call(this) 41 | 42 | /** 43 | * Provides information on the YOURLS {@link DB}. 44 | * 45 | * @public 46 | * @type {DB} 47 | */ 48 | this.db = new DB() 49 | 50 | /** 51 | * The current version of yourls. 52 | * 53 | * This is not the same as the version of YOURLS that is being connected to. The {@link YOURLS#version} method 54 | * should be used to provide that information. 55 | * 56 | * @public 57 | * @type {string} 58 | */ 59 | this.VERSION = '2.1.0' 60 | }, { 61 | 62 | /** 63 | * Stores the specified information to be used later to connect to and authenticate with a YOURLS server. 64 | * 65 | * @param {string} [url=''] - the URL for the YOURLS server 66 | * @param {API~Credentials} [credentials] - the credentials to be used to authenticate with the YOURLS API (may be 67 | * null) 68 | * @param {API~Options} [options] - the options to be used to send requests to the YOURLS server (may be 69 | * null) 70 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 71 | * @public 72 | */ 73 | connect: function(url, credentials, options) { 74 | API.instance = new API(url, credentials, options) 75 | 76 | return this 77 | }, 78 | 79 | /** 80 | * Clears any information that may have been previously stored for connecting to and authenticating with a YOURLS 81 | * server. 82 | * 83 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 84 | * @public 85 | */ 86 | disconnect: function() { 87 | API.instance = null 88 | 89 | return this 90 | }, 91 | 92 | /** 93 | * Creates a short URL for the specified long url. 94 | * 95 | * Optionally, a descriptor can be provided to specify a keyword and/or title for the short URL that is 96 | * to be created. If a keyword is specified, it must be available and, if not, the YOURLS server will generate a 97 | * unique keyword. If descriptor is a string, it will be treated as the keyword. 98 | * 99 | * @param {string} url - the long URL to be shortened 100 | * @param {YOURLS~UrlDescriptor|string} [descriptor] - the optional descriptor (or keyword, if it's a string) to be 101 | * used for the short URL 102 | * @param {Function} callback - the callback function to be called with the result 103 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 104 | * @public 105 | */ 106 | shorten: function(url, descriptor, callback) { 107 | var data = { 108 | action: 'shorturl', 109 | url: url 110 | } 111 | 112 | switch (typeof descriptor) { 113 | case 'function': 114 | callback = descriptor 115 | descriptor = null 116 | break 117 | case 'string': 118 | descriptor = { keyword: descriptor } 119 | break 120 | default: 121 | // Do nothing 122 | } 123 | 124 | if (descriptor) { 125 | data.keyword = descriptor.keyword 126 | data.title = descriptor.title 127 | } 128 | 129 | this.sendRequest(data, [ 'shorturl', 'title', 'url' ], callback) 130 | 131 | return this 132 | }, 133 | 134 | /** 135 | * Retrieves the statistics for all shortened URLs. 136 | * 137 | * Optionally, criteria can be provided to also include a refined set of links in the result. This 138 | * includes filter, which provides limited control over the sorting, as well as limit and start, which allow for 139 | * pagination. If criteria is a number, it will be treated as the limit. 140 | * 141 | * No links will be included in the result unless a limit is specified that is greater than zero. In that case, this 142 | * method would effectively be doing the same as {@link DB#stats}. 143 | * 144 | * @param {YOURLS~SearchCriteria|number} [criteria] - the optional criteria (or limit, if it's a number) to be used to 145 | * search for links to be included in the result 146 | * @param {Function} callback - the callback function to be called with the result 147 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 148 | * @public 149 | */ 150 | stats: function(criteria, callback) { 151 | var data = { action: 'stats' } 152 | 153 | switch (typeof criteria) { 154 | case 'function': 155 | callback = criteria 156 | criteria = null 157 | break 158 | case 'number': 159 | criteria = { limit: criteria } 160 | break 161 | default: 162 | // Do nothing 163 | } 164 | 165 | if (criteria) { 166 | data.filter = criteria.filter 167 | data.limit = criteria.limit 168 | data.start = criteria.start 169 | } 170 | 171 | this.sendRequest(data, [ 'links', 'stats' ], function(result, response) { 172 | callback(YOURLS._sanitizeStatsResult(result), response) 173 | }) 174 | 175 | return this 176 | }, 177 | 178 | /** 179 | * Creates an instance of {@link URL} for the specified shortened url which can be used to lookup more 180 | * detailed information relating to it. 181 | * 182 | * No data is fetched just by calling this method; one of the methods on the returned instance need to be called for 183 | * that to happen. 184 | * 185 | * @param {string} url - the shortened URL (or its keyword) 186 | * @return {URL} The {@link URL} created for the shortened url or null if url 187 | * is null. 188 | * @public 189 | */ 190 | url: function(url) { 191 | return url ? new URL(url) : null 192 | }, 193 | 194 | /** 195 | * Retrieves the version of the connected YOURLS API. 196 | * 197 | * Optionally, db can be passed to indicate that the YOURLS database version should also be included in 198 | * the result. 199 | * 200 | * @param {boolean} [db] - true to include the database version; otherwise false 201 | * @param {Function} callback - the callback function to be called with the result 202 | * @return {YOURLS} A reference to this {@link YOURLS} for chaining purposes. 203 | * @public 204 | */ 205 | version: function(db, callback) { 206 | var data = { action: 'version' } 207 | 208 | if (typeof db === 'function') { 209 | callback = db 210 | db = null 211 | } 212 | 213 | if (db != null) { 214 | data.db = Number(db) 215 | } 216 | 217 | this.sendRequest(data, [ 'db_version', 'version' ], callback) 218 | 219 | return this 220 | } 221 | 222 | }, { 223 | 224 | /** 225 | * Sanitizes the result of {@link YOURLS#stats} so that it's more usable in application code by transforming the links 226 | * from an object mapping into an array. 227 | * 228 | * This method simply returns result if it is null, has no links property or one that is 229 | * already an array (future-proofing). 230 | * 231 | * @param {Object} result - the result to be sanitized (may be null) 232 | * @param {Object} [result.links] - the links to be transformed into an array (may be null) 233 | * @return {Object} The modified result or null if result is null. 234 | * @private 235 | * @static 236 | */ 237 | _sanitizeStatsResult: function(result) { 238 | // Future-proofing by sanitizing links *only* when not already an array 239 | if (!result || !result.links || isArray(result.links)) { 240 | return result 241 | } 242 | 243 | var index = 1 244 | var link 245 | var links = [] 246 | 247 | while ((link = result.links['link_' + index]) != null) { 248 | links.push(link) 249 | index++ 250 | } 251 | 252 | result.links = links 253 | 254 | return result 255 | } 256 | 257 | }) 258 | 259 | /** 260 | * The singleton instance of {@link YOURLS}. 261 | */ 262 | export default new YOURLS() 263 | 264 | /** 265 | * Contains criteria which can be used to search for a refined set of shortened URLs. 266 | * 267 | * Pagination can be achieved by using limit and start. 268 | * 269 | * No links will be returned unless limit is specified and has a value that is greater than zero. 270 | * 271 | * @typedef {Object} YOURLS~SearchCriteria 272 | * @property {string} [filter] - The filter to be applied (either "top", "bottom", 273 | * "rand", or "last"). 274 | * @property {number} [limit] - The maximum number of links whose statistical information is to be counted. 275 | * null or 0 will result in no links being included in the result. 276 | * @property {number} [start] - The offset from where the search should begin. 277 | */ 278 | 279 | /** 280 | * Contains additional information which can be used when shortening a URL. 281 | * 282 | * If keyword is specified, it must be available and, if not, the YOURLS server will generate a unique 283 | * keyword. 284 | * 285 | * @typedef {Object} YOURLS~UrlDescriptor 286 | * @property {string} [keyword] - The optional keyword to be used for the shortened URL. 287 | * @property {string} [title] - The optional title to be associated with the shortened URL. 288 | */ 289 | --------------------------------------------------------------------------------